packages feed

LambdaHack 0.4.99.0 → 0.4.100.0

raw patch · 101 files changed

+5231/−3256 lines, 101 filesdep +data-defaultdep ~miniutterdep ~vtyPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: data-default

Dependency ranges changed: miniutter, vty

API changes (from Hackage documentation)

- Game.LambdaHack.Client.UI.InventoryClient: msgNoChangeDunLeader :: Msg
- Game.LambdaHack.Client.UI.InventoryClient: msgNoChangeLvlLeader :: Msg
- Game.LambdaHack.Common.ActorState: calmEnough :: Actor -> [ItemFull] -> Bool
- Game.LambdaHack.Common.ActorState: hpEnough :: Actor -> [ItemFull] -> Bool
- Game.LambdaHack.Common.ActorState: radiusBlind :: Int -> Bool
- Game.LambdaHack.Common.ClientOptions: snoMore :: DebugModeCli -> !Bool
- Game.LambdaHack.Common.Dice: z :: Int -> Dice
- Game.LambdaHack.Common.Dice: zl :: Int -> Dice
- Game.LambdaHack.Common.Effect: ActivateInv :: !Char -> Effect a
- Game.LambdaHack.Common.Effect: AddArmorMelee :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: AddArmorRanged :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: AddHurtMelee :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: AddHurtRanged :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: AddLight :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: AddMaxCalm :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: AddMaxHP :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: AddSight :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: AddSkills :: !Skills -> Aspect a
- Game.LambdaHack.Common.Effect: AddSmell :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: AddSpeed :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: Applicable :: Feature
- Game.LambdaHack.Common.Effect: ApplyPerfume :: Effect a
- Game.LambdaHack.Common.Effect: Ascend :: !Int -> Effect a
- Game.LambdaHack.Common.Effect: Burn :: !Int -> Effect a
- Game.LambdaHack.Common.Effect: CallFriend :: !a -> Effect a
- Game.LambdaHack.Common.Effect: ChangeTo :: !GroupName -> Feature
- Game.LambdaHack.Common.Effect: CreateItem :: !a -> Effect a
- Game.LambdaHack.Common.Effect: Dominate :: Effect a
- Game.LambdaHack.Common.Effect: DropBestWeapon :: Effect a
- Game.LambdaHack.Common.Effect: DropEqp :: !Char -> !Bool -> Effect a
- Game.LambdaHack.Common.Effect: Durable :: Feature
- Game.LambdaHack.Common.Effect: EqpSlot :: !EqpSlot -> !Text -> Feature
- Game.LambdaHack.Common.Effect: EqpSlotAddArmorMelee :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddArmorRanged :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddHurtMelee :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddHurtRanged :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddLight :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddMaxCalm :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddMaxHP :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddSight :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddSkills :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddSmell :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotAddSpeed :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotPeriodic :: EqpSlot
- Game.LambdaHack.Common.Effect: EqpSlotWeapon :: EqpSlot
- Game.LambdaHack.Common.Effect: Escape :: !Int -> Effect a
- Game.LambdaHack.Common.Effect: Explode :: !GroupName -> Effect a
- Game.LambdaHack.Common.Effect: Fragile :: Feature
- Game.LambdaHack.Common.Effect: Hurt :: !Dice -> Effect a
- Game.LambdaHack.Common.Effect: Identified :: Feature
- Game.LambdaHack.Common.Effect: Identify :: !CStore -> Effect a
- Game.LambdaHack.Common.Effect: Impress :: Effect a
- Game.LambdaHack.Common.Effect: InsertMove :: !a -> Effect a
- Game.LambdaHack.Common.Effect: NoEffect :: !Text -> Effect a
- Game.LambdaHack.Common.Effect: OnSmash :: !(Effect a) -> Effect a
- Game.LambdaHack.Common.Effect: OneOf :: ![Effect a] -> Effect a
- Game.LambdaHack.Common.Effect: Paralyze :: !a -> Effect a
- Game.LambdaHack.Common.Effect: Periodic :: !a -> Aspect a
- Game.LambdaHack.Common.Effect: PolyItem :: !CStore -> Effect a
- Game.LambdaHack.Common.Effect: Precious :: Feature
- Game.LambdaHack.Common.Effect: PullActor :: !ThrowMod -> Effect a
- Game.LambdaHack.Common.Effect: PushActor :: !ThrowMod -> Effect a
- Game.LambdaHack.Common.Effect: RefillCalm :: !Int -> Effect a
- Game.LambdaHack.Common.Effect: RefillHP :: !Int -> Effect a
- Game.LambdaHack.Common.Effect: SendFlying :: !ThrowMod -> Effect a
- Game.LambdaHack.Common.Effect: Summon :: !Freqs -> !a -> Effect a
- Game.LambdaHack.Common.Effect: Tactic :: !Tactic -> Feature
- Game.LambdaHack.Common.Effect: Teleport :: !a -> Effect a
- Game.LambdaHack.Common.Effect: ThrowMod :: !Int -> !Int -> ThrowMod
- Game.LambdaHack.Common.Effect: TimedAspect :: !Int -> !(Aspect a) -> Effect a
- Game.LambdaHack.Common.Effect: ToThrow :: !ThrowMod -> Feature
- Game.LambdaHack.Common.Effect: aspectTrav :: Aspect a -> (a -> State s b) -> State s (Aspect b)
- Game.LambdaHack.Common.Effect: data Aspect a
- Game.LambdaHack.Common.Effect: data Effect a
- Game.LambdaHack.Common.Effect: data EqpSlot
- Game.LambdaHack.Common.Effect: data Feature
- Game.LambdaHack.Common.Effect: data ThrowMod
- Game.LambdaHack.Common.Effect: effectTrav :: Effect a -> (a -> State s b) -> State s (Effect b)
- Game.LambdaHack.Common.Effect: instance Binary EqpSlot
- Game.LambdaHack.Common.Effect: instance Binary Feature
- Game.LambdaHack.Common.Effect: instance Binary ThrowMod
- Game.LambdaHack.Common.Effect: instance Binary a => Binary (Aspect a)
- Game.LambdaHack.Common.Effect: instance Binary a => Binary (Effect a)
- Game.LambdaHack.Common.Effect: instance Constructor C1_0Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_0Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_0EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_0Feature
- Game.LambdaHack.Common.Effect: instance Constructor C1_0ThrowMod
- Game.LambdaHack.Common.Effect: instance Constructor C1_10Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_10Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_10EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_11Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_11Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_11EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_12Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_12EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_13Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_14Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_15Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_16Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_17Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_18Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_19Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_1Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_1Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_1EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_1Feature
- Game.LambdaHack.Common.Effect: instance Constructor C1_20Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_21Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_22Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_23Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_24Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_25Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_26Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_27Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_2Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_2Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_2EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_2Feature
- Game.LambdaHack.Common.Effect: instance Constructor C1_3Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_3Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_3EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_3Feature
- Game.LambdaHack.Common.Effect: instance Constructor C1_4Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_4Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_4EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_4Feature
- Game.LambdaHack.Common.Effect: instance Constructor C1_5Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_5Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_5EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_5Feature
- Game.LambdaHack.Common.Effect: instance Constructor C1_6Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_6Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_6EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_6Feature
- Game.LambdaHack.Common.Effect: instance Constructor C1_7Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_7Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_7EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_7Feature
- Game.LambdaHack.Common.Effect: instance Constructor C1_8Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_8Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_8EqpSlot
- Game.LambdaHack.Common.Effect: instance Constructor C1_8Feature
- Game.LambdaHack.Common.Effect: instance Constructor C1_9Aspect
- Game.LambdaHack.Common.Effect: instance Constructor C1_9Effect
- Game.LambdaHack.Common.Effect: instance Constructor C1_9EqpSlot
- Game.LambdaHack.Common.Effect: instance Datatype D1Aspect
- Game.LambdaHack.Common.Effect: instance Datatype D1Effect
- Game.LambdaHack.Common.Effect: instance Datatype D1EqpSlot
- Game.LambdaHack.Common.Effect: instance Datatype D1Feature
- Game.LambdaHack.Common.Effect: instance Datatype D1ThrowMod
- Game.LambdaHack.Common.Effect: instance Eq EqpSlot
- Game.LambdaHack.Common.Effect: instance Eq Feature
- Game.LambdaHack.Common.Effect: instance Eq ThrowMod
- Game.LambdaHack.Common.Effect: instance Eq a => Eq (Aspect a)
- Game.LambdaHack.Common.Effect: instance Eq a => Eq (Effect a)
- Game.LambdaHack.Common.Effect: instance Functor Aspect
- Game.LambdaHack.Common.Effect: instance Functor Effect
- Game.LambdaHack.Common.Effect: instance Generic (Aspect a)
- Game.LambdaHack.Common.Effect: instance Generic (Effect a)
- Game.LambdaHack.Common.Effect: instance Generic EqpSlot
- Game.LambdaHack.Common.Effect: instance Generic Feature
- Game.LambdaHack.Common.Effect: instance Generic ThrowMod
- Game.LambdaHack.Common.Effect: instance Hashable EqpSlot
- Game.LambdaHack.Common.Effect: instance Hashable Feature
- Game.LambdaHack.Common.Effect: instance Hashable ThrowMod
- Game.LambdaHack.Common.Effect: instance Hashable a => Hashable (Aspect a)
- Game.LambdaHack.Common.Effect: instance Hashable a => Hashable (Effect a)
- Game.LambdaHack.Common.Effect: instance Ord EqpSlot
- Game.LambdaHack.Common.Effect: instance Ord Feature
- Game.LambdaHack.Common.Effect: instance Ord ThrowMod
- Game.LambdaHack.Common.Effect: instance Ord a => Ord (Aspect a)
- Game.LambdaHack.Common.Effect: instance Ord a => Ord (Effect a)
- Game.LambdaHack.Common.Effect: instance Read ThrowMod
- Game.LambdaHack.Common.Effect: instance Read a => Read (Aspect a)
- Game.LambdaHack.Common.Effect: instance Read a => Read (Effect a)
- Game.LambdaHack.Common.Effect: instance Selector S1_0_0ThrowMod
- Game.LambdaHack.Common.Effect: instance Selector S1_0_1ThrowMod
- Game.LambdaHack.Common.Effect: instance Show EqpSlot
- Game.LambdaHack.Common.Effect: instance Show Feature
- Game.LambdaHack.Common.Effect: instance Show ThrowMod
- Game.LambdaHack.Common.Effect: instance Show a => Show (Aspect a)
- Game.LambdaHack.Common.Effect: instance Show a => Show (Effect a)
- Game.LambdaHack.Common.Effect: throwLinger :: ThrowMod -> !Int
- Game.LambdaHack.Common.Effect: throwVelocity :: ThrowMod -> !Int
- Game.LambdaHack.Common.Feature: Aura :: !(Effect Int) -> Feature
- Game.LambdaHack.Common.Feature: Cause :: !(Effect Int) -> Feature
- Game.LambdaHack.Common.Feature: ChangeTo :: !GroupName -> Feature
- Game.LambdaHack.Common.Feature: Clear :: Feature
- Game.LambdaHack.Common.Feature: CloseTo :: !GroupName -> Feature
- Game.LambdaHack.Common.Feature: Dark :: Feature
- Game.LambdaHack.Common.Feature: HideAs :: !GroupName -> Feature
- Game.LambdaHack.Common.Feature: Impenetrable :: Feature
- Game.LambdaHack.Common.Feature: NoActor :: Feature
- Game.LambdaHack.Common.Feature: NoItem :: Feature
- Game.LambdaHack.Common.Feature: OftenActor :: Feature
- Game.LambdaHack.Common.Feature: OftenItem :: Feature
- Game.LambdaHack.Common.Feature: OpenTo :: !GroupName -> Feature
- Game.LambdaHack.Common.Feature: RevealAs :: !GroupName -> Feature
- Game.LambdaHack.Common.Feature: Suspect :: Feature
- Game.LambdaHack.Common.Feature: Trail :: Feature
- Game.LambdaHack.Common.Feature: Walkable :: Feature
- Game.LambdaHack.Common.Feature: data Feature
- Game.LambdaHack.Common.Feature: instance Binary Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_0Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_10Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_11Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_12Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_13Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_14Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_15Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_16Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_1Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_2Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_3Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_4Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_5Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_6Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_7Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_8Feature
- Game.LambdaHack.Common.Feature: instance Constructor C1_9Feature
- Game.LambdaHack.Common.Feature: instance Datatype D1Feature
- Game.LambdaHack.Common.Feature: instance Eq Feature
- Game.LambdaHack.Common.Feature: instance Generic Feature
- Game.LambdaHack.Common.Feature: instance Hashable Feature
- Game.LambdaHack.Common.Feature: instance Ord Feature
- Game.LambdaHack.Common.Feature: instance Read Feature
- Game.LambdaHack.Common.Feature: instance Show Feature
- Game.LambdaHack.Common.ItemStrongest: permittedRanged :: ItemFull -> Maybe Int -> Bool
- Game.LambdaHack.Common.ItemStrongest: unknownPrecious :: ItemFull -> Bool
- Game.LambdaHack.Common.Level: atI :: Level -> Point -> ItemBag
- Game.LambdaHack.Common.Misc: instance Binary GroupName
- Game.LambdaHack.Common.Misc: instance Eq GroupName
- Game.LambdaHack.Common.Misc: instance Hashable GroupName
- Game.LambdaHack.Common.Misc: instance IsString GroupName
- Game.LambdaHack.Common.Misc: instance Ord GroupName
- Game.LambdaHack.Common.Misc: instance Read GroupName
- Game.LambdaHack.Common.Misc: instance Show GroupName
- Game.LambdaHack.Common.Msg: mergeHistory :: [(Msg, History)] -> History
- Game.LambdaHack.Common.Request: ApplyBlind :: ReqFailure
- Game.LambdaHack.Common.Request: DurablePeriodicAbuse :: ReqFailure
- Game.LambdaHack.Common.Request: ProjectBlind :: ReqFailure
- Game.LambdaHack.Content.ModeKind: TBlock :: Tactic
- Game.LambdaHack.Content.ModeKind: TExplore :: Tactic
- Game.LambdaHack.Content.ModeKind: TFollow :: Tactic
- Game.LambdaHack.Content.ModeKind: TPatrol :: Tactic
- Game.LambdaHack.Content.ModeKind: TRoam :: Tactic
- Game.LambdaHack.Content.ModeKind: data Tactic
- Game.LambdaHack.Content.ModeKind: instance Binary Player
- Game.LambdaHack.Content.ModeKind: instance Binary Tactic
- Game.LambdaHack.Content.ModeKind: instance Bounded Tactic
- Game.LambdaHack.Content.ModeKind: instance Constructor C1_0Tactic
- Game.LambdaHack.Content.ModeKind: instance Constructor C1_1Tactic
- Game.LambdaHack.Content.ModeKind: instance Constructor C1_2Tactic
- Game.LambdaHack.Content.ModeKind: instance Constructor C1_3Tactic
- Game.LambdaHack.Content.ModeKind: instance Constructor C1_4Tactic
- Game.LambdaHack.Content.ModeKind: instance Datatype D1Tactic
- Game.LambdaHack.Content.ModeKind: instance Enum Tactic
- Game.LambdaHack.Content.ModeKind: instance Eq Player
- Game.LambdaHack.Content.ModeKind: instance Eq Tactic
- Game.LambdaHack.Content.ModeKind: instance Generic Player
- Game.LambdaHack.Content.ModeKind: instance Generic Tactic
- Game.LambdaHack.Content.ModeKind: instance Hashable Tactic
- Game.LambdaHack.Content.ModeKind: instance Ord Tactic
- Game.LambdaHack.Content.ModeKind: instance Show Player
- Game.LambdaHack.Content.ModeKind: instance Show Tactic
- Game.LambdaHack.Server.EndServer: dropEqpItems :: (MonadAtomic m, MonadServer m) => ActorId -> Actor -> Bool -> m ()
- Game.LambdaHack.Server.HandleEffectServer: dropEqpItem :: (MonadAtomic m, MonadServer m) => ActorId -> Actor -> Bool -> ItemId -> Int -> m ()
- Game.LambdaHack.Server.HandleEffectServer: effectsSem :: (MonadAtomic m, MonadServer m) => [Effect Int] -> ActorId -> ActorId -> Bool -> m Bool
- Game.LambdaHack.Server.HandleEffectServer: itemEffect :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> ItemId -> ItemFull -> Bool -> Bool -> m Bool
- Game.LambdaHack.Server.ItemServer: createItems :: (MonadAtomic m, MonadServer m) => Int -> Point -> LevelId -> m ()
- Game.LambdaHack.Server.MonadServer: debugPrint :: MonadServer m => Text -> m ()
- Game.LambdaHack.Server.PeriodicServer: leadLevelFlip :: (MonadAtomic m, MonadServer m) => m ()
+ Game.LambdaHack.Atomic: UpdTimeItem :: !ItemId -> !Container -> !ItemTimer -> !ItemTimer -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdTimeItem :: !ItemId -> !Container -> !ItemTimer -> !ItemTimer -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: instance Constructor C1_49UpdAtomic
+ Game.LambdaHack.Client.Key: Delete :: Key
+ Game.LambdaHack.Client.Key: Insert :: Key
+ Game.LambdaHack.Client.Key: instance Constructor C1_18Key
+ Game.LambdaHack.Client.Key: instance Constructor C1_19Key
+ Game.LambdaHack.Client.Key: returnKM :: KM
+ Game.LambdaHack.Client.State: EscAIExited :: EscAI
+ Game.LambdaHack.Client.State: EscAIMenu :: EscAI
+ Game.LambdaHack.Client.State: EscAINothing :: EscAI
+ Game.LambdaHack.Client.State: EscAIStarted :: EscAI
+ Game.LambdaHack.Client.State: data EscAI
+ Game.LambdaHack.Client.State: instance Eq EscAI
+ Game.LambdaHack.Client.State: instance Show EscAI
+ Game.LambdaHack.Client.State: sescAI :: StateClient -> !EscAI
+ Game.LambdaHack.Client.State: slastLost :: StateClient -> !(EnumSet ActorId)
+ Game.LambdaHack.Client.UI.Config: configColorIsBold :: Config -> !Bool
+ Game.LambdaHack.Client.UI.Frontend: FrontAutoYes :: !Bool -> FrontReq
+ Game.LambdaHack.Client.UI.MonadClientUI: setFrontAutoYes :: MonadClientUI m => Bool -> m ()
+ Game.LambdaHack.Common.Actor: calmEnough :: Actor -> [ItemFull] -> Bool
+ Game.LambdaHack.Common.Actor: calmEnough10 :: Actor -> [ItemFull] -> Bool
+ Game.LambdaHack.Common.Actor: hpEnough :: Actor -> [ItemFull] -> Bool
+ Game.LambdaHack.Common.Actor: hpEnough10 :: Actor -> [ItemFull] -> Bool
+ Game.LambdaHack.Common.Actor: verbCStore :: CStore -> Text
+ Game.LambdaHack.Common.ActorState: aidFromC :: Container -> Maybe ActorId
+ Game.LambdaHack.Common.ActorState: lidFromC :: Container -> State -> LevelId
+ Game.LambdaHack.Common.ActorState: maxActorSkills :: ActorId -> [ItemFull] -> State -> Skills
+ Game.LambdaHack.Common.ActorState: mergeItemQuant :: ItemQuant -> ItemQuant -> ItemQuant
+ Game.LambdaHack.Common.ClientOptions: instance Selector S1_0_12DebugModeCli
+ Game.LambdaHack.Common.ClientOptions: scolorIsBold :: DebugModeCli -> !(Maybe Bool)
+ Game.LambdaHack.Common.ClientOptions: sdisableAutoYes :: DebugModeCli -> !Bool
+ Game.LambdaHack.Common.Dice: ds :: Int -> Dice
+ Game.LambdaHack.Common.Flavour: instance Binary FancyName
+ Game.LambdaHack.Common.Flavour: instance Constructor C1_0FancyName
+ Game.LambdaHack.Common.Flavour: instance Constructor C1_1FancyName
+ Game.LambdaHack.Common.Flavour: instance Constructor C1_2FancyName
+ Game.LambdaHack.Common.Flavour: instance Datatype D1FancyName
+ Game.LambdaHack.Common.Flavour: instance Eq FancyName
+ Game.LambdaHack.Common.Flavour: instance Generic FancyName
+ Game.LambdaHack.Common.Flavour: instance Hashable FancyName
+ Game.LambdaHack.Common.Flavour: instance Ord FancyName
+ Game.LambdaHack.Common.Flavour: instance Show FancyName
+ Game.LambdaHack.Common.Flavour: zipLiquid :: [Color] -> [Flavour]
+ Game.LambdaHack.Common.Item: itemTimer :: ItemFull -> !ItemTimer
+ Game.LambdaHack.Common.Item: type ItemQuant = (Int, ItemTimer)
+ Game.LambdaHack.Common.Item: type ItemTimer = [Time]
+ Game.LambdaHack.Common.ItemStrongest: allRecharging :: [Effect] -> [Effect]
+ Game.LambdaHack.Common.ItemStrongest: strengthCreateOrgan :: ItemFull -> [GroupName ItemKind]
+ Game.LambdaHack.Common.ItemStrongest: strengthDropOrgan :: ItemFull -> [GroupName ItemKind]
+ Game.LambdaHack.Common.ItemStrongest: stripOnSmash :: [Effect] -> [Effect]
+ Game.LambdaHack.Common.ItemStrongest: stripRecharging :: [Effect] -> [Effect]
+ Game.LambdaHack.Common.Level: lembed :: Level -> !ItemFloor
+ Game.LambdaHack.Common.Misc: CEmbed :: !LevelId -> !Point -> Container
+ Game.LambdaHack.Common.Misc: TBlock :: Tactic
+ Game.LambdaHack.Common.Misc: TExplore :: Tactic
+ Game.LambdaHack.Common.Misc: TFollow :: Tactic
+ Game.LambdaHack.Common.Misc: TPatrol :: Tactic
+ Game.LambdaHack.Common.Misc: TRoam :: Tactic
+ Game.LambdaHack.Common.Misc: data Tactic
+ Game.LambdaHack.Common.Misc: instance Binary (GroupName a)
+ Game.LambdaHack.Common.Misc: instance Binary Tactic
+ Game.LambdaHack.Common.Misc: instance Bounded Tactic
+ Game.LambdaHack.Common.Misc: instance Constructor C1_0Tactic
+ Game.LambdaHack.Common.Misc: instance Constructor C1_1Tactic
+ Game.LambdaHack.Common.Misc: instance Constructor C1_2Tactic
+ Game.LambdaHack.Common.Misc: instance Constructor C1_3Container
+ Game.LambdaHack.Common.Misc: instance Constructor C1_3Tactic
+ Game.LambdaHack.Common.Misc: instance Constructor C1_4Tactic
+ Game.LambdaHack.Common.Misc: instance Datatype D1Tactic
+ Game.LambdaHack.Common.Misc: instance Enum Tactic
+ Game.LambdaHack.Common.Misc: instance Eq (GroupName a)
+ Game.LambdaHack.Common.Misc: instance Eq Tactic
+ Game.LambdaHack.Common.Misc: instance Generic Tactic
+ Game.LambdaHack.Common.Misc: instance Hashable (GroupName a)
+ Game.LambdaHack.Common.Misc: instance Hashable Tactic
+ Game.LambdaHack.Common.Misc: instance IsString (GroupName a)
+ Game.LambdaHack.Common.Misc: instance Ord (GroupName a)
+ Game.LambdaHack.Common.Misc: instance Ord Tactic
+ Game.LambdaHack.Common.Misc: instance Read (GroupName a)
+ Game.LambdaHack.Common.Misc: instance Show (GroupName a)
+ Game.LambdaHack.Common.Misc: instance Show Tactic
+ Game.LambdaHack.Common.Msg: prependMsg :: Msg -> Report -> Report
+ Game.LambdaHack.Common.PointArray: forceA :: Enum c => Array c -> Array c
+ Game.LambdaHack.Common.PointArray: generateA :: Enum c => X -> Y -> (Point -> c) -> Array c
+ Game.LambdaHack.Common.PointArray: mapA :: (Enum c, Enum d) => (c -> d) -> Array c -> Array d
+ Game.LambdaHack.Common.PointArray: mapWithKeyM_A :: Enum c => Monad m => (Point -> c -> m ()) -> Array c -> m ()
+ Game.LambdaHack.Common.Request: ApplyRead :: ReqFailure
+ Game.LambdaHack.Common.Request: ApplyUnskilled :: ReqFailure
+ Game.LambdaHack.Common.Request: DisplaceImmobile :: ReqFailure
+ Game.LambdaHack.Common.Request: NotCalmPrecious :: ReqFailure
+ Game.LambdaHack.Common.Request: ProjectFragile :: ReqFailure
+ Game.LambdaHack.Common.Request: ProjectOutOfReach :: ReqFailure
+ Game.LambdaHack.Common.Request: ProjectUnskilled :: ReqFailure
+ Game.LambdaHack.Common.Request: impossibleReqFailure :: ReqFailure -> Bool
+ Game.LambdaHack.Common.Request: permittedApply :: [Char] -> Int -> ItemFull -> Actor -> [ItemFull] -> Either ReqFailure Bool
+ Game.LambdaHack.Common.Request: permittedPrecious :: Bool -> Bool -> ItemFull -> Either ReqFailure Bool
+ Game.LambdaHack.Common.Request: permittedProject :: [Char] -> Bool -> Int -> ItemFull -> Actor -> [ItemFull] -> Either ReqFailure Bool
+ Game.LambdaHack.Common.Tile: ascendTo :: Ops TileKind -> Id TileKind -> [Int]
+ Game.LambdaHack.Common.Tile: embedItems :: Ops TileKind -> Id TileKind -> [GroupName ItemKind]
+ Game.LambdaHack.Common.Time: timeDeltaDiv :: Delta Time -> Int -> Delta Time
+ Game.LambdaHack.Content.ItemKind: ActivateInv :: !Char -> Effect
+ Game.LambdaHack.Content.ItemKind: AddArmorMelee :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddArmorRanged :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddHurtMelee :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddHurtRanged :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddLight :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddMaxCalm :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddMaxHP :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddSight :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddSkills :: !Skills -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddSmell :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: AddSpeed :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: Applicable :: Feature
+ Game.LambdaHack.Content.ItemKind: ApplyPerfume :: Effect
+ Game.LambdaHack.Content.ItemKind: Ascend :: !Int -> Effect
+ Game.LambdaHack.Content.ItemKind: Burn :: !Int -> Effect
+ Game.LambdaHack.Content.ItemKind: CallFriend :: !Dice -> Effect
+ Game.LambdaHack.Content.ItemKind: CreateItem :: !CStore -> !(GroupName ItemKind) -> !TimerDice -> Effect
+ Game.LambdaHack.Content.ItemKind: Dominate :: Effect
+ Game.LambdaHack.Content.ItemKind: DropBestWeapon :: Effect
+ Game.LambdaHack.Content.ItemKind: DropItem :: !CStore -> !(GroupName ItemKind) -> !Bool -> Effect
+ Game.LambdaHack.Content.ItemKind: Durable :: Feature
+ Game.LambdaHack.Content.ItemKind: EqpSlot :: !EqpSlot -> !Text -> Feature
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddArmorMelee :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddArmorRanged :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddHurtMelee :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddHurtRanged :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddLight :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddMaxCalm :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddMaxHP :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddSight :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddSkills :: Ability -> EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddSmell :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotAddSpeed :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotPeriodic :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotTimeout :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: EqpSlotWeapon :: EqpSlot
+ Game.LambdaHack.Content.ItemKind: Escape :: !Int -> Effect
+ Game.LambdaHack.Content.ItemKind: Explode :: !(GroupName ItemKind) -> Effect
+ Game.LambdaHack.Content.ItemKind: Fragile :: Feature
+ Game.LambdaHack.Content.ItemKind: Hurt :: !Dice -> Effect
+ Game.LambdaHack.Content.ItemKind: Identified :: Feature
+ Game.LambdaHack.Content.ItemKind: Identify :: !CStore -> Effect
+ Game.LambdaHack.Content.ItemKind: Impress :: Effect
+ Game.LambdaHack.Content.ItemKind: InsertMove :: !Dice -> Effect
+ Game.LambdaHack.Content.ItemKind: NoEffect :: !Text -> Effect
+ Game.LambdaHack.Content.ItemKind: OnSmash :: !Effect -> Effect
+ Game.LambdaHack.Content.ItemKind: OneOf :: ![Effect] -> Effect
+ Game.LambdaHack.Content.ItemKind: OverfillCalm :: !Int -> Effect
+ Game.LambdaHack.Content.ItemKind: OverfillHP :: !Int -> Effect
+ Game.LambdaHack.Content.ItemKind: Paralyze :: !Dice -> Effect
+ Game.LambdaHack.Content.ItemKind: Periodic :: Aspect a
+ Game.LambdaHack.Content.ItemKind: PolyItem :: !CStore -> Effect
+ Game.LambdaHack.Content.ItemKind: Precious :: Feature
+ Game.LambdaHack.Content.ItemKind: PullActor :: !ThrowMod -> Effect
+ Game.LambdaHack.Content.ItemKind: PushActor :: !ThrowMod -> Effect
+ Game.LambdaHack.Content.ItemKind: Recharging :: !Effect -> Effect
+ Game.LambdaHack.Content.ItemKind: RefillCalm :: !Int -> Effect
+ Game.LambdaHack.Content.ItemKind: RefillHP :: !Int -> Effect
+ Game.LambdaHack.Content.ItemKind: SendFlying :: !ThrowMod -> Effect
+ Game.LambdaHack.Content.ItemKind: Summon :: !(Freqs ItemKind) -> !Dice -> Effect
+ Game.LambdaHack.Content.ItemKind: Tactic :: !Tactic -> Feature
+ Game.LambdaHack.Content.ItemKind: Teleport :: !Dice -> Effect
+ Game.LambdaHack.Content.ItemKind: Temporary :: !Text -> Effect
+ Game.LambdaHack.Content.ItemKind: ThrowMod :: !Int -> !Int -> ThrowMod
+ Game.LambdaHack.Content.ItemKind: Timeout :: !a -> Aspect a
+ Game.LambdaHack.Content.ItemKind: TimerActorTurn :: !Dice -> TimerDice
+ Game.LambdaHack.Content.ItemKind: TimerGameTurn :: !Dice -> TimerDice
+ Game.LambdaHack.Content.ItemKind: TimerNone :: TimerDice
+ Game.LambdaHack.Content.ItemKind: ToThrow :: !ThrowMod -> Feature
+ Game.LambdaHack.Content.ItemKind: aspectTrav :: Aspect a -> (a -> State s b) -> State s (Aspect b)
+ Game.LambdaHack.Content.ItemKind: data Aspect a
+ Game.LambdaHack.Content.ItemKind: data Effect
+ Game.LambdaHack.Content.ItemKind: data EqpSlot
+ Game.LambdaHack.Content.ItemKind: data Feature
+ Game.LambdaHack.Content.ItemKind: data ThrowMod
+ Game.LambdaHack.Content.ItemKind: data TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Binary Effect
+ Game.LambdaHack.Content.ItemKind: instance Binary EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Binary Feature
+ Game.LambdaHack.Content.ItemKind: instance Binary ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Binary TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Binary a => Binary (Aspect a)
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_0Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_0Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_0EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_0Feature
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_0ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_0TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_10Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_10Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_10EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_11Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_11Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_11EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_12Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_12Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_12EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_13Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_13EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_14Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_15Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_16Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_17Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_18Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_19Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_1Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_1Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_1EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_1Feature
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_1TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_20Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_21Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_22Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_23Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_24Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_25Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_26Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_27Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_28Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_29Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_2Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_2Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_2EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_2Feature
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_2TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_30Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_3Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_3Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_3EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_3Feature
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_4Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_4Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_4EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_4Feature
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_5Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_5Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_5EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_5Feature
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_6Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_6Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_6EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_6Feature
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_7Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_7Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_7EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_7Feature
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_8Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_8Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_8EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_9Aspect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_9Effect
+ Game.LambdaHack.Content.ItemKind: instance Constructor C1_9EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Datatype D1Aspect
+ Game.LambdaHack.Content.ItemKind: instance Datatype D1Effect
+ Game.LambdaHack.Content.ItemKind: instance Datatype D1EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Datatype D1Feature
+ Game.LambdaHack.Content.ItemKind: instance Datatype D1ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Datatype D1TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Eq Effect
+ Game.LambdaHack.Content.ItemKind: instance Eq EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Eq Feature
+ Game.LambdaHack.Content.ItemKind: instance Eq ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Eq TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Eq a => Eq (Aspect a)
+ Game.LambdaHack.Content.ItemKind: instance Functor Aspect
+ Game.LambdaHack.Content.ItemKind: instance Generic (Aspect a)
+ Game.LambdaHack.Content.ItemKind: instance Generic Effect
+ Game.LambdaHack.Content.ItemKind: instance Generic EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Generic Feature
+ Game.LambdaHack.Content.ItemKind: instance Generic ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Generic TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Hashable Effect
+ Game.LambdaHack.Content.ItemKind: instance Hashable EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Hashable Feature
+ Game.LambdaHack.Content.ItemKind: instance Hashable ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Hashable TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Hashable a => Hashable (Aspect a)
+ Game.LambdaHack.Content.ItemKind: instance Ord Effect
+ Game.LambdaHack.Content.ItemKind: instance Ord EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Ord Feature
+ Game.LambdaHack.Content.ItemKind: instance Ord ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Ord TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Ord a => Ord (Aspect a)
+ Game.LambdaHack.Content.ItemKind: instance Read Effect
+ Game.LambdaHack.Content.ItemKind: instance Read ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Read TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Read a => Read (Aspect a)
+ Game.LambdaHack.Content.ItemKind: instance Selector S1_0_0ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Selector S1_0_1ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Show Effect
+ Game.LambdaHack.Content.ItemKind: instance Show EqpSlot
+ Game.LambdaHack.Content.ItemKind: instance Show Feature
+ Game.LambdaHack.Content.ItemKind: instance Show ThrowMod
+ Game.LambdaHack.Content.ItemKind: instance Show TimerDice
+ Game.LambdaHack.Content.ItemKind: instance Show a => Show (Aspect a)
+ Game.LambdaHack.Content.ItemKind: throwLinger :: ThrowMod -> !Int
+ Game.LambdaHack.Content.ItemKind: throwVelocity :: ThrowMod -> !Int
+ Game.LambdaHack.Content.ItemKind: toOrganActorTurn :: GroupName ItemKind -> Dice -> Effect
+ Game.LambdaHack.Content.ItemKind: toOrganGameTurn :: GroupName ItemKind -> Dice -> Effect
+ Game.LambdaHack.Content.ItemKind: toOrganNone :: GroupName ItemKind -> Effect
+ Game.LambdaHack.Content.ModeKind: instance Binary a => Binary (Player a)
+ Game.LambdaHack.Content.ModeKind: instance Eq a => Eq (Player a)
+ Game.LambdaHack.Content.ModeKind: instance Generic (Player a)
+ Game.LambdaHack.Content.ModeKind: instance Show a => Show (Player a)
+ Game.LambdaHack.Content.TileKind: Cause :: !Effect -> Feature
+ Game.LambdaHack.Content.TileKind: ChangeTo :: !(GroupName TileKind) -> Feature
+ Game.LambdaHack.Content.TileKind: Clear :: Feature
+ Game.LambdaHack.Content.TileKind: CloseTo :: !(GroupName TileKind) -> Feature
+ Game.LambdaHack.Content.TileKind: Dark :: Feature
+ Game.LambdaHack.Content.TileKind: Embed :: !(GroupName ItemKind) -> Feature
+ Game.LambdaHack.Content.TileKind: HideAs :: !(GroupName TileKind) -> Feature
+ Game.LambdaHack.Content.TileKind: Impenetrable :: Feature
+ Game.LambdaHack.Content.TileKind: NoActor :: Feature
+ Game.LambdaHack.Content.TileKind: NoItem :: Feature
+ Game.LambdaHack.Content.TileKind: OftenActor :: Feature
+ Game.LambdaHack.Content.TileKind: OftenItem :: Feature
+ Game.LambdaHack.Content.TileKind: OpenTo :: !(GroupName TileKind) -> Feature
+ Game.LambdaHack.Content.TileKind: RevealAs :: !(GroupName TileKind) -> Feature
+ Game.LambdaHack.Content.TileKind: Suspect :: Feature
+ Game.LambdaHack.Content.TileKind: Trail :: Feature
+ Game.LambdaHack.Content.TileKind: Walkable :: Feature
+ Game.LambdaHack.Content.TileKind: data Feature
+ Game.LambdaHack.Content.TileKind: instance Binary Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_0Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_10Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_11Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_12Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_13Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_14Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_15Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_16Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_1Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_2Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_3Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_4Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_5Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_6Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_7Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_8Feature
+ Game.LambdaHack.Content.TileKind: instance Constructor C1_9Feature
+ Game.LambdaHack.Content.TileKind: instance Datatype D1Feature
+ Game.LambdaHack.Content.TileKind: instance Eq Feature
+ Game.LambdaHack.Content.TileKind: instance Generic Feature
+ Game.LambdaHack.Content.TileKind: instance Hashable Feature
+ Game.LambdaHack.Content.TileKind: instance Ord Feature
+ Game.LambdaHack.Content.TileKind: instance Read Feature
+ Game.LambdaHack.Content.TileKind: instance Show Feature
+ Game.LambdaHack.Server.HandleEffectServer: dropCStoreItem :: (MonadAtomic m, MonadServer m) => CStore -> ActorId -> Actor -> Bool -> ItemId -> ItemQuant -> m ()
+ Game.LambdaHack.Server.HandleEffectServer: effectAndDestroy :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> ItemId -> Container -> Bool -> [Effect] -> [Aspect Int] -> ItemQuant -> m ()
+ Game.LambdaHack.Server.HandleEffectServer: itemEffectCause :: (MonadAtomic m, MonadServer m) => ActorId -> Point -> Effect -> m Bool
+ Game.LambdaHack.Server.ItemServer: embedItemsInDungeon :: (MonadAtomic m, MonadServer m) => m ()
+ Game.LambdaHack.Server.ItemServer: rollItem :: (MonadAtomic m, MonadServer m) => LevelId -> Freqs ItemKind -> m (Maybe (ItemKnown, ItemFull, ItemSeed, GroupName ItemKind))
+ Game.LambdaHack.Server.MonadServer: debugPossiblyPrint :: MonadServer m => Text -> m ()
+ Game.LambdaHack.Server.MonadServer: debugPossiblyPrintAndExit :: MonadServer m => Text -> m ()
+ Game.LambdaHack.Server.MonadServer: serverPrint :: MonadServer m => Text -> m ()
+ Game.LambdaHack.Server.PeriodicServer: leadLevelSwitch :: (MonadAtomic m, MonadServer m) => m ()
- Game.LambdaHack.Atomic: SfxActivate :: !ActorId -> !ItemId -> !Int -> SfxAtomic
+ Game.LambdaHack.Atomic: SfxActivate :: !ActorId -> !ItemId -> !CStore -> SfxAtomic
- Game.LambdaHack.Atomic: SfxCatch :: !ActorId -> !ItemId -> SfxAtomic
+ Game.LambdaHack.Atomic: SfxCatch :: !ActorId -> !ItemId -> !CStore -> SfxAtomic
- Game.LambdaHack.Atomic: SfxCheck :: !ActorId -> !ItemId -> !Int -> SfxAtomic
+ Game.LambdaHack.Atomic: SfxCheck :: !ActorId -> !ItemId -> !CStore -> SfxAtomic
- Game.LambdaHack.Atomic: SfxEffect :: !FactionId -> !ActorId -> !(Effect Int) -> SfxAtomic
+ Game.LambdaHack.Atomic: SfxEffect :: !FactionId -> !ActorId -> !(Effect) -> SfxAtomic
- Game.LambdaHack.Atomic: SfxProject :: !ActorId -> !ItemId -> SfxAtomic
+ Game.LambdaHack.Atomic: SfxProject :: !ActorId -> !ItemId -> !CStore -> SfxAtomic
- Game.LambdaHack.Atomic: UpdCreateItem :: !ItemId -> !Item -> !Int -> !Container -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdCreateItem :: !ItemId -> !Item -> !ItemQuant -> !Container -> UpdAtomic
- Game.LambdaHack.Atomic: UpdDestroyItem :: !ItemId -> !Item -> !Int -> !Container -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdDestroyItem :: !ItemId -> !Item -> !ItemQuant -> !Container -> UpdAtomic
- Game.LambdaHack.Atomic: UpdLoseItem :: !ItemId -> !Item -> !Int -> !Container -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdLoseItem :: !ItemId -> !Item -> !ItemQuant -> !Container -> UpdAtomic
- Game.LambdaHack.Atomic: UpdRestart :: !FactionId -> !DiscoveryKind -> !FactionPers -> !State -> !DebugModeCli -> !GroupName -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdRestart :: !FactionId -> !DiscoveryKind -> !FactionPers -> !State -> !DebugModeCli -> !(GroupName ModeKind) -> UpdAtomic
- Game.LambdaHack.Atomic: UpdSpotItem :: !ItemId -> !Item -> !Int -> !Container -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdSpotItem :: !ItemId -> !Item -> !ItemQuant -> !Container -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: SfxActivate :: !ActorId -> !ItemId -> !Int -> SfxAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: SfxActivate :: !ActorId -> !ItemId -> !CStore -> SfxAtomic
- Game.LambdaHack.Atomic.CmdAtomic: SfxCatch :: !ActorId -> !ItemId -> SfxAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: SfxCatch :: !ActorId -> !ItemId -> !CStore -> SfxAtomic
- Game.LambdaHack.Atomic.CmdAtomic: SfxCheck :: !ActorId -> !ItemId -> !Int -> SfxAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: SfxCheck :: !ActorId -> !ItemId -> !CStore -> SfxAtomic
- Game.LambdaHack.Atomic.CmdAtomic: SfxEffect :: !FactionId -> !ActorId -> !(Effect Int) -> SfxAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: SfxEffect :: !FactionId -> !ActorId -> !(Effect) -> SfxAtomic
- Game.LambdaHack.Atomic.CmdAtomic: SfxProject :: !ActorId -> !ItemId -> SfxAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: SfxProject :: !ActorId -> !ItemId -> !CStore -> SfxAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdCreateItem :: !ItemId -> !Item -> !Int -> !Container -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdCreateItem :: !ItemId -> !Item -> !ItemQuant -> !Container -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdDestroyItem :: !ItemId -> !Item -> !Int -> !Container -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdDestroyItem :: !ItemId -> !Item -> !ItemQuant -> !Container -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdLoseItem :: !ItemId -> !Item -> !Int -> !Container -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdLoseItem :: !ItemId -> !Item -> !ItemQuant -> !Container -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdRestart :: !FactionId -> !DiscoveryKind -> !FactionPers -> !State -> !DebugModeCli -> !GroupName -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdRestart :: !FactionId -> !DiscoveryKind -> !FactionPers -> !State -> !DebugModeCli -> !(GroupName ModeKind) -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdSpotItem :: !ItemId -> !Item -> !Int -> !Container -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdSpotItem :: !ItemId -> !Item -> !ItemQuant -> !Container -> UpdAtomic
- Game.LambdaHack.Atomic.MonadStateWrite: insertItemActor :: MonadStateWrite m => ItemId -> Int -> ActorId -> CStore -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: insertItemActor :: MonadStateWrite m => ItemId -> ItemQuant -> ActorId -> CStore -> m ()
- Game.LambdaHack.Atomic.MonadStateWrite: insertItemContainer :: MonadStateWrite m => ItemId -> Int -> Container -> m ()
+ Game.LambdaHack.Atomic.MonadStateWrite: insertItemContainer :: MonadStateWrite m => ItemId -> ItemQuant -> Container -> m ()
- Game.LambdaHack.Client.AI.ConditionClient: benAvailableItems :: MonadClient m => ActorId -> (ItemFull -> Maybe Int -> Bool) -> [CStore] -> m [((Maybe (Int, (Int, Int)), (Int, CStore)), (ItemId, ItemFull))]
+ Game.LambdaHack.Client.AI.ConditionClient: benAvailableItems :: MonadClient m => ActorId -> (Maybe Int -> ItemFull -> Actor -> [ItemFull] -> Bool) -> [CStore] -> m [((Maybe (Int, Int), (Int, CStore)), (ItemId, ItemFull))]
- Game.LambdaHack.Client.AI.ConditionClient: benGroundItems :: MonadClient m => ActorId -> m [((Maybe (Int, (Int, Int)), (Int, CStore)), (ItemId, ItemFull))]
+ Game.LambdaHack.Client.AI.ConditionClient: benGroundItems :: MonadClient m => ActorId -> m [((Maybe (Int, Int), (Int, CStore)), (ItemId, ItemFull))]
- Game.LambdaHack.Client.AI.Preferences: effectToBenefit :: COps -> Actor -> [ItemFull] -> Faction -> Effect Int -> Int
+ Game.LambdaHack.Client.AI.Preferences: effectToBenefit :: COps -> Actor -> [ItemFull] -> Faction -> Effect -> Int
- Game.LambdaHack.Client.AI.Preferences: totalUsefulness :: COps -> Actor -> [ItemFull] -> Faction -> ItemFull -> Maybe (Int, (Int, Int))
+ Game.LambdaHack.Client.AI.Preferences: totalUsefulness :: COps -> Actor -> [ItemFull] -> Faction -> ItemFull -> Maybe (Int, Int)
- Game.LambdaHack.Client.CommonClient: actorSkillsClient :: MonadClient m => ActorId -> Maybe ActorId -> m Skills
+ Game.LambdaHack.Client.CommonClient: actorSkillsClient :: MonadClient m => ActorId -> m Skills
- Game.LambdaHack.Client.CommonClient: itemToFullClient :: MonadClient m => m (ItemId -> Int -> ItemFull)
+ Game.LambdaHack.Client.CommonClient: itemToFullClient :: MonadClient m => m (ItemId -> ItemQuant -> ItemFull)
- Game.LambdaHack.Client.State: StateClient :: !(Maybe TgtMode) -> !Target -> !Int -> !(EnumMap ActorId (Target, Maybe PathEtc)) -> !(EnumSet LevelId) -> !(EnumMap ActorId (Array BfsDistance, Point, Int, Maybe [Point])) -> !(EnumSet ActorId) -> !(Maybe RunParams) -> !Report -> !History -> !(EnumMap LevelId Time) -> ![CmdAtomic] -> !DiscoveryKind -> !DiscoveryEffect -> !FactionPers -> !StdGen -> !LastRecord -> ![KM] -> !Int -> !(Maybe ActorId) -> !FactionId -> !Bool -> !Bool -> !Bool -> !Bool -> !Bool -> !Int -> !ItemSlots -> !SlotChar -> !GroupName -> !DebugModeCli -> StateClient
+ Game.LambdaHack.Client.State: StateClient :: !(Maybe TgtMode) -> !Target -> !Int -> !(EnumMap ActorId (Target, Maybe PathEtc)) -> !(EnumSet LevelId) -> !(EnumMap ActorId (Array BfsDistance, Point, Int, Maybe [Point])) -> !(EnumSet ActorId) -> !(Maybe RunParams) -> !Report -> !History -> !(EnumMap LevelId Time) -> ![CmdAtomic] -> !DiscoveryKind -> !DiscoveryEffect -> !FactionPers -> !StdGen -> !LastRecord -> ![KM] -> !(EnumSet ActorId) -> !Int -> !(Maybe ActorId) -> !FactionId -> !Bool -> !Bool -> !Bool -> !Bool -> !Bool -> !Int -> !ItemSlots -> !SlotChar -> !(GroupName ModeKind) -> !EscAI -> !DebugModeCli -> StateClient
- Game.LambdaHack.Client.State: sgameMode :: StateClient -> !GroupName
+ Game.LambdaHack.Client.State: sgameMode :: StateClient -> !(GroupName ModeKind)
- Game.LambdaHack.Client.UI.Config: Config :: ![(KM, ([CmdCategory], HumanCmd))] -> ![(Int, (Text, Text))] -> !Bool -> !Bool -> !String -> !Int -> !Int -> !Bool -> !Bool -> Config
+ Game.LambdaHack.Client.UI.Config: Config :: ![(KM, ([CmdCategory], HumanCmd))] -> ![(Int, (Text, Text))] -> !Bool -> !Bool -> !String -> !Bool -> !Int -> !Int -> !Bool -> !Bool -> Config
- Game.LambdaHack.Client.UI.HandleHumanGlobalClient: gameRestartHuman :: MonadClientUI m => GroupName -> m (SlideOrCmd RequestUI)
+ Game.LambdaHack.Client.UI.HandleHumanGlobalClient: gameRestartHuman :: MonadClientUI m => GroupName ModeKind -> m (SlideOrCmd RequestUI)
- Game.LambdaHack.Client.UI.HandleHumanGlobalClient: moveItemHuman :: MonadClientUI m => [CStore] -> CStore -> Part -> Bool -> m (SlideOrCmd (RequestTimed AbMoveItem))
+ Game.LambdaHack.Client.UI.HandleHumanGlobalClient: moveItemHuman :: MonadClientUI m => [CStore] -> CStore -> (Maybe Part) -> Bool -> m (SlideOrCmd (RequestTimed AbMoveItem))
- Game.LambdaHack.Client.UI.HumanCmd: GameRestart :: !GroupName -> HumanCmd
+ Game.LambdaHack.Client.UI.HumanCmd: GameRestart :: !(GroupName ModeKind) -> HumanCmd
- Game.LambdaHack.Client.UI.HumanCmd: MoveItem :: ![CStore] -> !CStore -> !Part -> !Part -> !Bool -> HumanCmd
+ Game.LambdaHack.Client.UI.HumanCmd: MoveItem :: ![CStore] -> !CStore -> !(Maybe Part) -> !Part -> !Bool -> HumanCmd
- Game.LambdaHack.Client.UI.InventoryClient: getGroupItem :: MonadClientUI m => (Item -> Bool) -> Part -> Part -> [CStore] -> [CStore] -> m (SlideOrCmd ((ItemId, ItemFull), Container))
+ Game.LambdaHack.Client.UI.InventoryClient: getGroupItem :: MonadClientUI m => (ItemFull -> Bool) -> Text -> Text -> [CStore] -> [CStore] -> m (SlideOrCmd ((ItemId, ItemFull), Container))
- Game.LambdaHack.Client.UI.InventoryClient: getStoreItem :: MonadClientUI m => (Actor -> [ItemFull] -> Text) -> (Actor -> Text) -> Part -> Container -> m (SlideOrCmd ((ItemId, ItemFull), Container))
+ Game.LambdaHack.Client.UI.InventoryClient: getStoreItem :: MonadClientUI m => (Actor -> [ItemFull] -> Container -> Text) -> Container -> Bool -> m (SlideOrCmd ((ItemId, ItemFull), Container))
- Game.LambdaHack.Client.UI.MsgClient: itemOverlay :: MonadClient m => CStore -> ItemBag -> m Overlay
+ Game.LambdaHack.Client.UI.MsgClient: itemOverlay :: MonadClient m => Container -> LevelId -> ItemBag -> m Overlay
- Game.LambdaHack.Common.Actor: mapActorItems_ :: Monad m => (ItemId -> Int -> m a) -> Actor -> m ()
+ Game.LambdaHack.Common.Actor: mapActorItems_ :: Monad m => (ItemId -> ItemQuant -> m a) -> Actor -> m ()
- Game.LambdaHack.Common.ActorState: actorSkills :: ActorId -> Maybe ActorId -> [ItemFull] -> State -> Skills
+ Game.LambdaHack.Common.ActorState: actorSkills :: Maybe ActorId -> ActorId -> [ItemFull] -> State -> Skills
- Game.LambdaHack.Common.ActorState: bagAssocsK :: State -> ItemBag -> [(ItemId, (Item, Int))]
+ Game.LambdaHack.Common.ActorState: bagAssocsK :: State -> ItemBag -> [(ItemId, (Item, ItemQuant))]
- Game.LambdaHack.Common.ActorState: dispEnemy :: ActorId -> Maybe ActorId -> ActorId -> [ItemFull] -> State -> Bool
+ Game.LambdaHack.Common.ActorState: dispEnemy :: ActorId -> ActorId -> [ItemFull] -> State -> Bool
- Game.LambdaHack.Common.ActorState: itemToFull :: COps -> DiscoveryKind -> DiscoveryEffect -> ItemId -> Item -> Int -> ItemFull
+ Game.LambdaHack.Common.ActorState: itemToFull :: COps -> DiscoveryKind -> DiscoveryEffect -> ItemId -> Item -> ItemQuant -> ItemFull
- Game.LambdaHack.Common.ActorState: sharedAllOwned :: Actor -> State -> ItemBag
+ Game.LambdaHack.Common.ActorState: sharedAllOwned :: Bool -> Actor -> State -> ItemBag
- Game.LambdaHack.Common.ActorState: sharedAllOwnedFid :: FactionId -> State -> ItemBag
+ Game.LambdaHack.Common.ActorState: sharedAllOwnedFid :: Bool -> FactionId -> State -> ItemBag
- Game.LambdaHack.Common.ClientOptions: DebugModeCli :: !(Maybe String) -> !(Maybe Int) -> !Bool -> !Bool -> !(Maybe Bool) -> !Bool -> !Bool -> !Int -> !(Maybe String) -> !Bool -> !Bool -> !Bool -> DebugModeCli
+ Game.LambdaHack.Common.ClientOptions: DebugModeCli :: !(Maybe String) -> !(Maybe Bool) -> !(Maybe Int) -> !Bool -> !Bool -> !(Maybe Bool) -> !Bool -> !Bool -> !Int -> !(Maybe String) -> !Bool -> !Bool -> !Bool -> DebugModeCli
- Game.LambdaHack.Common.ContentDef: ContentDef :: (a -> Char) -> (a -> Text) -> (a -> Freqs) -> (a -> [Text]) -> ([a] -> [Text]) -> [a] -> ContentDef a
+ Game.LambdaHack.Common.ContentDef: ContentDef :: (a -> Char) -> (a -> Text) -> (a -> Freqs a) -> (a -> [Text]) -> ([a] -> [Text]) -> ![a] -> ContentDef a
- Game.LambdaHack.Common.ContentDef: content :: ContentDef a -> [a]
+ Game.LambdaHack.Common.ContentDef: content :: ContentDef a -> ![a]
- Game.LambdaHack.Common.ContentDef: getFreq :: ContentDef a -> a -> Freqs
+ Game.LambdaHack.Common.ContentDef: getFreq :: ContentDef a -> a -> Freqs a
- Game.LambdaHack.Common.EffectDescription: effectToSuffix :: Effect Int -> Text
+ Game.LambdaHack.Common.EffectDescription: effectToSuffix :: Effect -> Text
- Game.LambdaHack.Common.EffectDescription: kindEffectToSuffix :: Effect Dice -> Text
+ Game.LambdaHack.Common.EffectDescription: kindEffectToSuffix :: Effect -> Text
- Game.LambdaHack.Common.Faction: Faction :: !Text -> !Color -> !Player -> !Dipl -> !(Maybe Status) -> !(Maybe (ActorId, Maybe Target)) -> !ItemBag -> !(EnumMap (Id ItemKind) Int) -> Faction
+ Game.LambdaHack.Common.Faction: Faction :: !Text -> !Color -> !(Player Int) -> !Dipl -> !(Maybe Status) -> !(Maybe (ActorId, Maybe Target)) -> !ItemBag -> !(EnumMap (Id ItemKind) Int) -> Faction
- Game.LambdaHack.Common.Faction: Status :: !Outcome -> !Int -> !(Maybe GroupName) -> Status
+ Game.LambdaHack.Common.Faction: Status :: !Outcome -> !Int -> !(Maybe (GroupName ModeKind)) -> Status
- Game.LambdaHack.Common.Faction: automatePlayer :: Bool -> Player -> Player
+ Game.LambdaHack.Common.Faction: automatePlayer :: Bool -> Player a -> Player a
- Game.LambdaHack.Common.Faction: gplayer :: Faction -> !Player
+ Game.LambdaHack.Common.Faction: gplayer :: Faction -> !(Player Int)
- Game.LambdaHack.Common.Faction: stNewGame :: Status -> !(Maybe GroupName)
+ Game.LambdaHack.Common.Faction: stNewGame :: Status -> !(Maybe (GroupName ModeKind))
- Game.LambdaHack.Common.Item: ItemAspectEffect :: ![Aspect Int] -> ![Effect Int] -> ItemAspectEffect
+ Game.LambdaHack.Common.Item: ItemAspectEffect :: ![Aspect Int] -> ![Effect] -> ItemAspectEffect
- Game.LambdaHack.Common.Item: ItemDisco :: Id ItemKind -> ItemKind -> Maybe ItemAspectEffect -> ItemDisco
+ Game.LambdaHack.Common.Item: ItemDisco :: !(Id ItemKind) -> !ItemKind -> !(Maybe ItemAspectEffect) -> ItemDisco
- Game.LambdaHack.Common.Item: ItemFull :: !Item -> !Int -> !(Maybe ItemDisco) -> ItemFull
+ Game.LambdaHack.Common.Item: ItemFull :: !Item -> !Int -> !ItemTimer -> !(Maybe ItemDisco) -> ItemFull
- Game.LambdaHack.Common.Item: itemAE :: ItemDisco -> Maybe ItemAspectEffect
+ Game.LambdaHack.Common.Item: itemAE :: ItemDisco -> !(Maybe ItemAspectEffect)
- Game.LambdaHack.Common.Item: itemKind :: ItemDisco -> ItemKind
+ Game.LambdaHack.Common.Item: itemKind :: ItemDisco -> !ItemKind
- Game.LambdaHack.Common.Item: itemKindId :: ItemDisco -> Id ItemKind
+ Game.LambdaHack.Common.Item: itemKindId :: ItemDisco -> !(Id ItemKind)
- Game.LambdaHack.Common.Item: jeffects :: ItemAspectEffect -> ![Effect Int]
+ Game.LambdaHack.Common.Item: jeffects :: ItemAspectEffect -> ![Effect]
- Game.LambdaHack.Common.Item: type ItemBag = EnumMap ItemId Int
+ Game.LambdaHack.Common.Item: type ItemBag = EnumMap ItemId ItemQuant
- Game.LambdaHack.Common.ItemDescription: itemDesc :: CStore -> ItemFull -> Overlay
+ Game.LambdaHack.Common.ItemDescription: itemDesc :: Container -> LevelId -> Time -> ItemFull -> Overlay
- Game.LambdaHack.Common.ItemDescription: partItem :: CStore -> ItemFull -> (Part, Part)
+ Game.LambdaHack.Common.ItemDescription: partItem :: Container -> LevelId -> Time -> ItemFull -> (Part, Part)
- Game.LambdaHack.Common.ItemDescription: partItemAW :: CStore -> ItemFull -> Part
+ Game.LambdaHack.Common.ItemDescription: partItemAW :: Container -> LevelId -> Time -> ItemFull -> Part
- Game.LambdaHack.Common.ItemDescription: partItemN :: Bool -> Int -> CStore -> ItemFull -> (Part, Part)
+ Game.LambdaHack.Common.ItemDescription: partItemN :: Bool -> Int -> Container -> LevelId -> Time -> ItemFull -> (Part, Part)
- Game.LambdaHack.Common.ItemDescription: partItemWownW :: Part -> CStore -> ItemFull -> Part
+ Game.LambdaHack.Common.ItemDescription: partItemWownW :: Part -> Container -> LevelId -> Time -> ItemFull -> Part
- Game.LambdaHack.Common.ItemDescription: partItemWs :: Int -> CStore -> ItemFull -> Part
+ Game.LambdaHack.Common.ItemDescription: partItemWs :: Int -> Container -> LevelId -> Time -> ItemFull -> Part
- Game.LambdaHack.Common.ItemDescription: textAllAE :: Bool -> CStore -> ItemFull -> [Text]
+ Game.LambdaHack.Common.ItemDescription: textAllAE :: Bool -> Container -> ItemFull -> [Text]
- Game.LambdaHack.Common.ItemStrongest: strengthOnSmash :: ItemFull -> [Effect Int]
+ Game.LambdaHack.Common.ItemStrongest: strengthOnSmash :: ItemFull -> [Effect]
- Game.LambdaHack.Common.Kind: Ops :: (Id a -> a) -> (GroupName -> Id a) -> (GroupName -> (a -> Bool) -> Rnd (Maybe (Id a))) -> (forall b. (Id a -> a -> b -> b) -> b -> b) -> (forall b. GroupName -> (Int -> Id a -> a -> b -> b) -> b -> b) -> !(Id a, Id a) -> !(Maybe (Speedup a)) -> Ops a
+ Game.LambdaHack.Common.Kind: Ops :: (Id a -> a) -> (GroupName a -> Id a) -> (GroupName a -> (a -> Bool) -> Rnd (Maybe (Id a))) -> (forall b. (Id a -> a -> b -> b) -> b -> b) -> (forall b. GroupName a -> (Int -> Id a -> a -> b -> b) -> b -> b) -> !(Id a, Id a) -> !(Maybe (Speedup a)) -> Ops a
- Game.LambdaHack.Common.Kind: ofoldrGroup :: Ops a -> forall b. GroupName -> (Int -> Id a -> a -> b -> b) -> b -> b
+ Game.LambdaHack.Common.Kind: ofoldrGroup :: Ops a -> forall b. GroupName a -> (Int -> Id a -> a -> b -> b) -> b -> b
- Game.LambdaHack.Common.Kind: opick :: Ops a -> GroupName -> (a -> Bool) -> Rnd (Maybe (Id a))
+ Game.LambdaHack.Common.Kind: opick :: Ops a -> GroupName a -> (a -> Bool) -> Rnd (Maybe (Id a))
- Game.LambdaHack.Common.Kind: ouniqGroup :: Ops a -> GroupName -> Id a
+ Game.LambdaHack.Common.Kind: ouniqGroup :: Ops a -> GroupName a -> Id a
- Game.LambdaHack.Common.Level: Level :: !AbsDepth -> !ActorPrio -> !ItemFloor -> !TileMap -> !X -> !Y -> !SmellMap -> !Text -> !([Point], [Point]) -> !Int -> !Int -> !Time -> !Int -> !Freqs -> !Int -> !Freqs -> !Int -> !Int -> !Bool -> Level
+ Game.LambdaHack.Common.Level: Level :: !AbsDepth -> !ActorPrio -> !ItemFloor -> !ItemFloor -> !TileMap -> !X -> !Y -> !SmellMap -> !Text -> !([Point], [Point]) -> !Int -> !Int -> !Time -> !Int -> !(Freqs ItemKind) -> !Int -> !(Freqs ItemKind) -> !Int -> !Int -> !Bool -> Level
- Game.LambdaHack.Common.Level: lactorFreq :: Level -> !Freqs
+ Game.LambdaHack.Common.Level: lactorFreq :: Level -> !(Freqs ItemKind)
- Game.LambdaHack.Common.Level: litemFreq :: Level -> !Freqs
+ Game.LambdaHack.Common.Level: litemFreq :: Level -> !(Freqs ItemKind)
- Game.LambdaHack.Common.Misc: data GroupName
+ Game.LambdaHack.Common.Misc: data GroupName a
- Game.LambdaHack.Common.Misc: toGroupName :: Text -> GroupName
+ Game.LambdaHack.Common.Misc: toGroupName :: Text -> GroupName a
- Game.LambdaHack.Common.Misc: type Freqs = [(GroupName, Int)]
+ Game.LambdaHack.Common.Misc: type Freqs a = [(GroupName a, Int)]
- Game.LambdaHack.Common.Msg: addReport :: Report -> History -> History
+ Game.LambdaHack.Common.Msg: addReport :: History -> Time -> Report -> History
- Game.LambdaHack.Common.Msg: singletonHistory :: Report -> History
+ Game.LambdaHack.Common.Msg: singletonHistory :: Time -> Report -> History
- Game.LambdaHack.Common.Request: ReqUIGameRestart :: !ActorId -> !GroupName -> !Int -> ![(Int, (Text, Text))] -> RequestUI
+ Game.LambdaHack.Common.Request: ReqUIGameRestart :: !ActorId -> !(GroupName ModeKind) -> !Int -> ![(Int, (Text, Text))] -> RequestUI
- Game.LambdaHack.Common.Tile: causeEffects :: Ops TileKind -> Id TileKind -> [Effect Int]
+ Game.LambdaHack.Common.Tile: causeEffects :: Ops TileKind -> Id TileKind -> [Effect]
- Game.LambdaHack.Content.CaveKind: CaveKind :: !Char -> !Text -> !Freqs -> !X -> !Y -> !DiceXY -> !DiceXY -> !DiceXY -> !Dice -> !Dice -> !Rational -> !Rational -> !Int -> !Chance -> !Chance -> !Int -> !Int -> !Freqs -> !Dice -> !Freqs -> !Freqs -> !Bool -> !GroupName -> !GroupName -> !GroupName -> !GroupName -> !GroupName -> !GroupName -> !GroupName -> CaveKind
+ Game.LambdaHack.Content.CaveKind: CaveKind :: !Char -> !Text -> !(Freqs CaveKind) -> !X -> !Y -> !DiceXY -> !DiceXY -> !DiceXY -> !Dice -> !Dice -> !Rational -> !Rational -> !Int -> !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) -> CaveKind
- Game.LambdaHack.Content.CaveKind: cactorFreq :: CaveKind -> !Freqs
+ Game.LambdaHack.Content.CaveKind: cactorFreq :: CaveKind -> !(Freqs ItemKind)
- Game.LambdaHack.Content.CaveKind: cdarkCorTile :: CaveKind -> !GroupName
+ Game.LambdaHack.Content.CaveKind: cdarkCorTile :: CaveKind -> !(GroupName TileKind)
- Game.LambdaHack.Content.CaveKind: cdefTile :: CaveKind -> !GroupName
+ Game.LambdaHack.Content.CaveKind: cdefTile :: CaveKind -> !(GroupName TileKind)
- Game.LambdaHack.Content.CaveKind: cfillerTile :: CaveKind -> !GroupName
+ Game.LambdaHack.Content.CaveKind: cfillerTile :: CaveKind -> !(GroupName TileKind)
- Game.LambdaHack.Content.CaveKind: cfreq :: CaveKind -> !Freqs
+ Game.LambdaHack.Content.CaveKind: cfreq :: CaveKind -> !(Freqs CaveKind)
- Game.LambdaHack.Content.CaveKind: citemFreq :: CaveKind -> !Freqs
+ Game.LambdaHack.Content.CaveKind: citemFreq :: CaveKind -> !(Freqs ItemKind)
- Game.LambdaHack.Content.CaveKind: clegendDarkTile :: CaveKind -> !GroupName
+ Game.LambdaHack.Content.CaveKind: clegendDarkTile :: CaveKind -> !(GroupName TileKind)
- Game.LambdaHack.Content.CaveKind: clegendLitTile :: CaveKind -> !GroupName
+ Game.LambdaHack.Content.CaveKind: clegendLitTile :: CaveKind -> !(GroupName TileKind)
- Game.LambdaHack.Content.CaveKind: clitCorTile :: CaveKind -> !GroupName
+ Game.LambdaHack.Content.CaveKind: clitCorTile :: CaveKind -> !(GroupName TileKind)
- Game.LambdaHack.Content.CaveKind: couterFenceTile :: CaveKind -> !GroupName
+ Game.LambdaHack.Content.CaveKind: couterFenceTile :: CaveKind -> !(GroupName TileKind)
- Game.LambdaHack.Content.CaveKind: cplaceFreq :: CaveKind -> !Freqs
+ Game.LambdaHack.Content.CaveKind: cplaceFreq :: CaveKind -> !(Freqs PlaceKind)
- Game.LambdaHack.Content.ItemKind: ItemKind :: !Char -> !Text -> !Freqs -> ![Flavour] -> !Dice -> !Rarity -> !Part -> !Int -> ![Aspect Dice] -> ![Effect Dice] -> ![Feature] -> !Text -> ![(GroupName, CStore)] -> ItemKind
+ Game.LambdaHack.Content.ItemKind: ItemKind :: !Char -> !Text -> !(Freqs ItemKind) -> ![Flavour] -> !Dice -> !Rarity -> !Part -> !Int -> ![Aspect Dice] -> ![Effect] -> ![Feature] -> !Text -> ![(GroupName ItemKind, CStore)] -> ItemKind
- Game.LambdaHack.Content.ItemKind: ieffects :: ItemKind -> ![Effect Dice]
+ Game.LambdaHack.Content.ItemKind: ieffects :: ItemKind -> ![Effect]
- Game.LambdaHack.Content.ItemKind: ifreq :: ItemKind -> !Freqs
+ Game.LambdaHack.Content.ItemKind: ifreq :: ItemKind -> !(Freqs ItemKind)
- Game.LambdaHack.Content.ItemKind: ikit :: ItemKind -> ![(GroupName, CStore)]
+ Game.LambdaHack.Content.ItemKind: ikit :: ItemKind -> ![(GroupName ItemKind, CStore)]
- Game.LambdaHack.Content.ModeKind: ModeKind :: !Char -> !Text -> !Freqs -> !Roster -> !Caves -> !Text -> ModeKind
+ Game.LambdaHack.Content.ModeKind: ModeKind :: !Char -> !Text -> !(Freqs ModeKind) -> !Roster -> !Caves -> !Text -> ModeKind
- Game.LambdaHack.Content.ModeKind: Player :: !Text -> !GroupName -> !Skills -> !Bool -> !Bool -> !Bool -> !Bool -> !Tactic -> !Int -> !Int -> !LeaderMode -> !Bool -> Player
+ Game.LambdaHack.Content.ModeKind: Player :: !Text -> !(GroupName ItemKind) -> !Skills -> !Bool -> !Bool -> !Bool -> !Bool -> !Tactic -> !a -> !a -> !LeaderMode -> !Bool -> Player a
- Game.LambdaHack.Content.ModeKind: Roster :: ![Player] -> ![(Text, Text)] -> ![(Text, Text)] -> Roster
+ Game.LambdaHack.Content.ModeKind: Roster :: ![Player Dice] -> ![(Text, Text)] -> ![(Text, Text)] -> Roster
- Game.LambdaHack.Content.ModeKind: data Player
+ Game.LambdaHack.Content.ModeKind: data Player a
- Game.LambdaHack.Content.ModeKind: fcanEscape :: Player -> !Bool
+ Game.LambdaHack.Content.ModeKind: fcanEscape :: Player a -> !Bool
- Game.LambdaHack.Content.ModeKind: fentryLevel :: Player -> !Int
+ Game.LambdaHack.Content.ModeKind: fentryLevel :: Player a -> !a
- Game.LambdaHack.Content.ModeKind: fgroup :: Player -> !GroupName
+ Game.LambdaHack.Content.ModeKind: fgroup :: Player a -> !(GroupName ItemKind)
- Game.LambdaHack.Content.ModeKind: fhasGender :: Player -> !Bool
+ Game.LambdaHack.Content.ModeKind: fhasGender :: Player a -> !Bool
- Game.LambdaHack.Content.ModeKind: fhasNumbers :: Player -> !Bool
+ Game.LambdaHack.Content.ModeKind: fhasNumbers :: Player a -> !Bool
- Game.LambdaHack.Content.ModeKind: fhasUI :: Player -> !Bool
+ Game.LambdaHack.Content.ModeKind: fhasUI :: Player a -> !Bool
- Game.LambdaHack.Content.ModeKind: finitialActors :: Player -> !Int
+ Game.LambdaHack.Content.ModeKind: finitialActors :: Player a -> !a
- Game.LambdaHack.Content.ModeKind: fleaderMode :: Player -> !LeaderMode
+ Game.LambdaHack.Content.ModeKind: fleaderMode :: Player a -> !LeaderMode
- Game.LambdaHack.Content.ModeKind: fname :: Player -> !Text
+ Game.LambdaHack.Content.ModeKind: fname :: Player a -> !Text
- Game.LambdaHack.Content.ModeKind: fneverEmpty :: Player -> !Bool
+ Game.LambdaHack.Content.ModeKind: fneverEmpty :: Player a -> !Bool
- Game.LambdaHack.Content.ModeKind: fskillsOther :: Player -> !Skills
+ Game.LambdaHack.Content.ModeKind: fskillsOther :: Player a -> !Skills
- Game.LambdaHack.Content.ModeKind: ftactic :: Player -> !Tactic
+ Game.LambdaHack.Content.ModeKind: ftactic :: Player a -> !Tactic
- Game.LambdaHack.Content.ModeKind: mfreq :: ModeKind -> !Freqs
+ Game.LambdaHack.Content.ModeKind: mfreq :: ModeKind -> !(Freqs ModeKind)
- Game.LambdaHack.Content.ModeKind: rosterList :: Roster -> ![Player]
+ Game.LambdaHack.Content.ModeKind: rosterList :: Roster -> ![Player Dice]
- Game.LambdaHack.Content.ModeKind: type Caves = IntMap (GroupName, Maybe Bool)
+ Game.LambdaHack.Content.ModeKind: type Caves = IntMap (GroupName CaveKind, Maybe Bool)
- Game.LambdaHack.Content.PlaceKind: PlaceKind :: !Char -> !Text -> !Freqs -> !Rarity -> !Cover -> !Fence -> ![Text] -> ![(Char, GroupName)] -> PlaceKind
+ Game.LambdaHack.Content.PlaceKind: PlaceKind :: !Char -> !Text -> !(Freqs PlaceKind) -> !Rarity -> !Cover -> !Fence -> ![Text] -> ![(Char, GroupName TileKind)] -> PlaceKind
- Game.LambdaHack.Content.PlaceKind: pfreq :: PlaceKind -> !Freqs
+ Game.LambdaHack.Content.PlaceKind: pfreq :: PlaceKind -> !(Freqs PlaceKind)
- Game.LambdaHack.Content.PlaceKind: poverride :: PlaceKind -> ![(Char, GroupName)]
+ Game.LambdaHack.Content.PlaceKind: poverride :: PlaceKind -> ![(Char, GroupName TileKind)]
- Game.LambdaHack.Content.RuleKind: RuleKind :: !Char -> !Text -> !Freqs -> !(Maybe (Point -> Point -> Bool)) -> !(Maybe (Point -> Point -> Bool)) -> !Text -> (FilePath -> IO FilePath) -> !Version -> !FilePath -> !String -> !Text -> !Bool -> !FovMode -> !Int -> !Int -> !FilePath -> !String -> !Bool -> !Int -> RuleKind
+ Game.LambdaHack.Content.RuleKind: RuleKind :: !Char -> !Text -> !(Freqs RuleKind) -> !(Maybe (Point -> Point -> Bool)) -> !(Maybe (Point -> Point -> Bool)) -> !Text -> (FilePath -> IO FilePath) -> !Version -> !FilePath -> !String -> !Text -> !Bool -> !FovMode -> !Int -> !Int -> !FilePath -> !String -> !Bool -> !Int -> RuleKind
- Game.LambdaHack.Content.RuleKind: rfreq :: RuleKind -> !Freqs
+ Game.LambdaHack.Content.RuleKind: rfreq :: RuleKind -> !(Freqs RuleKind)
- Game.LambdaHack.Content.TileKind: TileKind :: !Char -> !Text -> !Freqs -> !Color -> !Color -> ![Feature] -> TileKind
+ Game.LambdaHack.Content.TileKind: TileKind :: !Char -> !Text -> !(Freqs TileKind) -> !Color -> !Color -> ![Feature] -> TileKind
- Game.LambdaHack.Content.TileKind: tfreq :: TileKind -> !Freqs
+ Game.LambdaHack.Content.TileKind: tfreq :: TileKind -> !(Freqs TileKind)
- Game.LambdaHack.Server.CommonServer: actorSkillsServer :: MonadServer m => ActorId -> Maybe ActorId -> m Skills
+ Game.LambdaHack.Server.CommonServer: actorSkillsServer :: MonadServer m => ActorId -> m Skills
- Game.LambdaHack.Server.CommonServer: addActor :: (MonadAtomic m, MonadServer m) => GroupName -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Text -> Time -> m (Maybe ActorId)
+ Game.LambdaHack.Server.CommonServer: addActor :: (MonadAtomic m, MonadServer m) => GroupName ItemKind -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Text -> Time -> m (Maybe ActorId)
- Game.LambdaHack.Server.CommonServer: addActorIid :: (MonadAtomic m, MonadServer m) => ItemId -> ItemFull -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Text -> Time -> m (Maybe ActorId)
+ Game.LambdaHack.Server.CommonServer: addActorIid :: (MonadAtomic m, MonadServer m) => ItemId -> ItemFull -> Bool -> FactionId -> Point -> LevelId -> (Actor -> Actor) -> Text -> Time -> m (Maybe ActorId)
- Game.LambdaHack.Server.DungeonGen: findGenerator :: COps -> LevelId -> LevelId -> LevelId -> AbsDepth -> Int -> (GroupName, Maybe Bool) -> Rnd Level
+ Game.LambdaHack.Server.DungeonGen: findGenerator :: COps -> LevelId -> LevelId -> LevelId -> AbsDepth -> Int -> (GroupName CaveKind, Maybe Bool) -> Rnd Level
- Game.LambdaHack.Server.DungeonGen: levelFromCaveKind :: COps -> CaveKind -> AbsDepth -> TileMap -> ([Point], [Point]) -> Int -> Freqs -> Int -> Freqs -> Int -> Bool -> Level
+ Game.LambdaHack.Server.DungeonGen: levelFromCaveKind :: COps -> CaveKind -> AbsDepth -> TileMap -> ([Point], [Point]) -> Int -> Freqs ItemKind -> Int -> Freqs ItemKind -> Int -> Bool -> Level
- Game.LambdaHack.Server.DungeonGen.Place: Place :: !(Id PlaceKind) -> !Area -> !Bool -> !GroupName -> !(Id TileKind) -> !(Id TileKind) -> !(Id TileKind) -> Place
+ Game.LambdaHack.Server.DungeonGen.Place: Place :: !(Id PlaceKind) -> !Area -> !Bool -> !(GroupName TileKind) -> !(Id TileKind) -> !(Id TileKind) -> !(Id TileKind) -> Place
- Game.LambdaHack.Server.DungeonGen.Place: buildFenceRnd :: COps -> GroupName -> Area -> Rnd TileMapEM
+ Game.LambdaHack.Server.DungeonGen.Place: buildFenceRnd :: COps -> GroupName TileKind -> Area -> Rnd TileMapEM
- Game.LambdaHack.Server.DungeonGen.Place: qlegend :: Place -> !GroupName
+ Game.LambdaHack.Server.DungeonGen.Place: qlegend :: Place -> !(GroupName TileKind)
- Game.LambdaHack.Server.Fov: type PersLit = EnumMap LevelId (PerceptionLit, EnumMap FactionId ActorEqpBody)
+ Game.LambdaHack.Server.Fov: type PersLit = EnumMap LevelId (PerceptionLit, EnumMap FactionId ActorEqpBody, Array Bool)
- Game.LambdaHack.Server.HandleEffectServer: itemEffectAndDestroy :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> ItemId -> ItemFull -> CStore -> m ()
+ Game.LambdaHack.Server.HandleEffectServer: itemEffectAndDestroy :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> ItemId -> Container -> m ()
- Game.LambdaHack.Server.HandleRequestServer: handleRequestAI :: (MonadAtomic m, MonadServer m) => FactionId -> ActorId -> RequestAI -> m ActorId
+ Game.LambdaHack.Server.HandleRequestServer: handleRequestAI :: (MonadAtomic m, MonadServer m) => FactionId -> ActorId -> RequestAI -> m (ActorId, m ())
- Game.LambdaHack.Server.HandleRequestServer: handleRequestUI :: (MonadAtomic m, MonadServer m) => FactionId -> RequestUI -> m (Maybe ActorId)
+ Game.LambdaHack.Server.HandleRequestServer: handleRequestUI :: (MonadAtomic m, MonadServer m) => FactionId -> RequestUI -> m (Maybe ActorId, m ())
- Game.LambdaHack.Server.ItemRev: newItem :: COps -> FlavourMap -> DiscoveryKindRev -> Freqs -> LevelId -> AbsDepth -> AbsDepth -> Rnd (Maybe (ItemKnown, ItemFull, ItemSeed, Int, GroupName))
+ Game.LambdaHack.Server.ItemRev: newItem :: COps -> FlavourMap -> DiscoveryKindRev -> Freqs ItemKind -> LevelId -> AbsDepth -> AbsDepth -> Rnd (Maybe (ItemKnown, ItemFull, ItemSeed, GroupName ItemKind))
- Game.LambdaHack.Server.ItemServer: itemToFullServer :: MonadServer m => m (ItemId -> Int -> ItemFull)
+ Game.LambdaHack.Server.ItemServer: itemToFullServer :: MonadServer m => m (ItemId -> ItemQuant -> ItemFull)
- Game.LambdaHack.Server.ItemServer: mapActorCStore_ :: MonadServer m => CStore -> (ItemId -> Int -> m a) -> Actor -> m ()
+ Game.LambdaHack.Server.ItemServer: mapActorCStore_ :: MonadServer m => CStore -> (ItemId -> ItemQuant -> m a) -> Actor -> m ()
- Game.LambdaHack.Server.ItemServer: rollAndRegisterItem :: (MonadAtomic m, MonadServer m) => LevelId -> Freqs -> Container -> Bool -> m (Maybe (ItemId, (ItemFull, GroupName)))
+ Game.LambdaHack.Server.ItemServer: rollAndRegisterItem :: (MonadAtomic m, MonadServer m) => LevelId -> Freqs ItemKind -> Container -> Bool -> Maybe Int -> m (Maybe (ItemId, (ItemFull, GroupName ItemKind)))
- Game.LambdaHack.Server.PeriodicServer: addAnyActor :: (MonadAtomic m, MonadServer m) => Freqs -> LevelId -> Time -> Maybe Point -> m (Maybe ActorId)
+ Game.LambdaHack.Server.PeriodicServer: addAnyActor :: (MonadAtomic m, MonadServer m) => Freqs ItemKind -> LevelId -> Time -> Maybe Point -> m (Maybe ActorId)
- Game.LambdaHack.Server.State: DebugModeSer :: !Bool -> !Bool -> !Bool -> !Bool -> !Bool -> !GroupName -> !Bool -> !(Maybe Int) -> !(Maybe StdGen) -> !(Maybe StdGen) -> !(Maybe FovMode) -> !Bool -> !Int -> !Bool -> !(Maybe String) -> !Bool -> !DebugModeCli -> DebugModeSer
+ Game.LambdaHack.Server.State: DebugModeSer :: !Bool -> !Bool -> !Bool -> !Bool -> !Bool -> !(Maybe (GroupName ModeKind)) -> !Bool -> !(Maybe Int) -> !(Maybe StdGen) -> !(Maybe StdGen) -> !(Maybe FovMode) -> !Bool -> !Int -> !Bool -> !(Maybe String) -> !Bool -> !DebugModeCli -> DebugModeSer
- Game.LambdaHack.Server.State: sgameMode :: DebugModeSer -> !GroupName
+ Game.LambdaHack.Server.State: sgameMode :: DebugModeSer -> !(Maybe (GroupName ModeKind))

Files

CHANGELOG.md view
@@ -1,3 +1,15 @@+## [v0.4.100.0, aka 'The last thaw'](https://github.com/LambdaHack/LambdaHack/compare/v0.4.99.0..v0.4.100.0)++- unexpectedly thaw and freeze again v0.5.0.0 of LambdaHack content API+- unexpectedly implement timeouts and temporary effects easily without FRP+- make a couple of skill levels meaningful and tweak skills of some actors+- make AI prefer exploration of easier levels+- permit overfull HP and Calm+- let non-projectile actors block view+- make colorful characters bold (if it resizes your fonts, turn off via colorIsBold = False in config file or --noColorIsBold on commandline)+- start the game with a screensaver safari mode+- add i386 Linux and Windows compilation targets to Makefile+ ## [v0.4.99.0, aka 'Player escapes'](https://github.com/LambdaHack/LambdaHack/compare/v0.2.14...v0.4.99.0)  - balance the example game content a bit (campaign still unbalanced)
Game/LambdaHack/Atomic/BroadcastAtomicWrite.hs view
@@ -167,10 +167,14 @@       inActor = map fActor inPrio       -- Items.       pMaybe p = maybe Nothing (\x -> Just (p, x))-      inFloor = mapMaybe (\p -> pMaybe p $ EM.lookup p (lfloor lvl)) inFov-      fItem p (iid, k) = UpdSpotItem iid (getItemBody iid s) k (CFloor lid p)-      fBag (p, bag) = map (fItem p) $ EM.assocs bag-      inItem = concatMap fBag inFloor+      inContainer fc itemFloor =+        let inItem = mapMaybe (\p -> pMaybe p $ EM.lookup p itemFloor) inFov+            fItem p (iid, kit) =+              UpdSpotItem 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.       inTileMap = map (\p -> (p, hideTile (scops s) lvl p)) inFov       atomicTile = if null inTileMap then [] else [UpdSpotTile lid inTileMap]@@ -178,4 +182,4 @@       inSmellFov = ES.elems $ smellVisible inPer       inSm = mapMaybe (\p -> pMaybe p $ EM.lookup p (lsmell lvl)) inSmellFov       atomicSmell = if null inSm then [] else [UpdSpotSmell lid inSm]-  in inItem ++ inActor ++ atomicTile ++ atomicSmell+  in inFloor ++ inEmbed ++ inActor ++ atomicTile ++ atomicSmell
Game/LambdaHack/Atomic/CmdAtomic.hs view
@@ -24,9 +24,8 @@ import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ClientOptions import qualified Game.LambdaHack.Common.Color as Color-import qualified Game.LambdaHack.Common.Effect as Effect+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Common.Faction-import qualified Game.LambdaHack.Common.Feature as F import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind import Game.LambdaHack.Common.Level@@ -37,9 +36,10 @@ import Game.LambdaHack.Common.State import Game.LambdaHack.Common.Time import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind+import Game.LambdaHack.Content.ItemKind (ItemKind) import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.TileKind+import Game.LambdaHack.Content.TileKind (TileKind)+import qualified Game.LambdaHack.Content.TileKind as TK  data CmdAtomic =     UpdAtomic !UpdAtomic@@ -53,12 +53,12 @@   -- Create/destroy actors and items.     UpdCreateActor !ActorId !Actor ![(ItemId, Item)]   | UpdDestroyActor !ActorId !Actor ![(ItemId, Item)]-  | UpdCreateItem !ItemId !Item !Int !Container-  | UpdDestroyItem !ItemId !Item !Int !Container+  | UpdCreateItem !ItemId !Item !ItemQuant !Container+  | UpdDestroyItem !ItemId !Item !ItemQuant !Container   | UpdSpotActor !ActorId !Actor ![(ItemId, Item)]   | UpdLoseActor !ActorId !Actor ![(ItemId, Item)]-  | UpdSpotItem !ItemId !Item !Int !Container-  | UpdLoseItem !ItemId !Item !Int !Container+  | UpdSpotItem !ItemId !Item !ItemQuant !Container+  | UpdLoseItem !ItemId !Item !ItemQuant !Container   -- Move actors and items.   | UpdMoveActor !ActorId !Point !Point   | UpdWaitActor !ActorId !Bool@@ -69,9 +69,7 @@   | UpdRefillHP !ActorId !Int64   | UpdRefillCalm !ActorId !Int64   | UpdOldFidActor !ActorId !FactionId !FactionId-  | UpdTrajectory !ActorId-                       !(Maybe ([Vector], Speed))-                       !(Maybe ([Vector], Speed))+  | UpdTrajectory !ActorId !(Maybe ([Vector], Speed)) !(Maybe ([Vector], Speed))   | UpdColorActor !ActorId !Color.Color !Color.Color   -- Change faction attributes.   | UpdQuitFaction !FactionId !(Maybe Actor) !(Maybe Status) !(Maybe Status)@@ -92,6 +90,7 @@   | UpdSpotSmell !LevelId ![(Point, Time)]   | UpdLoseSmell !LevelId ![(Point, Time)]   -- Assorted.+  | UpdTimeItem !ItemId !Container !ItemTimer !ItemTimer   | UpdAgeGame !(Delta Time) ![LevelId]   | UpdDiscover !LevelId !Point !ItemId !(Kind.Id ItemKind) !ItemSeed   | UpdCover !LevelId !Point !ItemId !(Kind.Id ItemKind) !ItemSeed@@ -101,7 +100,7 @@   | UpdCoverSeed !LevelId !Point !ItemId !ItemSeed   | UpdPerception !LevelId !Perception !Perception   | UpdRestart-      !FactionId !DiscoveryKind !FactionPers !State !DebugModeCli !GroupName+      !FactionId !DiscoveryKind !FactionPers !State !DebugModeCli !(GroupName ModeKind)   | UpdRestartServer !State   | UpdResume !FactionId !FactionPers   | UpdResumeServer !State@@ -117,13 +116,13 @@ data SfxAtomic =     SfxStrike !ActorId !ActorId !ItemId !HitAtomic   | SfxRecoil !ActorId !ActorId !ItemId !HitAtomic-  | SfxProject !ActorId !ItemId-  | SfxCatch !ActorId !ItemId-  | SfxActivate !ActorId !ItemId !Int-  | SfxCheck !ActorId !ItemId !Int-  | SfxTrigger !ActorId !Point !F.Feature-  | SfxShun !ActorId !Point !F.Feature-  | SfxEffect !FactionId !ActorId !(Effect.Effect Int)+  | SfxProject !ActorId !ItemId !CStore+  | SfxCatch !ActorId !ItemId !CStore+  | SfxActivate !ActorId !ItemId !CStore+  | SfxCheck !ActorId !ItemId !CStore+  | SfxTrigger !ActorId !Point !TK.Feature+  | SfxShun !ActorId !Point !TK.Feature+  | SfxEffect !FactionId !ActorId !(IK.Effect)   | SfxMsgFid !FactionId !Msg   | SfxMsgAll !Msg   | SfxActorStart !ActorId@@ -174,6 +173,7 @@   UpdAlterSmell lid p fromSm toSm -> Just $ UpdAlterSmell lid p toSm fromSm   UpdSpotSmell lid sms -> Just $ UpdLoseSmell lid sms   UpdLoseSmell lid sms -> Just $ UpdSpotSmell lid sms+  UpdTimeItem iid c fromIt toIt -> Just $ UpdTimeItem iid c toIt fromIt   UpdAgeGame delta lids -> Just $ UpdAgeGame (timeDeltaReverse delta) lids   UpdDiscover lid p iid ik seed -> Just $ UpdCover lid p iid ik seed   UpdCover lid p iid ik seed -> Just $ UpdDiscover lid p iid ik seed@@ -195,10 +195,10 @@ undoSfxAtomic cmd = case cmd of   SfxStrike source target iid b -> SfxRecoil source target iid b   SfxRecoil source target iid b -> SfxStrike source target iid b-  SfxProject aid iid -> SfxCatch aid iid-  SfxCatch aid iid -> SfxProject aid iid-  SfxActivate aid iid k -> SfxCheck aid iid k-  SfxCheck aid iid k -> SfxActivate aid iid k+  SfxProject aid iid cstore -> SfxCatch aid iid cstore+  SfxCatch aid iid cstore -> SfxProject aid iid cstore+  SfxActivate aid iid cstore -> SfxCheck aid iid cstore+  SfxCheck aid iid cstore -> SfxActivate aid iid cstore   SfxTrigger aid p feat -> SfxShun aid p feat   SfxShun aid p feat -> SfxTrigger aid p feat   SfxEffect{} -> cmd  -- not ideal?
Game/LambdaHack/Atomic/HandleAtomicWrite.hs view
@@ -32,9 +32,9 @@ import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Time import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind-import Game.LambdaHack.Content.ModeKind as ModeKind-import Game.LambdaHack.Content.TileKind as TileKind+import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.ModeKind+import Game.LambdaHack.Content.TileKind (TileKind)  -- | The game-state semantics of atomic game commands. -- Special effects (@SfxAtomic@) don't modify state.@@ -47,12 +47,12 @@ handleUpdAtomic cmd = case cmd of   UpdCreateActor aid body ais -> updCreateActor aid body ais   UpdDestroyActor aid body ais -> updDestroyActor aid body ais-  UpdCreateItem iid item k c -> updCreateItem iid item k c-  UpdDestroyItem iid item k c -> updDestroyItem iid item k c+  UpdCreateItem iid item kit c -> updCreateItem iid item kit c+  UpdDestroyItem iid item kit c -> updDestroyItem iid item kit c   UpdSpotActor aid body ais -> updCreateActor aid body ais   UpdLoseActor aid body ais -> updDestroyActor aid body ais-  UpdSpotItem iid item k c -> updCreateItem iid item k c-  UpdLoseItem iid item k c -> updDestroyItem iid item k c+  UpdSpotItem iid item kit c -> updCreateItem iid item kit c+  UpdLoseItem iid item kit c -> updDestroyItem iid item kit c   UpdMoveActor aid fromP toP -> updMoveActor aid fromP toP   UpdWaitActor aid toWait -> updWaitActor aid toWait   UpdDisplaceActor source target -> updDisplaceActor source target@@ -80,6 +80,7 @@   UpdAlterSmell lid p fromSm toSm -> updAlterSmell lid p fromSm toSm   UpdSpotSmell lid sms -> updSpotSmell lid sms   UpdLoseSmell lid sms -> updLoseSmell lid sms+  UpdTimeItem iid c fromIt toIt -> updTimeItem iid c fromIt toIt   UpdAgeGame t lids -> updAgeGame t lids   UpdDiscover{} -> return ()      -- We can't keep dicovered data in State,   UpdCover{} -> return ()         -- because server saves all atomic commands@@ -163,8 +164,8 @@ -- | Create a few copies of an item that is already registered for the dungeon -- (in @sitemRev@ field of @StateServer@). updCreateItem :: MonadStateWrite m-              => ItemId -> Item -> Int -> Container -> m ()-updCreateItem iid item k c = assert (k > 0) $ do+              => 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.@@ -172,12 +173,13 @@         assert (itemsMatch item1 item2)                item2 -- keep the first found level   modifyState $ updateItemD $ EM.insertWith f iid item-  insertItemContainer iid k c+  insertItemContainer iid kit c +-- TODO: verify kit -- | Destroy some copies (possibly not all) of an item. updDestroyItem :: MonadStateWrite m-               => ItemId -> Item -> Int -> Container -> m ()-updDestroyItem iid item k c = assert (k > 0) $ do+               => ItemId -> Item -> ItemQuant -> Container -> m ()+updDestroyItem iid item (k, _) c = assert (k > 0) $ do   -- 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@.@@ -214,8 +216,12 @@             => ItemId -> Int -> ActorId -> CStore -> CStore             -> m () updMoveItem iid k aid c1 c2 = assert (k > 0 && c1 /= c2) $ do-  deleteItemActor iid k aid c1-  insertItemActor iid k aid c2+  bag <- getsState $ getActorBag aid c1+  case iid `EM.lookup` bag of+    Nothing -> assert `failure` (iid, k, aid, c1, c2)+    Just (_, it) -> do+      deleteItemActor iid k aid c1+      insertItemActor iid (k, take k it) aid c2  -- TODO: optimize (a single call to updatePrio is enough) updAgeActor :: MonadStateWrite m => ActorId -> Delta Time -> m ()@@ -445,6 +451,18 @@       upd m = foldr f m sms   updateLevel lid $ updateSmell upd +updTimeItem :: MonadStateWrite m+            => ItemId -> Container -> ItemTimer -> ItemTimer+            -> m ()+updTimeItem iid c fromIt toIt = assert (fromIt /= toIt) $ do+  bag <- getsState $ getCBag c+  case iid `EM.lookup` bag of+    Just (k, it) -> do+      assert (fromIt == it `blame` (k, it, iid, c, fromIt, toIt)) skip+      deleteItemContainer iid k c+      insertItemContainer iid (k, toIt) c+    Nothing -> assert `failure` (bag, iid, c, fromIt, toIt)+ -- | Age the game. -- -- TODO: It leaks information that there is activity on various level,@@ -459,8 +477,7 @@ ageLevel delta lid =   updateLevel lid $ \lvl -> lvl {ltime = timeShift (ltime lvl) delta} -updRestart :: MonadStateWrite m-           => State -> m ()+updRestart :: MonadStateWrite m => State -> m () updRestart = putState  updRestartServer :: MonadStateWrite m => State -> m ()
Game/LambdaHack/Atomic/MonadStateWrite.hs view
@@ -31,6 +31,10 @@ updateFloor :: (ItemFloor -> ItemFloor) -> Level -> Level updateFloor f lvl = lvl {lfloor = f (lfloor lvl)} +-- | Update the items embedded in a tile on the level.+updateEmbed :: (ItemFloor -> ItemFloor) -> Level -> Level+updateEmbed f lvl = lvl {lembed = f (lembed lvl)}+ -- | Update the tile map. updateTile :: (TileMap -> TileMap) -> Level -> Level updateTile f lvl = lvl {ltile = f (ltile lvl)}@@ -56,64 +60,74 @@   modifyState $ updateFactionD $ EM.alter alt fid  insertItemContainer :: MonadStateWrite m-                    => ItemId -> Int -> Container -> m ()-insertItemContainer iid k c = case c of-  CFloor lid pos -> insertItemFloor iid k lid pos-  CActor aid store -> insertItemActor iid k aid store+                    => ItemId -> ItemQuant -> Container -> m ()+insertItemContainer iid kit c = case c of+  CFloor lid pos -> insertItemFloor iid kit lid pos+  CEmbed lid pos -> insertItemEmbed iid kit lid pos+  CActor aid store -> insertItemActor iid kit aid store   CTrunk{} -> return () +-- New @kit@ lands at the front of the list. insertItemFloor :: MonadStateWrite m-                => ItemId -> Int -> LevelId -> Point -> m ()-insertItemFloor iid k lid pos =-  let bag = EM.singleton iid k-      mergeBag = EM.insertWith (EM.unionWith (+)) pos bag+                => ItemId -> ItemQuant -> LevelId -> Point -> m ()+insertItemFloor iid kit lid pos =+  let bag = EM.singleton iid kit+      mergeBag = EM.insertWith (EM.unionWith mergeItemQuant) pos bag   in updateLevel lid $ updateFloor mergeBag +insertItemEmbed :: MonadStateWrite m+                => ItemId -> ItemQuant -> LevelId -> Point -> m ()+insertItemEmbed iid kit lid pos =+  let bag = EM.singleton iid kit+      mergeBag = EM.insertWith (EM.unionWith mergeItemQuant) pos bag+  in updateLevel lid $ updateEmbed mergeBag+ insertItemActor :: MonadStateWrite m-                => ItemId -> Int -> ActorId -> CStore -> m ()-insertItemActor iid k aid cstore = case cstore of+                => ItemId -> ItemQuant -> ActorId -> CStore -> m ()+insertItemActor iid kit aid cstore = case cstore of   CGround -> do     b <- getsState $ getActorBody aid-    insertItemFloor iid k (blid b) (bpos b)-  COrgan -> insertItemBody iid k aid-  CEqp -> insertItemEqp iid k aid-  CInv -> insertItemInv iid k aid+    insertItemFloor iid kit (blid b) (bpos b)+  COrgan -> insertItemBody iid kit aid+  CEqp -> insertItemEqp iid kit aid+  CInv -> insertItemInv iid kit aid   CSha -> do     b <- getsState $ getActorBody aid-    insertItemSha iid k (bfid b)+    insertItemSha iid kit (bfid b)  insertItemBody :: MonadStateWrite m-               => ItemId -> Int -> ActorId -> m ()-insertItemBody iid k aid = do-  let bag = EM.singleton iid k-      upd = EM.unionWith (+) bag+               => ItemId -> ItemQuant -> ActorId -> m ()+insertItemBody iid kit aid = do+  let bag = EM.singleton iid kit+      upd = EM.unionWith mergeItemQuant bag   updateActor aid $ \b -> b {borgan = upd (borgan b)}  insertItemEqp :: MonadStateWrite m-              => ItemId -> Int -> ActorId -> m ()-insertItemEqp iid k aid = do-  let bag = EM.singleton iid k-      upd = EM.unionWith (+) bag+              => ItemId -> ItemQuant -> ActorId -> m ()+insertItemEqp iid kit aid = do+  let bag = EM.singleton iid kit+      upd = EM.unionWith mergeItemQuant bag   updateActor aid $ \b -> b {beqp = upd (beqp b)}  insertItemInv :: MonadStateWrite m-              => ItemId -> Int -> ActorId -> m ()-insertItemInv iid k aid = do-  let bag = EM.singleton iid k-      upd = EM.unionWith (+) bag+              => ItemId -> ItemQuant -> ActorId -> m ()+insertItemInv iid kit aid = do+  let bag = EM.singleton iid kit+      upd = EM.unionWith mergeItemQuant bag   updateActor aid $ \b -> b {binv = upd (binv b)}  insertItemSha :: MonadStateWrite m-               => ItemId -> Int -> FactionId -> m ()-insertItemSha iid k fid = do-  let bag = EM.singleton iid k-      upd = EM.unionWith (+) bag+              => ItemId -> ItemQuant -> FactionId -> m ()+insertItemSha iid kit fid = do+  let bag = EM.singleton iid kit+      upd = EM.unionWith mergeItemQuant bag   updateFaction fid $ \fact -> fact {gsha = upd (gsha fact)}  deleteItemContainer :: MonadStateWrite m                     => ItemId -> Int -> Container -> m () deleteItemContainer iid k c = case c of   CFloor lid pos -> deleteItemFloor iid k lid pos+  CEmbed lid pos -> deleteItemEmbed iid k lid pos   CActor aid store -> deleteItemActor iid k aid store   CTrunk{} -> return () @@ -127,6 +141,16 @@                                    `twith` (iid, k, lid, pos)   in updateLevel lid $ updateFloor $ EM.alter rmFromFloor pos +deleteItemEmbed :: MonadStateWrite m+                => ItemId -> Int -> LevelId -> Point -> m ()+deleteItemEmbed iid k lid pos =+  let rmFromFloor (Just bag) =+        let nbag = rmFromBag k iid bag+        in if EM.null nbag then Nothing else Just nbag+      rmFromFloor Nothing = assert `failure` "item already removed"+                                   `twith` (iid, k, lid, pos)+  in updateLevel lid $ updateEmbed $ EM.alter rmFromFloor pos+ deleteItemActor :: MonadStateWrite m                 => ItemId -> Int -> ActorId -> CStore -> m () deleteItemActor iid k aid cstore = case cstore of@@ -156,13 +180,15 @@ deleteItemSha iid k fid = do   updateFaction fid $ \fact -> fact {gsha = rmFromBag k iid (gsha fact)} +-- Removing the part of the kit from the front of the list,+-- so that @DestroyItem kit (CreateItem kit x) == x@. rmFromBag :: Int -> ItemId -> ItemBag -> ItemBag rmFromBag k iid bag =   let rfb Nothing = assert `failure` "rm from empty slot" `twith` (k, iid, bag)-      rfb (Just n) =+      rfb (Just (n, it)) =         case compare n k of           LT -> assert `failure` "rm more than there is"                        `twith` (n, k, iid, bag)           EQ -> Nothing-          GT -> Just (n - k)+          GT -> Just (n - k, drop k it)   in EM.alter rfb iid bag
Game/LambdaHack/Atomic/PosAtomicRead.hs view
@@ -16,7 +16,7 @@ import Game.LambdaHack.Atomic.CmdAtomic import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState-import qualified Game.LambdaHack.Common.Effect as Effect+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind@@ -28,7 +28,7 @@ import Game.LambdaHack.Common.Point import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Content.ModeKind as ModeKind+import Game.LambdaHack.Content.ModeKind  -- All functions here that take an atomic action are executed -- in the state just before the action is executed.@@ -131,6 +131,7 @@   UpdLoseSmell lid sms -> do     let ps = map fst sms     return $! PosSmell lid ps+  UpdTimeItem _ c _ _ -> singleContainer c   UpdAgeGame _ _ -> return PosAll   UpdDiscover lid p _ _ _ -> return $! PosSight lid [p]   UpdCover lid p _ _ _ -> return $! PosSight lid [p]@@ -159,8 +160,8 @@     (slid, sp) <- posOfAid source     (tlid, tp) <- posOfAid target     return $! assert (slid == tlid) $ PosSight slid [sp, tp]-  SfxProject aid _ -> singleAid aid-  SfxCatch aid _ -> singleAid aid+  SfxProject aid _ _ -> singleAid aid+  SfxCatch aid _ _ -> singleAid aid   SfxActivate aid _ _ -> singleAid aid   SfxCheck aid _ _ -> singleAid aid   SfxTrigger aid p _ -> do@@ -192,6 +193,7 @@  singleContainer :: MonadStateRead m => Container -> m PosAtomic singleContainer (CFloor lid p) = return $! PosSight lid [p]+singleContainer (CEmbed lid p) = return $! PosSight lid [p] singleContainer (CActor aid CSha) = do  -- shared stash is private   b <- getsState $ getActorBody aid   return $! PosFidAndSer (Just $ blid b) (bfid b)@@ -259,10 +261,8 @@            , UpdSpotActor target tb {bpos = bpos sb, boldpos = bpos tb} tais            ]   UpdMoveItem iid k aid cstore1 cstore2 | cstore1 == CSha  -- CSha is private-                                          || cstore2 == CSha -> do-    item <- getsState $ getItemBody iid-    return [ UpdLoseItem iid item k (CActor aid cstore1)-           , UpdSpotItem iid item k (CActor aid cstore2) ]+                                          || cstore2 == CSha ->+    containerMoveItem iid k (CActor aid cstore1) (CActor aid cstore2)   -- No need to cover @UpdSearchTile@, because if an actor sees only   -- one of the positions and so doesn't notice the search results,   -- he's left with a hidden tile, which doesn't cause any trouble@@ -276,9 +276,9 @@   SfxStrike source target _ _ -> do     -- Hack: make a fight detectable even if one of combatants not visible.     sb <- getsState $ getActorBody source-    return $! [ SfxEffect (bfid sb) source (Effect.RefillCalm (-1))+    return $! [ SfxEffect (bfid sb) source (IK.RefillCalm (-1))               | not $ bproj sb ]-              ++ [SfxEffect (bfid sb) target (Effect.RefillHP (-1))]+              ++ [SfxEffect (bfid sb) target (IK.RefillHP (-1))]   _ -> return [cmd]  -- | Messages for some unseen game object creation/destruction/alteration.@@ -327,9 +327,18 @@                 -> m [UpdAtomic] generalMoveItem iid k c1 c2 = do   case (c1, c2) of-    (CActor aid1 cstore1, CActor aid2 cstore2) | aid1 == aid2 -> do+    (CActor aid1 cstore1, CActor aid2 cstore2) | aid1 == aid2 ->       return [UpdMoveItem iid k aid1 cstore1 cstore2]-    _ -> do+    _ -> containerMoveItem iid k c1 c2++containerMoveItem :: MonadStateRead m+                  => ItemId -> Int -> Container -> Container+                  -> m [UpdAtomic]+containerMoveItem iid k c1 c2 = do+  bag <- getsState $ getCBag c1+  case iid `EM.lookup` bag of+    Nothing -> assert `failure` (iid, k, c1, c2)+    Just (_, it) -> do       item <- getsState $ getItemBody iid-      return [ UpdLoseItem iid item k c1-             , UpdSpotItem iid item k c2 ]+      return [ UpdLoseItem iid item (k, take k it) c1+             , UpdSpotItem iid item (k, take k it) c2 ]
Game/LambdaHack/Client/AI.hs view
@@ -21,6 +21,7 @@ import Game.LambdaHack.Client.State import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.Faction+import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.MonadStateRead import Game.LambdaHack.Common.Msg import Game.LambdaHack.Common.Random
Game/LambdaHack/Client/AI/ConditionClient.hs view
@@ -33,9 +33,9 @@ import Game.LambdaHack.Client.CommonClient 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 qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemStrongest@@ -44,9 +44,11 @@ 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 qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Vector+import qualified Game.LambdaHack.Content.ItemKind as IK  -- | Require that the target enemy is visible by the party. condTgtEnemyPresentM :: MonadClient m => ActorId -> m Bool@@ -124,7 +126,7 @@   -- We do consider OFF weapons, because e.g., enemies might have turned   -- them off or they can be wrong for other party members, but are OK for us.   let lootIsWeapon =-        not $ null $ strongestSlot Effect.EqpSlotWeapon floorAssocs+        not $ null $ strongestSlot IK.EqpSlotWeapon floorAssocs   return $ lootIsWeapon  -- keep it lazy  -- | Check whether the actor has no weapon in equipment.@@ -132,23 +134,29 @@ condNoEqpWeaponM aid = do   allAssocs <- fullAssocsClient aid [CEqp]   -- We do not consider OFF weapons, because they apparently are not good.-  return $ null $ strongestSlot Effect.EqpSlotWeapon allAssocs+  return $ null $ strongestSlot IK.EqpSlotWeapon allAssocs     -- keep it lazy  -- | Require that the actor can project any items. condCanProjectM :: MonadClient m => ActorId -> m Bool condCanProjectM aid = do-  actorBlind <- radiusBlind <$> sumOrganEqpClient Effect.EqpSlotAddSight aid-  benList <- benAvailableItems aid permittedRanged [CEqp, CInv, CGround]-  let missiles = filter (maybe True ((< 0) . snd . snd) . fst . fst) benList-  return $ not actorBlind && not (null missiles)+  actorSk <- actorSkillsClient aid+  let skill = EM.findWithDefault 0 Ability.AbProject actorSk+      q _ itemFull b activeItems =+        either (const False) id+        $ permittedProject " " False skill itemFull b activeItems+  benList <- benAvailableItems aid q [CEqp, CInv, CGround]+  let missiles = filter (maybe True ((< 0) . snd) . fst . fst) benList+  return $ not (null missiles)     -- keep it lazy  -- | Produce the list of items with a given property available to the actor -- and the items' values. benAvailableItems :: MonadClient m-                  => ActorId -> (ItemFull -> Maybe Int -> Bool) -> [CStore]-                  -> m [( (Maybe (Int, (Int, Int)), (Int, CStore))+                  => ActorId+                  -> (Maybe Int -> ItemFull -> Actor -> [ItemFull] -> Bool)+                  -> [CStore]+                  -> m [( (Maybe (Int, Int), (Int, CStore))                         , (ItemId, ItemFull) )] benAvailableItems aid permitted cstores = do   cops <- getsState scops@@ -158,10 +166,10 @@   fact <- getsState $ (EM.! bfid b) . sfactionD   let ben cstore bag =         [ ((benefit, (k, cstore)), (iid, itemFull))-        | (iid, k) <- EM.assocs bag-        , let itemFull = itemToF iid k+        | (iid, kit@(k, _)) <- EM.assocs bag+        , let itemFull = itemToF iid kit         , let benefit = totalUsefulness cops b activeItems fact itemFull-        , permitted itemFull (fst <$> benefit)]+        , permitted (fst <$> benefit) itemFull b activeItems ]       benCStore cs = do         bag <- getsState $ getActorBag aid cs         return $! ben cs bag@@ -182,23 +190,28 @@   benItemL <- benGroundItems aid   return $ not $ null benItemL  -- keep it lazy --- | Produce the list of items on the ground beneath the actor.+-- | Produce the list of items on the ground beneath the actor+-- that are worth picking up. benGroundItems :: MonadClient m                => ActorId-               -> m [( (Maybe (Int, (Int, Int))+               -> m [( (Maybe (Int, Int)                      , (Int, CStore)), (ItemId, ItemFull) )] benGroundItems aid = do   b <- getsState $ getActorBody aid   canEscape <- factionCanEscape (bfid b)-  let desirableItem ItemFull{itemBase} use+  let -- A hack to prevent monsters from picking up treasure.+      preciousWithoutSlot item =+        IK.Precious `elem` jfeature item  -- risk from treasure hunters+        && isNothing (strengthEqpSlot item)  -- unlikely to be useful+      desirableItem use ItemFull{itemBase} _ _         | canEscape = use /= Just 0-                      || Effect.Precious `elem` jfeature itemBase+                      || IK.Precious `elem` jfeature itemBase         | otherwise = use /= Just 0+                      && not (isNothing use  -- needs resources to id+                              && preciousWithoutSlot itemBase)   benAvailableItems aid desirableItem [CGround] --- | Require the actor is in a bad position to melee.--- We do not check if the actor has a weapon, because having--- no innate weapon is rare.+-- | Require the actor is in a bad position to melee or can't melee at all. condMeleeBadM :: MonadClient m => ActorId -> m Bool condMeleeBadM aid = do   b <- getsState $ getActorBody aid@@ -224,7 +237,7 @@ condLightBetraysM aid = do   b <- getsState $ getActorBody aid   eqpItems <- map snd <$> fullAssocsClient aid [CEqp]-  let actorEqpShines = sumSlotNoFilter Effect.EqpSlotAddLight eqpItems > 0+  let actorEqpShines = sumSlotNoFilter IK.EqpSlotAddLight eqpItems > 0   aInAmbient<- getsState $ actorInAmbient b   return $! not aInAmbient     -- tile is dark, so actor could hide             && actorEqpShines  -- but actor betrayed by his equipped light
Game/LambdaHack/Client/AI/HandleAbilityClient.hs view
@@ -29,9 +29,7 @@ import Game.LambdaHack.Common.Ability import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction-import qualified Game.LambdaHack.Common.Feature as F import Game.LambdaHack.Common.Frequency import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemStrongest@@ -46,9 +44,10 @@ import qualified Game.LambdaHack.Common.Tile as Tile 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.Content.TileKind as TileKind+import qualified Game.LambdaHack.Content.TileKind as TK  type ToAny a = Strategy (RequestTimed a) -> Strategy RequestAnyAbility @@ -89,8 +88,7 @@       condFastThreatAdj = any (\(_, (_, b)) -> bspeed b activeItems > speed1_5)                           $ takeWhile ((== 1) . fst) threatDistL       condCanFlee = not (null fleeL || condFastThreatAdj)-  mleader <- getsClient _sleader-  actorSk <- actorSkillsClient aid mleader+  actorSk <- actorSkillsClient aid   let stratToFreq :: MonadStateRead m                   => Int -> m (Strategy RequestAnyAbility)                   -> m (Frequency RequestAnyAbility)@@ -142,7 +140,7 @@       distant =         [ ( [AbProject]  -- for high-value target, shoot even in melee           , stratToFreq 2 $ (toAny :: ToAny AbProject)-            <$> ranged aid+            <$> projectItem aid           , condTgtEnemyPresent && condCanProject && not condOnTriggerable )         , ( [AbApply]           , stratToFreq 2 $ (toAny :: ToAny AbApply)@@ -169,9 +167,6 @@         , ( [AbMelee], (toAny :: ToAny AbMelee)             <$> meleeAny aid  -- avoid getting damaged for naught           , condAnyFoeAdj )-        , ( [AbMoveItem], (toAny :: ToAny AbMoveItem)-            <$> unEquipItems aid  -- late, because better to throw than unequip-          , True )         , ( [AbMove]             -- TODO: forget old target (e.g., tile), to start shooting,             -- unless can't shoot, etc.@@ -179,6 +174,9 @@           , condMeleeBad && condThreatNearby && (condNotCalmEnough                                                  || condThreatAtHand                                                  || condNoUsableWeapon) )+        , ( [AbMoveItem], (toAny :: ToAny AbMoveItem)+            <$> unEquipItems aid  -- late, because better to throw than unequip+          , True )         , ( [AbMove]           , chase aid False           , True )@@ -215,7 +213,7 @@ pickup aid onlyWeapon = do   benItemL <- benGroundItems aid   let isWeapon (_, (_, itemFull)) =-        maybe False ((== Effect.EqpSlotWeapon) . fst)+        maybe False ((== IK.EqpSlotWeapon) . fst)         $ strengthEqpSlot $ itemBase itemFull       filterWeapon | onlyWeapon = filter isWeapon                    | otherwise = id@@ -224,7 +222,6 @@   -- Pick up the best desirable item, if any.   case reverse $ sortBy (comparing cmp) $ filterWeapon benItemL of     ((_, (k, _)), (iid, itemFull)) : _ -> do-      updateItemSlot (Just aid) iid       b <- getsState $ getActorBody aid       -- TODO: instead of pickup to eqp and then move to inv, pickup to inv       let toCStore = if goesIntoInv (itemBase itemFull)@@ -263,18 +260,18 @@       -- items should be removed in yieldUnneeded earlier or soon after.       filterNeeded (_, itemFull) =         not $ unneeded cops condLightBetrays body activeItems fact itemFull-      bestThree = bestByEqpSlot (filter filterNeeded invAssocs)-                                (filter filterNeeded eqpAssocs)+      bestThree = bestByEqpSlot (filter filterNeeded eqpAssocs)+                                (filter filterNeeded invAssocs)                                 (filter filterNeeded shaAssocs)       bEqpInv = msum $ map (improve CInv)                 $ map (\(_, (eqp, inv, _)) -> (inv, eqp)) bestThree+      bEqpSha = msum $ map (improve CSha)+                $ map (\(_, (eqp, _, sha)) -> (sha, eqp)) bestThree   if nullStrategy bEqpInv     then if rsharedStash && calmEnough body activeItems-         then return-              $! msum $ map (improve CSha)-              $ map (\(_, (eqp, _, sha)) -> (sha, eqp)) bestThree+         then return $! bEqpSha          else return reject-    else return bEqpInv+    else return $! bEqpInv  unEquipItems :: MonadClient m              => ActorId -> m (Strategy (RequestTimed AbMoveItem))@@ -298,13 +295,13 @@            then Just $ ReqMoveItem iidEqp (itemK itemEqp) CEqp csha  -- share            else Nothing       yieldUnneeded = mapMaybe yieldSingleUnneeded eqpAssocs-      improve :: CStore -> ( Effect.EqpSlot+      improve :: CStore -> ( IK.EqpSlot                            , ( [(Int, (ItemId, ItemFull))]                              , [(Int, (ItemId, ItemFull))] ) )               -> Strategy (RequestTimed AbMoveItem)       improve fromCStore (slot, (bestInv, bestEqp)) =         case (bestInv, bestEqp) of-          _ | slot == Effect.EqpSlotPeriodic+          _ | slot == IK.EqpSlotPeriodic               && fromCStore == CEqp               && not (eqpOverfull body 0) ->             -- Don't get rid of periodic items from eqp unless eqp full.@@ -323,20 +320,19 @@       getK ((_, (_, itemFull)) : _) = itemK itemFull       betterThanInv _ [] = True       betterThanInv vEqp ((vInv, _) : _) = vEqp > vInv-      bestThree = bestByEqpSlot invAssocs eqpAssocs shaAssocs+      bestThree = bestByEqpSlot eqpAssocs invAssocs shaAssocs+      bInvSha = msum $ map (improve CInv)+                $ map (\((slot, _), (_, inv, sha)) ->+                        (slot, (sha, inv))) bestThree+      bEqpSha = msum $ map (improve CEqp)+                $ map (\((slot, _), (eqp, _, sha)) ->+                        (slot, (sha, eqp))) bestThree   case yieldUnneeded of-    [] ->-      if rsharedStash && calmEnough body activeItems-      then do-        let bInvSha = msum $ map (improve CInv)-                      $ map (\((slot, _), (_, inv, sha)) ->-                               (slot, (sha, inv))) bestThree-        if nullStrategy bInvSha-          then return $! msum $ map (improve CEqp)-                         $ map (\((slot, _), (eqp, _, sha)) ->-                                 (slot, (sha, eqp))) bestThree-          else return $! bInvSha-        else return reject+    [] -> if rsharedStash && calmEnough body activeItems+          then if nullStrategy bInvSha+               then return $! bEqpSha+               else return $! bInvSha+          else return reject     _ ->       -- Here AI hides from the human player the Ring of Speed And Bleeding,       -- which is a bit harsh, but fair. However any subsequent such@@ -347,7 +343,7 @@       return $! liftFrequency $ uniformFreq "yield unneeded" yieldUnneeded  groupByEqpSlot :: [(ItemId, ItemFull)]-               -> M.Map (Effect.EqpSlot, Text) [(ItemId, ItemFull)]+               -> M.Map (IK.EqpSlot, Text) [(ItemId, ItemFull)] groupByEqpSlot is =   let f (iid, itemFull) = case strengthEqpSlot $ itemBase itemFull of         Nothing -> Nothing@@ -358,37 +354,37 @@ bestByEqpSlot :: [(ItemId, ItemFull)]               -> [(ItemId, ItemFull)]               -> [(ItemId, ItemFull)]-              -> [((Effect.EqpSlot, Text)+              -> [((IK.EqpSlot, Text)                   , ( [(Int, (ItemId, ItemFull))]                     , [(Int, (ItemId, ItemFull))]                     , [(Int, (ItemId, ItemFull))] ) )]-bestByEqpSlot invAssocs eqpAssocs shaAssocs =+bestByEqpSlot eqpAssocs invAssocs shaAssocs =   let eqpMap = M.map (\g -> (g, [], [])) $ groupByEqpSlot eqpAssocs       invMap = M.map (\g -> ([], g, [])) $ groupByEqpSlot invAssocs       shaMap = M.map (\g -> ([], [], g)) $ groupByEqpSlot shaAssocs       appendThree (g1, g2, g3) (h1, h2, h3) = (g1 ++ h1, g2 ++ h2, g3 ++ h3)-      invEqpShaMap = M.unionsWith appendThree [invMap, eqpMap, shaMap]+      eqpInvShaMap = M.unionsWith appendThree [eqpMap, invMap, shaMap]       bestSingle eqpSlot g = strongestSlot eqpSlot g       bestThree (eqpSlot, _) (g1, g2, g3) = (bestSingle eqpSlot g1,                                              bestSingle eqpSlot g2,                                              bestSingle eqpSlot g3)-  in M.assocs $ M.mapWithKey bestThree invEqpShaMap+  in M.assocs $ M.mapWithKey bestThree eqpInvShaMap  hinders :: Bool -> Actor -> [ItemFull] -> ItemFull -> Bool hinders condLightBetrays body activeItems itemFull =   -- Fast actors want to hide in darkness to ambush opponents and want   -- to hit hard for the short span they get to survive melee.   (bspeed body activeItems > speedNormal-   && (isJust (strengthFromEqpSlot Effect.EqpSlotAddLight itemFull)-       || 0 > fromMaybe 0 (strengthFromEqpSlot Effect.EqpSlotAddHurtMelee+   && (isJust (strengthFromEqpSlot IK.EqpSlotAddLight itemFull)+       || 0 > fromMaybe 0 (strengthFromEqpSlot IK.EqpSlotAddHurtMelee                                                itemFull)-       || 0 > fromMaybe 0 (strengthFromEqpSlot Effect.EqpSlotAddHurtRanged+       || 0 > fromMaybe 0 (strengthFromEqpSlot IK.EqpSlotAddHurtRanged                                                itemFull)))   -- Distressed actors want to hide in the dark.   || (let heavilyDistressed =  -- actor hit by a proj or similarly distressed             deltaSerious (bcalmDelta body)       in condLightBetrays && heavilyDistressed-         && isJust (strengthFromEqpSlot Effect.EqpSlotAddLight itemFull))+         && isJust (strengthFromEqpSlot IK.EqpSlotAddLight itemFull))   -- TODO:   -- teach AI to turn shields OFF (or stash) when ganging up on an enemy   -- (friends close, only one enemy close)@@ -399,12 +395,10 @@  harmful :: Kind.COps -> Actor -> [ItemFull] -> Faction -> ItemFull -> Bool harmful cops body activeItems fact itemFull =-  -- items that are known and their effects are not stricly beneficial+  -- Items that are known and their effects are not stricly beneficial   -- should not be equipped (either they are harmful or they waste eqp space).   maybe False (\(u, _) -> u <= 0)     (totalUsefulness cops body activeItems fact itemFull)-  && (maybe True ((/= Effect.EqpSlotWeapon) . fst)-      $ strengthEqpSlot $ itemBase itemFull)  unneeded :: Kind.COps -> Bool -> Actor -> [ItemFull] -> Faction -> ItemFull          -> Bool@@ -417,8 +411,7 @@ meleeBlocker aid = do   b <- getsState $ getActorBody aid   fact <- getsState $ (EM.! bfid b) . sfactionD-  mleader <- getsClient _sleader-  actorSk <- actorSkillsClient aid mleader+  actorSk <- actorSkillsClient aid   mtgtMPath <- getsClient $ EM.lookup aid . stargetD   case mtgtMPath of     Just (_, Just (_ : q : _, (goal, _))) -> do@@ -462,6 +455,7 @@   let freq = uniformFreq "melee adjacent" $ concat mels   return $! liftFrequency freq +-- TODO: take charging status into account -- Fast monsters don't pay enough attention to features. trigger :: MonadClient m         => ActorId -> Bool -> m (Strategy (RequestTimed AbTrigger))@@ -472,31 +466,38 @@   b <- getsState $ getActorBody aid   activeItems <- activeItemsClient aid   fact <- getsState $ (EM.! bfid b) . sfactionD-  lvl <- getLevel $ blid b+  let lid = blid b+  lvl <- getLevel lid   unexploredD <- unexploredDepth   s <- getState-  per <- getPerFid $ blid b+  per <- getPerFid lid   let canSee = ES.member (bpos b) (totalVisible per)-      unexploredCurrent = ES.notMember (blid b) explored+      unexploredCurrent = ES.notMember lid explored       allExplored = ES.size explored == EM.size dungeon       t = lvl `at` bpos b-      feats = TileKind.tfeature $ okind t+      feats = TK.tfeature $ okind t       ben feat = case feat of-        F.Cause (Effect.Ascend k) ->  -- change levels sensibly, in teams+        TK.Cause (IK.Ascend k) ->  -- change levels sensibly, in teams           let aimless = ftactic (gplayer fact) `elem` [TRoam, TPatrol]+              easeDelta = abs (fromEnum lid) - abs (fromEnum lid + k)+              unexpForth = unexploredD (signum k) lid+              unexpBack = unexploredD (- signum k) lid               expBenefit =                 if aimless                 then 100  -- faction is not exploring, so switch at will                 else if unexploredCurrent                 then 0  -- don't leave the level until explored-                else if unexploredD (signum k) (blid b)-                then 1000-                else if unexploredD (- signum k) (blid b)+                else if unexpForth+                then if easeDelta > 0  -- alway try as easy level as possible+                        || not unexpBack  -- no other choice for exploration+                     then 1000 * abs easeDelta+                     else 0+                else if unexpBack                 then 0  -- wait for stairs in the opposite direciton                 else if lescape lvl                 then 0  -- all explored, stay on the escape level                 else 2  -- no escape anywhere, switch levels occasionally-              (lid2, pos2) = whereTo (blid b) (bpos b) k dungeon+              (lid2, pos2) = whereTo lid (bpos b) k dungeon               actorsThere = posToActors pos2 lid2 s           in if boldpos b == bpos b   -- probably used stairs last turn                 && boldlid b == lid2  -- in the opposite direction@@ -509,13 +510,13 @@                   in if fleeViaStairs                      then 1000 * eben + 1  -- strongly prefer correct direction                      else eben-        F.Cause ef@Effect.Escape{} -> do  -- flee via this way, too+        TK.Cause ef@IK.Escape{} -> do  -- flee via this way, too           -- Only some factions try to escape but they first explore all           -- for high score.           if not (fcanEscape $ gplayer fact) || not allExplored           then 0           else effectToBenefit cops b activeItems fact ef-        F.Cause ef | not fleeViaStairs ->+        TK.Cause ef | not fleeViaStairs ->           effectToBenefit cops b activeItems fact ef         _ -> 0       benFeat = zip (map ben feats) feats@@ -524,45 +525,62 @@            | (benefit, feat) <- benFeat            , benefit > 0 ] -ranged :: MonadClient m => ActorId -> m (Strategy (RequestTimed AbProject))-ranged aid = do+projectItem :: MonadClient m => ActorId -> m (Strategy (RequestTimed AbProject))+projectItem aid = do   btarget <- getsClient $ getTarget aid-  b@Actor{bpos, blid} <- getsState $ getActorBody aid-  mfpos <- aidTgtToPos aid blid btarget+  b@Actor{bpos} <- getsState $ getActorBody aid+  mfpos <- aidTgtToPos aid (blid b) btarget   seps <- getsClient seps   case (btarget, mfpos) of     (Just TEnemy{}, Just fpos) -> do-      actorBlind <- radiusBlind <$> sumOrganEqpClient Effect.EqpSlotAddSight aid       mnewEps <- makeLine b fpos seps       case mnewEps of-        Just newEps | not actorBlind -> do  -- ProjectBlind+        Just newEps -> do+          actorSk <- actorSkillsClient aid+          let skill = EM.findWithDefault 0 AbProject actorSk           -- ProjectAimOnself, ProjectBlockActor, ProjectBlockTerrain           -- and no actors or obstracles along the path.-          benList <- benAvailableItems aid permittedRanged [CEqp, CInv, CGround]+          let q _ itemFull b2 activeItems =+                either (const False) id+                $ permittedProject " " False skill itemFull b2 activeItems+          benList <- benAvailableItems aid q [CEqp, CInv, CGround]+          localTime <- getsState $ getLocalTime (blid b)           let coeff CGround = 2               coeff COrgan = 3  -- can't give to others               coeff CEqp = 1               coeff CInv = 1               coeff CSha = undefined  -- banned-              fRanged ((mben, (_, cstore)), (iid, ItemFull{itemBase})) =-                let trange = totalRange itemBase+              fRanged ((mben, (_, cstore)), (iid, itemFull@ItemFull{..})) =+                let it1 = case strengthFromEqpSlot IK.EqpSlotTimeout+                                                   itemFull of+                      Nothing -> []+                      Just timeout ->+                        let timeoutTurns =+                              timeDeltaScale (Delta timeTurn) timeout+                            pending startT =+                              timeShift startT timeoutTurns > localTime+                        in filter pending itemTimer+                    len = length it1+                    recharged = len < itemK+                    trange = totalRange itemBase                     bestRange = chessDist bpos fpos + 2  -- margin for fleeing                     rangeMult =  -- penalize wasted or unsafely low range                       10 + max 0 (10 - abs (trange - bestRange))-                    durableBonus = if Effect.Durable `elem` jfeature itemBase+                    durableBonus = if IK.Durable `elem` jfeature itemBase                                    then 2  -- we or foes keep it after the throw                                    else 1                     benR = durableBonus                            * coeff cstore                            * case mben of-                               Nothing -> -20  -- experimenting is fun-                               Just (_, (_, ben)) -> ben+                               Nothing -> -1  -- experiment if no options+                               Just (_, ben) -> ben+                           * (if recharged then 1 else 0)                 in if benR < 0 && trange >= chessDist bpos fpos                    then Just ( -benR * rangeMult `div` 10                              , ReqProject fpos newEps iid cstore )                    else Nothing               benRanged = mapMaybe fRanged benList-          return $! liftFrequency $ toFreq "ranged" benRanged+          return $! liftFrequency $ toFreq "projectItem" benRanged         _ -> return reject     _ -> return reject @@ -572,16 +590,19 @@ applyItem :: MonadClient m           => ActorId -> ApplyItemGroup -> m (Strategy (RequestTimed AbApply)) applyItem aid applyGroup = do-  actorBlind <- radiusBlind <$> sumOrganEqpClient Effect.EqpSlotAddSight aid-  let permitted itemFull@ItemFull{itemBase=item} _ =-        not (unknownPrecious itemFull)-        && if jsymbol item == '?' && actorBlind-           then False-           else Effect.Applicable `elem` jfeature item-  benList <- benAvailableItems aid permitted [CEqp, CInv, CGround]+  actorSk <- actorSkillsClient aid+  let skill = EM.findWithDefault 0 AbProject actorSk+      q _ itemFull b activeItems =+        either (const False) id+        $ permittedApply " " skill itemFull b activeItems+  benList <- benAvailableItems aid q [CEqp, CInv, CGround]+  b <- getsState $ getActorBody aid+  organs <- mapM (getsState . getItemBody) $ EM.keys $ borgan b+  localTime <- getsState $ getLocalTime (blid b)   let itemLegal itemFull = case applyGroup of         ApplyFirstAid ->-          let getP (Effect.RefillHP p) _ | p > 0 = True+          let getP (IK.RefillHP p) _ | p > 0 = True+              getP (IK.OverfillHP p) _ | p > 0 = True               getP _ acc = acc           in case itemDisco itemFull of             Just ItemDisco{itemAE=Just ItemAspectEffect{jeffects}} ->@@ -593,19 +614,46 @@       coeff CEqp = 1       coeff CInv = 1       coeff CSha = undefined  -- banned-      fTool ((mben, (_, cstore)), (iid, itemFull)) =-        let durableBonus = if Effect.Durable `elem` jfeature (itemBase itemFull)+      fTool ((mben, (_, cstore)), (iid, itemFull@ItemFull{..})) =+        let it1 = case strengthFromEqpSlot IK.EqpSlotTimeout itemFull of+              Nothing -> []+              Just timeout ->+                let timeoutTurns = timeDeltaScale (Delta timeTurn) timeout+                    pending startT = timeShift startT timeoutTurns > localTime+                in filter pending itemTimer+            len = length it1+            recharged = len < itemK+            durableBonus = if IK.Durable `elem` jfeature itemBase                            then 5  -- we keep it after use                            else 1-            benR = durableBonus+            oldGrps = map (toGroupName . jname) organs+            createOrganAgain =+              -- This assumes the organ creation is beneficial. If it's+              -- a drawback of an otherwise good item, we should reverse+              -- the condition.+              let newGrps = strengthCreateOrgan itemFull+              in not $ null $ intersect newGrps oldGrps+            dropOrganVoid =+              -- This assumes the organ dropping is beneficial. If it's+              -- a drawback of an otherwise good item, or a marginal+              -- advantage only, we should reverse or ignore the condition.+              -- We ignore a very general @grp@ being used for a very+              -- common and easy to drop organ, etc.+              let newGrps = strengthDropOrgan itemFull+                  hasDropOrgan = not $ null newGrps+              in hasDropOrgan && null (intersect newGrps oldGrps)+            benR = case mben of+                     Nothing -> 0+                       -- experimenting is fun, but it's better to risk+                       -- foes' skin than ours -- TODO: when {activated}+                       -- is implemented, enable this for items too heavy,+                       -- etc. for throwing+                     Just (_, ben) -> ben+                   * (if recharged then 1 else 0)  -- wait until can be used+                   * (if not createOrganAgain then 1 else 0)+                   * (if not dropOrganVoid then 1 else 0)+                   * durableBonus                    * coeff cstore-                   * case mben of-                       Nothing -> 0-                         -- experimenting is fun, but it's better to risk-                         -- foes' skin than ours -- TODO: when {activated}-                         -- is implemented, enable this for items too heavy,-                         -- etc. for throwing-                       Just (_, (_, ben)) -> ben         in if itemLegal itemFull            then if benR > 0                 then Just (benR, ReqApply iid cstore)@@ -633,7 +681,6 @@   b <- getsState $ getActorBody aid   lvl <- getLevel $ blid b   fact <- getsState $ (EM.! bfid b) . sfactionD-  mleader <- getsClient _sleader   let friendlyFid fid = fid == bfid b || isAllied fact fid   friends <- getsState $ actorRegularList friendlyFid (blid b)   allFoes <- getsState $ actorRegularAssocs (isAtWar fact) (blid b)@@ -645,7 +692,7 @@       nFrHere = nFriends b + 1       qualifyActor (aid2, body2) = do         activeItems <- activeItemsClient aid2-        dEnemy <- getsState $ dispEnemy aid mleader aid2 activeItems+        dEnemy <- getsState $ dispEnemy aid aid2 activeItems         let nFr = nFriends body2         return $! if displaceable body2 && dEnemy && nFr < nFrHere           then Just (nFr * nFr, bpos body2 `vectorToFrom` bpos b)@@ -690,7 +737,7 @@           Nothing -> do             tfact <- getsState $ (EM.! bfid b2) . sfactionD             activeItems <- activeItemsClient aid2-            dEnemy <- getsState $ dispEnemy aid mleader aid2 activeItems+            dEnemy <- getsState $ dispEnemy aid aid2 activeItems             if not (isAtWar tfact (bfid b)) || dEnemy then               return $! returN "displace other" $ target `vectorToFrom` source             else return reject  -- DisplaceDying, DisplaceSupported@@ -758,7 +805,6 @@   let spos = bpos sb           -- source position       tpos = spos `shift` dir  -- target position       t = lvl `at` tpos-  mleader <- getsClient _sleader   -- We start by checking actors at the the target position,   -- which gives a partial information (actors can be invisible),   -- as opposed to accessibility (and items) which are always accurate@@ -769,7 +815,7 @@       -- @target@ can be a foe, as well as a friend.       tfact <- getsState $ (EM.! bfid b2) . sfactionD       activeItems <- activeItemsClient target-      dEnemy <- getsState $ dispEnemy source mleader target activeItems+      dEnemy <- getsState $ dispEnemy source target activeItems       if boldpos sb /= tpos -- avoid trivial Displace loops          && accessible cops lvl spos tpos -- DisplaceAccess          && (not (isAtWar tfact (bfid sb))@@ -794,7 +840,7 @@         -- Movement requires full access.         return $! Just $ RequestAnyAbility $ ReqMove dir         -- The potential invisible actor is hit.-      else if not $ EM.null $ lvl `atI` tpos then+      else if EM.member tpos $ lfloor lvl then         -- This is, e.g., inaccessible open door with an item in it.         assert `failure` "AI causes AlterBlockItem" `twith` (run, source, dir)       else if not (Tile.isWalkable cotile t)  -- not implied
Game/LambdaHack/Client/AI/PickActorClient.hs view
@@ -21,6 +21,7 @@ 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.MonadStateRead import Game.LambdaHack.Common.Point import Game.LambdaHack.Common.Random@@ -82,6 +83,8 @@   case ours of     _ | -- Keep the leader: only a leader is allowed to pick another leader.         mleader /= Just oldAid+        -- Keep the leader: the faction forbids client leader change on level.+        || snd (autoDungeonLevel fact)         -- Keep the leader: he is on stairs and not stuck         -- and we don't want to clog stairs or get pushed to another level.         || not leaderStuck && Tile.isStair cotile t
Game/LambdaHack/Client/AI/PickTargetClient.hs view
@@ -3,6 +3,7 @@   ( targetStrategy, createPath   ) where +import Control.Applicative import Control.Exception.Assert.Sugar import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES@@ -19,11 +20,12 @@ import Game.LambdaHack.Common.Ability import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState-import qualified Game.LambdaHack.Common.Effect as Effect 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.Random@@ -31,6 +33,7 @@ import qualified Game.LambdaHack.Common.Tile as Tile 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 @@ -95,7 +98,7 @@   -- and only if they can shoot at the moment)   canEscape <- factionCanEscape (bfid b)   explored <- getsClient sexplored-  smellRadius <- sumOrganEqpClient Effect.EqpSlotAddSmell aid+  smellRadius <- sumOrganEqpClient IK.EqpSlotAddSmell aid   let canSmell = smellRadius > 0       meleeNearby | canEscape = nearby `div` 2  -- not aggresive                   | otherwise = nearby@@ -113,13 +116,20 @@       nearbyFoes = filter (targetableEnemy . snd) allFoes       unknownId = ouniqGroup "unknown space"       itemUsefulness iid k =-        case totalUsefulness cops b activeItems fact (itemToF iid k) of-          Just (v, _) -> v-          Nothing -> 30  -- experimenting is fun+        fst <$> totalUsefulness cops b activeItems fact (itemToF iid k)+      -- TODO: factor out from here and benGroundItems       desirableItem iid item k-        | canEscape = itemUsefulness iid k /= 0-                           || Effect.Precious `elem` jfeature item-        | otherwise = itemUsefulness iid k /= 0+        | canEscape = itemUsefulness iid k /= Just 0+                        || IK.Precious `elem` jfeature item+        | otherwise =+            let use = itemUsefulness iid k+                -- A hack to prevent monsters from picking up treasure.+                preciousWithoutSlot item2 =+                  IK.Precious `elem` jfeature item2  -- risk from treasure hunters+                  && isNothing (strengthEqpSlot item2)  -- unlikely to be useful+            in use /= Just 0+               && not (isNothing use  -- needs resources to id+                       && preciousWithoutSlot item)       desirableBag bag = any (\(iid, k) ->                                desirableItem iid (itemD EM.! iid) k)                          $ EM.assocs bag@@ -254,6 +264,7 @@           pickNewTarget  -- prefer close foes to anything         TPoint lid pos -> do           let allExplored = ES.size explored == EM.size dungeon+          bag <- getsState $ getCBag $ CFloor lid pos           if lid /= blid b  -- wrong level              -- Below we check the target could not be picked again in              -- pickNewTarget, and only in this case it is invalidated.@@ -262,7 +273,7 @@              -- to equally interesting, but perhaps a bit closer targets,              -- most probably already targeted by other actors.              || (EM.findWithDefault 0 AbMoveItem actorSk <= 0  -- closestItems-                 || not (desirableBag (lvl `atI` pos)))+                 || not (desirableBag bag))                 && (not canSmell  -- closestSmell                     || pos == bpos b  -- in case server resends deleted smell                     || let sml = EM.findWithDefault timeZero pos (lsmell lvl)
Game/LambdaHack/Client/AI/Preferences.hs view
@@ -10,122 +10,150 @@ import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Dice as Dice-import qualified Game.LambdaHack.Common.Effect as Effect 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.Misc-import Game.LambdaHack.Content.ItemKind+import Game.LambdaHack.Content.ItemKind (ItemKind)+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.ModeKind  -- | How much AI benefits from applying the effect. Multipllied by item p. -- Negative means harm to the enemy when thrown at him. Effects with zero -- benefit won't ever be used, neither actively nor passively. effectToBenefit :: Kind.COps -> Actor -> [ItemFull] -> Faction-                -> Effect.Effect Int -> Int+                -> IK.Effect -> Int effectToBenefit cops b activeItems fact eff =   let dungeonDweller = not $ fcanEscape $ gplayer fact   in case eff of-    Effect.NoEffect _ -> 0-    Effect.RefillHP p ->-      let hpMax = sumSlotNoFilter Effect.EqpSlotAddMaxHP activeItems+    IK.NoEffect _ -> 0+    IK.Hurt d -> -(min 99 $ round (10 * Dice.meanDice d))+    IK.Burn p -> -15 * p           -- usually splash damage, etc.+    IK.Explode _ -> -10+    IK.RefillHP p ->+      let hpMax = sumSlotNoFilter IK.EqpSlotAddMaxHP activeItems       in if p > 0          then 1 + 10 * min p (fromIntegral $ (xM hpMax - bhp b) `divUp` oneM)          else max (-99) (10 * p)-    Effect.Hurt d -> -(min 99 $ round (10 * Dice.meanDice d))-    Effect.RefillCalm p ->-      let calmMax = sumSlotNoFilter Effect.EqpSlotAddMaxCalm activeItems+    IK.OverfillHP p ->+      let hpMax = sumSlotNoFilter IK.EqpSlotAddMaxHP activeItems       in if p > 0+         then 2 + 10 * min p (fromIntegral $ (xM hpMax - bhp b) `divUp` oneM)+         else max (-99) (10 * p)+    IK.RefillCalm p ->+      let calmMax = sumSlotNoFilter IK.EqpSlotAddMaxCalm activeItems+      in if p > 0          then 1 + min p (fromIntegral $ (xM calmMax - bcalm b) `divUp` oneM)          else max (-20) p-    Effect.Dominate -> -200-    Effect.Impress -> -10-    Effect.CallFriend p -> 20 * p-    Effect.Summon{} | dungeonDweller -> 1 -- probably summons friends or crazies-    Effect.Summon{} -> 0                  -- probably generates enemies-    Effect.CreateItem p -> 20 * p-    Effect.ApplyPerfume -> -10-    Effect.Burn p -> -15 * p           -- usually splash damage, etc.-    Effect.Ascend{} -> 1               -- change levels sensibly, in teams-    Effect.Escape{} -> 10000           -- AI wants to win; spawners to guard-    Effect.Paralyze p -> -20 * p-    Effect.InsertMove p -> 50 * p-    Effect.DropBestWeapon -> -50-    Effect.DropEqp ' ' False -> -80-    Effect.DropEqp ' ' True -> -100-    Effect.DropEqp _ False -> -40-    Effect.DropEqp _ True -> -50-    Effect.SendFlying _ -> -10  -- but useful on self sometimes, too-    Effect.PushActor _ -> -10  -- but useful on self sometimes, too-    Effect.PullActor _ -> -10-    Effect.Teleport p | p < 5 -> 5 * p  -- blink to shoot at foe-    Effect.Teleport p | p < 10 -> 1  -- neither escape nor repositioning-    Effect.Teleport p -> -5 * p  -- get rid of the foe-    Effect.PolyItem _ -> 0  -- AI would loop-    Effect.Identify _ -> 0  -- AI would loop-    Effect.ActivateInv ' ' -> -100-    Effect.ActivateInv _ -> -50-    Effect.Explode _ -> -10-    Effect.OneOf _ -> 1  -- usually a mixed blessing, but slightly beneficial-    Effect.OnSmash _ -> -10-    Effect.TimedAspect k asp -> k * (aspectToBenefit cops b asp) `div` 50+    IK.OverfillCalm p ->+      let calmMax = sumSlotNoFilter IK.EqpSlotAddMaxCalm activeItems+      in if p > 0+         then 2 + min p (fromIntegral $ (xM calmMax - bcalm b) `divUp` oneM)+         else max (-20) p+    IK.Dominate -> -200+    IK.Impress -> -10+    IK.CallFriend d -> round $ 20 * Dice.meanDice d+    IK.Summon{} | dungeonDweller -> 1 -- probably summons friends or crazies+    IK.Summon{} -> 0                  -- probably generates enemies+    IK.Ascend{} -> 1               -- change levels sensibly, in teams+    IK.Escape{} -> 10000           -- AI wants to win; spawners to guard+    IK.Paralyze d ->  round $ -20 * Dice.meanDice d+    IK.InsertMove d -> round $ 50 * Dice.meanDice d+    IK.Teleport d ->+      let p = round $ Dice.meanDice d+      in if p <= 9 then 10  -- blink to shoot at foe+         else if p <= 19 then 1  -- neither escape nor repositioning+         else -5 * p  -- get rid of the foe+    IK.CreateItem COrgan grp _ ->  -- TODO: use the timeout+      let (total, count) = organBenefit grp cops b+      in total `divUp` count  -- average over all matching grp; rarities ignored+    IK.CreateItem _ _ _ -> 30+    IK.DropItem COrgan grp True ->  -- calculated for future use, general pickup+      let (total, _) = organBenefit grp cops b+      in - total  -- sum over all matching grp; simplification: rarities ignored+    IK.DropItem _ _ False -> -15+    IK.DropItem _ _ True -> -30+    IK.PolyItem _ -> 0  -- AI would loop+    IK.Identify _ -> 1  -- not higher, or AI would loop+    IK.SendFlying _ -> -10  -- but useful on self sometimes, too+    IK.PushActor _ -> -10  -- but useful on self sometimes, too+    IK.PullActor _ -> -10+    IK.DropBestWeapon -> -50+    IK.ActivateInv ' ' -> -100+    IK.ActivateInv _ -> -50+    IK.ApplyPerfume -> -10+    IK.OneOf _ -> 1  -- usually a mixed blessing, but slightly beneficial+    IK.OnSmash _ -> -10+    IK.Recharging e -> effectToBenefit cops b activeItems fact e+                           `divUp` 3  -- TODO: use Timeout+    IK.Temporary _ -> 0 --- | Return the value to add to effect value and another to multiply it.-aspectToBenefit :: Kind.COps -> Actor -> Effect.Aspect Int -> Int+-- TODO: calculating this for "temporary conditions" takes forever+organBenefit :: GroupName ItemKind -> Kind.COps -> Actor -> (Int, Int)+organBenefit t cops@Kind.COps{coitem=Kind.Ops{ofoldrGroup}} b =+  let travA x = St.evalState (IK.aspectTrav x (return . round . Dice.meanDice)) ()+      f p _ kind (sacc, pacc) =+        let paspect asp = p * aspectToBenefit cops b (travA asp)+        in ( sacc + sum (map paspect $ IK.iaspects kind)+           , pacc + p )+  in ofoldrGroup t f (0, 0)++-- | Return the value to add to effect value.+aspectToBenefit :: Kind.COps -> Actor -> IK.Aspect Int -> Int aspectToBenefit _cops _b asp =   case asp of-    Effect.Periodic{} -> 0-    Effect.AddMaxHP p -> p * 10-    Effect.AddMaxCalm p -> p `divUp` 2-    Effect.AddSpeed p -> p * 10000-    Effect.AddSkills m -> 5 * sum (EM.elems m)-    Effect.AddHurtMelee p -> p `divUp` 3-    Effect.AddHurtRanged p -> p `divUp` 5-    Effect.AddArmorMelee p -> p `divUp` 5-    Effect.AddArmorRanged p -> p `divUp` 10-    Effect.AddSight p -> p * 10-    Effect.AddSmell p -> p * 2-    Effect.AddLight p -> p * 10+    IK.Periodic{} -> 0+    IK.Timeout{} -> 0+    IK.AddMaxHP p -> p * 10+    IK.AddMaxCalm p -> p `divUp` 2+    IK.AddSpeed p -> p * 10000+    IK.AddSkills m -> 5 * sum (EM.elems m)+    IK.AddHurtMelee p -> p `divUp` 3+    IK.AddHurtRanged p -> p `divUp` 5  -- TODO: should be summed with damage+    IK.AddArmorMelee p -> p `divUp` 5+    IK.AddArmorRanged p -> p `divUp` 10+    IK.AddSight p -> p * 10+    IK.AddSmell p -> p * 2+    IK.AddLight p -> p * 10  -- | Determine the total benefit from having an item in eqp or inv, -- according to item type, and also the benefit confered by equipping the item -- and from meleeing with it or applying it or throwing it. totalUsefulness :: Kind.COps -> Actor -> [ItemFull] -> Faction -> ItemFull-                -> Maybe (Int, (Int, Int))+                -> Maybe (Int, Int) totalUsefulness cops b activeItems fact itemFull =   let ben effects aspects =         let effBens = map (effectToBenefit cops b activeItems fact) effects             aspBens = map (aspectToBenefit cops b) aspects-            periodicEffBens =-              case strengthFromEqpSlot Effect.EqpSlotPeriodic itemFull of+            periodicEffBens = map (effectToBenefit cops b activeItems fact)+                                  (allRecharging effects)+            periodicBens =+              case strengthFromEqpSlot IK.EqpSlotPeriodic itemFull of                 Nothing -> []-                Just in100 -> map (\eff -> eff * in100 `div` 5) effBens-            selfBens = aspBens ++ periodicEffBens+                Just timeout ->+                  map (\eff -> eff * 10 `divUp` timeout) periodicEffBens+            selfBens = aspBens ++ periodicBens             eqpSum = if not (null selfBens) && minimum selfBens < -10                                             && maximum selfBens > 10                      then 0  -- significant mixed blessings out of AI control                      else sum selfBens             effSum = sum effBens             isWeapon =-              isJust (strengthFromEqpSlot Effect.EqpSlotWeapon itemFull)+              isJust (strengthFromEqpSlot IK.EqpSlotWeapon itemFull)             totalSum = if goesIntoInv $ itemBase itemFull                        then effSum-                       else if isWeapon-                            then effSum + eqpSum+                       else if isWeapon && effSum < 0+                            then - effSum + eqpSum                             else eqpSum-        in (totalSum, (eqpSum, effSum))+        in (totalSum, effSum)   in case itemDisco itemFull of     Just ItemDisco{itemAE=Just ItemAspectEffect{jaspects, jeffects}} ->       Just $ ben jeffects jaspects-    Just ItemDisco{itemKind=ItemKind{iaspects, ieffects}} ->+    Just ItemDisco{itemKind=IK.ItemKind{iaspects, ieffects}} ->       let travA x =-            St.evalState (Effect.aspectTrav x (return . round . Dice.meanDice))+            St.evalState (IK.aspectTrav x (return . round . Dice.meanDice))                          ()           jaspects = map travA iaspects-          travE x =-            St.evalState (Effect.effectTrav x (return . round . Dice.meanDice))-                         ()-          jeffects = map travE ieffects-      in Just $ ben jeffects jaspects+      in Just $ ben ieffects jaspects     _ -> Nothing
Game/LambdaHack/Client/Bfs.hs view
@@ -9,13 +9,11 @@ #endif   ) where -import Control.Arrow (second) import Control.Exception.Assert.Sugar import Data.Binary import Data.Bits (Bits, complement, (.&.), (.|.)) import Data.List import Data.Maybe-import qualified Data.Sequence as Seq  import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray@@ -38,48 +36,65 @@ apartBfs :: BfsDistance apartBfs = pred minKnownBfs +-- TODO: costly; peephole optimize, optmize BFS, don't call so often -- | Fill out the given BFS array. fillBfs :: (Point -> Point -> MoveLegal)  -- ^ is a move from known tile legal         -> (Point -> Point -> Bool)       -- ^ is a move from unknown legal         -> Point                          -- ^ starting position         -> PointArray.Array BfsDistance   -- ^ initial array, with @apartBfs@         -> PointArray.Array BfsDistance   -- ^ array with calculated distances+{-# INLINE fillBfs #-} fillBfs isEnterable passUnknown origin aInitial =-  let maxUnknownBfs = pred apartBfs-      maxKnownBfs = pred maxBound-      bfs :: Seq.Seq (Point, BfsDistance)+  let maxKnownBfs = pred maxBound+      predMaxKnownBfs = pred maxKnownBfs+      bfs :: BfsDistance+          -> [Point]+          -> [Point]           -> PointArray.Array BfsDistance           -> PointArray.Array BfsDistance-      bfs q a =-        case Seq.viewr q of-          Seq.EmptyR -> a  -- no more positions to check-          _ Seq.:> (_, d)-            | d == maxUnknownBfs || d == maxKnownBfs -> a  -- too far-          q1 Seq.:> (pos, oldDistance) | oldDistance >= minKnownBfs ->-            let distance = succ oldDistance-                allMvs = map (shift pos) moves-                freshMv p = a PointArray.! p == apartBfs-                freshMvs = filter freshMv allMvs-                legal p = (p, isEnterable pos p)-                legalities = map legal freshMvs-                notBlocked = filter ((/= MoveBlocked) . snd) legalities-                legalToDist l = if l == MoveToOpen-                                then distance-                                else distance .&. complement minKnownBfs-                mvs = map (second legalToDist) notBlocked-                q2 = foldr (Seq.<|) q1 mvs-                s2 = a PointArray.// mvs-            in bfs q2 s2-          q1 Seq.:> (pos, oldDistance) ->-            let distance = succ oldDistance-                allMvs = map (shift pos) moves-                goodMv p = a PointArray.! p == apartBfs && passUnknown pos p-                mvs = zip (filter goodMv allMvs) (repeat distance)-                q2 = foldr (Seq.<|) q1 mvs-                s2 = a PointArray.// mvs-            in bfs q2 s2-      origin0 = (origin, minKnownBfs)-  in bfs (Seq.singleton origin0) (aInitial PointArray.// [origin0])+      bfs distance predK predU a =+        let distCompl = distance .&. complement minKnownBfs+            processKnown (succK2, succU2, a2) pos =+              let fKnown (lK, lU) move =+                    let p = shift pos move+                        freshMv = a2 PointArray.! p == apartBfs+                        legality = isEnterable pos p+                        (notBlocked, enteredUnknown) = case legality of+                          MoveBlocked -> (False, undefined)+                          MoveToOpen -> (True, False)+                          MoveToUnknown -> (True, True)+                    in if freshMv && notBlocked+                       then if enteredUnknown+                            then (lK, p : lU)+                            else (p : lK, lU)+                       else (lK, lU)+                  (mvsK, mvsU) = foldl' fKnown ([], []) moves+                  upd = zip mvsK (repeat distance)+                        ++ zip mvsU (repeat distCompl)+                  !a3 = a2 PointArray.// upd+              in (mvsK ++ succK2, mvsU ++ succU2, a3)+            processUnknown (succU2, a2) pos =+              let fUnknown lU move =+                    let p = shift pos move+                        freshMv = a2 PointArray.! p == apartBfs+                        notBlocked = passUnknown pos p+                    in if freshMv && notBlocked+                       then p : lU+                       else lU+                  mvsU = foldl' fUnknown [] moves+                  upd = zip mvsU (repeat distCompl)+                  !a3 = a2 PointArray.// upd+              in (mvsU ++ succU2, a3)+            (succU4, !a4) = foldl' processUnknown ([], a) predU+            (succK6, succU6, !a6) = foldl' processKnown ([], succU4, a4) predK+        in if null succK6 && null succU6+           then a6  -- no more dungeon positions to check+           else if distance == predMaxKnownBfs  -- wasting one Known slot+                then a6  -- too far+                else bfs (succ distance) succK6 succU6 a6+  in PointArray.forceA  -- no more modifications of this array+     $ bfs (succ minKnownBfs) [origin] []+           (aInitial PointArray.// [(origin, minKnownBfs)])  -- TODO: Use http://harablog.wordpress.com/2011/09/07/jump-point-search/ -- to determine a few really different paths and compare them,@@ -94,6 +109,7 @@             -> (Point -> Point -> Bool)             -> Point -> Point -> Int -> PointArray.Array BfsDistance             -> Maybe [Point]+{-# INLINE findPathBfs #-} findPathBfs isEnterable passUnknown source target sepsRaw bfs =   assert (bfs PointArray.! source == minKnownBfs) $   let targetDist = bfs PointArray.! target
Game/LambdaHack/Client/BfsClient.hs view
@@ -22,7 +22,6 @@ import qualified Game.LambdaHack.Common.Ability as Ability import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind import Game.LambdaHack.Common.Level@@ -33,7 +32,7 @@ import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.TileKind+import Game.LambdaHack.Content.TileKind (TileKind)  -- | Get cached BFS data and path or, if not stored, generate, -- store and return. Due to laziness, they are not calculated until needed.@@ -42,22 +41,28 @@                    -> m (PointArray.Array BfsDistance, Maybe [Point]) getCacheBfsAndPath aid target = do   seps <- getsClient seps+  b <- getsState $ getActorBody aid+  let origin = bpos b+  (isEnterable, passUnknown) <- condBFS aid   let pathAndStore :: PointArray.Array BfsDistance                    -> m (PointArray.Array BfsDistance, Maybe [Point])       pathAndStore bfs = do-        computePath <- computePathBFS aid-        let mpath = computePath target seps bfs+        let mpath = findPathBfs isEnterable passUnknown origin target seps bfs         modifyClient $ \cli ->           cli {sbfsD = EM.insert aid (bfs, target, seps, mpath) (sbfsD cli)}         return (bfs, mpath)   mbfs <- getsClient $ EM.lookup aid . sbfsD   case mbfs of-    Just (bfs, targetOld, sepsOld, mpath) | targetOld == target-                                            && sepsOld == seps ->-      return (bfs, mpath)-    Just (bfs, _, _, _) -> pathAndStore bfs-    Nothing -> do-      bfs <- computeBFS aid+    Just (bfs, targetOld, sepsOld, mpath)+      -- TODO: hack: in screensavers this is not always ensured, so check here:+      | bfs PointArray.! bpos b == succ apartBfs ->+      if targetOld == target && sepsOld == seps+      then return (bfs, mpath)+      else pathAndStore bfs+    _ -> do+      Level{lxsize, lysize} <- getLevel $ blid b+      let vInitial = PointArray.replicateA lxsize lysize apartBfs+          bfs = fillBfs isEnterable passUnknown origin vInitial       pathAndStore bfs  getCacheBfs :: MonadClient m => ActorId -> m (PointArray.Array BfsDistance)@@ -68,33 +73,12 @@     Just (bfs, _, _, _) -> return bfs     Nothing -> fmap fst $ getCacheBfsAndPath aid (Point 0 0) -computeBFS :: MonadClient m => ActorId -> m (PointArray.Array BfsDistance)-computeBFS = computeAnythingBFS $ \isEnterable passUnknown aid -> do-  b <- getsState $ getActorBody aid-  Level{lxsize, lysize} <- getLevel $ blid b-  let origin = bpos b-      vInitial = PointArray.replicateA lxsize lysize apartBfs-  -- Here we don't want '$!', because we want the BFS data lazy.-  return ${-keep it!-} fillBfs isEnterable passUnknown origin vInitial--computePathBFS :: MonadClient m-               => ActorId-               -> m (Point -> Int -> PointArray.Array BfsDistance-                     -> Maybe [Point])-computePathBFS = computeAnythingBFS $ \isEnterable passUnknown aid -> do-  b <- getsState $ getActorBody aid-  let origin = bpos b-  -- Here we don't want '$!', because we want the BFS data lazy.-  return ${-keep it!-} findPathBfs isEnterable passUnknown origin--computeAnythingBFS :: MonadClient m-                   => ((Point -> Point -> MoveLegal)-                       -> (Point -> Point -> Bool)-                       -> ActorId-                       -> m a)-                   -> ActorId-                   -> m a-computeAnythingBFS fAnything aid = do+condBFS :: MonadClient m+        => ActorId+        -> m (Point -> Point -> MoveLegal,+              Point -> Point -> Bool)+{-# INLINE condBFS #-}+condBFS aid = do   cops@Kind.COps{cotile=cotile@Kind.Ops{ouniqGroup}} <- getsState scops   b <- getsState $ getActorBody aid   -- We assume the actor eventually becomes a leader (or has the same@@ -114,6 +98,7 @@       conditions = catMaybes $ chAccess : chDoorAccess       -- Legality of move from a known tile, assuming doors freely openable.       isEnterable :: Point -> Point -> MoveLegal+      {-# INLINE isEnterable #-}       isEnterable spos tpos =         let st = lvl `at` spos             tt = lvl `at` tpos@@ -129,13 +114,14 @@                 else MoveBlocked       -- Legality of move from an unknown tile, assuming unknown are open.       passUnknown :: Point -> Point -> Bool+      {-# INLINE passUnknown #-}       passUnknown = case chAccess of  -- spos is unknown, so not a door         Nothing -> \_ tpos -> let tt = lvl `at` tpos                               in tt == unknownId         Just ch -> \spos tpos -> let tt = lvl `at` tpos                                  in tt == unknownId                                     && ch spos tpos-  fAnything isEnterable passUnknown aid+  return (isEnterable, passUnknown)  accessCacheBfs :: MonadClient m => ActorId -> Point -> m (Maybe Int) {-# INLINE accessCacheBfs #-}@@ -229,35 +215,46 @@ closestTriggers onlyDir exploredToo aid = do   Kind.COps{cotile} <- getsState scops   body <- getsState $ getActorBody aid-  lvl <- getLevel $ blid body+  let lid = blid body+  lvl <- getLevel lid   dungeon <- getsState sdungeon   explored <- getsClient sexplored   unexploredD <- unexploredDepth   let allExplored = ES.size explored == EM.size dungeon-      unexUp = onlyDir /= Just False && unexploredD 1 (blid body)-      unexDown = onlyDir /= Just True && unexploredD (-1) (blid body)-      unexEffect (Effect.Ascend p) = if p > 0 then unexUp else unexDown-      unexEffect _ =-        -- Escape (or guard) only after exploring, for high score, etc.-        allExplored-      isTrigger-        | exploredToo = \t -> Tile.isWalkable cotile t-                              && not (null $ Tile.causeEffects cotile t)-        | otherwise = \t -> Tile.isWalkable cotile t-                            && any unexEffect (Tile.causeEffects cotile t)-      f :: [Point] -> Point -> Kind.Id TileKind -> [Point]-      f acc p t = if isTrigger t then p : acc else acc+      f :: [(Int, Point)] -> Point -> Kind.Id TileKind -> [(Int, Point)]+      f acc p t =+        if Tile.isWalkable cotile t && not (null $ Tile.causeEffects cotile t)+        then if exploredToo+             then (1, p) : acc  -- direction irrelevant+             else case Tile.ascendTo cotile t of+               [] ->+                 -- Escape (or guard) after exploring, for high score, etc.+                 if allExplored then (1, p) : acc else acc+               l ->+                 let g k = k > 0+                           && onlyDir /= Just False+                           && unexploredD 1 lid+                           ||+                           k < 0+                           && onlyDir /= Just True+                           && unexploredD (-1) lid+                 in map (,p) (filter g l) ++ acc+        else acc   let triggersAll = PointArray.ifoldlA f [] $ ltile lvl       -- Don't target stairs under the actor. Most of the time they       -- are blocked and stay so, so we seek other stairs, if any.       -- If no other stairs in this direction, let's wait here.-      triggers | length triggersAll > 1 = delete (bpos body) triggersAll+      triggers | length triggersAll > 1 =+                 filter ((/= bpos body) . snd) triggersAll                | otherwise = triggersAll   case triggers of     [] -> return []     _ -> do       bfs <- getCacheBfs aid-      let ds = mapMaybe (\p -> fmap (,p) (accessBfs bfs p)) triggers+      -- Prefer stairs to easier levels.+      let mix (k, p) dist = ((abs (fromEnum lid + k), dist), p)+          ds = mapMaybe (\(k, p) -> fmap (mix (k, p)) (accessBfs bfs p))+                        triggers       return $! map snd $ sortBy (comparing fst) ds  unexploredDepth :: MonadClient m => m (Int -> LevelId -> Bool)
Game/LambdaHack/Client/CommonClient.hs view
@@ -22,7 +22,7 @@ import qualified Game.LambdaHack.Common.Ability as Ability import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState-import qualified Game.LambdaHack.Common.Effect as Effect+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemStrongest@@ -161,19 +161,18 @@             then Nothing  -- ProjectBlockActor, ProjectAimOnself             else tryLines epsOld (Nothing, minBound) -actorSkillsClient :: MonadClient m-                  => ActorId -> Maybe ActorId -> m Ability.Skills-actorSkillsClient aid mleader = do+actorSkillsClient :: MonadClient m => ActorId -> m Ability.Skills+actorSkillsClient aid = do   activeItems <- activeItemsClient aid-  getsState $ actorSkills aid mleader activeItems+  -- Newest Leader in _sleader, not yet in sfactionD.+  mleader <- getsClient _sleader+  getsState $ actorSkills mleader aid activeItems  maxActorSkillsClient :: MonadClient m                      => ActorId -> m Ability.Skills maxActorSkillsClient aid = do   activeItems <- activeItemsClient aid-  skOther <- getsState $ actorSkills aid Nothing activeItems-  skLeader <- getsState $ actorSkills aid (Just aid) activeItems-  return $! Ability.maxSkills skOther skLeader+  getsState $ maxActorSkills aid activeItems  updateItemSlot :: MonadClient m => Maybe ActorId -> ItemId -> m () updateItemSlot maid iid = do@@ -209,7 +208,7 @@   activeAssocs <- fullAssocsClient aid [CEqp, COrgan]   return $! map snd activeAssocs -itemToFullClient :: MonadClient m => m (ItemId -> Int -> ItemFull)+itemToFullClient :: MonadClient m => m (ItemId -> ItemQuant -> ItemFull) itemToFullClient = do   cops <- getsState scops   discoKind <- getsClient sdiscoKind@@ -226,11 +225,15 @@ pickWeaponClient source target = do   eqpAssocs <- fullAssocsClient source [CEqp]   bodyAssocs <- fullAssocsClient source [COrgan]-  mleader <- getsClient _sleader-  actorSk <- actorSkillsClient source mleader+  actorSk <- actorSkillsClient source+  sb <- getsState $ getActorBody source   let allAssocs = eqpAssocs ++ bodyAssocs-  case filter (not . unknownPrecious . snd . snd)-       $ strongestSlotNoFilter Effect.EqpSlotWeapon allAssocs of+      calm10 = calmEnough10 sb $ map snd allAssocs+      forced = assert (not $ bproj sb) False+      legalPrecious = either (const False) id . permittedPrecious calm10 forced+      strongest = strongestSlotNoFilter IK.EqpSlotWeapon allAssocs+      strongestLegal = filter (legalPrecious . snd . snd) strongest+  case strongestLegal of     _ | EM.findWithDefault 0 Ability.AbMelee actorSk <= 0 -> return []     [] -> return []     iis@((maxS, _) : _) -> do@@ -242,7 +245,7 @@       return $! [ReqMelee target iid cstore]  sumOrganEqpClient :: MonadClient m-                  => Effect.EqpSlot -> ActorId -> m Int+                  => IK.EqpSlot -> ActorId -> m Int sumOrganEqpClient eqpSlot aid = do   activeItems <- activeItemsClient aid   return $! sumSlotNoFilter eqpSlot activeItems
Game/LambdaHack/Client/HandleAtomicClient.hs view
@@ -10,6 +10,7 @@ import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES import Data.Maybe+import qualified Data.Text as T import qualified NLP.Miniutter.English as MU  import Game.LambdaHack.Atomic@@ -19,7 +20,6 @@ import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.ClientOptions-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind@@ -31,8 +31,9 @@ import Game.LambdaHack.Common.Point import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Content.ItemKind-import Game.LambdaHack.Content.TileKind+import Game.LambdaHack.Content.ItemKind (ItemKind)+import qualified Game.LambdaHack.Content.ItemKind as IK+import qualified Game.LambdaHack.Content.TileKind as TK  -- * RespUpdAtomicAI @@ -64,11 +65,11 @@         -- to @toTile@. See @updAlterTile@.         let subject = ""  -- a hack, we we don't handle adverbs well             verb = "turn into"-            msg = makeSentence [ "the", MU.Text $ tname $ okind t-                               , "at position", MU.Text $ tshow p+            msg = makeSentence [ "the", MU.Text $ TK.tname $ okind t+                               , "at position", MU.Text $ T.pack $ show p                                , "suddenly"  -- adverb                                , MU.SubjectVerbSg subject verb-                               , MU.AW $ MU.Text $ tname $ okind toTile ]+                               , MU.AW $ MU.Text $ TK.tname $ okind toTile ]         return [ cmd  -- reveal the tile                , UpdMsgAll msg  -- show the message                ]@@ -211,18 +212,21 @@           else Just $ UpdLoseActor aid b ais         outActor = mapMaybe fActor outPrio     -- Wipe out remembered items on tiles that now came into view.-    Level{lfloor, lsmell} <- getLevel lid-    let inFov = totalVisible perNew ES.\\ totalVisible perOld+    lvl <- getLevel lid+    let inFov = ES.elems $ totalVisible perNew ES.\\ totalVisible perOld         pMaybe p = maybe Nothing (\x -> Just (p, x))-        inFloor = mapMaybe (\p -> pMaybe p $ EM.lookup p lfloor)-                           (ES.elems inFov)-        fItem p (iid, k) = UpdLoseItem iid (getItemBody iid s) k (CFloor lid p)-        fBag (p, bag) = map (fItem p) $ EM.assocs bag-        inItem = concatMap fBag inFloor+        inContainer fc itemFloor =+          let inItem = mapMaybe (\p -> pMaybe p $ EM.lookup p itemFloor) inFov+              fItem p (iid, kit) =+                UpdLoseItem 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 = smellVisible perNew ES.\\ smellVisible perOld-        inSm = mapMaybe (\p -> pMaybe p $ EM.lookup p lsmell)+        inSm = mapMaybe (\p -> pMaybe p $ EM.lookup p (lsmell lvl))                         (ES.elems inSmellFov)         inSmell = if null inSm then [] else [UpdLoseSmell lid inSm]     let seenNew = seenAtomicCli False fid perNew@@ -233,18 +237,19 @@     assert (allB seenOld psActor) skip     -- Verify that we forget only currently invisible actors.     assert (allB (not . seenNew) psActor) skip-    psItemSmell <- mapM posUpdAtomic $ inItem ++ inSmell+    let inTileSmell = inFloor ++ inEmbed ++ inSmell+    psItemSmell <- mapM posUpdAtomic inTileSmell     -- Verify that we forget only previously invisible items and smell.     assert (allB (not . seenOld) psItemSmell) skip     -- Verify that we forget only currently seen items and smell.     assert (allB seenNew psItemSmell) skip-    return $! cmd : outActor ++ inItem ++ inSmell+    return $! cmd : outActor ++ inTileSmell   _ -> return [cmd]  deleteSmell :: MonadClient m => ActorId -> Point -> m [UpdAtomic] deleteSmell aid pos = do   b <- getsState $ getActorBody aid-  smellRadius <- sumOrganEqpClient Effect.EqpSlotAddSmell aid+  smellRadius <- sumOrganEqpClient IK.EqpSlotAddSmell aid   if smellRadius <= 0 then return []   else do     lvl <- getLevel $ blid b
Game/LambdaHack/Client/ItemSlot.hs view
@@ -22,7 +22,7 @@ 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.State  newtype SlotChar = SlotChar {slotChar :: Char}@@ -42,6 +42,7 @@   sectionBy (sort ls) Nothing  where   succSlot c d = ord (slotChar d) - ord (slotChar c) == 1+  succ2Slot c d = ord (slotChar d) - ord (slotChar c) == 2    sectionBy []     Nothing       = T.empty   sectionBy []     (Just (c, d)) = finish (c,d)@@ -52,6 +53,9 @@    finish (c, d) | c == d         = T.pack [slotChar c]                 | succSlot c d   = T.pack [slotChar c, slotChar d]+                | succ2Slot c d  = T.pack [ slotChar c+                                          , chr (1 + ord (slotChar c))+                                          , slotChar d ]                 | otherwise      = T.pack [slotChar c, '-', slotChar d]  allSlots :: [SlotChar]@@ -72,11 +76,11 @@   candidates = take (length allSlots)                $ drop (1 + fromJust (elemIndex lastSlot allSlots))                $ cycle allSlots-  onPerson = maybe (sharedAllOwnedFid fid s)-                   (\body -> sharedAllOwned body s)+  onPerson = maybe (sharedAllOwnedFid True fid s)+                   (\body -> sharedAllOwned True body s)                    mbody   onGroud = maybe EM.empty-                  (\body -> sdungeon s EM.! blid body `atI` bpos body)+                  (\b -> getCBag (CFloor (blid b) (bpos b)) s)                   mbody   inBags = ES.unions $ map EM.keysSet [onPerson, onGroud]   f l = maybe True (`ES.notMember` inBags) $ EM.lookup l letterSlots
Game/LambdaHack/Client/Key.hs view
@@ -4,7 +4,7 @@   ( Key(..), showKey, handleDir, dirAllKey   , moveBinding, mkKM, keyTranslate   , Modifier(..), KM(..), showKM-  , escKM, spaceKM, pgupKM, pgdnKM+  , escKM, spaceKM, returnKM, pgupKM, pgdnKM   ) where  import Control.Exception.Assert.Sugar@@ -34,6 +34,8 @@   | Down   | End   | Begin+  | Insert+  | Delete   | Home   | KP !Char      -- ^ a keypad key for a character (digits and operators)   | Char !Char    -- ^ a single printable character@@ -76,6 +78,8 @@ showKey PgUp     = "PGUP" showKey PgDn     = "PGDOWN" showKey Begin    = "BEGIN"+showKey Insert   = "INSERT"+showKey Delete   = "DELETE" showKey (KP c)   = "KEYPAD_" <> T.singleton c showKey (Unknown s) = s @@ -90,6 +94,9 @@ spaceKM :: KM spaceKM = KM {modifier = NoModifier, key = Space} +returnKM :: KM+returnKM = KM {modifier = NoModifier, key = Return}+ pgupKM :: KM pgupKM = KM {modifier = NoModifier, key = PgUp} @@ -120,18 +127,34 @@ dirViShiftKey :: [Key] dirViShiftKey = map (Char . Char.toUpper) dirViChar +dirMoveNoModifier :: Bool -> Bool -> [Key]+dirMoveNoModifier configVi configLaptop =+  dirKeypadKey ++ if configVi then dirViKey+                  else if configLaptop then dirLaptopKey+                  else []++dirRunNoModifier :: Bool -> Bool -> [Key]+dirRunNoModifier configVi configLaptop =+  dirKeypadShiftKey ++ if configVi then dirViShiftKey+                       else if configLaptop then dirLaptopShiftKey+                       else []++dirRunControl :: [Key]+dirRunControl = dirKeypadKey+                ++ dirKeypadShiftKey+                ++ (map Char dirKeypadShiftChar)+ dirAllKey :: Bool -> Bool -> [Key]-dirAllKey configVi configLaptop = dirKeypadKey ++ if configVi-                                                  then dirViKey-                                                  else if configLaptop-                                                       then dirLaptopKey-                                                       else []+dirAllKey configVi configLaptop =+  dirMoveNoModifier configVi configLaptop+  ++ dirRunNoModifier configVi configLaptop+  ++ dirRunControl  -- | Configurable event handler for the direction keys. -- Used for directed commands such as close door. handleDir :: Bool -> Bool -> KM -> (Vector -> a) -> a -> a handleDir configVi configLaptop KM{modifier=NoModifier, key} h k =-  let assocs = zip (dirAllKey configVi configLaptop) $ moves ++ moves+  let assocs = zip (dirAllKey configVi configLaptop) $ cycle moves   in maybe k h (lookup key assocs) handleDir _ _ _ _ k = k @@ -144,19 +167,9 @@         map (assign move) (zip (zipWith KM (repeat modifier) keys) moves)       mapRun modifier keys =         map (assign run) (zip (zipWith KM (repeat modifier) keys) moves)-      dirOtherKey | configVi = dirViKey-                  | configLaptop = dirLaptopKey-                  | otherwise = []-      dirOtherShiftKey | configVi = dirViShiftKey-                       | configLaptop = dirLaptopShiftKey-                       | otherwise = []-  in mapMove NoModifier dirKeypadKey-     ++ mapMove NoModifier dirOtherKey-     ++ mapRun NoModifier dirKeypadShiftKey-     ++ mapRun NoModifier dirOtherShiftKey-     ++ mapRun Control dirKeypadKey-     ++ mapRun Control dirKeypadShiftKey-     ++ mapRun Control (map Char dirKeypadShiftChar)+  in mapMove NoModifier (dirMoveNoModifier configVi configLaptop)+     ++ mapRun NoModifier (dirRunNoModifier configVi configLaptop)+     ++ mapRun Control dirRunControl  mkKM :: String -> KM mkKM s = let mkKey sk =@@ -205,17 +218,36 @@ keyTranslate "Tab"           = Tab keyTranslate "ISO_Left_Tab"  = BackTab keyTranslate "BackSpace"     = BackSpace+keyTranslate "Up"            = Up keyTranslate "KP_Up"         = Up+keyTranslate "Down"          = Down keyTranslate "KP_Down"       = Down+keyTranslate "Left"          = Left keyTranslate "KP_Left"       = Left+keyTranslate "Right"         = Right keyTranslate "KP_Right"      = Right+keyTranslate "Home"          = Home keyTranslate "KP_Home"       = Home+keyTranslate "End"           = End keyTranslate "KP_End"        = End keyTranslate "Page_Up"       = PgUp keyTranslate "KP_Page_Up"    = PgUp+keyTranslate "Prior"         = PgUp+keyTranslate "KP_Prior"      = PgUp keyTranslate "Page_Down"     = PgDn keyTranslate "KP_Page_Down"  = PgDn+keyTranslate "Next"          = PgDn+keyTranslate "KP_Next"       = PgDn+keyTranslate "Begin"         = Begin keyTranslate "KP_Begin"      = Begin+keyTranslate "Clear"         = Begin+keyTranslate "KP_Clear"      = Begin+keyTranslate "Center"        = Begin+keyTranslate "KP_Center"     = Begin+keyTranslate "Insert"        = Insert+keyTranslate "KP_Insert"     = Insert+keyTranslate "Delete"        = Delete+keyTranslate "KP_Delete"     = Delete keyTranslate "KP_Enter"      = Return keyTranslate ['K','P','_',c] = KP c keyTranslate [c]             = Char c
Game/LambdaHack/Client/State.hs view
@@ -3,7 +3,7 @@ module Game.LambdaHack.Client.State   ( StateClient(..), defStateClient, defaultHistory   , updateTarget, getTarget, updateLeader, sside-  , PathEtc, TgtMode(..), RunParams(..), LastRecord+  , PathEtc, TgtMode(..), RunParams(..), LastRecord, EscAI(..)   , toggleMarkVision, toggleMarkSmell, toggleMarkSuspect   ) where @@ -36,6 +36,7 @@ import Game.LambdaHack.Common.State import Game.LambdaHack.Common.Time import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ModeKind  -- | Client state, belonging to a single faction. -- Some of the data, e.g, the history, carries over@@ -70,6 +71,8 @@   , srandom      :: !R.StdGen      -- ^ current random generator   , slastRecord  :: !LastRecord    -- ^ state of key sequence recording   , slastPlay    :: ![K.KM]        -- ^ state of key sequence playback+  , slastLost    :: !(ES.EnumSet ActorId)+                                   -- ^ actors that just got out of sight   , swaitTimes   :: !Int           -- ^ player just waited this many times   , _sleader     :: !(Maybe ActorId)                                    -- ^ current picked party leader@@ -82,7 +85,8 @@   , scurDifficulty :: !Int         -- ^ current game difficulty level   , sslots       :: !ItemSlots     -- ^ map from slots to items   , slastSlot    :: !SlotChar      -- ^ last used slot-  , sgameMode    :: !GroupName     -- ^ current game mode+  , sgameMode    :: !(GroupName ModeKind)  -- ^ current game mode+  , sescAI       :: !EscAI         -- ^ just canceled AI control with ESC   , sdebugCli    :: !DebugModeCli  -- ^ client debugging mode   }   deriving Show@@ -109,6 +113,9 @@                   , Int     -- commands left to record for this batch                   ) +data EscAI = EscAINothing | EscAIStarted | EscAIMenu | EscAIExited+  deriving (Show, Eq)+ -- | Initial game client state. defStateClient :: History -> Report -> FactionId -> Bool -> StateClient defStateClient shistory sreport _sside sisAI =@@ -133,6 +140,7 @@     , srandom = R.mkStdGen 42  -- will be set later     , slastRecord = ([], [], 0)     , slastPlay = []+    , slastLost = ES.empty     , swaitTimes = 0     , _sleader = Nothing  -- no heroes yet alive     , _sside@@ -144,7 +152,8 @@     , scurDifficulty = difficultyDefault     , sslots = (EM.empty, IM.empty)     , slastSlot = SlotChar 'a'-    , sgameMode = "campaign"+    , sgameMode = "starting"+    , sescAI = EscAINothing     , sdebugCli = defDebugModeCli     } @@ -152,7 +161,7 @@ defaultHistory = do   dateTime <- getClockTime   let curDate = MU.Text $ T.pack $ calendarTimeToString $ toUTCTime dateTime-  return $! singletonHistory $ singletonReport+  return $! singletonHistory timeZero $ singletonReport          $! makeSentence ["Human history log started on", curDate]  -- | Update target parameters within client state.@@ -248,8 +257,10 @@         srandom = read g         slastRecord = ([], [], 0)         slastPlay = []+        slastLost = ES.empty         swaitTimes = 0         squit = False+        sescAI = EscAINothing     return $! StateClient{..}  instance Binary RunParams where
Game/LambdaHack/Client/UI.hs view
@@ -20,6 +20,7 @@ import Control.Exception.Assert.Sugar import Control.Monad import qualified Data.EnumMap.Strict as EM+import qualified Data.EnumSet as ES import qualified Data.Map.Strict as M import Data.Maybe @@ -92,7 +93,7 @@   -- For human UI we invalidate whole @sbfsD@ at the start of each   -- UI player input, which is an overkill, but doesn't affects   -- screensavers, because they are UI, but not human.-  modifyClient $ \cli -> cli {sbfsD = EM.empty}+  modifyClient $ \cli -> cli {sbfsD = EM.empty, slastLost = ES.empty}   let loop :: Maybe (Bool, Overlay) -> m RequestUI       loop mover = do         (lastBlank, over) <- case mover of@@ -125,9 +126,20 @@                 {swaitTimes = if swaitTimes cli > 0                               then - swaitTimes cli                               else 0}-              if km == K.escKM && isNothing stgtMode && isJust mover-                then cmdHumanSem Clear-                else cmdHumanSem cmd+              escAI <- getsClient sescAI+              case escAI of+                EscAIStarted -> do+                  modifyClient $ \cli -> cli {sescAI = EscAIMenu}+                  cmdHumanSem cmd+                EscAIMenu -> do+                  unless (km `elem` [K.escKM, K.returnKM]) $+                    modifyClient $ \cli -> cli {sescAI = EscAIExited}+                  cmdHumanSem cmd+                _ -> do+                  modifyClient $ \cli -> cli {sescAI = EscAINothing}+                  if km == K.escKM && isNothing stgtMode && isJust mover+                  then cmdHumanSem Clear+                  else cmdHumanSem cmd             Nothing -> let msgKey = "unknown command <" <> K.showKM km <> ">"                        in fmap Left $ promptToSlideshow msgKey         -- The command was failed or successful and if the latter,@@ -170,6 +182,7 @@   let pong ats = return $ ReqUIPong ats       underAI = isAIFact fact   if escPressed && underAI && fleaderMode (gplayer fact) /= LeaderNull then do+    modifyClient $ \cli -> cli {sescAI = EscAIStarted}     -- Ask server to turn off AI for the faction's leader.     let atomicCmd = UpdAtomic $ UpdAutoFaction side False     pong [atomicCmd]
Game/LambdaHack/Client/UI/Config.hs view
@@ -37,6 +37,7 @@   , configVi          :: !Bool  -- ^ the option for Vi keys takes precendence   , configLaptop      :: !Bool  -- ^ because the laptop keys are the default   , configFont        :: !String+  , configColorIsBold :: !Bool   , configHistoryMax  :: !Int   , configMaxFps      :: !Int   , configNoAnim      :: !Bool@@ -77,6 +78,7 @@       -- because the laptop keys are the default.       configLaptop = not configVi && getOption "movementLaptopKeys_uk8o79jl"       configFont = getOption "font"+      configColorIsBold = getOption "colorIsBold"       configHistoryMax = getOption "historyMax"       configMaxFps = max 1 $ getOption "maxFps"       configNoAnim = getOption "noAnim"@@ -111,6 +113,8 @@   let stdRuleset = Kind.stdRuleset corule   in (\dbg -> dbg {sfont =         sfont dbg `mplus` Just (configFont sconfig)}) .+     (\dbg -> dbg {scolorIsBold =+        scolorIsBold dbg `mplus` Just (configColorIsBold sconfig)}) .      (\dbg -> dbg {smaxFps =         smaxFps dbg `mplus` Just (configMaxFps sconfig)}) .      (\dbg -> dbg {snoAnim =
Game/LambdaHack/Client/UI/DisplayAtomicClient.hs view
@@ -5,6 +5,7 @@  import Control.Exception.Assert.Sugar import Control.Monad+import qualified Data.EnumSet as ES import qualified Data.EnumMap.Strict as EM import qualified Data.IntMap.Strict as IM import Data.Maybe@@ -25,7 +26,6 @@ import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Color as Color import qualified Game.LambdaHack.Common.Dice as Dice-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemDescription@@ -37,9 +37,9 @@ import Game.LambdaHack.Common.Point import Game.LambdaHack.Common.State import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.TileKind+import qualified Game.LambdaHack.Content.TileKind as TK  -- * RespUpdAtomicUI @@ -64,16 +64,30 @@     destroyActorUI aid body "die" "be destroyed" verbose     side <- getsClient sside     when (bfid body == side && not (bproj body)) stopPlayBack-  UpdCreateItem iid _ k c -> do-    -- TODO: probably not Nothing if container not CGround-    updateItemSlot Nothing iid-    itemVerbMU iid k (MU.Text $ "appear" <+> ppContainer c) (storeFromC c)+  UpdCreateItem iid _ kit c -> do+    case c of+      CActor aid COrgan -> do+        let verb =+              MU.Text $ "become" <+> case fst kit of+                                       1 -> ""+                                       k -> tshow k <> "-fold"+        -- This describes all such items already among organs,+        -- which is useful, because it shows "charging".+        itemAidVerbMU aid verb iid (Left Nothing) COrgan+      CActor aid store -> do+        when (store == CGround) $ updateItemSlotSide aid iid+        itemVerbMU iid kit (MU.Text $ "appear" <+> ppContainer c) c+      CEmbed{} -> return ()+      CFloor{} -> do+        updateItemSlot Nothing iid+        itemVerbMU iid kit (MU.Text $ "appear" <+> ppContainer c) c+      CTrunk{} -> return ()     stopPlayBack-  UpdDestroyItem iid _ k c -> itemVerbMU iid k "disappear" (storeFromC c)+  UpdDestroyItem iid _ kit c -> itemVerbMU iid kit "disappear" c   UpdSpotActor aid body _ -> createActorUI aid body verbose "be spotted"   UpdLoseActor aid body _ ->     destroyActorUI aid body "be missing in action" "be lost" verbose-  UpdSpotItem iid _ k c -> do+  UpdSpotItem iid _ kit c -> do     -- We assign slots to all items visible on the floor,     -- but some of the slots are later on recycled and then     -- we report spotting the items again.@@ -81,35 +95,38 @@     case ( lookup iid $ map swap $ EM.assocs letterSlots          , lookup iid $ map swap $ IM.assocs numberSlots ) of       (Nothing, Nothing) -> do-        updateItemSlot Nothing iid         case c of-          CActor{} -> return ()  -- not actionable at this time+          CActor aid CGround -> updateItemSlotSide aid iid+          CActor{} -> return ()+          CEmbed{} -> return ()           CFloor lid p -> do+            updateItemSlot Nothing iid             scursorOld <- getsClient scursor             case scursorOld of               TEnemy{} -> return ()  -- probably too important to overwrite               TEnemyPos{} -> return ()               _ -> modifyClient $ \cli -> cli {scursor = TPoint lid p}-            itemVerbMU iid k "be spotted" CGround+            itemVerbMU iid kit "be spotted" c             stopPlayBack           CTrunk{} -> return ()       _ -> return ()  -- seen recently (still has a slot assigned)   UpdLoseItem{} -> skip   -- Move actors and items.   UpdMoveActor aid _ _ -> lookAtMove aid-  UpdWaitActor aid _ -> when verbose $ aVerbMU aid "wait"+  UpdWaitActor aid _ -> when verbose $ aidVerbMU aid "wait"   UpdDisplaceActor source target -> displaceActorUI source target   UpdMoveItem iid k aid c1 c2 -> moveItemUI verbose iid k aid c1 c2   -- Change actor attributes.   UpdAgeActor{} -> skip+  UpdRefillHP _ 0 -> skip   UpdRefillHP aid n -> do     when verbose $-      aVerbMU aid $ MU.Text $ (if n > 0 then "heal" else "lose")-                              <+> tshow (abs n) <> "HP"+      aidVerbMU aid $ MU.Text $ (if n > 0 then "heal" else "lose")+                              <+> tshow (abs $ n `divUp` oneM) <> "HP"     mleader <- getsClient _sleader     when (Just aid == mleader) $ do       b <- getsState $ getActorBody aid-      hpMax <- sumOrganEqpClient Effect.EqpSlotAddMaxHP aid+      hpMax <- sumOrganEqpClient IK.EqpSlotAddMaxHP aid       when (bhp b == xM hpMax && hpMax > 0) $ do         actorVerbMU aid b "recover your health fully"         stopPlayBack@@ -122,7 +139,7 @@         allFoes  <- getsState $ actorRegularList (isAtWar fact) (blid b)         let closeFoes = filter ((<= 3) . chessDist (bpos b) . bpos) allFoes         when (null closeFoes) $ do  -- obvious where the feeling comes from-          aVerbMU aid "hear something"+          aidVerbMU aid "hear something"           msgDuplicateScrap   UpdOldFidActor{} -> skip   UpdTrajectory{} -> skip@@ -161,7 +178,9 @@         showDipl War = "at war"     msgAdd $ name1 <+> "and" <+> name2 <+> "are now" <+> showDipl toDipl <> "."   UpdTacticFaction{} -> skip-  UpdAutoFaction{} -> skip+  UpdAutoFaction fid b -> do+    side <- getsClient sside+    when (fid == side) $ setFrontAutoYes b   UpdRecordKill{} -> skip   -- Alter map.   UpdAlterTile{} -> when verbose $ return ()  -- TODO: door opens@@ -176,13 +195,13 @@     let t = lvl `at` p         verb | t == toTile = "confirm"              | otherwise = "reveal"-        subject2 = MU.Text $ tname $ okind fromTile+        subject2 = MU.Text $ TK.tname $ okind fromTile         verb2 = "be"     let msg = makeSentence [ MU.SubjectVerbSg subject verb                            , "that the"                            , MU.SubjectVerbSg subject2 verb2                            , "a hidden"-                           , MU.Text $ tname $ okind toTile ]+                           , MU.Text $ TK.tname $ okind toTile ]     msgAdd msg   UpdLearnSecrets{} -> skip   UpdSpotTile{} -> skip@@ -191,29 +210,42 @@   UpdSpotSmell{} -> skip   UpdLoseSmell{} -> skip   -- Assorted.-  UpdAgeGame {} -> skip-  UpdDiscover _ _ iid _ _ -> discover oldStateClient iid-  UpdCover{} ->  skip  -- don't spam when doing undo-  UpdDiscoverKind _ _ iid _ -> discover oldStateClient iid-  UpdCoverKind{} ->  skip  -- don't spam when doing undo-  UpdDiscoverSeed _ _ iid _ -> discover oldStateClient iid+  UpdTimeItem{} -> skip+  UpdAgeGame{} -> skip+  UpdDiscover lid p iid _ _ -> discover lid p oldStateClient iid+  UpdCover{} -> skip  -- don't spam when doing undo+  UpdDiscoverKind lid p iid _ -> discover lid p oldStateClient iid+  UpdCoverKind{} -> skip  -- don't spam when doing undo+  UpdDiscoverSeed lid p iid _ -> discover lid p oldStateClient iid   UpdCoverSeed{} -> skip  -- don't spam when doing undo   UpdPerception{} -> skip-  UpdRestart _ _ _ _ _ _ -> do+  UpdRestart fid _ _ _ _ _ -> do     mode <- getModeClient     msgAdd $ "New game started in" <+> mname mode <+> "mode." <+> mdesc mode     -- TODO: use a vertical animation instead, e.g., roll down,     -- and reveal the first frame of a new game, not blank screen.     history <- getsClient shistory     when (lengthHistory history > 1) $ fadeOutOrIn False+    fact <- getsState $ (EM.! fid) . sfactionD+    setFrontAutoYes $ isAIFact fact   UpdRestartServer{} -> skip-  UpdResume{} -> skip+  UpdResume fid _ -> do+    fact <- getsState $ (EM.! fid) . sfactionD+    setFrontAutoYes $ isAIFact fact   UpdResumeServer{} -> skip   UpdKillExit{} -> skip   UpdWriteSave -> when verbose $ msgAdd "Saving backup."   UpdMsgAll msg -> msgAdd msg   UpdRecordHistory _ -> recordHistory +updateItemSlotSide :: MonadClient m => ActorId -> ItemId -> m ()+updateItemSlotSide aid iid = do+  side <- getsClient sside+  b <- getsState $ getActorBody aid+  if bfid b == side+  then updateItemSlot (Just aid) iid+  else updateItemSlot Nothing iid+ lookAtMove :: MonadClientUI m => ActorId -> m () lookAtMove aid = do   body <- getsState $ getActorBody aid@@ -225,7 +257,7 @@     lookMsg <- lookAt False "" True (bpos body) aid ""     msgAdd lookMsg   fact <- getsState $ (EM.! bfid body) . sfactionD-  if side == bfid body then do+  if not (bproj body) && side == bfid body then do     foes <- getsState $ actorList (isAtWar fact) (blid body)     when (any (adjacent (bpos body) . bpos) foes) stopPlayBack   else when (isAtWar fact side) $ do@@ -238,28 +270,59 @@   subject <- partActorLeader aid b   msgAdd $ makeSentence [MU.SubjectVerbSg subject verb] -aVerbMU :: MonadClientUI m => ActorId -> MU.Part -> m ()-aVerbMU aid verb = do+aidVerbMU :: MonadClientUI m => ActorId -> MU.Part -> m ()+aidVerbMU aid verb = do   b <- getsState $ getActorBody aid   actorVerbMU aid b verb  itemVerbMU :: MonadClientUI m-           => ItemId -> Int -> MU.Part -> CStore -> m ()-itemVerbMU iid k verb cstore = assert (k > 0) $ do+           => ItemId -> ItemQuant -> MU.Part -> Container -> m ()+itemVerbMU iid kit@(k, _) verb c = assert (k > 0) $ do+  lid <- getsState $ lidFromC c+  localTime <- getsState $ getLocalTime lid   itemToF <- itemToFullClient-  let subject = partItemWs k cstore (itemToF iid k)+  let subject = partItemWs k c lid localTime (itemToF iid kit)       msg | k > 1 = makeSentence [MU.SubjectVerb MU.PlEtc MU.Yes subject verb]           | otherwise = makeSentence [MU.SubjectVerbSg subject verb]   msgAdd msg -aiVerbMU :: MonadClientUI m-         => ActorId -> MU.Part -> ItemId -> Int -> CStore -> m ()-aiVerbMU aid verb iid k cstore = do-  itemToF <- itemToFullClient-  subject <- partAidLeader aid-  let msg = makeSentence [ MU.SubjectVerbSg subject verb-                         , partItemWs k cstore (itemToF iid k) ]-  msgAdd msg+-- TODO: split into 3 parts wrt ek and reuse somehow, e.g., the secret part+-- We assume the item is inside the specified container.+-- So, this function can't be used for, e.g., @UpdDestroyItem@.+itemAidVerbMU :: MonadClientUI m+              => ActorId -> MU.Part+              -> ItemId -> Either (Maybe Int) Int -> CStore+              -> m ()+itemAidVerbMU aid verb iid ek cstore = do+  let c = CActor aid cstore+  bag <- getsState $ getCBag c+  -- The item may no longer be in @c@, but it was+  case iid `EM.lookup` bag of+    Nothing -> assert `failure` (aid, verb, iid, cstore)+    Just kit@(k, _) -> do+      itemToF <- itemToFullClient+      lid <- getsState $ lidFromC c+      localTime <- getsState $ getLocalTime lid+      subject <- partAidLeader aid+      let itemFull = itemToF iid kit+          object = case ek of+            Left (Just n) ->+              assert (n <= k `blame` (aid, verb, iid, cstore))+              $ partItemWs n c lid localTime itemFull+            Left Nothing ->+              let (name, stats) = partItem c lid localTime itemFull+              in MU.Phrase [name, stats]+            Right n ->+              assert (n <= k `blame` (aid, verb, iid, cstore))+              $ let itemSecret = itemNoDisco (itemBase itemFull, n)+                    (secretName, secretAE) = partItem c lid localTime itemSecret+                    name = MU.Phrase [secretName, secretAE]+                    nameList = if n == 1+                               then ["the", name]+                               else ["the", MU.Text $ tshow n, MU.Ws name]+                in MU.Phrase nameList+          msg = makeSentence [MU.SubjectVerbSg subject verb, object]+      msgAdd msg  msgDuplicateScrap :: MonadClientUI m => m () msgDuplicateScrap = do@@ -273,10 +336,17 @@     modifyClient $ \cli -> cli {sreport = repRest}  -- TODO: "XXX spots YYY"? or blink or show the changed cursor?-createActorUI :: MonadClientUI m => ActorId -> Actor -> Bool -> MU.Part -> m ()+createActorUI :: MonadClientUI m+              => ActorId -> Actor -> Bool -> MU.Part -> m () createActorUI aid body verbose verb = do   side <- getsClient sside-  when (bfid body /= side && not (bproj body) || verbose) $+  -- Don't spam if the actor was already visible (but, e.g., on a tile that is+  -- invisible this turn (in that case move is broken down to lose+spot)+  -- or on a distant tile, via teleport while the observer teleported, too).+  lastLost <- getsClient slastLost+  when (ES.notMember aid lastLost+        && (bfid body /= side+            && not (bproj body) || verbose)) $     actorVerbMU aid body verb   when (bfid body /= side) $ do     fact <- getsState $ (EM.! bfid body) . sfactionD@@ -288,7 +358,7 @@       -- into account.       modifyClient $ \cli -> cli {scursor = TEnemy aid False}     stopPlayBack-  when (bfid body == side && not (bproj body)) $ lookAtMove aid+  lookAtMove aid  destroyActorUI :: MonadClientUI m                => ActorId -> Actor -> MU.Part -> MU.Part -> Bool -> m ()@@ -298,40 +368,36 @@     actorVerbMU aid body verb     void $ displayMore ColorBW ""   else when verbose $ actorVerbMU aid body verboseVerb+  modifyClient $ \cli -> cli {slastLost = ES.insert aid $ slastLost cli}  moveItemUI :: MonadClientUI m            => Bool -> ItemId -> Int -> ActorId -> CStore -> CStore            -> m ()-moveItemUI verbose iid k aid c1 c2 = do-  side <- getsClient sside+moveItemUI verbose iid k aid cstore1 cstore2 = do+  let verb = verbCStore cstore2+  when (cstore2 == CGround) $ updateItemSlotSide aid iid   b <- getsState $ getActorBody aid-  case (c1, c2) of-    (_, _) | c1 == CGround -> do-      when (bfid b == side) $ updateItemSlot (Just aid) iid-      fact <- getsState $ (EM.! bfid b) . sfactionD-      let underAI = isAIFact fact-      mleader <- getsClient _sleader-      if Just aid == mleader && not underAI then do-        itemToF <- itemToFullClient-        (letterSlots, _) <- getsClient sslots-        bag <- getsState $ getCBag $ CActor aid c2-        let n = bag EM.! iid-        case lookup iid $ map swap $ EM.assocs letterSlots of-          Just l -> msgAdd $ makePhrase-                      [ "\n"-                      , slotLabel $ Left l-                      , "-"-                      , partItemWs n c2 (itemToF iid n)-                      , "\n" ]-          Nothing -> return ()-      else when (c1 == CGround && c1 /= c2) $-        aiVerbMU aid "get" iid k c2-    (_, CGround) | c1 /= c2 -> do-      when verbose $ aiVerbMU aid "drop" iid k c1-      if bfid b == side-        then updateItemSlot (Just aid) iid-        else updateItemSlot Nothing iid-    _ -> return ()+  fact <- getsState $ (EM.! bfid b) . sfactionD+  let underAI = isAIFact fact+  mleader <- getsClient _sleader+  if (cstore1 == CGround && Just aid == mleader && not underAI) then do+    itemAidVerbMU aid (MU.Text verb) iid (Right k) cstore2+    localTime <- getsState $ getLocalTime (blid b)+    itemToF <- itemToFullClient+    (letterSlots, _) <- getsClient sslots+    let c2 = CActor aid cstore2+    bag <- getsState $ getCBag c2+    let kit@(n, _) = bag EM.! iid+    case lookup iid $ map swap $ EM.assocs letterSlots of+      Just l -> msgAdd $ makePhrase+                  [ "\n"+                  , slotLabel $ Left l+                  , "-"+                  , partItemWs n c2 (blid b) localTime (itemToF iid kit)+                  , "\n" ]+      Nothing -> return ()+  else when (cstore2 /= CGround || verbose) $  -- dropping is silent+    itemAidVerbMU aid (MU.Text verb) iid (Left $ Just k) cstore2  displaceActorUI :: MonadClientUI m => ActorId -> ActorId -> m () displaceActorUI source target = do@@ -394,61 +460,70 @@       msgAdd msg   case (toSt, partingPart) of     (Just status, Just pp) -> do-      (bag, total) <- case mbody of-        Just body | fid == side -> getsState $ calculateTotal body+      startingSlide <- promptToSlideshow moreMsg+      recordHistory  -- we are going to exit or restart, so record+      let bodyToItemSlides b = do+            (bag, tot) <- getsState $ calculateTotal b+            let currencyName = MU.Text $ IK.iname $ okind+                               $ ouniqGroup "currency"+                itemMsg = makeSentence [ "Your loot is worth"+                                       , MU.CarWs tot currencyName ]+                          <+> moreMsg+            if EM.null bag then return (mempty, 0)+            else do+              mapM_ (updateItemSlot Nothing) $ EM.keys bag+              io <- itemOverlay (CFloor (blid b) (bpos b)) (blid b) bag+              sli <- overlayToSlideshow itemMsg io+              return (sli, tot)+      (itemSlides, total) <- case mbody of+        Just b | fid == side -> bodyToItemSlides b         _ -> case gleader fact of-          Nothing -> return (EM.empty, 0)+          Nothing -> return (mempty, 0)           Just (aid, _) -> do             b <- getsState $ getActorBody aid-            getsState $ calculateTotal b-      let currencyName = MU.Text $ iname $ okind $ ouniqGroup "currency"-          itemMsg = makeSentence [ "Your loot is worth"-                                 , MU.CarWs total currencyName ]-                    <+> moreMsg-      startingSlide <- promptToSlideshow moreMsg-      recordHistory  -- we are going to exit or restart, so record-      itemSlides <--        if EM.null bag then return mempty-        else do-          io <- itemOverlay CGround bag-          overlayToSlideshow itemMsg io-      -- Show score for any UI client, even though it is saved only-      -- for human UI clients.+            bodyToItemSlides b+      -- Show score for any UI client (except after ESC),+      -- even though it is saved only for human UI clients.       scoreSlides <- scoreToSlideshow total status       partingSlide <- promptToSlideshow $ pp <+> moreMsg       shutdownSlide <- promptToSlideshow pp-      -- TODO: First ESC cancels items display.-      void $ getInitConfirms ColorFull []-           $ startingSlide <> itemSlides-      -- TODO: Second ESC cancels high score and parting message display.-      -- The last slide stays onscreen during shutdown, etc.-             <> scoreSlides <> partingSlide <> shutdownSlide+      escAI <- getsClient sescAI+      unless (escAI == EscAIExited) $+        -- TODO: First ESC cancels items display.+        void $ getInitConfirms ColorFull []+             $ startingSlide <> itemSlides+        -- TODO: Second ESC cancels high score and parting message display.+        -- The last slide stays onscreen during shutdown, etc.+               <> scoreSlides <> partingSlide <> shutdownSlide       -- TODO: perhaps use a vertical animation instead, e.g., roll down       -- and put it before item and score screens (on blank background)       unless (fmap stOutcome toSt == Just Camping) $ fadeOutOrIn True     _ -> return () -discover :: MonadClientUI m => StateClient -> ItemId ->  m ()-discover oldcli iid = do+discover :: MonadClientUI m+         => LevelId -> Point -> StateClient -> ItemId -> m ()+discover lid p oldcli iid = do   cops <- getsState scops+  localTime <- getsState $ getLocalTime lid   itemToF <- itemToFullClient-  let itemFull = itemToF iid 1-      (knownName, knownAEText) = partItem CGround itemFull+  let itemFull = itemToF iid (1, [])+      c = CFloor lid p+      (knownName, knownAEText) = partItem c lid localTime itemFull       -- Wipe out the whole knowledge of the item to make sure the two names       -- in the message differ even if, e.g., the item is described as       -- "of many effects".       itemSecret = itemNoDisco (itemBase itemFull, itemK itemFull)-      (secretName, secretAEText) = partItem CGround itemSecret+      (secretName, secretAEText) = partItem c lid localTime itemSecret       msg = makeSentence         [ "the", MU.SubjectVerbSg (MU.Phrase [secretName, secretAEText])                                   "turn out to be"         , MU.AW $ MU.Phrase [knownName, knownAEText] ]       oldItemFull =         itemToFull cops (sdiscoKind oldcli) (sdiscoEffect oldcli)-                   iid (itemBase itemFull) 1+                   iid (itemBase itemFull) (1, [])   -- Compare descriptions of all aspects and effects to determine   -- if the discovery was meaningful to the player.-  when (textAllAE False CEqp itemFull /= textAllAE False CEqp oldItemFull) $+  when (textAllAE False c itemFull /= textAllAE False c oldItemFull) $     msgAdd msg  -- * RespSfxAtomicUI@@ -461,14 +536,18 @@     spart <- partAidLeader source     tpart <- partAidLeader target     msgAdd $ makeSentence [MU.SubjectVerbSg spart "shrink away from", tpart]-  SfxProject aid iid -> aiVerbMU aid "aim" iid 1 CInv-  SfxCatch aid iid -> aiVerbMU aid "catch" iid 1 CInv-  SfxActivate aid iid k -> aiVerbMU aid "activate" iid k CInv-  SfxCheck aid iid k -> aiVerbMU aid "deactivate" iid k CInv+  SfxProject aid iid cstore ->+    itemAidVerbMU aid "aim" iid (Left $ Just 1) cstore+  SfxCatch aid iid cstore ->+    itemAidVerbMU aid "catch" iid (Left $ Just 1) cstore+  SfxActivate aid iid cstore ->+    itemAidVerbMU aid "activate" iid (Left $ Just 1) cstore+  SfxCheck aid iid cstore ->+    itemAidVerbMU aid "deactivate" iid (Left $ Just 1) cstore   SfxTrigger aid _p _feat ->-    when verbose $ aVerbMU aid "trigger"  -- TODO: opens door, etc.+    when verbose $ aidVerbMU aid "trigger"  -- TODO: opens door, etc.   SfxShun aid _p _ ->-    when verbose $ aVerbMU aid "shun"  -- TODO: shuns stairs down+    when verbose $ aidVerbMU aid "shun"  -- TODO: shuns stairs down   SfxEffect fidSource aid effect -> do     b <- getsState $ getActorBody aid     side <- getsClient sside@@ -489,9 +568,10 @@                                    || bhp b < p)           (deadBefore, verbDie) =             case effect of-              Effect.Hurt p | deadPreviousTurn (xM $ Dice.maxDice p) ->+              IK.Hurt p | deadPreviousTurn (xM $ Dice.maxDice p) ->                 (True, hurtExtra)-              Effect.RefillHP p | deadPreviousTurn (xM p) -> (True, hurtExtra)+              IK.RefillHP p | deadPreviousTurn (xM p) -> (True, hurtExtra)+              IK.OverfillHP p | deadPreviousTurn (xM p) -> (True, hurtExtra)               _ -> (False, firstFall)           msgDie = makeSentence [MU.SubjectVerbSg subject verbDie]       msgAdd msgDie@@ -502,17 +582,25 @@                    else animate (blid b) $ deathBody $ bpos b         displayActorStart b animDie     else case effect of-        Effect.NoEffect t -> msgAdd $ "Nothing happens." <+> t-        Effect.RefillHP p | p == 1 -> skip  -- no spam from regen items-        Effect.RefillHP p | p > 0 -> do+        IK.NoEffect t -> msgAdd $ "Nothing happens." <+> t+        IK.Hurt{} -> skip  -- avoid spam; SfxStrike just sent+        IK.Burn{} ->           if fid == side then+            actorVerbMU aid b "feel burned"+          else+            actorVerbMU aid b "look burned"+        IK.Explode{} -> skip  -- lots of visual feedback+        IK.RefillHP p | p == 1 -> skip  -- no spam from regeneration+        IK.RefillHP p | p > 0 -> do+          if fid == side then             actorVerbMU aid b "feel healthier"           else             actorVerbMU aid b "look healthier"           let ps = (bpos b, bpos b)           animFrs <- animate (blid b) $ twirlSplash ps Color.BrBlue Color.Blue           displayActorStart b animFrs-        Effect.RefillHP _ -> do+        IK.RefillHP p | p == -1 -> skip  -- no spam from poison+        IK.RefillHP _ -> do           if fid == side then             actorVerbMU aid b "feel wounded"           else@@ -520,17 +608,32 @@           let ps = (bpos b, bpos b)           animFrs <- animate (blid b) $ twirlSplash ps Color.BrRed Color.Red           displayActorStart b animFrs-        Effect.Hurt{} -> skip  -- avoid spam; SfxStrike just sent-        Effect.RefillCalm p | p == 1 -> skip  -- no spam from regen items-        Effect.RefillCalm p | p > 0 -> do+        IK.OverfillHP p | p > 0 -> do           if fid == side then+            actorVerbMU aid b "feel healthier"+          else+            actorVerbMU aid b "look healthier"+          let ps = (bpos b, bpos b)+          animFrs <- animate (blid b) $ twirlSplash ps Color.BrBlue Color.Blue+          displayActorStart b animFrs+        IK.OverfillHP _ -> do+          if fid == side then+            actorVerbMU aid b "feel wounded"+          else+            actorVerbMU aid b "look wounded"+          let ps = (bpos b, bpos b)+          animFrs <- animate (blid b) $ twirlSplash ps Color.BrRed Color.Red+          displayActorStart b animFrs+        IK.RefillCalm p | p == 1 -> skip  -- no spam from regen items+        IK.RefillCalm p | p > 0 -> do+          if fid == side then             actorVerbMU aid b "feel calmer"           else             actorVerbMU aid b "look calmer"           let ps = (bpos b, bpos b)           animFrs <- animate (blid b) $ twirlSplash ps Color.BrBlue Color.Blue           displayActorStart b animFrs-        Effect.RefillCalm _ -> do+        IK.RefillCalm _ -> do           if fid == side then             actorVerbMU aid b "feel agitated"           else@@ -538,16 +641,32 @@           let ps = (bpos b, bpos b)           animFrs <- animate (blid b) $ twirlSplash ps Color.BrRed Color.Red           displayActorStart b animFrs-        Effect.Dominate -> do+        IK.OverfillCalm p | p > 0 -> do+          if fid == side then+            actorVerbMU aid b "feel calmer"+          else+            actorVerbMU aid b "look calmer"+          let ps = (bpos b, bpos b)+          animFrs <- animate (blid b) $ twirlSplash ps Color.BrBlue Color.Blue+          displayActorStart b animFrs+        IK.OverfillCalm _ -> do+          if fid == side then+            actorVerbMU aid b "feel agitated"+          else+            actorVerbMU aid b "look agitated"+          let ps = (bpos b, bpos b)+          animFrs <- animate (blid b) $ twirlSplash ps Color.BrRed Color.Red+          displayActorStart b animFrs+        IK.Dominate -> do           -- For subsequent messages use the proper name, never "you".           let subject = partActor b           if fid /= fidSource then do  -- before domination             if bcalm b == 0 then do -- sometimes only a coincidence, but nm-              aVerbMU aid $ MU.Text "yield, under extreme pressure"+              aidVerbMU aid $ MU.Text "yield, under extreme pressure"             else if fid == side then-              aVerbMU aid $ MU.Text "black out, dominated by foes"+              aidVerbMU aid $ MU.Text "black out, dominated by foes"             else-              aVerbMU aid $ MU.Text "decide abrubtly to switch allegiance"+              aidVerbMU aid $ MU.Text "decide abrubtly to switch allegiance"             fidName <- getsState $ gname . (EM.! fid) . sfactionD             let verb = "be no longer controlled by"             msgAdd $ makeSentence@@ -558,68 +677,63 @@             let verb = "be now under"             msgAdd $ makeSentence               [MU.SubjectVerbSg subject verb, MU.Text fidSourceName, "control"]-        Effect.Impress{} ->+        IK.Impress{} ->           actorVerbMU aid b           $ if boldfid b /= bfid b             then-              "get sobered and refocused by the fragrant moisture"+              "get refocused by the fragrant moisture"             else               "inhale the sweet smell that weakens resolve and erodes loyalty"-        Effect.CallFriend{} -> skip-        Effect.Summon{} -> skip-        Effect.CreateItem{} -> skip-        Effect.ApplyPerfume ->-          msgAdd "The fragrance quells all scents in the vicinity."-        Effect.Burn{} ->-          if fid == side then-            actorVerbMU aid b "feel burned"-          else-            actorVerbMU aid b "look burned"-        Effect.Ascend k | k > 0 -> actorVerbMU aid b "find a way upstairs"-        Effect.Ascend k | k < 0 -> actorVerbMU aid b "find a way downstairs"-        Effect.Ascend{} -> assert `failure` sfx-        Effect.Escape{} -> skip-        Effect.Paralyze{} -> actorVerbMU aid b "be paralyzed"-        Effect.InsertMove{} -> actorVerbMU aid b "move with extreme speed"-        Effect.DropBestWeapon -> actorVerbMU aid b "be disarmed"-        Effect.DropEqp _ False -> actorVerbMU aid b "be stripped"  -- TODO-        Effect.DropEqp _ True -> actorVerbMU aid b "be violently stripped"-        Effect.SendFlying{} -> actorVerbMU aid b "be sent flying"-        Effect.PushActor{} -> actorVerbMU aid b "be pushed"-        Effect.PullActor{} -> actorVerbMU aid b "be pulled"-        Effect.Teleport t | t > 9 -> actorVerbMU aid b "teleport"-        Effect.Teleport{} -> actorVerbMU aid b "blink"-        Effect.PolyItem cstore -> do+        IK.CallFriend{} -> skip+        IK.Summon{} -> skip+        IK.Ascend k | k > 0 -> actorVerbMU aid b "find a way upstairs"+        IK.Ascend k | k < 0 -> actorVerbMU aid b "find a way downstairs"+        IK.Ascend{} -> assert `failure` sfx+        IK.Escape{} -> skip+        IK.Paralyze{} -> actorVerbMU aid b "be paralyzed"+        IK.InsertMove{} -> actorVerbMU aid b "act with extreme speed"+        IK.Teleport t | t > 9 -> actorVerbMU aid b "teleport"+        IK.Teleport{} -> actorVerbMU aid b "blink"+        IK.CreateItem{} -> skip+        IK.DropItem COrgan _ True -> skip+        IK.DropItem _ _ False -> actorVerbMU aid b "be stripped"  -- TODO+        IK.DropItem _ _ True -> actorVerbMU aid b "be violently stripped"+        IK.PolyItem cstore -> do+          localTime <- getsState $ getLocalTime $ blid b           allAssocs <- fullAssocsClient aid [cstore]           case allAssocs of             [] -> return ()  -- invisible items?             (_, ItemFull{..}) : _ -> do               let itemSecret = itemNoDisco (itemBase, itemK)-                  (secretName, secretAEText) = partItem cstore itemSecret+                  (secretName, secretAEText) = partItem (CActor aid cstore) (blid b) localTime itemSecret                   subject = partActor b                   verb = "repurpose"                   store = MU.Text $ ppCStore cstore               msgAdd $ makeSentence                 [ MU.SubjectVerbSg subject verb                 , "the", secretName, secretAEText, store ]-        Effect.Identify cstore -> do+        IK.Identify cstore -> do  -- here cstore is the real location           allAssocs <- fullAssocsClient aid [cstore]           case allAssocs of             [] -> return ()  -- invisible items?             (_, ItemFull{..}) : _ -> do-              let itemSecret = itemNoDisco (itemBase, itemK)-                  (secretName, secretAEText) = partItem cstore itemSecret-                  subject = partActor b-                  verb = "compare"+              let subject = partActor b+                  verb = "inspect"                   store = MU.Text $ ppCStore cstore               msgAdd $ makeSentence                 [ MU.SubjectVerbSg subject verb-                , "old notes with the", secretName, secretAEText, store ]-        Effect.ActivateInv{} -> skip-        Effect.Explode{} -> skip  -- lots of visual feedback-        Effect.OneOf{} -> skip-        Effect.OnSmash{} -> assert `failure` sfx-        Effect.TimedAspect{} -> skip  -- TODO+                , "an item", store ]+        IK.SendFlying{} -> actorVerbMU aid b "be sent flying"+        IK.PushActor{} -> actorVerbMU aid b "be pushed"+        IK.PullActor{} -> actorVerbMU aid b "be pulled"+        IK.DropBestWeapon -> actorVerbMU aid b "be disarmed"+        IK.ActivateInv{} -> skip+        IK.ApplyPerfume ->+          msgAdd "The fragrance quells all scents in the vicinity."+        IK.OneOf{} -> skip+        IK.OnSmash{} -> assert `failure` sfx+        IK.Recharging{} -> assert `failure` sfx+        IK.Temporary t -> actorVerbMU aid b $ MU.Text t   SfxMsgFid _ msg -> msgAdd msg   SfxMsgAll msg -> msgAdd msg   SfxActorStart aid -> do@@ -666,14 +780,19 @@   spart <- partActorLeader source sb   tpart <- partActorLeader target tb   spronoun <- partPronounLeader source sb-  let itemFull = itemToF iid 1+  localTime <- getsState $ getLocalTime (blid sb)+  -- We don't show the charging status in the weapon strike message. Succinct.+  let itemFull = itemToF iid (1, [])       verb = case itemDisco itemFull of         Nothing -> "hit"  -- not identified-        Just ItemDisco{itemKind} -> iverbHit itemKind+        Just ItemDisco{itemKind} -> IK.iverbHit itemKind       isOrgan = iid `EM.member` borgan sb-      partItemChoice = if isOrgan-                       then partItemWownW spronoun COrgan-                       else partItemAW CEqp+      partItemChoice =+        if isOrgan+        then partItemWownW spronoun (CActor source COrgan) (blid sb) localTime+        -- The @CEqp@ store may be fake, but it results in an item description+        -- for an active item, which is the right one for a weapon.+        else partItemAW (CActor source CEqp) (blid sb) localTime       msg HitClear = makeSentence $         [MU.SubjectVerbSg spart verb, tpart]         ++ if bproj sb@@ -685,7 +804,7 @@               then [ MU.SubjectVerbSg spart "connect" ]               else [ MU.SubjectVerbSg spart "swing"                    , partItemChoice itemFull ]-        in makeSentence [ MU.Phrase sActs MU.:> ", but"+        in makeSentence [ MU.Phrase sActs <> ", but"                         , MU.SubjectVerbSg tpart "block"                         , if n > 1 then "doggedly" else "partly"                         ]
Game/LambdaHack/Client/UI/DrawClient.hs view
@@ -23,7 +23,6 @@ import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Color as Color import qualified Game.LambdaHack.Common.Dice as Dice-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemDescription@@ -40,9 +39,9 @@ import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Time import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.TileKind+import qualified Game.LambdaHack.Content.TileKind as TK  -- | Color mode for the display. data ColorMode =@@ -95,7 +94,7 @@       dis pos0 =         let tile = lvl `at` pos0             tk = tokind tile-            floorBag = lvl `atI` pos0+            floorBag = EM.findWithDefault EM.empty pos0 $ lfloor lvl             (letterSlots, numberSlots) = sslots cli             bagLetterSlots = EM.filter (`EM.member` floorBag) letterSlots             bagNumberSlots = IM.filter (`EM.member` floorBag) numberSlots@@ -116,8 +115,8 @@             -- over both visible and invisible tiles.             vcolor               | smarkSuspect && Tile.isSuspect cotile tile = Color.BrCyan-              | vis = tcolor tk-              | otherwise = tcolor2 tk+              | vis = TK.tcolor tk+              | otherwise = TK.tcolor2 tk             fgOnPathOrLine = case (vis, Tile.isWalkable cotile tile) of               _ | tile == unknownId -> Color.BrBlack               _ | Tile.isSuspect cotile tile -> Color.BrCyan@@ -141,7 +140,7 @@                   (timeDeltaToDigit smellTimeout smlt, rainbow pos0)                   | otherwise ->                   case floorIids of-                    [] -> (tsymbol tk, Color.defAttr {Color.fg = vcolor})+                    [] -> (TK.tsymbol tk, Color.defAttr {Color.fg = vcolor})                     iid : _ -> viewItem $ getItemBody iid s             vis = ES.member pos0 $ totalVisible per             a = case dm of@@ -255,8 +254,8 @@       let (darkL, bracedL, hpDelta, calmDelta,            ahpS, bhpS, acalmS, bcalmS) =             let b@Actor{bhp, bcalm} = getActorBody leader s-                amaxHP = sumSlotNoFilter Effect.EqpSlotAddMaxHP activeItems-                amaxCalm = sumSlotNoFilter Effect.EqpSlotAddMaxCalm activeItems+                amaxHP = sumSlotNoFilter IK.EqpSlotAddMaxHP activeItems+                amaxCalm = sumSlotNoFilter IK.EqpSlotAddMaxCalm activeItems             in ( not (actorInAmbient b s)                , braced b, bhpDelta b, bcalmDelta b                , tshow $ max 0 amaxHP, tshow (bhp `divUp` oneM)@@ -295,22 +294,21 @@     Just leader -> do       allAssocs <- fullAssocsClient leader [CEqp, COrgan]       let activeItems = map snd allAssocs-          damage = case strongestSlotNoFilter Effect.EqpSlotWeapon allAssocs of+          damage = case strongestSlotNoFilter IK.EqpSlotWeapon allAssocs of             (_, (_, itemFull)) : _->-              let getD :: Effect.Effect a -> Maybe Dice.Dice-                       -> Maybe Dice.Dice-                  getD (Effect.Hurt dice) _ = Just dice+              let getD :: IK.Effect -> Maybe Dice.Dice -> Maybe Dice.Dice+                  getD (IK.Hurt dice) _ = Just dice                   getD _ acc = acc                   mdice = case itemDisco itemFull of                     Just ItemDisco{itemAE=Just ItemAspectEffect{jeffects}} ->                       foldr getD Nothing jeffects                     Just ItemDisco{itemKind} ->-                      foldr getD Nothing (ieffects itemKind)+                      foldr getD Nothing (IK.ieffects itemKind)                     Nothing -> Nothing                   tdice = case mdice of                     Nothing -> "0"                     Just dice -> tshow dice-                  bonus = sumSlotNoFilter Effect.EqpSlotAddHurtMelee activeItems+                  bonus = sumSlotNoFilter IK.EqpSlotAddHurtMelee activeItems                   unknownBonus = unknownMelee activeItems                   tbonus = if bonus == 0                            then if unknownBonus then "+?" else ""
Game/LambdaHack/Client/UI/Frontend.hs view
@@ -32,6 +32,8 @@       -- ^ flush frames, possibly show fadeout/fadein and ask for a keypress   | FrontSlides {frontClear :: ![K.KM], frontSlides :: ![SingleFrame]}       -- ^ show a whole slideshow without interleaving with other clients+  | FrontAutoYes !Bool+      -- ^ set the frontend option for auto-answering prompts   | FrontFinish       -- ^ exit frontend loop @@ -65,12 +67,11 @@     then return km     else promptGetKey fs keys frame -getConfirmGeneric :: RawFrontend -> [K.KM] -> SingleFrame -> IO (Maybe Bool)-getConfirmGeneric fs clearKeys frame = do-  let DebugModeCli{snoMore} = fdebugCli fs-  -- TODO: turn noMore off somehow when faction not under computer control;-  -- perhaps by adding a FrontReq request that turns it off/on?-  if snoMore then do+getConfirmGeneric :: Bool -> RawFrontend -> [K.KM] -> SingleFrame+                  -> IO (Maybe Bool)+getConfirmGeneric autoYes fs clearKeys frame = do+  let DebugModeCli{sdisableAutoYes} = fdebugCli fs+  if autoYes && not sdisableAutoYes then do     fdisplay fs True (Just frame)     return $ Just True   else do@@ -84,33 +85,33 @@  -- Read UI requests from the client and send them to the frontend, loopFrontend :: RawFrontend -> ChanFrontend -> IO ()-loopFrontend fs ChanFrontend{..} = loop+loopFrontend fs ChanFrontend{..} = loop False  where   writeKM :: K.KM -> IO ()   writeKM km = STM.atomically $ STM.writeTQueue responseF km -  loop :: IO ()-  loop = do+  loop :: Bool -> IO ()+  loop autoYes = do     efr <- STM.atomically $ STM.readTQueue requestF     case efr of       FrontNormalFrame{..} -> do         fdisplay fs False (Just frontFrame)-        loop+        loop autoYes       FrontRunningFrame{..} -> do         fdisplay fs True (Just frontFrame)-        loop+        loop autoYes       FrontDelay -> do         fdisplay fs False Nothing-        loop+        loop autoYes       FrontKey{..} -> do         km <- promptGetKey fs frontKM frontFr         writeKM km-        loop+        loop autoYes       FrontSlides{frontSlides = []} -> do         -- Hack.         fsyncFrames fs         writeKM K.spaceKM-        loop+        loop autoYes       FrontSlides{..} -> do         let displayFrs frs srf =               case frs of@@ -119,7 +120,7 @@                   fdisplay fs False (Just x)                   writeKM K.spaceKM                 x : xs -> do-                  go <- getConfirmGeneric fs frontClear x+                  go <- getConfirmGeneric autoYes fs frontClear x                   case go of                     Nothing -> writeKM K.escKM                     Just True -> displayFrs xs (x : srf)@@ -127,7 +128,9 @@                       [] -> displayFrs frs srf                       y : ys -> displayFrs (y : frs) ys         displayFrs frontSlides []-        loop+        loop autoYes+      FrontAutoYes b ->+        loop b       FrontFinish ->         return ()         -- Do not loop again.
Game/LambdaHack/Client/UI/Frontend/Curses.hs view
@@ -121,6 +121,7 @@     C.KeyBeg         -> (K.Begin,   K.NoModifier)     C.KeyB2          -> (K.Begin,   K.NoModifier)     C.KeyClear       -> (K.Begin,   K.NoModifier)+    C.KeyIC          -> (K.Insert,  K.NoModifier)     -- No KP_ keys; see <https://github.com/skogsbaer/hscurses/issues/10>     -- TODO: try to get the Control modifier for keypad keys from the escape     -- gibberish and use Control-keypad for KP_ movement.
Game/LambdaHack/Client/UI/Frontend/Gtk.hs view
@@ -10,8 +10,10 @@   , frontendName, startup   ) where +import Control.Applicative import Control.Concurrent import Control.Concurrent.Async+import qualified Control.Concurrent.STM as STM import qualified Control.Exception as Ex hiding (handle) import Control.Exception.Assert.Sugar import Control.Monad@@ -43,7 +45,7 @@ data FrontendSession = FrontendSession   { sview       :: !TextView                    -- ^ the widget to draw to   , stags       :: !(M.Map Color.Attr TextTag)  -- ^ text color tags for fg/bg-  , schanKey    :: !(Chan K.KM)                 -- ^ channel for keyboard input+  , schanKey    :: !(STM.TQueue K.KM)           -- ^ channel for keyboard input   , sframeState :: !(MVar FrameState)       -- ^ State of the frame finite machine. This mvar is locked       -- for a short time only, because it's needed, among others,@@ -103,7 +105,7 @@              mapM (\ ak -> do                       tt <- textTagNew Nothing                       textTagTableAdd ttt tt-                      doAttr tt ak+                      doAttr sdebugCli tt ak                       return (ak, tt))                [ Color.Attr{fg, bg}                | fg <- [minBound..maxBound], bg <- Color.legalBG ]@@ -114,7 +116,7 @@   textViewSetEditable sview False   textViewSetCursorVisible sview False   -- Set up the channel for keyboard input.-  schanKey <- newChan+  schanKey <- STM.atomically $ STM.newTQueue   -- Set up the frame state.   let frameState = FNone   -- Create the session record.@@ -139,13 +141,17 @@     let !key = K.keyTranslate n #endif         !modifier = modifierTranslate mods+        readAll = do+          res <- STM.atomically $ STM.tryReadTQueue schanKey+          when (isJust res) $ readAll     liftIO $ do       unless (deadKey n) $ do-        -- If ESC, also mark it specially.-        when (key == K.Esc) $+        -- If ESC, also mark it specially and reset the key channel.+        when (key == K.Esc) $ do           void $ tryPutMVar escMVar ()+          readAll         -- Store the key in the channel.-        writeChan schanKey K.KM{key, modifier}+        STM.atomically $ STM.writeTQueue schanKey K.KM{key, modifier}       return True   -- Set the font specified in config, if any.   f <- fontDescriptionFromString $ fromMaybe "" sfont@@ -157,7 +163,7 @@     liftIO $ case but of       RightButton -> do         fsd <- fontSelectionDialogNew ("Choose font" :: String)-        cf  <- readIORef currentfont  -- TODO: "Terminus,Monospace" fails+        cf  <- readIORef currentfont         fds <- fontDescriptionToString cf         fontSelectionDialogSetFontName fsd (fds :: String)         fontSelectionDialogSetPreviewText fsd ("eee...@.##+##" :: String)@@ -388,9 +394,14 @@  -- Display all queued frames, synchronously. displayAllFramesSync :: FrontendSession -> FrameState -> IO ()-displayAllFramesSync sess@FrontendSession{sdebugCli=DebugModeCli{..}} fs = do+displayAllFramesSync sess@FrontendSession{sdebugCli=DebugModeCli{..}, sescMVar}+                     fs = do+  escPressed <- case sescMVar of+    Nothing -> return False+    Just escMVar -> not <$> isEmptyMVar escMVar   let maxFps = fromMaybe defaultMaxFps smaxFps   case fs of+    _ | escPressed -> return ()     FPushed{..} ->       case tryReadLQueue fpushed of         Just (Just frame, queue) -> do@@ -423,7 +434,7 @@ fpromptGetKey sess@FrontendSession{..}               frame = do   pushFrame sess True True $ Just frame-  km <- readChan schanKey+  km <- STM.atomically $ STM.readTQueue schanKey   case km of     K.KM{key=K.Space} ->       -- Drop frames up to the first empty frame.@@ -462,10 +473,21 @@ modifierTranslate mods =   if Control `elem` mods then K.Control else K.NoModifier -doAttr :: TextTag -> Color.Attr -> IO ()-doAttr tt attr@Color.Attr{fg, bg}+doAttr :: DebugModeCli -> TextTag -> Color.Attr -> IO ()+doAttr sdebugCli tt attr@Color.Attr{fg, bg}   | attr == Color.defAttr = return ()-  | fg == Color.defFG = set tt [textTagBackground := Color.colorToRGB bg]-  | bg == Color.defBG = set tt [textTagForeground := Color.colorToRGB fg]-  | otherwise         = set tt [textTagForeground := Color.colorToRGB fg,-                                textTagBackground := Color.colorToRGB bg]+  | fg == Color.defFG = set tt $ extraAttr sdebugCli+                                 ++ [textTagBackground := Color.colorToRGB bg]+  | bg == Color.defBG = set tt $ extraAttr sdebugCli+                                 ++ [textTagForeground := Color.colorToRGB fg]+  | otherwise         = set tt $ extraAttr sdebugCli+                                 ++ [ textTagForeground := Color.colorToRGB fg+                                    , textTagBackground := Color.colorToRGB bg ]++extraAttr :: DebugModeCli -> [AttrOp TextTag]+extraAttr DebugModeCli{scolorIsBold} =+  if scolorIsBold == Just True+  then [ textTagWeight := fromEnum WeightBold+--     , textTagStretch := StretchUltraExpanded+       ]+  else []
Game/LambdaHack/Client/UI/Frontend/Vty.hs view
@@ -11,8 +11,12 @@ import Control.Concurrent import Control.Concurrent.Async import qualified Control.Exception as Ex hiding (handle)+import Data.Default import Graphics.Vty import qualified Graphics.Vty as Vty+import qualified Control.Concurrent.STM as STM+import Control.Monad+import Data.Maybe  import qualified Game.LambdaHack.Client.Key as K import Game.LambdaHack.Client.UI.Animation@@ -23,6 +27,7 @@ -- | Session data maintained by the frontend. data FrontendSession = FrontendSession   { svty      :: !Vty  -- internal vty session+  , schanKey  :: !(STM.TQueue K.KM)  -- ^ channel for keyboard input   , sescMVar  :: !(Maybe (MVar ()))   , sdebugCli :: !DebugModeCli  -- ^ client configuration       -- ^ Configuration of the frontend session.@@ -35,13 +40,36 @@ -- | Starts the main program loop using the frontend input and output. startup :: DebugModeCli -> (FrontendSession -> IO ()) -> IO () startup sdebugCli k = do-  svty <- mkVty-  -- TODO: implement sescMVar, when we switch to a new vty that has-  -- a separate key listener thread or something. Avoid polling.-  a <- async $ k FrontendSession{sescMVar = Nothing, ..}-               `Ex.finally` Vty.shutdown svty+  svty <- mkVty def+  schanKey <- STM.atomically $ STM.newTQueue+  escMVar <- newEmptyMVar+  let sess = FrontendSession{sescMVar = Just escMVar, ..}+  void $ async $ storeKeys sess+  a <- async $ k sess `Ex.finally` Vty.shutdown svty   wait a +storeKeys :: FrontendSession -> IO ()+storeKeys sess@FrontendSession{..} = do+  e <- nextEvent svty  -- blocks here, so no polling+  case e of+    EvKey n mods -> do+      let !key = keyTranslate n+          !modifier = modifierTranslate mods+          readAll = do+            res <- STM.atomically $ STM.tryReadTQueue schanKey+            when (isJust res) $ readAll+      -- If ESC, also mark it specially and reset the key channel.+      case sescMVar of+        Just escMVar -> do+          when (key == K.Esc) $ do+            void $ tryPutMVar escMVar ()+            readAll+        Nothing -> return ()+      -- Store the key in the channel.+      STM.atomically $ STM.writeTQueue schanKey K.KM{key, modifier}+    _ -> return ()+  storeKeys sess+ -- | Output to the screen via the frontend. fdisplay :: FrontendSession    -- ^ frontend session data          -> Bool@@ -50,24 +78,32 @@ fdisplay _ _ Nothing = return () fdisplay FrontendSession{svty} _ (Just rawSF) =   let SingleFrame{sfLevel} = overlayOverlay rawSF-      img = (foldr (<->) empty_image-             . map (foldr (<|>) empty_image+      img = (foldr (<->) emptyImage+             . map (foldr (<|>) emptyImage                       . map (\ Color.AttrChar{..} ->                                 char (setAttr acAttr) acChar)))             $ map decodeLine sfLevel-      pic = pic_for_image img+      pic = picForImage img   in update svty pic  -- | Input key via the frontend.-nextEvent :: FrontendSession -> IO K.KM-nextEvent sess@FrontendSession{svty} = do-  e <- next_event svty-  case e of-    EvKey n mods -> do-      let key = keyTranslate n-          modifier = modifierTranslate mods-      return $! K.KM {key, modifier}-    _ -> nextEvent sess+nextKeyEvent :: FrontendSession -> IO K.KM+nextKeyEvent FrontendSession{..} = do+  km <- STM.atomically $ STM.readTQueue schanKey+  case km of+    K.KM{key=K.Space} ->+      -- Drop frames up to the first empty frame.+      -- Keep the last non-empty frame, if any.+      -- Pressing SPACE repeatedly can be used to step+      -- through intermediate stages of an animation,+      -- whereas any other key skips the whole animation outright.+--      onQueue dropStartLQueue sess+      return ()+    _ ->+      -- Show the last non-empty frame and empty the queue.+--      trimFrameState sess+      return ()+  return km  fsyncFrames :: FrontendSession -> IO () fsyncFrames _ = return ()@@ -76,7 +112,7 @@ fpromptGetKey :: FrontendSession -> SingleFrame -> IO K.KM fpromptGetKey sess frame = do   fdisplay sess True $ Just frame-  nextEvent sess+  nextKeyEvent sess  -- TODO: Ctrl-Home and Ctrl-End are the same as Home and End on some terminals -- so we should probably go back to using 0-9 (and Shift) for movement@@ -86,8 +122,8 @@   case n of     KEsc          -> K.Esc     KEnter        -> K.Return-    (KASCII ' ')  -> K.Space-    (KASCII '\t') -> K.Tab+    (KChar ' ')   -> K.Space+    (KChar '\t')  -> K.Tab     KBackTab      -> K.BackTab     KBS           -> K.BackSpace     KUp           -> K.Up@@ -99,8 +135,9 @@     KPageUp       -> K.PgUp     KPageDown     -> K.PgDn     KBegin        -> K.Begin-    KNP5          -> K.Begin-    (KASCII c)    -> K.Char c+    KCenter       -> K.Begin+    KIns          -> K.Insert+    (KChar c)     -> K.Char c     _             -> K.Unknown (tshow n)  -- | Translates modifiers to our own encoding.@@ -113,7 +150,7 @@ -- settings this is needed or not and the characters really get bold or not. -- HSCurses does this by default, but in Vty you have to request the hack. hack :: Color.Color -> Attr -> Attr-hack c a = if Color.isBright c then with_style a bold else a+hack c a = if Color.isBright c then withStyle a bold else a  setAttr :: Color.Attr -> Attr setAttr Color.Attr{fg, bg} =@@ -122,8 +159,8 @@ --  then def_attr --  else   hack fg $ hack bg $-    def_attr { attr_fore_color = SetTo (aToc fg)-             , attr_back_color = SetTo (aToc bg) }+    defAttr { attrForeColor = SetTo (aToc fg)+            , attrBackColor = SetTo (aToc bg) }  aToc :: Color.Color -> Color aToc Color.Black     = black@@ -134,11 +171,11 @@ aToc Color.Magenta   = magenta aToc Color.Cyan      = cyan aToc Color.White     = white-aToc Color.BrBlack   = bright_black-aToc Color.BrRed     = bright_red-aToc Color.BrGreen   = bright_green-aToc Color.BrYellow  = bright_yellow-aToc Color.BrBlue    = bright_blue-aToc Color.BrMagenta = bright_magenta-aToc Color.BrCyan    = bright_cyan-aToc Color.BrWhite   = bright_white+aToc Color.BrBlack   = brightBlack+aToc Color.BrRed     = brightRed+aToc Color.BrGreen   = brightGreen+aToc Color.BrYellow  = brightYellow+aToc Color.BrBlue    = brightBlue+aToc Color.BrMagenta = brightMagenta+aToc Color.BrCyan    = brightCyan+aToc Color.BrWhite   = brightWhite
Game/LambdaHack/Client/UI/HandleHumanClient.hs view
@@ -37,8 +37,8 @@   Move v -> fmap anyToUI <$> moveRunHuman False v   Run v -> fmap anyToUI <$> moveRunHuman True v   Wait -> Right <$> fmap ReqUITimed waitHuman-  MoveItem cLegalRaw toCStore verb _ auto ->-    fmap ReqUITimed <$> moveItemHuman cLegalRaw toCStore verb auto+  MoveItem cLegalRaw toCStore mverb _ auto ->+    fmap ReqUITimed <$> moveItemHuman cLegalRaw toCStore mverb auto   Project ts -> fmap ReqUITimed <$> projectHuman ts   Apply ts -> fmap ReqUITimed <$> applyHuman ts   AlterDir ts -> fmap ReqUITimed <$> alterDirHuman ts
Game/LambdaHack/Client/UI/HandleHumanGlobalClient.hs view
@@ -1,6 +1,9 @@-{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DataKinds, GADTs #-} -- | Semantics of 'Command.Cmd' client commands that return server commands. -- A couple of them do not take time, the rest does.+-- Here prompts and menus and 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. -- TODO: document module Game.LambdaHack.Client.UI.HandleHumanGlobalClient   ( -- * Commands that usually take time@@ -39,11 +42,8 @@ import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.ClientOptions-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction-import qualified Game.LambdaHack.Common.Feature as F import Game.LambdaHack.Common.Item-import Game.LambdaHack.Common.ItemDescription import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind import Game.LambdaHack.Common.Level@@ -56,8 +56,10 @@ import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Vector+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.TileKind+import Game.LambdaHack.Content.TileKind (TileKind)+import qualified Game.LambdaHack.Content.TileKind as TK  -- * Move and Run @@ -87,7 +89,7 @@         runStopOrCmd <- moveRunAid leader dir         case runStopOrCmd of           Left stopMsg -> failWith stopMsg-          Right runCmd -> do+          Right runCmd@(RequestAnyAbility ReqMove{}) -> do             sel <- getsClient sselected             let runMembers = if noRunWithMulti fact                              then [leader]  -- TODO: warn?@@ -99,6 +101,8 @@                                       , runInitDir = Just dir }             when run $ modifyClient $ \cli -> cli {srunning = Just runParams}             return $ Right runCmd+          -- Any other command does ends the run immediately.+          Right runCmd -> return $ Right runCmd         -- When running, the invisible actor is hit (not displaced!),         -- so that running in the presence of roving invisible         -- actors is equivalent to moving (with visible actors@@ -116,7 +120,7 @@         -- We always see actors from our own faction.         if bfid tb == bfid sb && not (bproj tb) then do           let autoLvl = snd $ autoDungeonLevel fact-          if autoLvl then failWith msgNoChangeLvlLeader+          if autoLvl then failSer NoChangeLvlLeader           else do             -- Select adjacent actor by bumping into him. Takes no time.             success <- pickLeader True target@@ -164,8 +168,11 @@   tb <- getsState $ getActorBody target   tfact <- getsState $ (EM.! bfid tb) . sfactionD   activeItems <- activeItemsClient target-  disp <- getsState $ dispEnemy leader (Just leader) target activeItems-  let spos = bpos sb+  disp <- getsState $ dispEnemy leader target activeItems+  actorSk <- getsState $ maxActorSkills target activeItems+  let immobile = EM.findWithDefault 0 AbDisplace actorSk <= 0+                 && EM.findWithDefault 0 AbMove actorSk <= 0+      spos = bpos sb       tpos = bpos tb       adj = checkAdjacent sb tb       atWar = isAtWar tfact (bfid sb)@@ -174,6 +181,8 @@           && actorDying tb then failSer DisplaceDying   else if not (bproj tb) && atWar           && braced tb then failSer DisplaceBraced+  else if not (bproj tb) && atWar+          && immobile then failSer DisplaceImmobile   else if not disp && atWar then failSer DisplaceSupported   else do     let lid = blid sb@@ -199,10 +208,11 @@ -- * MoveItem  moveItemHuman :: MonadClientUI m-              => [CStore] -> CStore -> MU.Part -> Bool+              => [CStore] -> CStore -> (Maybe MU.Part) -> Bool               -> m (SlideOrCmd (RequestTimed AbMoveItem))-moveItemHuman cLegalRaw destCStore verb auto = do+moveItemHuman cLegalRaw destCStore mverb auto = do   assert (destCStore `notElem` cLegalRaw) skip+  let verb = fromMaybe (MU.Text $ verbCStore destCStore) mverb   leader <- getLeaderUI   b <- getsState $ getActorBody leader   activeItems <- activeItemsClient leader@@ -217,24 +227,20 @@   case ggi of     Right ((iid, itemFull), CActor _ fromCStore) -> do       let k = itemK itemFull-          msgAndSer toCStore = do-            subject <- partAidLeader leader-            msgAdd $ makeSentence-              [ MU.SubjectVerbSg subject verb, partItemWs k toCStore itemFull ]+          retReq toCStore =             return $ Right $ ReqMoveItem iid k fromCStore toCStore       if fromCStore == CGround       then case destCStore of-        CEqp | goesIntoInv (itemBase itemFull) -> do-          updateItemSlot (Just leader) iid  -- slot not yet assigned-          msgAndSer CInv+        CEqp | goesIntoInv (itemBase itemFull) ->+          retReq CInv         CEqp | eqpOverfull b k -> do           msgAdd $ "Warning:" <+> showReqFailure EqpOverfull <> "."-          updateItemSlot (Just leader) iid  -- slot not yet assigned-          msgAndSer CInv-        _ -> msgAndSer destCStore+          retReq CInv+        _ ->+          retReq destCStore       else case destCStore of         CEqp | eqpOverfull b k -> failSer EqpOverfull-        _ -> msgAndSer destCStore+        _ -> retReq destCStore     Left slides -> return $ Left slides     _ -> assert `failure` ggi @@ -244,12 +250,10 @@              => [Trigger] -> m (SlideOrCmd (RequestTimed AbProject)) projectHuman ts = do   leader <- getLeaderUI-  b <- getsState $ getActorBody leader   tgtPos <- leaderTgtToPos   tgt <- getsClient $ getTarget leader   case tgtPos of     Nothing -> failWith "last target invalid"-    Just pos | pos == bpos b -> failWith "cannot aim at oneself"     Just pos -> do       -- Set cursor to the personal target, temporarily.       oldCursor <- getsClient scursor@@ -281,51 +285,49 @@   let lid = blid sb       spos = bpos sb   Level{lxsize, lysize} <- getLevel lid-  do-      case bla lxsize lysize eps spos tpos of-        Nothing -> failSer ProjectAimOnself-        Just [] -> assert `failure` "project from the edge of level"-                          `twith` (spos, tpos, sb, ts)-        Just (pos : _) -> do-          lvl <- getLevel lid-          let t = lvl `at` pos-          if not $ Tile.isWalkable cotile t-            then failSer ProjectBlockTerrain-            else do-              actorBlind <--                radiusBlind <$> sumOrganEqpClient Effect.EqpSlotAddSight leader-              mab <- getsState $ posToActor pos lid-              if maybe True (bproj . snd . fst) mab-              then if actorBlind-                   then failSer ProjectBlind-                   else projectEps ts tpos eps-              else failSer ProjectBlockActor+  case bla lxsize lysize eps spos tpos of+    Nothing -> failSer ProjectAimOnself+    Just [] -> assert `failure` "project from the edge of level"+                      `twith` (spos, tpos, sb, ts)+    Just (pos : _) -> do+      lvl <- getLevel lid+      let t = lvl `at` pos+      if not $ Tile.isWalkable cotile t+        then failSer ProjectBlockTerrain+        else do+          mab <- getsState $ posToActor pos lid+          if maybe True (bproj . snd . fst) mab+          then projectEps ts tpos eps+          else failSer ProjectBlockActor  projectEps :: MonadClientUI m            => [Trigger] -> Point -> Int            -> m (SlideOrCmd (RequestTimed AbProject)) projectEps ts tpos eps = do   leader <- getLeaderUI-  sb <- getsState $ getActorBody leader+  b <- getsState $ getActorBody leader+  actorSk <- actorSkillsClient leader+  let skill = EM.findWithDefault 0 AbProject actorSk+  activeItems <- activeItemsClient leader   let cLegal = [CGround, CInv, CEqp]       (verb1, object1) = case ts of         [] -> ("aim", "item")         tr : _ -> (verb tr, object tr)       triggerSyms = triggerSymbols ts-      p item =-        let goodKind = if ' ' `elem` triggerSyms-                       then case strengthEqpSlot item of-                         Just (Effect.EqpSlotAddLight, _) -> True-                         Just _ -> False-                         Nothing -> True-                       else jsymbol item `elem` triggerSyms-            trange = totalRange item-        in goodKind-           && trange >= chessDist (bpos sb) tpos-  ggi <- getGroupItem p object1 verb1 cLegal cLegal+      p itemFull@ItemFull{itemBase} =+        let legal = permittedProject triggerSyms False skill itemFull b activeItems+        in case legal of+          Left{} -> legal+          Right False -> legal+          Right True -> Right $ totalRange itemBase >= chessDist (bpos b) tpos+      prompt = makePhrase ["What", object1, "to", verb1]+      promptGeneric = "What item to fling"+  ggi <- getGroupItem (either (const False) id . p) prompt promptGeneric cLegal cLegal   case ggi of-    Right ((iid, _), CActor _ fromCStore) -> do-      return $ Right $ ReqProject tpos eps iid fromCStore+    Right ((iid, itemFull), CActor _ fromCStore) ->+      case p itemFull of+        Left reqFail -> failSer reqFail+        Right _ -> return $ Right $ ReqProject tpos eps iid fromCStore     Left slides -> return $ Left slides     _ -> assert `failure` ggi @@ -340,28 +342,24 @@            => [Trigger] -> m (SlideOrCmd (RequestTimed AbApply)) applyHuman ts = do   leader <- getLeaderUI-  actorBlind <- radiusBlind <$> sumOrganEqpClient Effect.EqpSlotAddSight leader+  b <- getsState $ getActorBody leader+  actorSk <- actorSkillsClient leader+  let skill = EM.findWithDefault 0 AbProject actorSk+  activeItems <- activeItemsClient leader   let cLegal = [CGround, CInv, CEqp]       (verb1, object1) = case ts of         [] -> ("activate", "item")         tr : _ -> (verb tr, object tr)       triggerSyms = triggerSymbols ts-      blindScroll item = jsymbol item == '?' && actorBlind-      p item = not (blindScroll item)-               && if ' ' `elem` triggerSyms-                  then Effect.Applicable `elem` jfeature item-                  else jsymbol item `elem` triggerSyms-  ggi <- getGroupItem p object1 verb1 cLegal cLegal+      p itemFull = permittedApply triggerSyms skill itemFull b activeItems+      prompt = makePhrase ["What", object1, "to", verb1]+      promptGeneric = "What item to activate"+  ggi <- getGroupItem (either (const False) id . p) prompt promptGeneric cLegal cLegal   case ggi of-    Right ((iid, itemFull), CActor _ fromCStore) -> do-      let durable = Effect.Durable `elem` jfeature (itemBase itemFull)-          periodic = isJust-                     $ strengthFromEqpSlot Effect.EqpSlotPeriodic itemFull-      if durable && periodic-        then failSer DurablePeriodicAbuse-        else if (blindScroll $ itemBase itemFull)-             then failSer ApplyBlind-             else return $ Right $ ReqApply iid fromCStore+    Right ((iid, itemFull), CActor _ fromCStore) ->+      case p itemFull of+        Left reqFail -> failSer reqFail+        Right _ -> return $ Right $ ReqApply iid fromCStore     Left slides -> return $ Left slides     _ -> assert `failure` ggi @@ -377,7 +375,7 @@         tr : _ -> verb tr       keys = zipWith K.KM (repeat K.NoModifier)                           (K.dirAllKey configVi configLaptop)-      prompt = makePhrase ["What to", verb1 MU.:> "? [movement key"]+      prompt = makePhrase ["What to", verb1 <> "? [movement key"]   me <- displayChoiceUI prompt emptyOverlay keys   case me of     Left slides -> failSlides slides@@ -399,19 +397,19 @@     [] -> failWith $ guessAlter cops alterFeats t     feat : _ -> return $ Right $ ReqAlter tpos $ Just feat -alterFeatures :: [Trigger] -> [F.Feature]+alterFeatures :: [Trigger] -> [TK.Feature] alterFeatures [] = [] alterFeatures (AlterFeature{feature} : ts) = feature : alterFeatures ts alterFeatures (_ : ts) = alterFeatures ts  -- | Guess and report why the bump command failed.-guessAlter :: Kind.COps -> [F.Feature] -> Kind.Id TileKind -> Msg-guessAlter Kind.COps{cotile} (F.OpenTo _ : _) t+guessAlter :: Kind.COps -> [TK.Feature] -> Kind.Id TileKind -> Msg+guessAlter Kind.COps{cotile} (TK.OpenTo _ : _) t   | Tile.isClosable cotile t = "already open"-guessAlter _ (F.OpenTo _ : _) _ = "cannot be opened"-guessAlter Kind.COps{cotile} (F.CloseTo _ : _) t+guessAlter _ (TK.OpenTo _ : _) _ = "cannot be opened"+guessAlter Kind.COps{cotile} (TK.CloseTo _ : _) t   | Tile.isOpenable cotile t = "already closed"-guessAlter _ (F.CloseTo _ : _) _ = "cannot be closed"+guessAlter _ (TK.CloseTo _ : _) _ = "cannot be closed" guessAlter _ _ _ = "never mind"  -- * TriggerTile@@ -423,12 +421,12 @@   tgtMode <- getsClient stgtMode   if isJust tgtMode then do     let getK tfs = case tfs of-          TriggerFeature {feature = F.Cause (Effect.Ascend k)} : _ -> Just k+          TriggerFeature {feature = TK.Cause (IK.Ascend k)} : _ -> Just k           _ : rest -> getK rest           [] -> Nothing         mk = getK ts     case mk of-      Nothing -> failWith  "never mind"+      Nothing -> failWith "never mind"       Just k -> fmap Left $ tgtAscendHuman k   else triggerTile ts @@ -450,16 +448,16 @@         Right () -> return $ Right $ ReqTrigger $ Just feat         Left slides -> return $ Left slides -triggerFeatures :: [Trigger] -> [F.Feature]+triggerFeatures :: [Trigger] -> [TK.Feature] triggerFeatures [] = [] triggerFeatures (TriggerFeature{feature} : ts) = feature : triggerFeatures ts triggerFeatures (_ : ts) = triggerFeatures ts  -- | Verify important feature triggers, such as fleeing the dungeon. verifyTrigger :: MonadClientUI m-              => ActorId -> F.Feature -> m (SlideOrCmd ())+              => ActorId -> TK.Feature -> m (SlideOrCmd ()) verifyTrigger leader feat = case feat of-  F.Cause Effect.Escape{} -> do+  TK.Cause IK.Escape{} -> do     b <- getsState $ getActorBody leader     side <- getsClient sside     fact <- getsState $ (EM.! side) . sfactionD@@ -484,13 +482,13 @@   _ -> return $ Right ()  -- | Guess and report why the bump command failed.-guessTrigger :: Kind.COps -> [F.Feature] -> Kind.Id TileKind -> Msg-guessTrigger Kind.COps{cotile} fs@(F.Cause (Effect.Ascend k) : _) t-  | Tile.hasFeature cotile (F.Cause (Effect.Ascend (-k))) t =+guessTrigger :: Kind.COps -> [TK.Feature] -> Kind.Id TileKind -> Msg+guessTrigger Kind.COps{cotile} fs@(TK.Cause (IK.Ascend k) : _) t+  | Tile.hasFeature cotile (TK.Cause (IK.Ascend (-k))) t =     if k > 0 then "the way goes down, not up"     else if k < 0 then "the way goes up, not down"     else assert `failure` fs-guessTrigger _ fs@(F.Cause (Effect.Ascend k) : _) _ =+guessTrigger _ fs@(TK.Cause (IK.Ascend k) : _) _ =     if k > 0 then "cannot ascend"     else if k < 0 then "cannot descend"     else assert `failure` fs@@ -525,23 +523,28 @@  -- * GameRestart; does not take time -gameRestartHuman :: MonadClientUI m => GroupName -> m (SlideOrCmd RequestUI)+gameRestartHuman :: MonadClientUI m => GroupName ModeKind -> m (SlideOrCmd RequestUI) gameRestartHuman t = do-  let msg = "You just requested a new" <+> tshow t <+> "game."-  b1 <- displayMore ColorFull msg-  if not b1 then failWith "never mind"+  let restart = do+        leader <- getLeaderUI+        DebugModeCli{sdifficultyCli} <- getsClient sdebugCli+        Config{configHeroNames} <- askConfig+        return $ Right+               $ ReqUIGameRestart leader t sdifficultyCli configHeroNames+  escAI <- getsClient sescAI+  if escAI == EscAIExited then restart   else do-    b2 <- displayYesNo ColorBW-            "Current progress will be lost! Really restart the game?"-    msg2 <- rndToAction $ oneOf-              [ "Yea, would be a pity to leave them all to die."-              , "Yea, a shame to get your own team stranded." ]-    if not b2 then failWith msg2+    let msg = "You just requested a new" <+> tshow t <+> "game."+    b1 <- displayMore ColorFull msg+    if not b1 then failWith "never mind"     else do-      leader <- getLeaderUI-      DebugModeCli{sdifficultyCli} <- getsClient sdebugCli-      Config{configHeroNames} <- askConfig-      return $ Right $ ReqUIGameRestart leader t sdifficultyCli configHeroNames+      b2 <- displayYesNo ColorBW+              "Current progress will be lost! Really restart the game?"+      msg2 <- rndToAction $ oneOf+                [ "Yea, would be a pity to leave them all to die."+                , "Yea, a shame to get your own team stranded." ]+      if not b2 then failWith msg2+      else restart  -- * GameExit; does not take time @@ -558,6 +561,8 @@  gameSaveHuman :: MonadClientUI m => m RequestUI gameSaveHuman = do+  -- Announce before the saving started, since it can take some time+  -- and may slow down the machine, even if not block the client.   -- TODO: do not save to history:   msgAdd "Saving game backup."   return ReqUIGameSave@@ -589,7 +594,10 @@ automateHuman = do   -- BFS is not updated while automated, which would lead to corruption.   modifyClient $ \cli -> cli {stgtMode = Nothing}-  go <- displayMore ColorBW "Ceding control to AI (ESC to regain)."-  if not go-    then failWith "Automation canceled."-    else return $ Right ReqUIAutomate+  escAI <- getsClient sescAI+  if escAI == EscAIExited then return $ Right ReqUIAutomate+  else do+    go <- displayMore ColorBW "Ceding control to AI (ESC to regain)."+    if not go+      then failWith "Automation canceled."+      else return $ Right ReqUIAutomate
Game/LambdaHack/Client/UI/HandleHumanLocalClient.hs view
@@ -46,9 +46,7 @@ import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.ClientOptions-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction-import qualified Game.LambdaHack.Common.Feature as F import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemDescription import qualified Game.LambdaHack.Common.Kind as Kind@@ -63,9 +61,9 @@ import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Time import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.RuleKind-import Game.LambdaHack.Content.TileKind+import qualified Game.LambdaHack.Content.TileKind as TK  -- * GameDifficultyCycle @@ -114,25 +112,31 @@ describeItemHuman :: MonadClientUI m => CStore -> m Slideshow describeItemHuman cstore = do   leader <- getLeaderUI-  describeItemC $ CActor leader cstore+  describeItemC (CActor leader cstore) False -describeItemC :: MonadClientUI m => Container -> m Slideshow-describeItemC c = do+describeItemC :: MonadClientUI m => Container -> Bool -> m Slideshow+describeItemC c noEnter = do   let subject body = partActor body       verbSha body activeItems = if calmEnough body activeItems                                  then "notice"                                  else "paw distractedly"-      shaBlurb body activeItems = makePhrase-        [MU.Capitalize-         $ MU.SubjectVerbSg (subject body) (verbSha body activeItems)]-      stdBlurb body = makePhrase-        [MU.Capitalize $ MU.SubjectVerbSg (subject body) "see"]-  itemToF <- itemToFullClient-  let verb = "describe"-  ggi <- getStoreItem shaBlurb stdBlurb verb c+      prompt body activeItems c2 = case c2 of+        CActor _ CSha ->+          makePhrase+            [MU.Capitalize+             $ MU.SubjectVerbSg (subject body) (verbSha body activeItems)]+        CTrunk{} ->+          makePhrase+            [MU.Capitalize $ MU.SubjectVerbSg (subject body) "recall"]+        _ ->+          makePhrase+            [MU.Capitalize $ MU.SubjectVerbSg (subject body) "see"]+  ggi <- getStoreItem prompt c noEnter   case ggi of-    Right ((iid, _), _) ->-      overlayToSlideshow "" $ itemDesc (storeFromC c) (itemToF iid 1)+    Right ((_, itemFull), c2) -> do+      lid2 <- getsState $ lidFromC c2+      localTime <- getsState $ getLocalTime lid2+      overlayToSlideshow "" $ itemDesc c2 lid2 localTime itemFull     Left slides -> return slides  -- * AllOwned@@ -142,7 +146,7 @@ allOwnedHuman = do   leader <- getLeaderUI   b <- getsState $ getActorBody leader-  describeItemC $ CTrunk (bfid b) (blid b) (bpos b)+  describeItemC (CTrunk (bfid b) (blid b) (bpos b)) False  -- * SelectActor @@ -214,12 +218,12 @@       let slastRecord = ([], [], maxK)       modifyClient $ \cli -> cli {slastRecord}       promptToSlideshow $ "Macro will be recorded for up to"-                          <+> tshow maxK <+> "steps."+                          <+> tshow maxK <+> "actions."  -- no MU, poweruser     _ -> do       let slastRecord = (seqPrevious, [], 0)       modifyClient $ \cli -> cli {slastRecord}       promptToSlideshow $ "Macro recording interrupted after"-                          <+> tshow (maxK - k - 1) <+> "steps."+                          <+> tshow (maxK - k - 1) <+> "actions."  -- * History @@ -229,11 +233,13 @@   arena <- getArenaUI   local <- getsState $ getLocalTime arena   global <- getsState stime-  let msg = makeSentence+  let turnsGlobal = global `timeFitUp` timeTurn+      turnsLocal = local `timeFitUp` timeTurn+      msg = makeSentence         [ "You survived for"-        , MU.CarWs (global `timeFitUp` timeTurn) "half-second turn"+        , MU.CarWs turnsGlobal "half-second turn"         , "(this level:"-        , MU.Text (tshow (local `timeFitUp` timeTurn)) MU.:> ")" ]+        , MU.Text (tshow turnsLocal) <> ")" ]         <+> "Past messages:"   overlayToBlankSlideshow msg $ renderHistory history @@ -272,6 +278,7 @@ mainMenuHuman :: MonadClientUI m => m Slideshow mainMenuHuman = do   Kind.COps{corule} <- getsState scops+  escAI <- getsClient sescAI   Binding{brevMap, bcmdList} <- askBinding   scurDifficulty <- getsClient scurDifficulty   DebugModeCli{sdifficultyCli} <- getsClient sdebugCli@@ -291,7 +298,10 @@                    | (km, (desc, [HumanCmd.CmdMenu], cmd)) <- bcmdList,                      cmd /= HumanCmd.GameDifficultyCycle ]         in [-             (fst (revLookup HumanCmd.Cancel), "back to playing")+             if escAI == EscAIMenu then+               (fst (revLookup HumanCmd.Automate), "back to screensaver")+             else+               (fst (revLookup HumanCmd.Cancel), "back to playing")            , (fst (revLookup HumanCmd.Accept), "see more help")            ]            ++ cmds@@ -387,9 +397,9 @@                      verb = "be here"                      desc = if not (null rest)  -- many actors                             then ""-                            else case itemDisco $ itemToF (btrunk body) 1 of+                            else case itemDisco $ itemToF (btrunk body) (1, []) of                               Nothing -> ""-                              Just ItemDisco{itemKind} -> idesc itemKind+                              Just ItemDisco{itemKind} -> IK.idesc itemKind                      pdesc = if desc == "" then "" else "(" <> desc <> ")"                  in makeSentence [MU.SubjectVerbSg subject verb] <+> pdesc           vis | lvl `at` p == unknownId = "that is"@@ -399,7 +409,7 @@       -- Show general info about current position.       lookMsg <- lookAt True vis canSee p leader enemyMsg       -- Check if there's something lying around at current position.-      let is = lvl `atI` p+      is <- getsState $ getCBag $ CFloor lidV p       if EM.size is <= 2 then         promptToSlideshow lookMsg       else do@@ -408,7 +418,7 @@  -- | Create a list of item names. floorItemOverlay :: MonadClientUI m => LevelId -> Point -> m Slideshow-floorItemOverlay lid p = describeItemC (CFloor lid p)+floorItemOverlay lid p = describeItemC (CFloor lid p) True  -- * TgtFloor @@ -542,7 +552,7 @@         Nothing -> Nothing         Just cpos ->           let tile = lvl `at` cpos-          in if Tile.hasFeature cotile (F.Cause $ Effect.Ascend k) tile+          in if Tile.hasFeature cotile (TK.Cause $ IK.Ascend k) tile              then Just cpos              else Nothing   case rightStairs of@@ -551,10 +561,10 @@       assert (nln /= lidV `blame` "stairs looped" `twith` nln) skip       nlvl <- getLevel nln       -- Do not freely reveal the other end of the stairs.-      let ascDesc (F.Cause (Effect.Ascend _)) = True+      let ascDesc (TK.Cause (IK.Ascend _)) = True           ascDesc _ = False           scursor =-            if any ascDesc $ tfeature $ okind (nlvl `at` npos)+            if any ascDesc $ TK.tfeature $ okind (nlvl `at` npos)             then TPoint nln npos  -- already known as an exit, focus on it             else scursorOld  -- unknown, do not reveal       modifyClient $ \cli -> cli {scursor, stgtMode = Just (TgtMode nln)}
Game/LambdaHack/Client/UI/HumanCmd.hs view
@@ -5,13 +5,16 @@   ) where  import Control.Exception.Assert.Sugar+import Data.Maybe import Data.Text (Text) import qualified NLP.Miniutter.English as MU -import qualified Game.LambdaHack.Common.Feature as F+import Game.LambdaHack.Common.Actor (verbCStore) import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg import Game.LambdaHack.Common.Vector+import Game.LambdaHack.Content.ModeKind+import qualified Game.LambdaHack.Content.TileKind as TK  data CmdCategory =     CmdMenu | CmdMove | CmdItem | CmdTgt | CmdAuto | CmdMeta@@ -35,14 +38,14 @@     Move !Vector   | Run !Vector   | Wait-  | MoveItem ![CStore] !CStore !MU.Part !MU.Part !Bool+  | MoveItem ![CStore] !CStore !(Maybe MU.Part) !MU.Part !Bool   | Project     ![Trigger]   | Apply       ![Trigger]   | AlterDir    ![Trigger]   | TriggerTile ![Trigger]   | StepToTarget     -- Below this line, commands do not take time.-  | GameRestart !GroupName+  | GameRestart !(GroupName ModeKind)   | GameExit   | GameSave   | Tactic@@ -83,8 +86,9 @@  data Trigger =     ApplyItem {verb :: !MU.Part, object :: !MU.Part, symbol :: !Char}-  | AlterFeature {verb :: !MU.Part, object :: !MU.Part, feature :: !F.Feature}-  | TriggerFeature {verb :: !MU.Part, object :: !MU.Part, feature :: !F.Feature}+  | AlterFeature {verb :: !MU.Part, object :: !MU.Part, feature :: !TK.Feature}+  | TriggerFeature+      {verb :: !MU.Part, object :: !MU.Part, feature :: !TK.Feature}   deriving (Show, Read, Eq, Ord)  -- | Commands that are forbidden on a remote level, because they@@ -106,7 +110,9 @@   Move v      -> "move" <+> compassText v   Run v       -> "run" <+> compassText v   Wait        -> "wait"-  MoveItem _ _ verb object _ -> makePhrase [verb, object]+  MoveItem _ store2 mverb object _ ->+    let verb = fromMaybe (MU.Text $ verbCStore store2) mverb+    in makePhrase [verb, object]   Project ts  -> triggerDescription ts   Apply ts    -> triggerDescription ts   AlterDir ts -> triggerDescription ts
Game/LambdaHack/Client/UI/InventoryClient.hs view
@@ -1,8 +1,7 @@ -- | Inventory management and party cycling. -- TODO: document module Game.LambdaHack.Client.UI.InventoryClient-  ( failMsg, msgNoChangeDunLeader, msgNoChangeLvlLeader-  , getGroupItem, getAnyItem, getStoreItem+  ( failMsg, getGroupItem, getAnyItem, getStoreItem   , memberCycle, memberBack, pickLeader   ) where @@ -46,19 +45,31 @@ -- Note that this does not guarantee the chosen item belongs to the group, -- as the player can override the choice. getGroupItem :: MonadClientUI m-             => (Item -> Bool)  -- ^ which items to consider suitable-             -> MU.Part   -- ^ name of the item group-             -> MU.Part   -- ^ the verb describing the action+             => (ItemFull -> Bool)  -- ^ which items to consider suitable+             -> Text      -- ^ specific prompt for only suitable items+             -> Text      -- ^ generic prompt              -> [CStore]  -- ^ initial legal containers              -> [CStore]  -- ^ legal containers after Calm taken into account              -> m (SlideOrCmd ((ItemId, ItemFull), Container))-getGroupItem p itemsName verb cLegalRaw cLegalAfterCalm = do+getGroupItem psuit prompt promptGeneric cLegalRaw cLegalAfterCalm = do+  side <- getsClient sside   leader <- getLeaderUI+  let aidNotEmpty store aid = do+        bag <- getsState $ getCBag (CActor aid store)+        return $! not $ EM.null bag+      partyNotEmpty store = do+        as <- getsState $ fidActorNotProjAssocs side+        bs <- mapM (aidNotEmpty store . fst) as+        return $! or bs+  -- Don't display stores empty for all actors+  cLegalNotEmpty <- filterM partyNotEmpty cLegalAfterCalm+  -- Move a store that is empty for this actor to the back.   getCStoreBag <- getsState $ \s cstore -> getCBag (CActor leader cstore) s-  let cNotEmpty = not . EM.null . getCStoreBag-      cLegal = filter cNotEmpty cLegalAfterCalm  -- don't display empty stores-      tsuitable = const $ makePhrase [MU.Capitalize (MU.Ws itemsName)]-  getItem p (\b _ -> tsuitable b) tsuitable verb+  let hasThisActor = not . EM.null . getCStoreBag+      cLegal = case find hasThisActor cLegalAfterCalm of+        Nothing -> cLegalNotEmpty+        Just cThisActor -> cThisActor : delete cThisActor cLegalNotEmpty+  getItem psuit (\_ _ _ -> prompt) (\_ _ _ -> promptGeneric)           (map (CActor leader) cLegalRaw)           (map (CActor leader) cLegal)           True INone@@ -75,10 +86,11 @@            -> m (SlideOrCmd ((ItemId, ItemFull), Container)) getAnyItem verb cLegalRaw cLegalAfterCalm askWhenLone askNumber = do   leader <- getLeaderUI-  soc <- getItem (const True) (\_ _ -> "Items") (const "Items") verb+  let prompt = makePhrase ["What to", verb]+  soc <- getItem (const True) (\_ _ _ -> prompt) (\_ _ _ -> prompt)                  (map (CActor leader) cLegalRaw)                  (map (CActor leader) cLegalAfterCalm)-                 askWhenLone INone+                 askWhenLone ISuitable   case soc of     Left _ -> return soc     Right ((iid, itemFull), c) -> do@@ -91,76 +103,73 @@ -- | Display all items from a store and let the human player choose any -- or switch to any other store. getStoreItem :: MonadClientUI m-             => (Actor -> [ItemFull] -> Text)-                                 -- ^ how to describe suitable items in CSha-             -> (Actor -> Text)  -- ^ how to describe suitable items elsewhere-             -> MU.Part          -- ^ the verb describing the action+             => (Actor -> [ItemFull] -> Container -> Text)+                                 -- ^ how to describe suitable items              -> Container        -- ^ initial container+             -> Bool             -- ^ whether Enter should be disabled              -> m (SlideOrCmd ((ItemId, ItemFull), Container))-getStoreItem shaBlurb stdBlurb verb cInitial = do+getStoreItem prompt cInitial noEnter = do   leader <- getLeaderUI   let allStores = map (CActor leader) [CEqp, CInv, CSha, CGround]       cLegalRaw = cInitial : delete cInitial allStores-  getItem (const True) shaBlurb stdBlurb verb cLegalRaw cLegalRaw-          True ISuitable+      dialogState = if noEnter then INoEnter else ISuitable+  getItem (const True) prompt prompt cLegalRaw cLegalRaw+          True dialogState -data ItemDialogState = INone | ISuitable | IAll+data ItemDialogState = INone | ISuitable | IAll | INoEnter   deriving (Show, Eq)  -- | Let the human player choose a single, preferably suitable, -- item from a list of items. getItem :: MonadClientUI m-        => (Item -> Bool)   -- ^ which items to consider suitable-        -> (Actor -> [ItemFull] -> Text)-                            -- ^ how to describe suitable items in CSha-        -> (Actor -> Text)  -- ^ how to describe suitable items elsewhere-        -> MU.Part          -- ^ the verb describing the action+        => (ItemFull -> Bool)  -- ^ which items to consider suitable+        -> (Actor -> [ItemFull] -> Container -> Text)+                            -- ^ specific prompt for only suitable items+        -> (Actor -> [ItemFull] -> Container -> Text)+                            -- ^ generic prompt         -> [Container]      -- ^ initial legal containers         -> [Container]      -- ^ legal containers with Calm taken into account         -> Bool             -- ^ whether to ask, when the only item                             --   in the starting container is suitable         -> ItemDialogState  -- ^ the dialog state to start in         -> m (SlideOrCmd ((ItemId, ItemFull), Container))-getItem p tshaSuit tsuitable verb cLegalRaw cLegal askWhenLone initalState = do+getItem psuit prompt promptGeneric cLegalRaw cLegal askWhenLone initalState = do   leader <- getLeaderUI   accessCBag <- getsState $ flip getCBag   let storeAssocs = EM.assocs . accessCBag       allAssocs = concatMap storeAssocs cLegal       rawAssocs = concatMap storeAssocs cLegalRaw+  mapM_ (updateItemSlot (Just leader)) $+    concatMap (EM.keys . accessCBag) cLegal   case (cLegal, allAssocs) of     ([cStart], [(iid, k)]) | not askWhenLone -> do       itemToF <- itemToFullClient       return $ Right ((iid, itemToF iid k), cStart)-    (_ : _, _ : _) -> do-      let groundCs = filter ((== CGround) . storeFromC) cLegal-      mapM_ (updateItemSlot (Just leader)) $-        concatMap (EM.keys . accessCBag) groundCs-      transition p tshaSuit tsuitable verb cLegal initalState+    (_ : _, _ : _) ->+      transition psuit prompt promptGeneric cLegal initalState     _ -> if null rawAssocs then do            let tLegal = map (MU.Text . ppContainer) cLegalRaw                ppLegal = makePhrase [MU.WWxW "nor" tLegal]            failWith $ "no items" <+> ppLegal          else failSer ItemNotCalm --- TODO: m is no longer needed and perhaps this can be simplified even more data DefItemKey m = DefItemKey-  { defLabel  :: Text-  , defCond   :: Bool+  { defLabel  :: Text  -- ^ can be undefined if not @defCond@+  , defCond   :: !Bool   , defAction :: K.Key -> m (SlideOrCmd ((ItemId, ItemFull), Container))   }  transition :: forall m. MonadClientUI m-           => (Item -> Bool)   -- ^ which items to consider suitable-           -> (Actor -> [ItemFull] -> Text)-                               -- ^ how to describe suitable items in CSha-           -> (Actor -> Text)  -- ^ how to describe suitable items elsewhere-           -> MU.Part          -- ^ the verb describing the action+           => (ItemFull -> Bool)  -- ^ which items to consider suitable+           -> (Actor -> [ItemFull] -> Container -> Text)+                            -- ^ specific prompt for only suitable items+           -> (Actor -> [ItemFull] -> Container -> Text)+                            -- ^ generic prompt            -> [Container]            -> ItemDialogState            -> m (SlideOrCmd ((ItemId, ItemFull), Container))-transition _ _ _ verb [] iDS = assert `failure` (verb, iDS)-transition psuit tshaSuit tsuitable verb-           cLegal@(cCur:cRest) itemDialogState = do+transition _ _ _ [] iDS = assert `failure` iDS+transition psuit prompt promptGeneric cLegal@(cCur:cRest) itemDialogState = do   (letterSlots, numberSlots) <- getsClient sslots   leader <- getLeaderUI   body <- getsState $ getActorBody leader@@ -171,31 +180,34 @@   itemToF <- itemToFullClient   let getResult :: ItemId -> ((ItemId, ItemFull), Container)       getResult iid = ((iid, itemToF iid (bag EM.! iid)), cCur)-      filterP s iid _ = psuit (getItemBody iid s)-  bagSuit <- getsState $ \s -> EM.filterWithKey (filterP s) bag-  let bagLetterSlots = EM.filter (`EM.member` bag) letterSlots+      filterP iid kit = psuit $ itemToF iid kit+      bagSuit = EM.filterWithKey filterP bag+      bagLetterSlots = EM.filter (`EM.member` bag) letterSlots       bagNumberSlots = IM.filter (`EM.member` bag) numberSlots       suitableLetterSlots = EM.filter (`EM.member` bagSuit) letterSlots       (autoDun, autoLvl) = autoDungeonLevel fact+      normalizeState INoEnter = INone+      normalizeState x = x       keyDefs :: [(K.Key, DefItemKey m)]       keyDefs = filter (defCond . snd)         [ (K.Char '?', DefItemKey            { defLabel = "?"            , defCond = True-           , defAction = \_ -> case itemDialogState of+           , defAction = \_ -> case normalizeState itemDialogState of                INone ->                  if EM.null bagSuit-                 then transition psuit tshaSuit tsuitable verb cLegal IAll-                 else transition psuit tshaSuit tsuitable verb cLegal ISuitable+                 then transition psuit prompt promptGeneric cLegal IAll+                 else transition psuit prompt promptGeneric cLegal ISuitable                ISuitable | bag /= bagSuit ->-                 transition psuit tshaSuit tsuitable verb cLegal IAll-               _ -> transition psuit tshaSuit tsuitable verb cLegal INone+                 transition psuit prompt promptGeneric cLegal IAll+               _ -> transition psuit prompt promptGeneric cLegal INone            })         , (K.Char '/', DefItemKey            { defLabel = "/"            , defCond = length cLegal > 1-           , defAction = \_ -> transition psuit tshaSuit tsuitable verb-                                          (cRest ++ [cCur]) itemDialogState+           , defAction = \_ ->+               transition psuit prompt promptGeneric+                          (cRest ++ [cCur]) (normalizeState itemDialogState)            })         , (K.Return,            let enterSlots = if itemDialogState == IAll@@ -206,7 +218,8 @@                Nothing -> assert `failure` "no suitable items"                                  `twith` enterSlots                Just ((l, _), _) -> "RET(" <> T.singleton (slotChar l) <> ")"-           , defCond = not $ EM.null enterSlots+           , defCond = not (EM.null enterSlots+                            || itemDialogState == INoEnter)            , defAction = \_ -> case EM.maxView enterSlots of                Nothing -> assert `failure` "no suitable items"                                  `twith` enterSlots@@ -233,7 +246,10 @@                      CActor _ cstore -> CActor newLeader cstore                      _ -> c                    newLegal = map newC cLegal-               transition psuit tshaSuit tsuitable verb newLegal itemDialogState+               accessCBag <- getsState $ flip getCBag+               mapM_ (updateItemSlot (Just newLeader)) $+                 concatMap (EM.keys . accessCBag) newLegal+               transition psuit prompt promptGeneric newLegal itemDialogState            })         , (K.BackTab, DefItemKey            { defLabel = "SHIFT-TAB"@@ -246,7 +262,10 @@                      CActor _ cstore -> CActor newLeader cstore                      _ -> c                    newLegal = map newC cLegal-               transition psuit tshaSuit tsuitable verb newLegal itemDialogState+               accessCBag <- getsState $ flip getCBag+               mapM_ (updateItemSlot (Just newLeader)) $+                 concatMap (EM.keys . accessCBag) newLegal+               transition psuit prompt promptGeneric newLegal itemDialogState            })         ]       lettersDef :: DefItemKey m@@ -261,22 +280,19 @@             _ -> assert `failure` "unexpected key:" `twith` K.showKey key         }       ppCur = ppContainer cCur-      tsuit = if storeFromC cCur == CSha-              then tshaSuit body activeItems-              else tsuitable body-      (labelLetterSlots, bagFiltered, prompt) =+      (labelLetterSlots, bagFiltered, promptChosen) =         case itemDialogState of-          INone     -> (suitableLetterSlots,-                        EM.empty,-                        makePhrase ["What to", verb] <+> ppCur <> "?")           ISuitable -> (suitableLetterSlots,                         bagSuit,-                        tsuit <+> ppCur <> ":")+                        prompt body activeItems cCur <+> ppCur <> ":")           IAll      -> (bagLetterSlots,                         bag,-                        "Items" <+> ppCur <> ":")-  io <- itemOverlay (storeFromC cCur) bagFiltered-  runDefItemKey keyDefs lettersDef io labelLetterSlots prompt+                        promptGeneric body activeItems cCur <+> ppCur <> ":")+          _         -> (suitableLetterSlots,+                        EM.empty,+                        prompt body activeItems cCur <+> ppCur <> ":")+  io <- itemOverlay cCur (blid body) bagFiltered+  runDefItemKey keyDefs lettersDef io bagLetterSlots promptChosen  runDefItemKey :: MonadClientUI m               => [(K.Key, DefItemKey m)]@@ -357,12 +373,6 @@       success <- pickLeader verbose np       assert (success `blame` "same leader" `twith` (leader, np, b)) skip       return mempty--msgNoChangeDunLeader :: Msg-msgNoChangeDunLeader = "level change is automatic for your team"--msgNoChangeLvlLeader :: Msg-msgNoChangeLvlLeader = "leader change is automatic for your team"  partyAfterLeader :: MonadStateRead m => ActorId -> m [(ActorId, Actor)] partyAfterLeader leader = do
Game/LambdaHack/Client/UI/MonadClientUI.hs view
@@ -12,7 +12,7 @@   , displayFrame, displayDelay, displayFrames, displayActorStart, drawOverlay     -- * Assorted primitives   , stopPlayBack, stopRunning, askConfig, askBinding-  , syncFrames, tryTakeMVarSescMVar, scoreToSlideshow+  , syncFrames, setFrontAutoYes, tryTakeMVarSescMVar, scoreToSlideshow   , getLeaderUI, getArenaUI, viewedLevel   , targetDescLeader, targetDescCursor   , leaderTgtToPos, leaderTgtAims, cursorToPos@@ -42,7 +42,7 @@ import Game.LambdaHack.Client.UI.KeyBindings import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState-import qualified Game.LambdaHack.Common.Effect as Effect+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Common.Faction import qualified Game.LambdaHack.Common.HighScore as HighScore import Game.LambdaHack.Common.ItemDescription@@ -219,6 +219,9 @@   km <- readConnFrontend   assert (km == K.spaceKM) skip +setFrontAutoYes :: MonadClientUI m => Bool -> m ()+setFrontAutoYes b = writeConnFrontend $ FrontAutoYes b+ tryTakeMVarSescMVar :: MonadClientUI m => m Bool tryTakeMVarSescMVar = do   mescMVar <- getsSession sescMVar@@ -226,7 +229,7 @@     Nothing -> return False     Just escMVar -> do       mUnit <- liftIO $ tryTakeMVar escMVar-      return $ isJust mUnit+      return $! isJust mUnit  scoreToSlideshow :: MonadClientUI m => Int -> Status -> m Slideshow scoreToSlideshow total status = do@@ -297,7 +300,7 @@     Just (TEnemy aid _) -> do       side <- getsClient sside       b <- getsState $ getActorBody aid-      maxHP <- sumOrganEqpClient Effect.EqpSlotAddMaxHP aid+      maxHP <- sumOrganEqpClient IK.EqpSlotAddMaxHP aid       let percentage = 100 * bhp b `div` xM (max 5 maxHP)           stars | percentage < 20  = "[_____]"                 | percentage < 40  = "[*____]"@@ -316,12 +319,13 @@       pointedText <-         if lid == lidV         then do-          lvl <- getLevel lid-          case EM.assocs $ lvl `atI` p of+          bag <- getsState $ getCBag (CFloor lid p)+          case EM.assocs bag of             [] -> return $! "exact spot" <+> (T.pack . show) p-            [(iid, k)] -> do+            [(iid, kit@(k, _))] -> do+              localTime <- getsState $ getLocalTime lid               itemToF <- itemToFullClient-              let (name, stats) = partItem CGround (itemToF iid k)+              let (name, stats) = partItem (CFloor lid p) lid localTime (itemToF iid kit)               return $! makePhrase $ if k == 1                                      then [name, stats]  -- "a sword" too wordy                                      else [MU.CarWs k name, stats]
Game/LambdaHack/Client/UI/MsgClient.hs view
@@ -36,7 +36,7 @@ import Game.LambdaHack.Common.Request import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile-import Game.LambdaHack.Content.TileKind+import qualified Game.LambdaHack.Content.TileKind as TK  -- | Add a message to the current report. msgAdd :: MonadClientUI m => Msg -> m ()@@ -49,11 +49,12 @@ -- | Store current report in the history and reset report. recordHistory :: MonadClientUI m => m () recordHistory = do+  time <- getsState stime   StateClient{sreport, shistory} <- getClient   unless (nullReport sreport) $ do     Config{configHistoryMax} <- askConfig     msgReset ""-    let nhistory = takeHistory configHistoryMax $! addReport sreport shistory+    let nhistory = takeHistory configHistoryMax $! addReport shistory time sreport     modifyClient $ \cli -> cli {shistory = nhistory}  type SlideOrCmd a = Either Slideshow a@@ -87,13 +88,14 @@   itemToF <- itemToFullClient   lidV <- viewedLevel   lvl <- getLevel lidV+  localTime <- getsState $ getLocalTime lidV   b <- getsState $ getActorBody aid   subject <- partAidLeader aid-  let is = lvl `atI` pos-      verb = MU.Text $ if pos == bpos b+  is <- getsState $ getCBag $ CFloor lidV pos+  let verb = MU.Text $ if pos == bpos b                        then "stand on"                        else if canSee then "notice" else "remember"-  let nWs (iid, k) = partItemWs k CGround (itemToF iid k)+  let nWs (iid, kit@(k, _)) = partItemWs k (CFloor lidV pos) lidV localTime (itemToF iid kit)       isd = case detailed of               _ | EM.size is == 0 -> ""               _ | EM.size is <= 2 ->@@ -105,7 +107,7 @@       obscured | knownLsecret lvl                  && tile /= hideTile cops lvl pos = "partially obscured"                | otherwise = ""-      tileText = obscured <+> tname (okind tile)+      tileText = obscured <+> TK.tname (okind tile)       tilePart | T.null tilePrefix = MU.Text tileText                | otherwise = MU.AW $ MU.Text tileText       tileDesc = [MU.Text tilePrefix, tilePart]@@ -118,20 +120,25 @@   else return $! msg <+> isd  -- | Create a list of item names.-itemOverlay :: MonadClient m => CStore -> ItemBag -> m Overlay-itemOverlay cstore bag = do+itemOverlay :: MonadClient m+            => Container -> LevelId -> ItemBag -> m Overlay+itemOverlay c lid bag = do+  localTime <- getsState $ getLocalTime lid   itemToF <- itemToFullClient   (letterSlots, numberSlots) <- getsClient sslots+  assert (all (`elem` EM.elems letterSlots ++ IM.elems numberSlots)+              (EM.keys bag)+          `blame` (c, lid, bag, letterSlots, numberSlots)) skip   let pr (l, iid) =         case EM.lookup iid bag of           Nothing -> Nothing-          Just k ->-            let itemFull = itemToF iid k+          Just kit@(k, _) ->+            let itemFull = itemToF iid kit                 -- TODO: add color item symbols as soon as we have a menu                 -- with all items visible on the floor or known to player                 -- symbol = jsymbol $ itemBase itemFull             in Just $ makePhrase [ slotLabel l, "-"  -- MU.String [symbol]-                                 , partItemWs k cstore itemFull ]+                                 , partItemWs k c lid localTime itemFull ]                            <> " "   return $! toOverlay $ mapMaybe pr     $ map (first Left) (EM.assocs letterSlots)
Game/LambdaHack/Client/UI/RunClient.hs view
@@ -35,7 +35,7 @@ import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.TileKind+import qualified Game.LambdaHack.Content.TileKind as TK  -- | Continue running in the given direction. continueRun :: MonadClient m@@ -125,7 +125,7 @@                     || Tile.isOpenable cotile t                     || Tile.isClosable cotile t                     || Tile.isChangeable cotile t) then-          if not $ EM.null $ lvl `atI` tpos then+          if EM.member tpos $ lfloor lvl then             Left $ showReqFailure AlterBlockItem           else             Right $ RequestAnyAbility $ ReqAlter tpos Nothing@@ -138,8 +138,9 @@             -- in hopes of killing a monster lurking within.             -- If the action had a cost, misclicks would incur the cost, too.             -- Right now the player may repeatedly alter tiles trying to learn-            -- about invisible pass-wall actors, but it costs a turn-            -- and does not harm the invisible actors, so it's not tempting.+            -- about invisible pass-wall actors, but when an actor detected,+            -- it costs a turn and does not harm the invisible actors,+            -- so it's not so tempting.        -- Ignore a known boring, not accessible tile.        else Left "never mind"   return $! runStopOrCmd@@ -153,7 +154,7 @@                => ActorId -> Int -> Maybe Vector -> m (Either Msg Vector) continueRunDir aid distLast mdir = do   sreport <- getsClient sreport -- TODO: check the message before it goes into history-  let boringMsgs = map BS.pack [ "You hear some noises."+  let boringMsgs = map BS.pack [ "You hear a distant"                                , "reveals that the" ]       boring repLine = any (`BS.isInfixOf` repLine) boringMsgs       -- TODO: use a regexp from the UI config instead@@ -227,7 +228,7 @@   let lid = blid body   lvl <- getLevel lid   let posHere = bpos body-      posHasItems pos = not $ EM.null $ lvl `atI` pos+      posHasItems pos = EM.member pos $ lfloor lvl       posThere = posHere `shift` dir   actorsThere <- getsState $ posToActors posThere lid   let posLast = boldpos body@@ -258,7 +259,7 @@       rightTilesLast = map (lvl `at`) rightPsLast       leftForwardTileHere = lvl `at` leftForwardPosHere       rightForwardTileHere = lvl `at` rightForwardPosHere-      featAt = actionFeatures smarkSuspect . okind+      featAt = TK.actionFeatures smarkSuspect . okind       terrainChangeMiddle = null (Tile.causeEffects cotile tileThere)                               -- step into; will stop next turn due to message                             && featAt tileThere
Game/LambdaHack/Client/UI/WidgetClient.hs view
@@ -109,12 +109,20 @@ -- of the screen are displayed below. As many slides as needed are shown. overlayToSlideshow :: MonadClientUI m => Msg -> Overlay -> m Slideshow overlayToSlideshow prompt overlay = do+  promptAI <- msgPromptAI   lid <- getArenaUI   Level{lxsize, lysize} <- getLevel lid  -- TODO: screen length or viewLevel   sreport <- getsClient sreport-  let msg = splitReport lxsize (addMsg sreport prompt)+  let msg = splitReport lxsize (prependMsg promptAI (addMsg sreport prompt))   return $! splitOverlay False (lysize + 1) msg overlay +msgPromptAI :: MonadClientUI m => m Msg+msgPromptAI = do+  side <- getsClient sside+  fact <- getsState $ (EM.! side) . sfactionD+  let underAI = isAIFact fact+  return $! if underAI then "[press ESC for Main Menu]" else ""+ overlayToBlankSlideshow :: MonadClientUI m => Msg -> Overlay -> m Slideshow overlayToBlankSlideshow prompt overlay = do   lid <- getArenaUI@@ -135,7 +143,8 @@   bfsmpath <- maybe (return Nothing) pathFromLeader mleader   tgtDesc <- maybe (return ("------", Nothing)) targetDescLeader mleader   cursorDesc <- targetDescCursor-  let over = renderReport sreport+  promptAI <- msgPromptAI+  let over = renderReport (prependMsg promptAI sreport)       topLineOnly = truncateToOverlay over   basicFrame <-     draw False ColorFull arena cursorPos tgtPos
Game/LambdaHack/Common/Actor.hs view
@@ -8,10 +8,11 @@   , Actor(..), ResDelta(..)   , deltaSerious, deltaMild, xM, minusM, minusTwoM, oneM   , bspeed, actorTemplate, timeShiftFromSpeed, braced, waitedLastTurn-  , actorDying, actorNewBorn, hpTooLow, unoccupied+  , actorDying, actorNewBorn, unoccupied+  , hpTooLow, calmEnough, calmEnough10, hpEnough, hpEnough10     -- * Assorted   , ActorDict, smellTimeout, checkAdjacent-  , mapActorItems_, ppCStore, ppContainer+  , mapActorItems_, ppContainer, ppCStore, verbCStore   ) where  import Control.Exception.Assert.Sugar@@ -23,7 +24,6 @@ import qualified NLP.Miniutter.English as MU  import qualified Game.LambdaHack.Common.Color as Color-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemStrongest import Game.LambdaHack.Common.Misc@@ -31,6 +31,7 @@ import Game.LambdaHack.Common.Random import Game.LambdaHack.Common.Time import Game.LambdaHack.Common.Vector+import qualified Game.LambdaHack.Content.ItemKind as IK  -- | Actor properties that are changing throughout the game. -- If they are dublets of properties from @ActorKind@,@@ -139,10 +140,10 @@ bspeed b activeItems =   case btrajectory b of     Nothing -> toSpeed $ max 1  -- avoid infinite wait-               $ sumSlotNoFilter Effect.EqpSlotAddSpeed activeItems+               $ sumSlotNoFilter IK.EqpSlotAddSpeed activeItems     Just (_, speed) -> speed --- | Add time taken by a single step at the actor's current speed.+-- | Add time taken by a single 1m step at the actor's current speed. timeShiftFromSpeed :: Actor -> [ItemFull] -> Time -> Time timeShiftFromSpeed b activeItems time =   let speed = bspeed b activeItems@@ -170,9 +171,25 @@  hpTooLow :: Actor -> [ItemFull] -> Bool hpTooLow b activeItems =-  let maxHP = sumSlotNoFilter Effect.EqpSlotAddMaxHP activeItems-  in bhp b <= oneM || 5 * bhp b < xM maxHP+  let maxHP = sumSlotNoFilter IK.EqpSlotAddMaxHP activeItems+  in bhp b <= oneM || 5 * bhp b < xM maxHP && bhp b < xM 10 +calmEnough :: Actor -> [ItemFull] -> Bool+calmEnough b activeItems =+  let calmMax = max 1 $ sumSlotNoFilter IK.EqpSlotAddMaxCalm activeItems+  in 2 * xM calmMax <= 3 * bcalm b++calmEnough10 :: Actor -> [ItemFull] -> Bool+calmEnough10 b activeItems = calmEnough b activeItems && bcalm b >= xM 10++hpEnough :: Actor -> [ItemFull] -> Bool+hpEnough b activeItems =+  let hpMax = max 1 $ sumSlotNoFilter IK.EqpSlotAddMaxHP activeItems+  in 2 * xM hpMax <= 3 * bhp b++hpEnough10 :: Actor -> [ItemFull] -> Bool+hpEnough10 b activeItems = hpEnough b activeItems && bhp b >= xM 10+ -- | Checks for the presence of actors in a position. -- Does not check if the tile is walkable. unoccupied :: [Actor] -> Point -> Bool@@ -188,11 +205,17 @@ checkAdjacent :: Actor -> Actor -> Bool checkAdjacent sb tb = blid sb == blid tb && adjacent (bpos sb) (bpos tb) -mapActorItems_ :: Monad m => (ItemId -> Int -> m a) -> Actor -> m ()+mapActorItems_ :: Monad m => (ItemId -> ItemQuant -> m a) -> Actor -> m () mapActorItems_ f Actor{binv, beqp, borgan} = do   let is = EM.assocs beqp ++ EM.assocs binv ++ EM.assocs borgan   mapM_ (uncurry f) is +ppContainer :: Container -> Text+ppContainer CFloor{} = "nearby"+ppContainer CEmbed{} = "embedded nearby"+ppContainer (CActor _ cstore) = ppCStore cstore+ppContainer CTrunk{} = "in our possession"+ ppCStore :: CStore -> Text ppCStore CGround = "on the ground" ppCStore COrgan = "among organs"@@ -200,10 +223,12 @@ ppCStore CInv = "in inventory" ppCStore CSha = "in shared stash" -ppContainer :: Container -> Text-ppContainer CFloor{} = "nearby"-ppContainer (CActor _ cstore) = ppCStore cstore-ppContainer CTrunk{} = "in our possession"+verbCStore :: CStore -> Text+verbCStore CGround = "drop"+verbCStore COrgan = "implant"+verbCStore CEqp = "equip"+verbCStore CInv = "pack"+verbCStore CSha = "stash"  instance Binary Actor where   put Actor{..} = do
Game/LambdaHack/Common/ActorState.hs view
@@ -6,14 +6,14 @@   , actorAssocsLvl, actorAssocs, actorList   , actorRegularAssocsLvl, actorRegularAssocs, actorRegularList   , bagAssocs, bagAssocsK, calculateTotal-  , sharedAllOwned, sharedAllOwnedFid+  , mergeItemQuant, sharedAllOwned, sharedAllOwnedFid   , getCBag, getActorBag, getBodyActorBag, getActorAssocs   , nearbyFreePoints, whereTo, getCarriedAssocs   , posToActors, posToActor, getItemBody, memActor, getActorBody-  , tryFindHeroK, getLocalTime-  , itemPrice, calmEnough, hpEnough, regenCalmDelta-  , actorInAmbient, actorSkills, dispEnemy, radiusBlind-  , fullAssocs, itemToFull, goesIntoInv, eqpOverfull, storeFromC+  , tryFindHeroK, getLocalTime, itemPrice, regenCalmDelta+  , actorInAmbient, actorSkills, maxActorSkills, dispEnemy+  , fullAssocs, itemToFull, goesIntoInv, eqpOverfull+  , storeFromC, lidFromC, aidFromC   ) where  import Control.Exception.Assert.Sugar@@ -25,7 +25,6 @@  import qualified Game.LambdaHack.Common.Ability as Ability import Game.LambdaHack.Common.Actor-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemStrongest@@ -37,8 +36,9 @@ import qualified Game.LambdaHack.Common.Tile as Tile 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+import Game.LambdaHack.Content.TileKind (TileKind)  fidActorNotProjAssocs :: FactionId -> State -> [(ActorId, Actor)] fidActorNotProjAssocs fid s =@@ -94,9 +94,9 @@   let iidItem iid = (iid, getItemBody iid s)   in map iidItem $ EM.keys bag -bagAssocsK :: State -> ItemBag -> [(ItemId, (Item, Int))]+bagAssocsK :: State -> ItemBag -> [(ItemId, (Item, ItemQuant))] bagAssocsK s bag =-  let iidItem (iid, k) = (iid, (getItemBody iid s, k))+  let iidItem (iid, kit) = (iid, (getItemBody iid s, kit))   in map iidItem $ EM.assocs bag  -- | Finds an actor at a position on the current level.@@ -133,30 +133,45 @@ -- | Calculate loot's worth for a faction of a given actor. calculateTotal :: Actor -> State -> (ItemBag, Int) calculateTotal body s =-  let bag = sharedAllOwned body s-      items = map (\(iid, k) -> (getItemBody iid s, k)) $ EM.assocs bag+  let bag = sharedAllOwned False body s+      items = map (\(iid, (k, _)) -> (getItemBody iid s, k)) $ EM.assocs bag   in (bag, sum $ map itemPrice items) +mergeItemQuant :: ItemQuant -> ItemQuant -> ItemQuant+mergeItemQuant (k1, it1) (k2, it2) = (k1 + k2, it1 ++ it2)+ sharedInv :: Actor -> State -> ItemBag sharedInv body s =   let bs = fidActorNotProjList (bfid body) s-  in EM.unionsWith (+) $ map binv $ if null bs then [body] else bs+  in EM.unionsWith mergeItemQuant+     $ map binv $ if null bs then [body] else bs  sharedEqp :: Actor -> State -> ItemBag sharedEqp body s =   let bs = fidActorNotProjList (bfid body) s-  in EM.unionsWith (+) $ map beqp $ if null bs then [body] else bs+  in EM.unionsWith mergeItemQuant+     $ map beqp $ if null bs then [body] else bs -sharedAllOwned :: Actor -> State -> ItemBag-sharedAllOwned body s =+sharedOrgan :: Actor -> State -> ItemBag+sharedOrgan body s =+  let bs = fidActorNotProjList (bfid body) s+  in EM.unionsWith mergeItemQuant+     $ map borgan $ if null bs then [body] else bs++sharedAllOwned :: Bool -> Actor -> State -> ItemBag+sharedAllOwned organs body s =   let shaBag = gsha $ sfactionD s EM.! bfid body-  in EM.unionsWith (+) [sharedEqp body s, sharedInv body s, shaBag]+  in EM.unionsWith mergeItemQuant+     $ [sharedEqp body s, sharedInv body s, shaBag]+       ++ [sharedOrgan body s | organs] -sharedAllOwnedFid :: FactionId -> State -> ItemBag-sharedAllOwnedFid fid s =+sharedAllOwnedFid :: Bool -> FactionId -> State -> ItemBag+sharedAllOwnedFid organs fid s =   let shaBag = gsha $ sfactionD s EM.! fid       bs = fidActorNotProjList fid s-  in EM.unionsWith (+) $ map binv bs ++ map beqp bs ++ [shaBag]+  in EM.unionsWith mergeItemQuant+     $ map binv bs ++ map beqp bs ++ [shaBag]+       ++ if organs then map borgan bs else []  -- | Price an item, taking count into consideration. itemPrice :: (Item, Int) -> Int@@ -219,20 +234,27 @@   bagAssocs s $ EM.unionsWith (const) [binv b, beqp b, borgan b]  getCBag :: Container -> State -> ItemBag+{-# INLINE getCBag #-} getCBag c s = case c of-  CFloor lid p -> sdungeon s EM.! lid `atI` p+  CFloor lid p -> EM.findWithDefault EM.empty p+                  $ lfloor (sdungeon s EM.! lid)+  CEmbed lid p -> EM.findWithDefault EM.empty p+                  $ lembed (sdungeon s EM.! lid)   CActor aid cstore -> getActorBag aid cstore s-  CTrunk fid _ _ -> sharedAllOwnedFid fid s+  CTrunk fid _ _ -> sharedAllOwnedFid False fid s  getActorBag :: ActorId -> CStore -> State -> ItemBag+{-# INLINE getActorBag #-} getActorBag aid cstore s =   let b = getActorBody aid s   in getBodyActorBag b cstore s  getBodyActorBag :: Actor -> CStore -> State -> ItemBag+{-# INLINE getBodyActorBag #-} getBodyActorBag b cstore s =   case cstore of-    CGround -> sdungeon s EM.! blid b `atI` bpos b+    CGround -> EM.findWithDefault EM.empty (bpos b)+               $ lfloor (sdungeon s EM.! blid b)     COrgan -> borgan b     CEqp -> beqp b     CInv -> binv b@@ -241,7 +263,7 @@ getActorAssocs :: ActorId -> CStore -> State -> [(ItemId, Item)] getActorAssocs aid cstore s = bagAssocs s $ getActorBag aid cstore s -getActorAssocsK :: ActorId -> CStore -> State -> [(ItemId, (Item, Int))]+getActorAssocsK :: ActorId -> CStore -> State -> [(ItemId, (Item, ItemQuant))] getActorAssocsK aid cstore s = bagAssocsK s $ getActorBag aid cstore s  -- | Checks if the actor is present on the current level.@@ -252,27 +274,17 @@ memActor aid lid s =   maybe False ((== lid) . blid) $ EM.lookup aid $ sactorD s -calmEnough :: Actor -> [ItemFull] -> Bool-calmEnough b activeItems =-  let calmMax = max 1 $ sumSlotNoFilter Effect.EqpSlotAddMaxCalm activeItems-  in 2 * xM calmMax <= 3 * bcalm b--hpEnough :: Actor -> [ItemFull] -> Bool-hpEnough b activeItems =-  let hpMax = max 1 $ sumSlotNoFilter Effect.EqpSlotAddMaxHP activeItems-  in 2 * xM hpMax <= 3 * bhp b- -- | Get current time from the dungeon data. getLocalTime :: LevelId -> State -> Time getLocalTime lid s = ltime $ sdungeon s EM.! lid  regenCalmDelta :: Actor -> [ItemFull] -> State -> Int64 regenCalmDelta b activeItems s =-  let calmMax = sumSlotNoFilter Effect.EqpSlotAddMaxCalm activeItems+  let calmMax = sumSlotNoFilter IK.EqpSlotAddMaxCalm activeItems       calmIncr = oneM  -- normal rate of calm regen       maxDeltaCalm = xM calmMax - bcalm b       -- Worry actor by enemies felt (even if not seen)-      -- on the level within 3 tiles.+      -- on the level within 3 steps.       fact = (EM.! bfid b) . sfactionD $ s       allFoes = actorRegularList (isAtWar fact) (blid b) $ s       isHeard body = not (waitedLastTurn body)@@ -288,8 +300,8 @@       lvl = (EM.! blid b) . sdungeon $ s   in Tile.isLit cotile (lvl `at` bpos b) -actorSkills :: ActorId -> Maybe ActorId -> [ItemFull] -> State -> Ability.Skills-actorSkills aid mleader activeItems s =+actorSkills :: Maybe ActorId -> ActorId -> [ItemFull] -> State -> Ability.Skills+actorSkills mleader aid activeItems s =   let body = getActorBody aid s       fact = (EM.! bfid body) . sfactionD $ s       factionSkills@@ -298,15 +310,24 @@       itemSkills = sumSkills activeItems   in itemSkills `Ability.addSkills` factionSkills +maxActorSkills :: ActorId -> [ItemFull] -> State -> Ability.Skills+maxActorSkills aid activeItems s =+  let body = getActorBody aid s+      fact = (EM.! bfid body) . sfactionD $ s+      factionSkills = Ability.maxSkills Ability.unitSkills+                                        (fskillsOther $ gplayer fact)+      itemSkills = sumSkills activeItems+  in itemSkills `Ability.addSkills` factionSkills+ -- Check whether an actor can displace an enemy. We assume they are adjacent.-dispEnemy :: ActorId -> Maybe ActorId -> ActorId -> [ItemFull] -> State -> Bool-dispEnemy source mleader target activeItems s =+dispEnemy :: ActorId -> ActorId -> [ItemFull] -> State -> Bool+dispEnemy source target activeItems s =   let hasSupport b =         let fact = (EM.! bfid b) . sfactionD $ s             friendlyFid fid = fid == bfid b || isAllied fact fid             sup = actorRegularList friendlyFid (blid b) s         in any (adjacent (bpos b) . bpos) sup-      actorSk = actorSkills target mleader activeItems s+      actorSk = maxActorSkills target activeItems s       sb = getActorBody source s       tb = getActorBody target s   in bproj tb@@ -316,26 +337,20 @@                 && EM.findWithDefault 0 Ability.AbMove actorSk <= 0              || hasSupport sb && hasSupport tb)  -- solo actors are flexible --- | Determine if the sight radius is high enough to deem the actor capable--- of projecting items and similar activities. Otherwise, the actor--- is assumed to use a combination of peripherial vision, hearing, etc.,--- and not the actual focused, long-distance sight sense.-radiusBlind :: Int -> Bool-radiusBlind radius = radius < 4- fullAssocs :: Kind.COps -> DiscoveryKind -> DiscoveryEffect            -> ActorId -> [CStore] -> State            -> [(ItemId, ItemFull)] fullAssocs cops disco discoEffect aid cstores s =   let allAssocs = concatMap (\cstore -> getActorAssocsK aid cstore s) cstores-      iToFull (iid, (item, k)) =-        (iid, itemToFull cops disco discoEffect iid item k)+      iToFull (iid, (item, kit)) =+        (iid, itemToFull cops disco discoEffect iid item kit)   in map iToFull allAssocs -itemToFull :: Kind.COps -> DiscoveryKind -> DiscoveryEffect -> ItemId -> Item -> Int+itemToFull :: Kind.COps -> DiscoveryKind -> DiscoveryEffect -> ItemId -> Item+           -> ItemQuant            -> ItemFull itemToFull Kind.COps{coitem=Kind.Ops{okind}}-           disco discoEffect iid itemBase itemK =+           disco discoEffect iid itemBase (itemK, itemTimer) =   let itemDisco = case EM.lookup (jkindIx itemBase) disco of         Nothing -> Nothing         Just itemKindId -> Just ItemDisco{ itemKindId@@ -347,12 +362,27 @@ goesIntoInv item = isNothing $ strengthEqpSlot item  eqpOverfull :: Actor -> Int -> Bool-eqpOverfull b n = let size = sum $ EM.elems $ beqp b+eqpOverfull b n = let size = sum $ map fst $ EM.elems $ beqp b                   in assert (size <= 10 `blame` (b, n, size))                      $ size + n > 10  storeFromC :: Container -> CStore storeFromC c = case c of   CFloor{} -> CGround+  CEmbed{} -> CGround   CActor _ cstore -> cstore   CTrunk{} -> CGround++-- | Determine the dungeon level of the container. If the item is in a shared+-- stash, the level depends on which actor asks.+lidFromC :: Container -> State -> LevelId+lidFromC (CFloor lid _) _ = lid+lidFromC (CEmbed lid _) _ = lid+lidFromC (CActor aid _) s = blid $ getActorBody aid s+lidFromC (CTrunk _ lid _) _ = lid++aidFromC :: Container -> Maybe ActorId+aidFromC CFloor{} = Nothing+aidFromC CEmbed{} = Nothing+aidFromC (CActor aid _) = Just aid+aidFromC CTrunk{} = Nothing
Game/LambdaHack/Common/ClientOptions.hs view
@@ -10,34 +10,36 @@ import Game.LambdaHack.Common.Faction  data DebugModeCli = DebugModeCli-  { sfont          :: !(Maybe String)+  { sfont           :: !(Maybe String)       -- ^ Font to use for the main game window.-  , smaxFps        :: !(Maybe Int)+  , 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.-  , snoDelay       :: !Bool+  , snoDelay        :: !Bool       -- ^ Don't maintain any requested delays between frames,       -- e.g., for screensaver.-  , snoMore        :: !Bool-      -- ^ Auto-answer all prompts, e.g., for screensaver.-  , snoAnim        :: !(Maybe Bool)+  , sdisableAutoYes :: !Bool+      -- ^ Never auto-answer all prompts, even if under AI control.+  , snoAnim         :: !(Maybe Bool)       -- ^ Don't show any animations.-  , snewGameCli    :: !Bool+  , snewGameCli     :: !Bool       -- ^ Start a new game, overwriting the save file.-  , sbenchmark     :: !Bool+  , sbenchmark      :: !Bool       -- ^ Don't create directories and files and show time stats.-  , sdifficultyCli :: !Int+  , sdifficultyCli  :: !Int       -- ^ The difficulty level for all UI clients.-  , ssavePrefixCli :: !(Maybe String)+  , ssavePrefixCli  :: !(Maybe String)       -- ^ Prefix of the save game file.-  , sfrontendStd   :: !Bool+  , sfrontendStd    :: !Bool       -- ^ Whether to use the stdout/stdin frontend for all clients.-  , sfrontendNull  :: !Bool+  , sfrontendNull   :: !Bool       -- ^ Whether to use void (no input/output) frontend for all clients.-  , sdbgMsgCli     :: !Bool+  , sdbgMsgCli      :: !Bool       -- ^ Show clients' internal debug messages.   }   deriving (Show, Eq, Generic)@@ -47,9 +49,10 @@ defDebugModeCli :: DebugModeCli defDebugModeCli = DebugModeCli   { sfont = Nothing+  , scolorIsBold = Nothing   , smaxFps = Nothing   , snoDelay = False-  , snoMore = False+  , sdisableAutoYes = False   , snoAnim = Nothing   , snewGameCli = False   , sbenchmark = False
Game/LambdaHack/Common/ContentDef.hs view
@@ -15,12 +15,12 @@  -- | The general type of a particular game content, e.g., item kinds. data ContentDef a = ContentDef-  { getSymbol      :: a -> Char    -- ^ symbol, e.g., to print on the map-  , getName        :: a -> Text    -- ^ name, e.g., to show to the player-  , getFreq        :: a -> Freqs   -- ^ frequency within groups+  { getSymbol      :: a -> Char  -- ^ symbol, e.g., to print on the map+  , getName        :: a -> Text  -- ^ name, e.g., to show to the player+  , getFreq        :: a -> Freqs a  -- ^ frequency within groups   , validateSingle :: a -> [Text]       -- ^ validate a content item and list all offences   , validateAll    :: [a] -> [Text]       -- ^ validate the whole defined content of this type and list all offences-  , content        :: [a]          -- ^ all the defined content of this type+  , content        :: ![a]       -- ^ all the defined content of this type   }
Game/LambdaHack/Common/Dice.hs view
@@ -4,7 +4,7 @@ module Game.LambdaHack.Common.Dice   ( -- * Frequency distribution for casting dice scaled with level depth     Dice, diceConst, diceLevel, diceScale, (|*|)-  , d, z, dl, zl, intToDice+  , d, ds, dl, intToDice   , maxDice, minDice, meanDice, reduceDice     -- * Dice for rolling a pair of integer parameters representing coordinates.   , DiceXY(..), maxDiceXY, minDiceXY, meanDiceXY@@ -35,7 +35,7 @@                      $ map swap $ runFrequency fr  -- Normalized mainly as an optimization, but it also makes many expected--- algeraic laws hold (wrt @Eq@), except for some laws about+-- 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@@ -69,7 +69,8 @@ liftA2AdditiveName name f fra frb =   let frRes = liftA2 f fra frb       nameRes =-        if nameFrequency fra == "0" then nameFrequency frb+        if nameFrequency fra == "0" then+          (if name == "+" then "" else name) <+> nameFrequency frb         else if nameFrequency frb == "0" then nameFrequency fra         else nameFrequency fra <+> name <+> nameFrequency frb   in renameFreq nameRes frRes@@ -80,6 +81,12 @@ zdieSimple :: Int -> SimpleDice zdieSimple n = uniformFreq ("z" <> tshow n) [0..n-1] +dieLevelSimple :: Int -> SimpleDice+dieLevelSimple n = uniformFreq ("ds" <> 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.@@ -94,10 +101,14 @@  instance Show Dice where   show Dice{..} = T.unpack $-    let scaled = "scaled(" <> nameFrequency diceLevel <> ")"+    let rawScaled = nameFrequency diceLevel+        scaled = if rawScaled == "0" then "" else rawScaled+        signAndScaled = case T.uncons scaled of+          Just ('-', _) -> scaled+          _ -> "+" <+> scaled     in (if nameFrequency diceLevel == "0" then nameFrequency diceConst         else if nameFrequency diceConst == "0" then scaled-        else nameFrequency diceConst <+> "+" <+> scaled)+        else nameFrequency diceConst <+> signAndScaled)        <+> if diceScale == 1 then "" else "|*|" <+> tshow diceScale  instance Hashable Dice@@ -142,14 +153,18 @@ d :: Int -> Dice d n = Dice (dieSimple n) (fromInteger 0) 1 -z :: Int -> Dice-z n = Dice (zdieSimple n) (fromInteger 0) 1+ds :: Int -> Dice+ds n = Dice (fromInteger 0) (dieLevelSimple n) 1  dl :: Int -> Dice-dl n = Dice (fromInteger 0) (dieSimple n) 1+dl = ds -zl :: Int -> Dice-zl n = Dice (fromInteger 0) (zdieSimple n) 1+-- Not exposed to save on documentation.+_z :: Int -> Dice+_z n = Dice (zdieSimple n) (fromInteger 0) 1++_zl :: Int -> Dice+_zl n = Dice (fromInteger 0) (zdieLevelSimple n) 1  intToDice :: Int -> Dice intToDice = fromInteger . fromIntegral
− Game/LambdaHack/Common/Effect.hs
@@ -1,217 +0,0 @@-{-# LANGUAGE DeriveFunctor, DeriveGeneric #-}--- | Effects of content on the game state. No operation in this module--- involves state or monad types.-module Game.LambdaHack.Common.Effect-  ( Effect(..), Aspect(..), ThrowMod(..), Feature(..), EqpSlot(..)-  , effectTrav, aspectTrav-  ) where--import qualified Control.Monad.State as St-import Data.Binary-import Data.Hashable (Hashable)-import Data.Text (Text)-import GHC.Generics (Generic)--import qualified Game.LambdaHack.Common.Ability as Ability-import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Content.ModeKind---- TODO: document each constructor--- | 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.-data Effect a =-    NoEffect !Text-  | Hurt !Dice.Dice-  | Burn !Int-  | Explode !GroupName    -- ^ explode, producing this group of shrapnel-  | RefillHP !Int-  | RefillCalm !Int-  | Dominate-  | Impress-  | CallFriend !a-  | Summon !Freqs !a-  | CreateItem !a-  | Ascend !Int-  | Escape !Int           -- ^ the Int says if can be placed on last level, etc.-  | Paralyze !a-  | InsertMove !a-  | Teleport !a-  | PolyItem !CStore-  | Identify !CStore-  | SendFlying !ThrowMod-  | PushActor !ThrowMod-  | PullActor !ThrowMod-  | DropBestWeapon-  | DropEqp !Char !Bool   -- ^ symbol @' '@ means all, @True@ means hit on drop-  | ActivateInv !Char     -- ^ symbol @' '@ means all-  | ApplyPerfume-  | OneOf ![Effect a]-  | OnSmash !(Effect a)   -- ^ trigger if item smashed (not applied nor meleed)-  | TimedAspect !Int !(Aspect a)-                          -- ^ enable the aspect for k clips-  deriving (Show, Read, Eq, Ord, Generic, Functor)---- | Aspects of items. Additive (starting at 0) for all items wielded--- by an actor and affect the actor (except @Periodic@ that only affect--- the item and so is not additive).-data Aspect a =-    Periodic !a        -- ^ is activated this many times in 100-  | AddHurtMelee !a    -- ^ percentage damage bonus in melee-  | AddArmorMelee !a   -- ^ percentage armor bonus against melee-  | AddHurtRanged !a   -- ^ percentage damage bonus in ranged-  | AddArmorRanged !a  -- ^ percentage armor bonus against ranged-  | AddMaxHP !a        -- ^ maximal hp-  | AddMaxCalm !a      -- ^ maximal calm-  | AddSpeed !a        -- ^ speed in m/10s-  | AddSkills !Ability.Skills  -- ^ skills in particular abilities-  | AddSight !a        -- ^ FOV radius, where 1 means a single tile-  | AddSmell !a        -- ^ smell radius, where 1 means a single tile-  | AddLight !a        -- ^ light radius, where 1 means a single tile-  deriving (Show, Read, Eq, Ord, Generic, Functor)---- | Parameters modifying a throw. Not additive and don't start at 0.-data ThrowMod = ThrowMod-  { throwVelocity :: !Int  -- ^ fly with this percentage of base throw speed-  , throwLinger   :: !Int  -- ^ fly for this percentage of 2 turns-  }-  deriving (Show, Read, Eq, Ord, Generic)---- | Features of item. Affect only the item in question, not the actor,--- and so not additive in any sense.-data Feature =-    ChangeTo !GroupName     -- ^ change to this group when altered-  | Fragile                 -- ^ break even when not hitting an enemy-  | Durable                 -- ^ don't break even hitting or applying-  | ToThrow !ThrowMod       -- ^ parameters modifying a throw-  | Identified              -- ^ the item starts identified-  | Applicable              -- ^ AI and uI flag: consider applying-  | EqpSlot !EqpSlot !Text  -- ^ AI and uI flag: goes to inventory-  | Precious                -- ^ AI and UI flag: careful, can be precious;-                            --   don't risk identifying by use-  | Tactic !Tactic          -- ^ overrides actor's tactic (TODO)-  deriving (Show, Eq, Ord, Generic)--data EqpSlot =-    EqpSlotPeriodic-  | EqpSlotAddHurtMelee-  | EqpSlotAddArmorMelee-  | EqpSlotAddHurtRanged-  | EqpSlotAddArmorRanged-  | EqpSlotAddMaxHP-  | EqpSlotAddMaxCalm-  | EqpSlotAddSpeed-  | EqpSlotAddSkills-  | EqpSlotAddSight-  | EqpSlotAddSmell-  | EqpSlotAddLight-  | EqpSlotWeapon-  deriving (Show, Eq, Ord, Generic)--instance Hashable a => Hashable (Effect a)--instance Hashable a => Hashable (Aspect a)--instance Hashable ThrowMod--instance Hashable Feature--instance Hashable EqpSlot--instance Binary a => Binary (Effect a)--instance Binary a => Binary (Aspect a)--instance Binary ThrowMod--instance Binary Feature--instance Binary EqpSlot---- TODO: Traversable?--- | Transform an effect using a stateful function.-effectTrav :: Effect a -> (a -> St.State s b) -> St.State s (Effect b)-effectTrav (NoEffect t) _ = return $! NoEffect t-effectTrav (RefillHP p) _ = return $! RefillHP p-effectTrav (Hurt dice) _ = return $! Hurt dice-effectTrav (RefillCalm p) _ = return $! RefillCalm p-effectTrav Dominate _ = return Dominate-effectTrav Impress _ = return Impress-effectTrav (CallFriend a) f = do-  b <- f a-  return $! CallFriend b-effectTrav (Summon freqs a) f = do-  b <- f a-  return $! Summon freqs b-effectTrav (CreateItem a) f = do-  b <- f a-  return $! CreateItem b-effectTrav ApplyPerfume _ = return ApplyPerfume-effectTrav (Burn p) _ = return $! Burn p-effectTrav (Ascend p) _ = return $! Ascend p-effectTrav (Escape p) _ = return $! Escape p-effectTrav (Paralyze a) f = do-  b <- f a-  return $! Paralyze b-effectTrav (InsertMove a) f = do-  b <- f a-  return $! InsertMove b-effectTrav DropBestWeapon _ = return DropBestWeapon-effectTrav (DropEqp symbol hit) _ = return $! DropEqp symbol hit-effectTrav (SendFlying tmod) _ = return $! SendFlying tmod-effectTrav (PushActor tmod) _ = return $! PushActor tmod-effectTrav (PullActor tmod) _ = return $! PullActor tmod-effectTrav (Teleport a) f = do-  b <- f a-  return $! Teleport b-effectTrav (PolyItem cstore) _ = return $! PolyItem cstore-effectTrav (Identify cstore) _ = return $! Identify cstore-effectTrav (ActivateInv symbol) _ = return $! ActivateInv symbol-effectTrav (OneOf la) f = do-  lb <- mapM (\a -> effectTrav a f) la-  return $! OneOf lb-effectTrav (OnSmash effa) f = do-  effb <- effectTrav effa f-  return $! OnSmash effb-effectTrav (Explode t) _ = return $! Explode t-effectTrav (TimedAspect k asp) f = do-  asp2 <- aspectTrav asp f-  return $! TimedAspect k asp2---- | Transform an aspect using a stateful function.-aspectTrav :: Aspect a -> (a -> St.State s b) -> St.State s (Aspect b)-aspectTrav (Periodic a) f = do-  b <- f a-  return $! Periodic b-aspectTrav (AddMaxHP a) f = do-  b <- f a-  return $! AddMaxHP b-aspectTrav (AddMaxCalm a) f = do-  b <- f a-  return $! AddMaxCalm b-aspectTrav (AddSpeed a) f = do-  b <- f a-  return $! AddSpeed b-aspectTrav (AddSkills as) _ = return $! AddSkills as-aspectTrav (AddHurtMelee a) f = do-  b <- f a-  return $! AddHurtMelee b-aspectTrav (AddHurtRanged a) f = do-  b <- f a-  return $! AddHurtRanged b-aspectTrav (AddArmorMelee a) f = do-  b <- f a-  return $! AddArmorMelee b-aspectTrav (AddArmorRanged a) f = do-  b <- f a-  return $! AddArmorRanged b-aspectTrav (AddSight a) f = do-  b <- f a-  return $! AddSight b-aspectTrav (AddSmell a) f = do-  b <- f a-  return $! AddSmell b-aspectTrav (AddLight a) f = do-  b <- f a-  return $! AddLight b
Game/LambdaHack/Common/EffectDescription.hs view
@@ -12,81 +12,109 @@ import qualified Data.Text as T import qualified NLP.Miniutter.English as MU --- import Game.LambdaHack.Common.Actor (ppCStore)+import Game.LambdaHack.Common.Actor import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Effect+import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ItemKind  -- | Suffix to append to a basic content name if the content causes the effect.-effectToSuff :: (Show a, Ord a, Num a)-             => Effect a -> (a -> Text) -> (a -> Maybe Int) -> Text-effectToSuff effect f g =-  case ( St.evalState (effectTrav effect $ return . f) ()-       , St.evalState (effectTrav effect $ return . g) () ) of-    (NoEffect t, _) -> t-    (RefillHP p, _) | p > 0 -> "of healing" <+> wrapInParens (affixBonus p)-    (RefillHP 0, _) -> assert `failure` effect-    (RefillHP p, _) -> "of wounding" <+> wrapInParens (affixBonus p)-    (Hurt dice, _) -> wrapInParens (tshow dice)-    (RefillCalm p, _) | p > 0 -> "of soothing" <+> wrapInParens (affixBonus p)-    (RefillCalm 0, _) -> assert `failure` effect-    (RefillCalm p, _) -> "of dismaying" <+> wrapInParens (affixBonus p)-    (Dominate, _) -> "of domination"-    (Impress, _) -> "of impression"-    (_, CallFriend (Just 1)) -> "of aid calling"-    (CallFriend t, _) -> "of aid calling"-                         <+> wrapInParens (dropPlus t <+> "friends")-    (_, Summon _freqs (Just 1)) -> "of summoning"  -- TODO-    (Summon _freqs t, _) -> "of summoning"-                            <+> wrapInParens (dropPlus t <+> "actors")-    (_, CreateItem (Just 1)) -> "of uncovering"-    (CreateItem t, _) -> "of uncovering"-                         <+> wrapInParens (dropPlus t <+> "items")-    (ApplyPerfume, _) -> "of smell removal"-    (Burn p, _) | p <= 0 -> assert `failure` effect-    (Burn p, _) -> wrapInParens (makePhrase [MU.CarWs p "burn"])-    (Ascend 1, _) -> "of ascending"-    (Ascend p, _) | p > 0 ->-      "of ascending" <+> wrapInParens (tshow p <+> "levels")-    (Ascend 0, _) -> assert `failure` effect-    (Ascend (-1), _) -> "of descending"-    (Ascend p, _) ->-      "of descending" <+> wrapInParens (tshow (-p) <+> "levels")-    (Escape{}, _) -> "of escaping"-    (_, Paralyze Nothing) -> "of paralysis (? clips)"-    (_, Paralyze (Just p)) ->-      let clipInTurn = timeTurn `timeFit` timeClip-          seconds = 0.5 * fromIntegral p / fromIntegral clipInTurn :: Double-      in "of paralysis" <+> wrapInParens (tshow seconds <> "s")-    (_, InsertMove Nothing) ->-      "of speed surge (? moves)"-    (_, InsertMove (Just p)) ->-      "of speed surge" <+> wrapInParens (makePhrase [MU.CarWs p "move"])-    (DropBestWeapon, _) -> "of disarming"-    (DropEqp ' ' False, _) -> "of equipment drop"-    (DropEqp symbol False, _) -> "of drop '" <> T.singleton symbol <> "'"-    (DropEqp ' ' True, _) -> "of equipment smash"-    (DropEqp symbol True, _) -> "of smash '" <> T.singleton symbol <> "'"-    (SendFlying tmod, _) -> "of impact" <+> tmodToSuff "" tmod-    (PushActor tmod, _) -> "of pushing" <+> tmodToSuff "" tmod-    (PullActor tmod, _) -> "of pulling" <+> tmodToSuff "" tmod-    (_, Teleport (Just p)) | p <= 1  -> assert `failure` effect-    (Teleport t, Teleport (Just p)) | p <= 9  ->-      "of blinking" <+> wrapInParens (dropPlus t <+> "steps")-    (Teleport t, _)->-      "of teleport" <+> wrapInParens (dropPlus t <+> "steps")-    (PolyItem _cstore, _) -> "of repurpose"  -- <+> ppCStore cstore-    (Identify _cstore, _) -> "of identify"  -- <+> ppCStore cstore-    (ActivateInv ' ', _) -> "of inventory burst"-    (ActivateInv symbol, _) -> "of burst '" <> T.singleton symbol <> "'"-    (Explode _, _) -> "of explosion"  -- TODO: first word + explosion? nothing?-    (OneOf l, _) ->+--+-- We show absolute time in seconds, not @moves@, because actors can have+-- different speeds (and actions can potentially take different time intervals).+-- We call the time taken by one player move, when walking, a @move@.+-- @Turn@ and @clip@ are used mostly internally, the former as an absolute+-- time unit.+-- We show distances in @steps@, because one step, from a tile to another+-- tile, is always 1 meter. We don't call steps @tiles@, reserving+-- that term for the context of terrain kinds or units of area.+effectToSuff :: Effect -> Text+effectToSuff effect =+  case effect of+    NoEffect t -> t+    Hurt dice -> wrapInParens (tshow dice)+    Burn p | p <= 0 -> assert `failure` effect+    Burn p -> wrapInParens (makePhrase [MU.CarWs p "burn"])+    Explode t -> "of" <+> tshow t <+> "explosion"+    RefillHP p | p > 0 ->+      "of limited healing" <+> wrapInParens (affixBonus p)+    RefillHP 0 -> assert `failure` effect+    RefillHP p ->+      "of limited wounding" <+> wrapInParens (affixBonus p)+    OverfillHP p | p > 0 -> "of healing" <+> wrapInParens (affixBonus p)+    OverfillHP 0 -> assert `failure` effect+    OverfillHP p -> "of wounding" <+> wrapInParens (affixBonus p)+    RefillCalm p | p > 0 ->+      "of limited soothing" <+> wrapInParens (affixBonus p)+    RefillCalm 0 -> assert `failure` effect+    RefillCalm p ->+      "of limited dismaying" <+> wrapInParens (affixBonus p)+    OverfillCalm p | p > 0 -> "of soothing" <+> wrapInParens (affixBonus p)+    OverfillCalm 0 -> assert `failure` effect+    OverfillCalm p -> "of dismaying" <+> wrapInParens (affixBonus p)+    Dominate -> "of domination"+    Impress -> "of impression"+    CallFriend 1 -> "of aid calling"+    CallFriend dice -> "of aid calling"+                       <+> wrapInParens (tshow dice <+> "friends")+    Summon _freqs 1 -> "of summoning"  -- TODO+    Summon _freqs dice -> "of summoning"+                          <+> wrapInParens (tshow dice <+> "actors")+    ApplyPerfume -> "of smell removal"+    Ascend 1 -> "of ascending"+    Ascend p | p > 0 ->+      "of ascending for" <+> tshow p <+> "levels"+    Ascend 0 -> assert `failure` effect+    Ascend (-1) -> "of descending"+    Ascend p ->+      "of descending for" <+> tshow (-p) <+> "levels"+    Escape{} -> "of escaping"+    Paralyze dice ->+      let time = case Dice.reduceDice dice of+            Nothing -> tshow dice+            Just p ->+              let clipInTurn = timeTurn `timeFit` timeClip+                  seconds =+                    0.5 * fromIntegral p / fromIntegral clipInTurn :: Double+              in tshow seconds <> "s"+      in "of paralysis for" <+> time+    InsertMove dice ->+      let moves = case Dice.reduceDice dice of+            Nothing -> tshow dice <+> "moves"+            Just p -> makePhrase [MU.CarWs p "move"]+      in "of speed surge for" <+> moves+    Teleport dice | dice <= 0 ->+      assert `failure` effect+    Teleport dice | dice <= 9 ->+      "of blinking" <+> wrapInParens (tshow dice <+> "steps")+    Teleport dice ->+      "of teleport" <+> wrapInParens (tshow dice <+> "steps")+    CreateItem COrgan grp tim ->+      let stime = if tim == TimerNone then "" else "for" <+> tshow tim <> ":"+      in "(keep" <+> stime <+> tshow grp <> ")"+    CreateItem _ grp _ ->+      let object = if grp == "useful" then "" else tshow grp+      in "of" <+> object <+> "uncovering"+    DropItem COrgan grp True -> "of nullify" <+> tshow grp+    DropItem _ grp hit ->+      let grpText = tshow grp+          hitText = if hit then "smash" else "drop"+      in "of" <+> hitText <+> grpText  -- TMI: <+> ppCStore store+    PolyItem store -> "of repurpose" <+> ppCStore store+    Identify store -> "of identify starting" <+> ppCStore store+    SendFlying tmod -> "of impact" <+> tmodToSuff "" tmod+    PushActor tmod -> "of pushing" <+> tmodToSuff "" tmod+    PullActor tmod -> "of pulling" <+> tmodToSuff "" tmod+    DropBestWeapon -> "of disarming"+    ActivateInv ' ' -> "of inventory burst"+    ActivateInv symbol -> "of burst '" <> T.singleton symbol <> "'"+    OneOf l ->       let subject = if length l <= 5 then "marvel" else "wonder"       in makePhrase ["of", MU.CardinalWs (length l) subject]-    (OnSmash _, _) -> ""  -- conditional effect, TMI-    (TimedAspect _ aspect, _) -> "keep (" <> rawAspectToSuff aspect <> ")"-    (effectF, effectG) -> assert `failure` (effect, effectF, effectG)+    OnSmash _ -> ""  -- printed inside a separate section+    Recharging _ -> ""  -- printed inside Periodic or Timeout+    Temporary _ -> ""  tmodToSuff :: Text -> ThrowMod -> Text tmodToSuff verb ThrowMod{..} =@@ -104,11 +132,12 @@ rawAspectToSuff :: Aspect Text -> Text rawAspectToSuff aspect =   case aspect of-    Periodic t -> wrapInParens $ dropPlus t <+> "in 100"+    Periodic{} -> ""  -- printed specially+    Timeout{}  -> ""  -- printed specially     AddMaxHP t -> wrapInParens $ t <+> "HP"     AddMaxCalm t -> wrapInParens $ t <+> "Calm"     AddSpeed t -> wrapInParens $ t <+> "speed"-    AddSkills p -> wrapInParens $ "+" <+> tshow (EM.toList p)+    AddSkills p -> wrapInParens $ "+" <+> T.pack (show $ EM.toList p)     AddHurtMelee t -> wrapInParens $ t <> "% melee"     AddHurtRanged  t -> wrapInParens $ t <> "% ranged"     AddArmorMelee t -> "[" <> t <> "%]"@@ -120,21 +149,17 @@ featureToSuff :: Feature -> Text featureToSuff feat =   case feat of-    ChangeTo t -> wrapInChevrons $ "changes to" <+> tshow t-    Fragile -> wrapInChevrons $ "fragile"-    Durable -> wrapInChevrons $ "durable"+    Fragile -> wrapInChevrons "fragile"+    Durable -> wrapInChevrons "durable"     ToThrow tmod -> wrapInChevrons $ tmodToSuff "flies" tmod     Identified -> ""     Applicable -> ""     EqpSlot{} -> ""-    Precious -> ""+    Precious -> wrapInChevrons "precious"     Tactic tactics -> "overrides tactics to" <+> tshow tactics -dropPlus :: Text -> Text-dropPlus = T.dropWhile (`elem` ['+', '-'])--effectToSuffix :: Effect Int -> Text-effectToSuffix effect = effectToSuff effect affixBonus Just+effectToSuffix :: Effect -> Text+effectToSuffix effect = effectToSuff effect  aspectToSuffix :: Aspect Int -> Text aspectToSuffix aspect = aspectToSuff aspect affixBonus@@ -156,8 +181,8 @@ affixDice :: Dice.Dice -> Text affixDice d = maybe "+?" affixBonus $ Dice.reduceDice d -kindEffectToSuffix :: Effect Dice.Dice -> Text-kindEffectToSuffix effect = effectToSuff effect affixDice Dice.reduceDice+kindEffectToSuffix :: Effect -> Text+kindEffectToSuffix = effectToSuffix  kindAspectToSuffix :: Aspect Dice.Dice -> Text kindAspectToSuffix aspect = aspectToSuff aspect affixDice
Game/LambdaHack/Common/Faction.hs view
@@ -26,7 +26,7 @@ import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Point import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind+import Game.LambdaHack.Content.ItemKind (ItemKind) import Game.LambdaHack.Content.ModeKind  -- | All factions in the game, indexed by faction identifier.@@ -35,7 +35,7 @@ data Faction = Faction   { gname    :: !Text            -- ^ individual name   , gcolor   :: !Color.Color     -- ^ color of actors or their frames-  , gplayer  :: !Player          -- ^ the player spec for this faction+  , gplayer  :: !(Player Int)    -- ^ the player spec for this faction   , gdipl    :: !Dipl            -- ^ diplomatic mode   , gquit    :: !(Maybe Status)  -- ^ cause of game end/exit   , gleader  :: !(Maybe (ActorId, Maybe Target))@@ -67,9 +67,10 @@  -- | Current game status. data Status = Status-  { stOutcome :: !Outcome            -- ^ current game outcome-  , stDepth   :: !Int                -- ^ depth of the final encounter-  , stNewGame :: !(Maybe GroupName)  -- ^ new game group to start, if any+  { stOutcome :: !Outcome  -- ^ current game outcome+  , stDepth   :: !Int      -- ^ depth of the final encounter+  , stNewGame :: !(Maybe (GroupName ModeKind))+                           -- ^ new game group to start, if any   }   deriving (Show, Eq, Ord) @@ -131,7 +132,7 @@                           LeaderAI AutoLeader{..} -> (autoDungeon, autoLevel)                           LeaderUI AutoLeader{..} -> (autoDungeon, autoLevel) -automatePlayer :: Bool -> Player -> Player+automatePlayer :: Bool -> Player a -> Player a automatePlayer st pl =   let autoLeader False Player{fleaderMode=LeaderAI auto} = LeaderUI auto       autoLeader True Player{fleaderMode=LeaderUI auto} = LeaderAI auto
− Game/LambdaHack/Common/Feature.hs
@@ -1,39 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--- | Terrain tile features.-module Game.LambdaHack.Common.Feature-  ( Feature(..)-  ) where--import Data.Binary-import Data.Hashable (Hashable)-import GHC.Generics (Generic)--import qualified Game.LambdaHack.Common.Effect as Effect-import Game.LambdaHack.Common.Misc---- | All possible terrain tile features.-data Feature =-    Cause !(Effect.Effect Int)  -- ^ causes the effect when triggered-  | OpenTo !GroupName    -- ^ goes from a closed to an open tile when altered-  | CloseTo !GroupName   -- ^ goes from an open to a closed tile when altered-  | ChangeTo !GroupName  -- ^ alters tile, but does not change walkability-  | HideAs !GroupName    -- ^ when hidden, looks as a tile of the group-  | RevealAs !GroupName  -- ^ if secret, 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 shine-  | Suspect              -- ^ may not be what it seems (clients only)-  | Aura !(Effect.Effect Int)  -- ^ sustains the effect continuously, TODO-  | Impenetrable         -- ^ can never be excavated nor seen through--  | OftenItem            -- ^ initial items often generated there-  | OftenActor           -- ^ initial actors and stairs often generated there-  | NoItem               -- ^ no items ever generated there-  | NoActor              -- ^ no actors nor stairs ever generated there-  | Trail                -- ^ used for visible trails throughout the level-  deriving (Show, Read, Eq, Ord, Generic)--instance Binary Feature--instance Hashable Feature
Game/LambdaHack/Common/File.hs view
@@ -73,13 +73,24 @@                  -> IO () tryCopyDataFiles dataDir pathsDataFile files =   let cpFile (fin, fout) = do-        pathsDataIn <- pathsDataFile fin-        bIn <- doesFileExist pathsDataIn-        let pathsDataOut = dataDir </> fout-        bOut <- doesFileExist pathsDataOut-        when (not bOut && bIn) $-          Ex.handle (\(_ :: Ex.IOException) -> return ())-                    (copyFile pathsDataIn pathsDataOut)+        mpathsDataIn <- do+          pathsDataIn1 <- pathsDataFile fin+          bIn1 <- doesFileExist pathsDataIn1+          if bIn1 then return $ Just pathsDataIn1+          else do+            currentDir <- getCurrentDirectory+            let pathsDataIn2 = currentDir </> fin+            bIn2 <- doesFileExist pathsDataIn2+            if bIn2 then return $ Just pathsDataIn2+            else return Nothing+        case mpathsDataIn of+          Nothing -> return ()+          Just pathsDataIn -> do+            let pathsDataOut = dataDir </> fout+            bOut <- doesFileExist pathsDataOut+            when (not bOut) $+              Ex.handle (\(_ :: Ex.IOException) -> return ())+                        (copyFile pathsDataIn pathsDataOut)   in mapM_ cpFile files  -- | Personal data directory for the game. Depends on the OS and the game,
Game/LambdaHack/Common/Flavour.hs view
@@ -4,7 +4,7 @@   ( -- * The @Flavour@ type     Flavour   , -- * Constructors-    zipPlain, zipFancy, stdFlav+    zipPlain, zipFancy, stdFlav, zipLiquid   , -- * Accessors     flavourToColor, flavourToName     -- * Assorted@@ -18,11 +18,18 @@  import Game.LambdaHack.Common.Color +data FancyName = Plain | Fancy | Liquid+  deriving (Show, Eq, Ord, Generic)++instance Hashable FancyName++instance Binary FancyName+ -- TODO: add more variety, as the number of items increases -- | The type of item flavours. data Flavour = Flavour-  { fancyName :: !Bool   -- ^ should the colour description be fancy or plain-  , baseColor :: !Color  -- ^ the colour of the flavour+  { fancyName :: !FancyName  -- ^ how fancy should the colour description be+  , baseColor :: !Color      -- ^ the colour of the flavour   }   deriving (Show, Eq, Ord, Generic) @@ -31,13 +38,14 @@ instance Binary Flavour  -- | Turn a colour set into a flavour set.-zipPlain, zipFancy :: [Color] -> [Flavour]-zipPlain = map (Flavour False)-zipFancy = map (Flavour True)+zipPlain, zipFancy, zipLiquid :: [Color] -> [Flavour]+zipPlain = map (Flavour Plain)+zipFancy = map (Flavour Fancy)+zipLiquid = map (Flavour Liquid)  -- | The standard full set of flavours. stdFlav :: [Flavour]-stdFlav = zipPlain stdCol ++ zipFancy stdCol+stdFlav = zipPlain stdCol ++ zipFancy stdCol ++ zipLiquid stdCol  -- | Get the underlying base colour of a flavour. flavourToColor :: Flavour -> Color@@ -45,10 +53,11 @@  -- | Construct the full name of a flavour. flavourToName :: Flavour -> Text-flavourToName Flavour{..} | fancyName = colorToFancyName baseColor-flavourToName Flavour{..}             = colorToPlainName baseColor+flavourToName Flavour{fancyName=Plain, ..} = colorToPlainName baseColor+flavourToName Flavour{fancyName=Fancy, ..} = colorToFancyName baseColor+flavourToName Flavour{fancyName=Liquid, ..} = colorToLiquidName baseColor --- | Human-readable names, for item colors. The simple set.+-- | Human-readable names for item colors. The plain set. colorToPlainName :: Color -> Text colorToPlainName Black     = "black" colorToPlainName Red       = "red"@@ -67,7 +76,7 @@ colorToPlainName BrCyan    = "aquamarine" colorToPlainName BrWhite   = "white" --- | Human-readable names, for item colors. The fancy set.+-- | Human-readable names for item colors. The fancy set. colorToFancyName :: Color -> Text colorToFancyName Black     = "smoky-black" colorToFancyName Red       = "apple-red"@@ -85,6 +94,25 @@ colorToFancyName BrMagenta = "magenta" colorToFancyName BrCyan    = "turquoise" colorToFancyName BrWhite   = "ghost-white"++-- | Human-readable names for item colors. The liquid set.+colorToLiquidName :: Color -> Text+colorToLiquidName Black     = "tarry"+colorToLiquidName Red       = "bloody"+colorToLiquidName Green     = "moldy"+colorToLiquidName Brown     = "muddy"+colorToLiquidName Blue      = "oily"+colorToLiquidName Magenta   = "swirling"+colorToLiquidName Cyan      = "bubbling"+colorToLiquidName White     = "cloudy"+colorToLiquidName BrBlack   = "pitchy"+colorToLiquidName BrRed     = "red-speckled"+colorToLiquidName BrGreen   = "sappy"+colorToLiquidName BrYellow  = "gold"+colorToLiquidName BrBlue    = "blue-speckled"+colorToLiquidName BrMagenta = "hazy"+colorToLiquidName BrCyan    = "misty"+colorToLiquidName BrWhite   = "shining"  -- | Simple names for team colors (bright colours preferred). colorToTeamName :: Color -> Text
Game/LambdaHack/Common/Frequency.hs view
@@ -35,39 +35,39 @@ -- the expected equalities, unless they do some kind of normalization -- (see 'Dice'). data Frequency a = Frequency-  { nameFrequency :: Text         -- ^ short description for debug, etc.;+  { runFrequency  :: ![(Int, a)]  -- ^ give acces to raw frequency values+  , nameFrequency :: Text         -- ^ short description for debug, etc.;                                   --   keep it lazy, because it's rarely used-  , runFrequency  :: ![(Int, a)]  -- ^ give acces to raw frequency values   }   deriving (Show, Read, Eq, Ord, Foldable, Traversable, Generic)  instance Monad Frequency where   {-# INLINE return #-}-  return x = Frequency "return" [(1, x)]-  Frequency name xs >>= f =-    Frequency ("bind (" <> name <> ")")-              [ (p * q, y) | (p, x) <- xs+  return x = Frequency [(1, x)] "return"+  Frequency xs name >>= f =+    Frequency [ (p * q, y) | (p, x) <- xs                            , (q, y) <- runFrequency (f x) ]+              ("bind (" <> name <> ")")  instance Functor Frequency where-  fmap f (Frequency name xs) = Frequency name (map (second f) xs)+  fmap f (Frequency xs name) = Frequency (map (second f) xs) name  instance Applicative Frequency where   pure  = return-  Frequency fname fs <*> Frequency yname ys =-    Frequency ("(" <> fname <> ") <*> (" <> yname <> ")")-              [ (p * q, f y) | (p, f) <- fs+  Frequency fs fname <*> Frequency ys yname =+    Frequency [ (p * q, f y) | (p, f) <- fs                              , (q, y) <- ys ]+              ("(" <> fname <> ") <*> (" <> yname <> ")")  instance MonadPlus Frequency where-  mplus (Frequency xname xs) (Frequency yname ys) =+  mplus (Frequency xs xname) (Frequency ys yname) =     let name = case (xs, ys) of           ([], []) -> "[]"           ([], _ ) -> yname           (_,  []) -> xname           _ -> "(" <> xname <> ") ++ (" <> yname <> ")"-    in Frequency name (xs ++ ys)-  mzero = Frequency "[]" []+    in Frequency (xs ++ ys) name+  mzero = Frequency [] "[]"  instance Alternative Frequency where   (<|>) = mplus@@ -79,19 +79,19 @@  -- | Uniform discrete frequency distribution. uniformFreq :: Text -> [a] -> Frequency a-uniformFreq name = Frequency name . map (\x -> (1, x))+uniformFreq name l = Frequency (map (\x -> (1, x)) l) name  -- | Takes a name and a list of frequencies and items -- into the frequency distribution. toFreq :: Text -> [(Int, a)] -> Frequency a-toFreq = Frequency+toFreq = flip Frequency  -- | Scale frequecy distribution, multiplying it -- by a positive integer constant. scaleFreq :: Show a => Int -> Frequency a -> Frequency a-scaleFreq n (Frequency name xs) =+scaleFreq n (Frequency xs name) =   assert (n > 0 `blame` "non-positive frequency scale" `twith` (name, n, xs)) $-  Frequency name (map (first (* n)) xs)+  Frequency (map (first (* n)) xs) name  -- | Change the description of the frequency. renameFreq :: Text -> Frequency a -> Frequency a@@ -99,27 +99,27 @@  -- | Set frequency of an element. setFreq :: Eq a => Frequency a -> a -> Int -> Frequency a-setFreq (Frequency name xs) x n =+setFreq (Frequency xs name) x n =   let f (_, y) | y == x = (n, x)       f my = my-  in Frequency name $ map f xs+  in Frequency (map f xs) name  -- | Test if the frequency distribution is empty. nullFreq :: Frequency a -> Bool-nullFreq (Frequency _ fs) = all (<= 0) $ map fst fs+nullFreq (Frequency fs _) = all (<= 0) $ map fst fs  maxFreq :: (Show a, Ord a) => Frequency a -> a-maxFreq fr@(Frequency _ xs) = case filter ((> 0 ) . fst) xs of+maxFreq fr@(Frequency xs _) = case filter ((> 0 ) . fst) xs of   [] -> assert `failure` fr   ys -> maximum $ map snd ys  minFreq :: (Show a, Ord a) => Frequency a -> a-minFreq fr@(Frequency _ xs) = case filter ((> 0 ) . fst) xs of+minFreq fr@(Frequency xs _) = case filter ((> 0 ) . fst) xs of   [] -> assert `failure` fr   ys -> minimum $ map snd ys  meanFreq :: (Show a, Integral a) => Frequency a -> Rational-meanFreq fr@(Frequency _ xs) = case filter ((> 0 ) . fst) xs of+meanFreq fr@(Frequency xs _) = case filter ((> 0 ) . fst) xs of   [] -> assert `failure` fr   ys -> let sumP = sum $ map fst ys             sumX = sum [ fromIntegral p * x | (p, x) <- ys ]
Game/LambdaHack/Common/HighScore.hs view
@@ -24,7 +24,7 @@ import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind+import Game.LambdaHack.Content.ItemKind (ItemKind)  -- | A single score record. Records are ordered in the highscore table, -- from the best to the worst, in lexicographic ordering wrt the fields below.
Game/LambdaHack/Common/Item.hs view
@@ -8,7 +8,7 @@   , ItemKindIx, DiscoveryKind, ItemSeed, ItemAspectEffect(..), DiscoveryEffect   , ItemFull(..), ItemDisco(..), itemNoDisco, itemNoAE     -- * Inventory management types-  , ItemBag, ItemDict, ItemKnown+  , ItemTimer, ItemQuant, ItemBag, ItemDict, ItemKnown   ) where  import qualified Control.Monad.State as St@@ -20,11 +20,11 @@ import GHC.Generics (Generic) import System.Random (mkStdGen) -import Game.LambdaHack.Common.Effect 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.Content.ItemKind  -- | A unique identifier of an item in the dungeon.@@ -48,7 +48,7 @@  data ItemAspectEffect = ItemAspectEffect   { jaspects :: ![Aspect Int]  -- ^ the aspects of the item-  , jeffects :: ![Effect Int]  -- ^ the effects when activated+  , jeffects :: ![Effect]      -- ^ the effects when activated   }   deriving (Show, Eq, Generic) @@ -61,22 +61,23 @@ type DiscoveryEffect = EM.EnumMap ItemId ItemAspectEffect  data ItemDisco = ItemDisco-  { itemKindId :: Kind.Id ItemKind-  , itemKind   :: ItemKind-  , itemAE     :: Maybe ItemAspectEffect+  { itemKindId :: !(Kind.Id ItemKind)+  , itemKind   :: !ItemKind+  , itemAE     :: !(Maybe ItemAspectEffect)   }   deriving Show  data ItemFull = ItemFull   { itemBase  :: !Item   , itemK     :: !Int+  , itemTimer :: !ItemTimer   , itemDisco :: !(Maybe ItemDisco)   }   deriving Show  itemNoDisco :: (Item, Int) -> ItemFull itemNoDisco (itemBase, itemK) =-  ItemFull {itemBase, itemK, itemDisco=Nothing}+  ItemFull {itemBase, itemK, itemTimer = [], itemDisco=Nothing}  itemNoAE :: ItemFull -> ItemFull itemNoAE itemFull@ItemFull{..} =@@ -107,14 +108,16 @@                      -> ItemAspectEffect seedToAspectsEffects (ItemSeed itemSeed) kind ldepth totalDepth =   let castD = castDice ldepth totalDepth-      rollAE = do-        aspects <- mapM (flip aspectTrav castD) (iaspects kind)-        effects <- mapM (flip effectTrav castD) (ieffects kind)-        return (aspects, effects)-      (jaspects, jeffects) = St.evalState rollAE (mkStdGen itemSeed)+      rollA = mapM (flip aspectTrav castD) (iaspects kind)+      jaspects = St.evalState rollA (mkStdGen itemSeed)+      jeffects = ieffects kind   in ItemAspectEffect{..} -type ItemBag = EM.EnumMap ItemId Int+type ItemTimer = [Time]++type ItemQuant = (Int, ItemTimer)++type ItemBag = EM.EnumMap ItemId ItemQuant  -- | All items in the dungeon (including in actor inventories), -- indexed by item identifier.
Game/LambdaHack/Common/ItemDescription.hs view
@@ -10,90 +10,149 @@ import qualified Data.Text as T import qualified NLP.Miniutter.English as MU +import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Color as Color-import qualified Game.LambdaHack.Common.Effect as Effect+import qualified Game.LambdaHack.Common.Dice as Dice import Game.LambdaHack.Common.EffectDescription import Game.LambdaHack.Common.Flavour import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemStrongest import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg-import Game.LambdaHack.Content.ItemKind+import Game.LambdaHack.Common.Time+import qualified Game.LambdaHack.Content.ItemKind as IK +-- TODO: remove _lid if still unused after some time -- | The part of speech describing the item parameterized by the number -- of effects/aspects to show..-partItemN :: Bool -> Int -> CStore -> ItemFull -> (MU.Part, MU.Part)-partItemN fullInfo n cstore itemFull =+partItemN :: Bool -> Int -> Container -> LevelId -> Time -> ItemFull+          -> (MU.Part, MU.Part)+partItemN fullInfo n c _lid localTime itemFull =   let genericName = jname $ itemBase itemFull   in case itemDisco itemFull of     Nothing ->       let flav = flavourToName $ jflavour $ itemBase itemFull       in (MU.Text $ flav <+> genericName, "")     Just _ ->-      let effTs = filter (not . T.null) $ textAllAE fullInfo cstore itemFull-          ts = take n effTs ++ if length effTs > n then ["(...)"] else []+      let effTs = filter (not . T.null) $ textAllAE fullInfo c itemFull+          it1 = case strengthFromEqpSlot IK.EqpSlotTimeout itemFull of+            Nothing -> []+            Just timeout ->+              let timeoutTurns = timeDeltaScale (Delta timeTurn) timeout+                  f startT = timeShift startT timeoutTurns > localTime+              in filter f (itemTimer itemFull)+          len = length it1+          timer = if len == 0+                  then ""+                  else if itemK itemFull == 1 && len == 1+                  then "(charging)"+                  else "(" <> tshow len <+> "charging" <> ")"+          ts = take n effTs+               ++ (if length effTs > n then ["(...)"] else [])+               ++ [timer]       in (MU.Text genericName, MU.Phrase $ map MU.Text ts)  -- | The part of speech describing the item.-partItem :: CStore -> ItemFull -> (MU.Part, MU.Part)+partItem :: Container -> LevelId -> Time -> ItemFull -> (MU.Part, MU.Part) partItem = partItemN False 4 -textAllAE :: Bool -> CStore -> ItemFull -> [Text]-textAllAE fullInfo cstore ItemFull{itemBase, itemDisco} =+textAllAE :: Bool -> Container -> ItemFull -> [Text]+textAllAE fullInfo c ItemFull{itemBase, itemDisco} =   let features | fullInfo = map featureToSuff $ sort $ jfeature itemBase                | otherwise = []   in case itemDisco of     Nothing -> features     Just ItemDisco{itemKind, itemAE} ->-      let periodicAspect :: Effect.Aspect a -> Bool-          periodicAspect (Effect.Periodic _) = True+      let periodicAspect :: IK.Aspect a -> Bool+          periodicAspect IK.Periodic = True           periodicAspect _ = False-          hurtEffect :: Effect.Effect a -> Bool-          hurtEffect (Effect.Hurt _) = True+          timeoutAspect :: IK.Aspect a -> Bool+          timeoutAspect IK.Timeout{} = True+          timeoutAspect _ = False+          hurtEffect :: IK.Effect -> Bool+          hurtEffect (IK.Hurt _) = True           hurtEffect _ = False+          notDetail :: IK.Effect -> Bool+          notDetail IK.Explode{} = fullInfo+          notDetail _ = True+          cstore = storeFromC c           active = cstore `elem` [CEqp, COrgan]                    || cstore == CGround && isJust (strengthEqpSlot itemBase)-          splitAE :: Ord a-                  => [Effect.Aspect a] -> (Effect.Aspect a -> Text)-                  -> [Effect.Effect a] -> (Effect.Effect a -> Text) -> [Text]-          splitAE aspects ppA effects ppE =-            let (periodicAs, restAs) = partition periodicAspect $ sort aspects-                (hurtEs, restEs) = partition hurtEffect $ sort effects-            in map ppA periodicAs ++ map ppE hurtEs-               ++ if active-                  then map ppA restAs ++ map ppE restEs-                  else map ppE restEs ++ map ppA restAs+          splitAE :: (Num a, Show a, Ord a)+                  => (a -> Text)+                  -> [IK.Aspect a] -> (IK.Aspect a -> Text)+                  -> [IK.Effect] -> (IK.Effect -> Text)+                  -> [Text]+          splitAE reduce_a aspects ppA effects ppE =+            let mperiodic = find periodicAspect aspects+                mtimeout = find timeoutAspect aspects+                restAs = sort aspects+                (hurtEs, restEs) = partition hurtEffect $ sort+                                   $ filter notDetail effects+                aes = map ppE hurtEs+                      ++ if active+                         then map ppA restAs ++ map ppE restEs+                         else map ppE restEs ++ map ppA restAs+                rechargingTs = T.intercalate (T.singleton ' ')+                               $ filter (not . T.null)+                               $ map ppE $ stripRecharging restEs+                onSmashTs = T.intercalate (T.singleton ' ')+                            $ filter (not . T.null)+                            $ map ppE $ stripOnSmash restEs+                durable = IK.Durable `elem` jfeature itemBase+                periodicOrTimeout = case mperiodic of+                  _ | T.null rechargingTs -> ""+                  Just IK.Periodic ->+                    case mtimeout of+                      Just (IK.Timeout 0) | not durable ->+                        "(each turn until gone:"+                        <+> rechargingTs <> ")"+                      Just (IK.Timeout t) ->+                        "(every" <+> reduce_a t <> ":"+                        <+> rechargingTs <> ")"+                      _ -> ""+                  _ -> case mtimeout of+                    Just (IK.Timeout t) ->+                      "(timeout" <+> reduce_a t <> ":"+                      <+> rechargingTs <> ")"+                    _ -> ""+                onSmash = if T.null onSmashTs then ""+                          else "(on smash:" <+> onSmashTs <> ")"+            in [periodicOrTimeout] ++ aes ++ [onSmash | fullInfo]           aets = case itemAE of             Just ItemAspectEffect{jaspects, jeffects} ->-              splitAE jaspects aspectToSuffix+              splitAE tshow+                      jaspects aspectToSuffix                       jeffects effectToSuffix             Nothing ->-              splitAE (iaspects itemKind) kindAspectToSuffix-                      (ieffects itemKind) kindEffectToSuffix+              splitAE (\d -> maybe "?" tshow $ Dice.reduceDice d)+                      (IK.iaspects itemKind) kindAspectToSuffix+                      (IK.ieffects itemKind) kindEffectToSuffix       in aets ++ features -partItemWs :: Int -> CStore -> ItemFull -> MU.Part-partItemWs count cstore itemFull =-  let (name, stats) = partItem cstore itemFull+-- TODO: use kit+partItemWs :: Int -> Container -> LevelId -> Time -> ItemFull -> MU.Part+partItemWs count c lid localTime itemFull =+  let (name, stats) = partItem c lid localTime itemFull   in MU.Phrase [MU.CarWs count name, stats] -partItemAW :: CStore -> ItemFull -> MU.Part-partItemAW cstore itemFull =-  let (name, stats) = partItem cstore itemFull+partItemAW :: Container -> LevelId -> Time -> ItemFull -> MU.Part+partItemAW c lid localTime itemFull =+  let (name, stats) = partItem c lid localTime itemFull   in MU.AW $ MU.Phrase [name, stats] -partItemWownW :: MU.Part -> CStore -> ItemFull -> MU.Part-partItemWownW partA cstore itemFull =-  let (name, stats) = partItem cstore itemFull+partItemWownW :: MU.Part -> Container -> LevelId -> Time -> ItemFull -> MU.Part+partItemWownW partA c lid localTime itemFull =+  let (name, stats) = partItem c lid localTime itemFull   in MU.WownW partA $ MU.Phrase [name, stats] -itemDesc :: CStore -> ItemFull -> Overlay-itemDesc cstore itemFull =-  let (name, stats) = partItemN True 99 cstore itemFull-      nstats = makePhrase [name, stats MU.:> ":"]+itemDesc :: Container -> LevelId -> Time -> ItemFull -> Overlay+itemDesc c lid localTime itemFull =+  let (name, stats) = partItemN True 99 c lid localTime itemFull+      nstats = makePhrase [name, stats]       desc = case itemDisco itemFull of         Nothing -> "This item is as unremarkable as can be."-        Just ItemDisco{itemKind} -> idesc itemKind+        Just ItemDisco{itemKind} -> IK.idesc itemKind       weight = jweight (itemBase itemFull)       (scaledWeight, unitWeight) =         if weight > 1000@@ -101,11 +160,12 @@         else (tshow weight, "g")       ln = abs $ fromEnum $ jlid (itemBase itemFull)       colorSymbol = uncurry (flip Color.AttrChar) (viewItem $ itemBase itemFull)-      f c = Color.AttrChar Color.defAttr c+      f color = Color.AttrChar Color.defAttr color       lxsize = fst normalLevelBound + 1  -- TODO       blurb =         "D"  -- dummy         <+> nstats+        <> ":"         <+> desc         <+> makeSentence ["Weighs", MU.Text scaledWeight <> unitWeight]         <+> makeSentence ["First found on level", MU.Text $ tshow ln]
Game/LambdaHack/Common/ItemStrongest.hs view
@@ -2,11 +2,12 @@ -- No operation in this module involves the state or any of our custom monads. module Game.LambdaHack.Common.ItemStrongest   ( -- * Strongest items-    strengthOnSmash, strengthToThrow, strengthEqpSlot, strengthFromEqpSlot+    strengthOnSmash, strengthCreateOrgan, strengthDropOrgan+  , strengthToThrow, strengthEqpSlot, strengthFromEqpSlot   , strongestSlotNoFilter, strongestSlot, sumSlotNoFilter, sumSkills     -- * Assorted   , totalRange, computeTrajectory, itemTrajectory-  , unknownPrecious, permittedRanged, unknownMelee+  , unknownMelee, allRecharging, stripRecharging, stripOnSmash   ) where  import Control.Applicative@@ -20,16 +21,13 @@  import qualified Game.LambdaHack.Common.Ability as Ability import qualified Game.LambdaHack.Common.Dice as Dice-import Game.LambdaHack.Common.Effect 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 -dice999 :: Dice.Dice -> Int-dice999 d = fromMaybe 999 $ Dice.reduceDice d- strengthAspect :: (Aspect Int -> [b]) -> ItemFull -> [b] strengthAspect f itemFull =   case itemDisco itemFull of@@ -50,48 +48,76 @@     [x] -> Just x     xs -> assert `failure` (xs, itemFull) -strengthEffect999 :: (Effect Int -> [b]) -> ItemFull -> [b]-strengthEffect999 f itemFull =+strengthEffect :: (Effect -> [b]) -> ItemFull -> [b]+strengthEffect f itemFull =   case itemDisco itemFull of     Just ItemDisco{itemAE=Just ItemAspectEffect{jeffects}} ->       concatMap f jeffects     Just ItemDisco{itemKind=ItemKind{ieffects}} ->-      -- Default for unknown power is 999 to encourage experimenting.-      let trav x = St.evalState (effectTrav x (return . dice999)) ()-      in concatMap f $ map trav ieffects+      concatMap f ieffects     Nothing -> []  strengthFeature :: (Feature -> [b]) -> Item -> [b] strengthFeature f item = concatMap f (jfeature item) +-- Simplification: does not take into account effects inside @Recharging@,+-- because @Hurt@, etc., are unlikely to have a timeout. strengthMelee :: ItemFull -> Maybe Int strengthMelee itemFull =   let durable = Durable `elem` jfeature (itemBase itemFull)       p (Hurt d) = [floor (Dice.meanDice d)]       p (Burn k) = [k]       p _ = []-      hasNoEffects = case itemDisco itemFull of+      hasExtraEffects = case itemDisco itemFull of         Just ItemDisco{itemAE=Just ItemAspectEffect{jeffects}} ->-          null jeffects+          any (\ef -> null $ p ef) jeffects         Just ItemDisco{itemKind=ItemKind{ieffects}} ->-          null ieffects-        Nothing -> True-  in if hasNoEffects+          any (\ef -> null $ p ef) ieffects+        Nothing -> False+      -- We assume extra weapon effects are usually useful and so such+      -- weapons are preferred over weapons with the same power, by default.+      -- If the player doesn't like a particular weapon's extra effect,+      -- he has to manage this manually.+      bonusExtraEffects = if hasExtraEffects then 1 else 0+      psum = sum (strengthEffect p itemFull)+  in if psum == 0      then Nothing-     else Just $ sum (strengthEffect999 p itemFull)-                 + if durable then 100 else 0+     else Just $ bonusExtraEffects + psum + if durable then 100 else 0  -- Called only by the server, so 999 is OK.-strengthOnSmash :: ItemFull -> [Effect Int]+strengthOnSmash :: ItemFull -> [Effect] strengthOnSmash =   let p (OnSmash eff) = [eff]       p _ = []-  in strengthEffect999 p+  in strengthEffect p +strengthCreateOrgan :: ItemFull -> [GroupName ItemKind]+strengthCreateOrgan =+  let p (CreateItem COrgan grp _) = [grp]+      p (Recharging (CreateItem COrgan grp _)) = [grp]+      p _ = []+  in strengthEffect p++strengthDropOrgan :: ItemFull -> [GroupName ItemKind]+strengthDropOrgan =+  let p (DropItem COrgan grp _) = [grp]+      p (Recharging (DropItem COrgan grp _)) = [grp]+      p _ = []+  in strengthEffect p+ strengthPeriodic :: ItemFull -> Maybe Int-strengthPeriodic =-  let p (Periodic k) = [k]+strengthPeriodic itemFull =+  let p Periodic = [()]       p _ = []+      isPeriodic = isJust $ strengthAspectMaybe p itemFull+      q (Timeout k) = [k]+      q _ = []+  in if isPeriodic then strengthAspectMaybe q itemFull else Nothing++strengthTimeout :: ItemFull -> Maybe Int+strengthTimeout =+  let p (Timeout k) = [k]+      p _ = []   in strengthAspectMaybe p  strengthAddMaxHP :: ItemFull -> Maybe Int@@ -112,12 +138,18 @@       p _ = []   in strengthAspectMaybe p -strengthAddSkills :: ItemFull -> Maybe Ability.Skills-strengthAddSkills =+strengthAllAddSkills :: ItemFull -> Maybe Ability.Skills+strengthAllAddSkills =   let p (AddSkills a) = [a]       p _ = []   in strengthAspectMaybe p +strengthAddSkills :: Ability.Ability -> ItemFull -> Maybe Int+strengthAddSkills ab =+  let p (AddSkills a) = [fromMaybe 0 $ EM.lookup ab a]+      p _ = []+  in strengthAspectMaybe p+ strengthAddHurtMelee :: ItemFull -> Maybe Int strengthAddHurtMelee =   let p (AddHurtMelee k) = [k]@@ -198,11 +230,12 @@ strengthFromEqpSlot :: EqpSlot -> ItemFull -> Maybe Int strengthFromEqpSlot eqpSlot =   case eqpSlot of-    EqpSlotPeriodic -> strengthPeriodic  -- a very crude approximation+    EqpSlotPeriodic -> strengthPeriodic+    EqpSlotTimeout -> strengthTimeout     EqpSlotAddMaxHP -> strengthAddMaxHP     EqpSlotAddMaxCalm -> strengthAddMaxCalm     EqpSlotAddSpeed -> strengthAddSpeed-    EqpSlotAddSkills -> \itemFull -> sum . EM.elems <$> strengthAddSkills itemFull+    EqpSlotAddSkills ab -> strengthAddSkills ab     EqpSlotAddHurtMelee -> strengthAddHurtMelee     EqpSlotAddHurtRanged -> strengthAddHurtRanged     EqpSlotAddArmorMelee -> strengthAddArmorMelee@@ -237,29 +270,9 @@ sumSkills :: [ItemFull] -> Ability.Skills sumSkills is =   let g itemFull = (Ability.scaleSkills (itemK itemFull))-                   <$> strengthAddSkills itemFull+                   <$> strengthAllAddSkills itemFull   in foldr Ability.addSkills Ability.zeroSkills $ mapMaybe g is -unknownPrecious :: ItemFull -> Bool-unknownPrecious itemFull =-  Durable `notElem` jfeature (itemBase itemFull)  -- if durable, no risk-  && case itemDisco itemFull of-    Just ItemDisco{itemAE=Just _} -> False-    _ -> Precious `elem` jfeature (itemBase itemFull)--permittedRanged :: ItemFull -> Maybe Int -> Bool-permittedRanged itemFull _ =-  let hasEffects = case itemDisco itemFull of-        Just ItemDisco{itemAE=Just ItemAspectEffect{jeffects=[]}} -> False-        Just ItemDisco{itemAE=Nothing, itemKind=ItemKind{ieffects=[]}} -> False-        _ -> True-  in hasEffects-     && not (unknownPrecious itemFull)-     && case strengthEqpSlot (itemBase itemFull) of-          Just (EqpSlotAddLight, _) -> True-          Just _ -> False-          Nothing -> True- unknownAspect :: (Aspect Dice.Dice -> [Dice.Dice]) -> ItemFull -> Bool unknownAspect f itemFull =   case itemDisco itemFull of@@ -274,3 +287,24 @@       p _ = []       f itemFull b = b || unknownAspect p itemFull   in foldr f False++allRecharging :: [Effect] -> [Effect]+allRecharging effs =+  let getRechargingEffect :: Effect -> Maybe (Effect)+      getRechargingEffect e@Recharging{} = Just e+      getRechargingEffect _ = Nothing+  in mapMaybe getRechargingEffect effs++stripRecharging :: [Effect] -> [Effect]+stripRecharging effs =+  let getRechargingEffect :: Effect -> Maybe (Effect)+      getRechargingEffect (Recharging e) = Just e+      getRechargingEffect _ = Nothing+  in mapMaybe getRechargingEffect effs++stripOnSmash :: [Effect] -> [Effect]+stripOnSmash effs =+  let getOnSmashEffect :: Effect -> Maybe (Effect)+      getOnSmashEffect (OnSmash e) = Just e+      getOnSmashEffect _ = Nothing+  in mapMaybe getOnSmashEffect effs
Game/LambdaHack/Common/Kind.hs view
@@ -35,16 +35,16 @@  -- | Content operations for the content of type @a@. data Ops a = Ops-  { okind         :: Id a -> a      -- ^ the content element at given id-  , ouniqGroup    :: GroupName -> Id a  -- ^ the id of the unique member of-                                        --   a singleton content group-  , opick         :: GroupName -> (a -> Bool) -> Rnd (Maybe (Id a))+  { okind         :: Id a -> a          -- ^ the content element at given id+  , ouniqGroup    :: GroupName a -> Id a  -- ^ the id of the unique member of+                                          --   a singleton content group+  , opick         :: GroupName a -> (a -> Bool) -> Rnd (Maybe (Id a))                                     -- ^ pick a random id belonging to a group                                     --   and satisfying a predicate   , ofoldrWithKey :: forall b. (Id a -> a -> b -> b) -> b -> b                                     -- ^ fold over all content elements of @a@   , ofoldrGroup   :: forall b.-                     GroupName -> (Int -> Id a -> a -> b -> b) -> b -> b+                     GroupName a -> (Int -> Id a -> a -> b -> b) -> b -> b                                     -- ^ fold over the given group only   , obounds       :: !(Id a, Id a)  -- ^ bounds of identifiers of content @a@   , ospeedup      :: !(Maybe (Speedup a))  -- ^ auxiliary speedup components@@ -57,7 +57,7 @@   assert (length content <= fromEnum (maxBound :: Id a)) $   let kindMap :: EM.EnumMap (Id a) a       !kindMap = EM.fromDistinctAscList $ zip [Id 0..] content-      kindFreq :: M.Map GroupName [(Int, (Id a, a))]+      kindFreq :: M.Map (GroupName a) [(Int, (Id a, a))]       kindFreq =         let tuples = [ (cgroup, (n, (i, k)))                      | (i, k) <- EM.assocs kindMap
Game/LambdaHack/Common/Level.hs view
@@ -6,7 +6,7 @@     -- * The @Level@ type and its components   , Level(..), ActorPrio, ItemFloor, TileMap, SmellMap     -- * Level query-  , at, atI, checkAccess, checkDoorAccess+  , at, checkAccess, checkDoorAccess   , accessible, accessibleUnknown, accessibleDir   , knownLsecret, isSecretPos, hideTile   , findPos, findPosTry, mapLevelActors_, mapDungeonActors_@@ -30,8 +30,9 @@ 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.RuleKind-import Game.LambdaHack.Content.TileKind+import Game.LambdaHack.Content.TileKind (TileKind)  -- | The complete dungeon is a map from level names to levels. type Dungeon = EM.EnumMap LevelId Level@@ -68,6 +69,7 @@   { ldepth      :: !AbsDepth   -- ^ absolute depth of the level   , lprio       :: !ActorPrio  -- ^ remembered actor times on the level   , lfloor      :: !ItemFloor  -- ^ remembered items lying on the floor+  , lembed      :: !ItemFloor  -- ^ items embedded in the tile   , ltile       :: !TileMap    -- ^ remembered level map   , lxsize      :: !X          -- ^ width of the level   , lysize      :: !Y          -- ^ height of the level@@ -79,12 +81,12 @@   , lclear      :: !Int        -- ^ total number of initially clear tiles   , ltime       :: !Time       -- ^ date of the last activity on the level   , lactorCoeff :: !Int        -- ^ the lower, the more monsters spawn-  , lactorFreq  :: !Freqs      -- ^ frequency of spawned actors; [] for clients+  , lactorFreq  :: !(Freqs ItemKind)  -- ^ frequency of spawned actors; [] for clients   , litemNum    :: !Int        -- ^ number of initial items, 0 for clients-  , litemFreq   :: !Freqs      -- ^ frequency of initial items; [] for clients+  , litemFreq   :: !(Freqs ItemKind)  -- ^ frequency of initial items; [] for clients   , lsecret     :: !Int        -- ^ secret tile seed   , lhidden     :: !Int        -- ^ secret tile density-  , lescape     :: !Bool       -- ^ has an Effect.Escape tile+  , lescape     :: !Bool       -- ^ has an IK.Escape tile   }   deriving (Show, Eq) @@ -95,12 +97,9 @@  -- | Query for tile kinds on the map. at :: Level -> Point -> Kind.Id TileKind+{-# INLINE at #-} at Level{ltile} p = ltile PointArray.! p --- | Query for items on the ground.-atI :: Level -> Point -> ItemBag-atI Level{lfloor} p = EM.findWithDefault EM.empty p lfloor- checkAccess :: Kind.COps -> Level -> Maybe (Point -> Point -> Bool) checkAccess Kind.COps{corule} _ =   case raccessible $ Kind.stdRuleset corule of@@ -217,6 +216,7 @@     put ldepth     put lprio     put (assertSparseItems lfloor)+    put (assertSparseItems lembed)     put ltile     put lxsize     put lysize@@ -237,6 +237,7 @@     ldepth <- get     lprio <- get     lfloor <- get+    lembed <- get     ltile <- get     lxsize <- get     lysize <- get
Game/LambdaHack/Common/Misc.hs view
@@ -8,7 +8,7 @@   , Container(..), CStore(..)     -- * Assorted   , normalLevelBound, divUp, GroupName, toGroupName, Freqs, breturn-  , serverSaveName, Rarity, validateRarity+  , serverSaveName, Rarity, validateRarity, Tactic(..)   ) where  import Control.Monad@@ -46,22 +46,22 @@ -- 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 = GroupName Text+newtype GroupName a = GroupName Text   deriving (Eq, Ord, Read, Hashable, Binary) -instance IsString GroupName where+instance IsString (GroupName a) where   fromString = GroupName . T.pack -instance Show GroupName where+instance Show (GroupName a) where   show (GroupName gn) = T.unpack gn -toGroupName :: Text -> GroupName+toGroupName :: Text -> GroupName a 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 = [(GroupName, Int)]+type Freqs a = [(GroupName a, Int)]  -- | Rarity on given depths. type Rarity = [(Int, Int)]@@ -86,6 +86,7 @@ -- | Item container type. data Container =     CFloor !LevelId !Point+  | CEmbed !LevelId !Point   | CActor !ActorId !CStore   | CTrunk !FactionId !LevelId !Point   -- ^ for bootstrapping actor bodies   deriving (Show, Eq, Ord, Generic)@@ -122,6 +123,34 @@ -- | A unique identifier of an actor in the dungeon. newtype ActorId = ActorId Int   deriving (Show, Eq, Ord, Enum, Binary)++-- TODO: alway shoot, never shoot, etc., but there is too many and this is best+-- expressed via skills, and also we risk micromanagement, so let's stop+-- and think first; perhaps only have as many tactics as needed for realistic+-- AI behaviour in our game modes; perhaps even expose only some of them to UI+data Tactic =+    TBlock    -- ^ always only wait, even if enemy in melee range+  | TFollow   -- ^ always follow leader's target or his position if no target+  | TExplore  -- ^ if enemy nearby, attack, if no items, etc., explore unknown+  | TRoam     -- ^ if enemy nearby, attack, if no items, etc., roam randomly+  | TPatrol   -- ^ find an open and uncrowded area, patrol it according+              --   to sight radius and fallback temporarily to @TRoam@+              --   when enemy is seen by the faction and is within+              --   the actor's sight radius+              --   TODO (currently the same as TExplore; should it chase+              --   targets too (TRoam) and only switch to TPatrol when none?)+  deriving (Eq, Ord, Enum, Bounded, Generic)++instance Show Tactic where+  show TBlock = "block and wait"+  show TFollow = "follow leader's target or position"+  show TExplore = "explore unknown, chase targets"+  show TRoam = "roam freely, chase targets"+  show TPatrol = "find and patrol an area (TODO)"++instance Binary Tactic++instance Hashable Tactic  -- Data.Binary 
Game/LambdaHack/Common/Msg.hs view
@@ -3,9 +3,9 @@ module Game.LambdaHack.Common.Msg   ( makePhrase, makeSentence   , Msg, (<>), (<+>), tshow, toWidth, moreMsg, yesnoMsg, truncateMsg-  , Report, emptyReport, nullReport, singletonReport, addMsg+  , Report, emptyReport, nullReport, singletonReport, addMsg, prependMsg   , splitReport, renderReport, findInReport, lastMsgOfReport-  , History, emptyHistory, lengthHistory, singletonHistory, mergeHistory+  , History, emptyHistory, lengthHistory, singletonHistory   , addReport, renderHistory, takeHistory, lastReportOfHistory   , Overlay(overlay), emptyOverlay, truncateToOverlay, toOverlay   , Slideshow(slideshow), splitOverlay, toSlideshow@@ -30,6 +30,7 @@ import Game.LambdaHack.Common.Color import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Point+import Game.LambdaHack.Common.Time  infixr 6 <+>  -- TODO: not needed when we require a very new minimorph (<+>) :: Text -> Text -> Text@@ -101,6 +102,10 @@  where y = encodeUtf8 y' addMsg (Report xns) y = Report $ (encodeUtf8 y, 1) : xns +prependMsg :: Msg -> Report -> Report+prependMsg m r | T.null m = r+prependMsg y (Report xns) = Report $ xns ++ [(encodeUtf8 y, 1)]+ -- | Split a messages into chunks that fit in one line. -- We assume the width of the messages line is the same as of level map. splitReport :: X -> Report -> Overlay@@ -146,42 +151,48 @@          else T.reverse ppost : splitText w (T.reverse ppre <> post)  -- | The history of reports.-newtype History = History [Report]+newtype History = History [(Time, Report)]   deriving (Show, Binary)  -- | Empty history of reports. emptyHistory :: History emptyHistory = History [] -lengthHistory :: History -> Int-lengthHistory (History rs) = length rs- -- | Construct a singleton history of reports.-singletonHistory :: Report -> History-singletonHistory r = addReport r emptyHistory+singletonHistory :: Time -> Report -> History+singletonHistory = addReport emptyHistory -mergeHistory :: [(Msg, History)] -> History-mergeHistory l =-  let unhist (History x) = x-      f (msg, h) = singletonReport msg : unhist h-  in History $ concatMap f l+-- | Add a report to history, handling repetitions.+addReport :: History -> Time -> Report -> History+addReport h _ (Report []) = h+addReport (History []) time m = History [(time, m)]+addReport (History oldRs@((oldTime, Report h) : hs)) time (Report m)  =+  case (reverse m, h) of+    ((s1, n1) : rs, (s2, n2) : hhs) | s1 == s2 ->+      let hist = (oldTime, Report ((s2, n1 + n2) : hhs)) : hs+      in History $ if null rs+                   then hist+                   else (time, Report (reverse rs)) : hist+    _ -> History $ (time, Report m) : oldRs +lengthHistory :: History -> Int+lengthHistory (History rs) = length rs+ -- | Render history as many lines of text, wrapping if necessary. renderHistory :: History -> Overlay renderHistory (History h) =   let w = fst normalLevelBound + 1-  in toOverlay $ concatMap (splitReportList w) h+  in toOverlay $ concatMap (splitReportForHistory w) h --- | Add a report to history, handling repetitions.-addReport :: Report -> History -> History-addReport (Report []) h = h-addReport m (History []) = History [m]-addReport (Report m) (History (Report h : hs)) =-  case (reverse m, h) of-    ((s1, n1) : rs, (s2, n2) : hhs) | s1 == s2 ->-      let hist = Report ((s2, n1 + n2) : hhs) : hs-      in History $ if null rs then hist else Report (reverse rs) : hist-    _ -> History $ Report m : Report h : hs+splitReportForHistory :: X -> (Time, Report) -> [Text]+splitReportForHistory w (time, r) =+  -- TODO: display time fractions with granularity enough to differ+  -- from previous and next report, if possible+  let turns = time `timeFitUp` timeTurn+      ts = splitText (w - 1) $ tshow turns <> ":" <+> renderReport r+  in case ts of+    [] -> []+    hd : tl -> hd : map (T.cons ' ') tl  -- | Take the given prefix of reports from a history. takeHistory :: Int -> History -> History@@ -190,7 +201,7 @@ lastReportOfHistory :: History -> Maybe Report lastReportOfHistory (History hist) = case hist of   [] -> Nothing-  rep : _ -> Just rep+  (_, rep) : _ -> Just rep  type ScreenLine = U.Vector Int32 
Game/LambdaHack/Common/PointArray.hs view
@@ -1,8 +1,9 @@ -- | Arrays, based on Data.Vector.Unboxed, indexed by @Point@. module Game.LambdaHack.Common.PointArray   ( Array-  , (!), (//), replicateA, replicateMA, generateMA, sizeA-  , foldlA, ifoldlA, imapA, minIndexA, minLastIndexA, maxIndexA, maxLastIndexA+  , (!), (//), replicateA, replicateMA, generateA, generateMA, sizeA+  , foldlA, ifoldlA, mapA, imapA, mapWithKeyM_A+  , minIndexA, minLastIndexA, maxIndexA, maxLastIndexA, forceA   ) where  import Control.Arrow ((***))@@ -22,9 +23,9 @@ -- TODO: perhaps make them an instance of Data.Vector.Generic? -- | Arrays indexed by @Point@. data Array c = Array-  { axsize  :: X-  , aysize  :: Y-  , avector :: U.Vector Word8+  { axsize  :: !X+  , aysize  :: !Y+  , avector :: !(U.Vector Word8)   }   deriving Eq @@ -32,12 +33,15 @@   show a = "PointArray.Array with size " ++ show (sizeA a)  cnv :: (Enum a, Enum b) => a -> b+{-# INLINE cnv #-} cnv = toEnum . fromEnum  pindex :: X -> Point -> Int+{-# INLINE pindex #-} pindex xsize (Point x y) = x + y * xsize  punindex :: X -> Int -> Point+{-# INLINE punindex #-} punindex xsize n = let (y, x) = n `quotRem` xsize                    in Point x y @@ -51,22 +55,33 @@  -- | Construct an array updated with the association list. (//) :: Enum c => Array c -> [(Point, c)] -> Array c+{-# INLINE (//) #-} (//) Array{..} l = let v = avector U.// map (pindex axsize *** cnv) l                    in Array{avector = v, ..}  -- | Create an array from a replicated element. replicateA :: Enum c => X -> Y -> c -> Array c+{-# INLINE replicateA #-} replicateA axsize aysize c =   Array{avector = U.replicate (axsize * aysize) $ cnv c, ..} --- | Create an  array from a replicated monadic action.+-- | Create an array from a replicated monadic action. replicateMA :: Enum c => Monad m => X -> Y -> m c -> m (Array c)+{-# INLINE replicateMA #-} replicateMA axsize aysize m = do   v <- U.replicateM (axsize * aysize) $ liftM cnv m   return $! Array{avector = v, ..} +-- | Create an array from a function.+generateA :: Enum c => X -> Y -> (Point -> c) -> Array c+{-# INLINE generateA #-}+generateA axsize aysize f =+  let g n = cnv $ f $ punindex axsize n+  in Array{avector = U.generate (axsize * aysize) g, ..}+ -- | Create an array from a monadic function. generateMA :: Enum c => Monad m => X -> Y -> (Point -> m c) -> m (Array c)+{-# INLINE generateMA #-} generateMA axsize aysize fm = do   let gm n = liftM cnv $ fm $ punindex axsize n   v <- U.generateM (axsize * aysize) gm@@ -74,25 +89,44 @@  -- | Content identifiers array size. sizeA :: Array c -> (X, Y)+{-# INLINE sizeA #-} sizeA Array{..} = (axsize, aysize)  -- | Fold left strictly over an array. foldlA :: Enum c => (a -> c -> a) -> a -> Array c -> a+{-# INLINE foldlA #-} foldlA f z0 Array{..} =   U.foldl' (\a c -> f a (cnv c)) z0 avector  -- | Fold left strictly over an array -- (function applied to each element and its index). ifoldlA :: Enum c => (a -> Point -> c -> a) -> a -> Array c -> a+{-# INLINE ifoldlA #-} ifoldlA f z0 Array{..} =   U.ifoldl' (\a n c -> f a (punindex axsize n) (cnv c)) z0 avector +-- | Map over an array.+mapA :: (Enum c, Enum d) => (c -> d) -> Array c -> Array d+{-# INLINE mapA #-}+mapA f Array{..} = Array{avector = U.map (cnv . f . cnv) avector, ..}+ -- | Map over an array (function applied to each element and its index). imapA :: (Enum c, Enum d) => (Point -> c -> d) -> Array c -> Array d+{-# INLINE imapA #-} imapA f Array{..} =   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.+mapWithKeyM_A :: Enum c => Monad m+              => (Point -> c -> m ()) -> Array c -> m ()+{-# INLINE mapWithKeyM_A #-}+mapWithKeyM_A f Array{..} =+  U.ifoldl' (\a n c -> a >> f (punindex axsize n) (cnv c))+            (return ())+            avector+ -- | Yield the point coordinates of a minimum element of the array. -- The array may not be empty. minIndexA :: Enum c => Array c -> Point@@ -126,6 +160,11 @@   $ avector  where   imax (i, x) (j, y) = i `seq` j `seq` if x <= y then (j, y) else (i, x)++-- | Force the array not to retain any extra memory.+forceA :: Enum c => Array c -> Array c+{-# INLINE forceA #-}+forceA Array{..} = Array{avector = U.force avector, ..}  instance Binary (Array c) where   put Array{..} = do
Game/LambdaHack/Common/Request.hs view
@@ -1,25 +1,29 @@-{-# LANGUAGE ExistentialQuantification, GADTs, StandaloneDeriving, DataKinds, KindSignatures #-}+{-# LANGUAGE DataKinds, ExistentialQuantification, GADTs, KindSignatures,+             StandaloneDeriving #-} -- | Abstract syntax of server commands. -- See -- <https://github.com/LambdaHack/LambdaHack/wiki/Client-server-architecture>. module Game.LambdaHack.Common.Request   ( RequestAI(..), RequestUI(..), RequestTimed(..), RequestAnyAbility(..)-  , ReqFailure(..), showReqFailure, anyToUI+  , ReqFailure(..), impossibleReqFailure, showReqFailure, anyToUI+  , permittedPrecious, permittedProject, permittedApply   ) where  import Data.Text (Text)  import Game.LambdaHack.Atomic+import Game.LambdaHack.Common.Ability import Game.LambdaHack.Common.Actor-import qualified Game.LambdaHack.Common.Feature as F+import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item+import Game.LambdaHack.Common.ItemStrongest import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg import Game.LambdaHack.Common.Point import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Common.Ability-import Game.LambdaHack.Common.Faction+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.ModeKind+import qualified Game.LambdaHack.Content.TileKind as TK  -- TODO: make remove second arg from ReqLeader; this requires a separate -- channel for Ping, probably, and then client sends as many commands@@ -36,7 +40,7 @@ data RequestUI =     forall a. ReqUITimed !(RequestTimed a)   | ReqUILeader !ActorId !(Maybe Target) !RequestUI-  | ReqUIGameRestart !ActorId !GroupName !Int ![(Int, (Text, Text))]+  | ReqUIGameRestart !ActorId !(GroupName ModeKind) !Int ![(Int, (Text, Text))]   | ReqUIGameExit !ActorId !Int   | ReqUIGameSave   | ReqUITactic !Tactic@@ -57,13 +61,13 @@   ReqMove :: !Vector -> RequestTimed AbMove   ReqMelee :: !ActorId -> !ItemId -> !CStore -> RequestTimed AbMelee   ReqDisplace :: !ActorId -> RequestTimed AbDisplace-  ReqAlter :: !Point -> !(Maybe F.Feature) -> RequestTimed AbAlter+  ReqAlter :: !Point -> !(Maybe TK.Feature) -> RequestTimed AbAlter   ReqWait :: RequestTimed AbWait   ReqMoveItem :: !ItemId -> !Int -> !CStore -> !CStore               -> RequestTimed AbMoveItem   ReqProject :: !Point -> !Int -> !ItemId -> !CStore -> RequestTimed AbProject   ReqApply :: !ItemId -> !CStore -> RequestTimed AbApply-  ReqTrigger :: !(Maybe F.Feature) -> RequestTimed AbTrigger+  ReqTrigger :: !(Maybe TK.Feature) -> RequestTimed AbTrigger  deriving instance Show (RequestTimed a) @@ -76,50 +80,149 @@   | DisplaceProjectiles   | DisplaceDying   | DisplaceBraced+  | DisplaceImmobile   | DisplaceSupported   | AlterDistant   | AlterBlockActor   | AlterBlockItem   | AlterNothing   | EqpOverfull-  | DurablePeriodicAbuse-  | ApplyBlind+  | ApplyUnskilled+  | ApplyRead   | ApplyOutOfReach   | ItemNothing   | ItemNotCalm+  | NotCalmPrecious   | ProjectAimOnself   | ProjectBlockTerrain   | ProjectBlockActor-  | ProjectBlind+  | ProjectUnskilled+  | ProjectFragile+  | ProjectOutOfReach   | TriggerNothing   | NoChangeDunLeader   | NoChangeLvlLeader +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 -> True+  AlterDistant -> True+  AlterBlockActor -> True  -- adjacent actor always visible+  AlterBlockItem -> True  -- adjacent item always visible+  AlterNothing -> True+  EqpOverfull -> True+  ApplyUnskilled -> False  -- unidentified skill items+  ApplyRead -> False  -- unidentified skill items+  ApplyOutOfReach -> True+  ItemNothing -> True+  ItemNotCalm -> False  -- unidentified skill items+  NotCalmPrecious -> False  -- unidentified skill items+  ProjectAimOnself -> True+  ProjectBlockTerrain -> True  -- adjacent terrain always visible+  ProjectBlockActor -> True  -- adjacent actor always visible+  ProjectUnskilled -> False  -- unidentified skill items+  ProjectFragile -> False  -- unidentified skill items+  ProjectOutOfReach -> True+  TriggerNothing -> True  -- terrain underneath always visibl+  NoChangeDunLeader -> True+  NoChangeLvlLeader -> True+ showReqFailure :: ReqFailure -> Msg 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 switch places with a distant actor"+  DisplaceDistant -> "trying to displace a distant actor"   DisplaceAccess -> "switching places without access"-  DisplaceProjectiles -> "trying to switch places with multiple projectiles"-  DisplaceDying -> "trying to switch places with a dying foe"-  DisplaceBraced -> "trying to switch places with a braced foe"-  DisplaceSupported -> "trying to switch places with a supported foe"+  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 immoblie foe"+  DisplaceSupported -> "trying to displace a supported foe"   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"-  DurablePeriodicAbuse -> "cannot apply a durable periodic item"-  ApplyBlind -> "blind actors cannot read"+  ApplyUnskilled -> "unskilled actors cannot read"+  ApplyRead -> "to read an item requires activate skill 2"   ApplyOutOfReach -> "cannot apply an item out of reach"   ItemNothing -> "wasting time on void item manipulation"   ItemNotCalm -> "you are too alarmed to sort through the shared stash"+  NotCalmPrecious -> "you are too alarmed to handle such an exquisite item"   ProjectAimOnself -> "cannot aim at oneself"   ProjectBlockTerrain -> "aiming obstructed by terrain"   ProjectBlockActor -> "aiming blocked by an actor"-  ProjectBlind -> "blind actors cannot aim"+  ProjectUnskilled -> "unskilled actors cannot aim"+  ProjectFragile -> "to lob a fragile item requires fling skill 2"+  ProjectOutOfReach -> "cannot aim an item out of reach"   TriggerNothing -> "wasting time on triggering nothing"   NoChangeDunLeader -> "no manual level change for your team"   NoChangeLvlLeader -> "no manual leader change for your team"++-- The item should not be activated 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 calm10 forced itemFull =+  let isPrecious = IK.Precious `elem` jfeature (itemBase itemFull)+  in if not calm10 && not forced && isPrecious then Left NotCalmPrecious+     else Right $ IK.Durable `elem` jfeature (itemBase itemFull)+                  || case itemDisco itemFull of+                    Just ItemDisco{itemAE=Just _} -> True+                    _ -> not isPrecious++permittedProject :: [Char] -> Bool -> Int -> ItemFull -> Actor -> [ItemFull]+                 -> Either ReqFailure Bool+permittedProject triggerSyms forced skill itemFull@ItemFull{itemBase}+                 b activeItems =+  let calm10 = calmEnough10 b activeItems+  in if not forced+        && skill < 1 then Left ProjectUnskilled+  else if not forced+          && IK.Fragile `elem` jfeature itemBase+          && skill < 2 then Left ProjectFragile+  else+    let legal = permittedPrecious calm10 forced itemFull+    in case legal of+      Left{} -> legal+      Right False -> legal+      Right True -> Right $+        let hasEffects = case itemDisco itemFull of+              Just ItemDisco{itemAE=Just ItemAspectEffect{jeffects=[]}} -> False+              Just ItemDisco{ itemAE=Nothing+                            , itemKind=IK.ItemKind{IK.ieffects=[]} } -> False+              _ -> True+            permittedSlot =+              if ' ' `elem` triggerSyms+              then case strengthEqpSlot itemBase of+                Just (IK.EqpSlotAddLight, _) -> True+                Just _ -> False+                Nothing -> True+              else jsymbol itemBase `elem` triggerSyms+        in hasEffects && permittedSlot++permittedApply :: [Char] -> Int -> ItemFull -> Actor -> [ItemFull]+               -> Either ReqFailure Bool+permittedApply triggerSyms skill itemFull@ItemFull{itemBase} b activeItems =+  let calm10 = calmEnough10 b activeItems+  in if skill < 1 then Left ApplyUnskilled+  else if jsymbol itemBase == '?'+        && skill < 2 then Left ApplyRead+  else+    let legal = permittedPrecious calm10 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/State.hs view
@@ -24,7 +24,7 @@ import Game.LambdaHack.Common.Point import qualified Game.LambdaHack.Common.PointArray as PointArray import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.TileKind+import Game.LambdaHack.Content.TileKind (TileKind)  -- | View on game state. "Remembered" fields carry a subset of the info -- in the client copies of the state. Clients never directly change@@ -54,6 +54,7 @@   in Level { ldepth            , lprio = EM.empty            , lfloor = EM.empty+           , lembed = EM.empty            , ltile = unknownTileMap unknownId outerId lxsize lysize            , lxsize            , lysize
Game/LambdaHack/Common/Tile.hs view
@@ -18,8 +18,8 @@   , kindHasFeature, hasFeature   , isClear, isLit, isWalkable, isPassableKind, isPassable, isDoor, isSuspect   , isExplorable, lookSimilar, speedup-  , openTo, closeTo, causeEffects, revealAs, hideAs-  , isOpenable, isClosable, isChangeable, isEscape, isStair+  , openTo, closeTo, embedItems, causeEffects, revealAs, hideAs+  , isOpenable, isClosable, isChangeable, isEscape, isStair, ascendTo #ifdef EXPOSE_INTERNAL   , TileSpeedup(..), Tab, createTab, accessTab #endif@@ -29,12 +29,14 @@ import qualified Data.Array.Unboxed as A import Data.Maybe -import qualified Game.LambdaHack.Common.Effect as Effect-import qualified Game.LambdaHack.Common.Feature as F 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.TileKind+import Game.LambdaHack.Content.ItemKind (ItemKind)+import qualified Game.LambdaHack.Content.ItemKind as IK+import Game.LambdaHack.Content.TileKind (TileKind)+import qualified Game.LambdaHack.Content.TileKind as TK  -- | The last time a hero left a smell in a given tile. To be used -- by monsters that hunt by smell.@@ -65,12 +67,12 @@ accessTab (Tab tab) ki = tab A.! ki  -- | Whether a tile kind has the given feature.-kindHasFeature :: F.Feature -> TileKind -> Bool+kindHasFeature :: TK.Feature -> TileKind -> Bool {-# INLINE kindHasFeature #-}-kindHasFeature f t = f `elem` tfeature t+kindHasFeature f t = f `elem` TK.tfeature t  -- | Whether a tile kind (specified by its id) has the given feature.-hasFeature :: Kind.Ops TileKind -> F.Feature -> Kind.Id TileKind -> Bool+hasFeature :: Kind.Ops TileKind -> TK.Feature -> Kind.Id TileKind -> Bool {-# INLINE hasFeature #-} hasFeature Kind.Ops{okind} f t = kindHasFeature f (okind t) @@ -145,10 +147,10 @@ lookSimilar :: TileKind -> TileKind -> Bool {-# INLINE lookSimilar #-} lookSimilar t u =-  tsymbol t == tsymbol u &&-  tname   t == tname   u &&-  tcolor  t == tcolor  u &&-  tcolor2 t == tcolor2 u+  TK.tsymbol t == TK.tsymbol u &&+  TK.tname   t == TK.tname   u &&+  TK.tcolor  t == TK.tcolor  u &&+  TK.tcolor2 t == TK.tcolor2 u  speedup :: Bool -> Kind.Ops TileKind -> TileSpeedup speedup allClear cotile =@@ -156,38 +158,38 @@   -- taken makes random lookups more or less efficient, so not optimizing   -- further, until I have benchmarks.   let isClearTab | allClear = createTab cotile-                              $ not . kindHasFeature F.Impenetrable+                              $ not . kindHasFeature TK.Impenetrable                  | otherwise = createTab cotile-                               $ kindHasFeature F.Clear-      isLitTab = createTab cotile $ not . kindHasFeature F.Dark-      isWalkableTab = createTab cotile $ kindHasFeature F.Walkable+                               $ kindHasFeature TK.Clear+      isLitTab = createTab cotile $ not . kindHasFeature TK.Dark+      isWalkableTab = createTab cotile $ kindHasFeature TK.Walkable       isPassableTab = createTab cotile isPassableKind       isDoorTab = createTab cotile $ \tk ->-        let getTo F.OpenTo{} = True-            getTo F.CloseTo{} = True+        let getTo TK.OpenTo{} = True+            getTo TK.CloseTo{} = True             getTo _ = False-        in any getTo $ tfeature tk-      isSuspectTab = createTab cotile $ kindHasFeature F.Suspect+        in any getTo $ TK.tfeature tk+      isSuspectTab = createTab cotile $ kindHasFeature TK.Suspect       isChangeableTab = createTab cotile $ \tk ->-        let getTo F.ChangeTo{} = True+        let getTo TK.ChangeTo{} = True             getTo _ = False-        in any getTo $ tfeature tk+        in any getTo $ TK.tfeature tk   in TileSpeedup {..}  isPassableKind :: TileKind -> Bool isPassableKind tk =-  let getTo F.Walkable = True-      getTo F.OpenTo{} = True-      getTo F.ChangeTo{} = True  -- can change to passable and may have loot-      getTo F.Suspect = True+  let getTo TK.Walkable = True+      getTo TK.OpenTo{} = True+      getTo TK.ChangeTo{} = True  -- can change to passable and may have loot+      getTo TK.Suspect = True       getTo _ = False-  in any getTo $ tfeature tk+  in any getTo $ TK.tfeature tk  openTo :: Kind.Ops TileKind -> Kind.Id TileKind -> Rnd (Kind.Id TileKind) openTo Kind.Ops{okind, opick} t = do-  let getTo (F.OpenTo grp) acc = grp : acc+  let getTo (TK.OpenTo grp) acc = grp : acc       getTo _ acc = acc-  case foldr getTo [] $ tfeature $ okind t of+  case foldr getTo [] $ TK.tfeature $ okind t of     [] -> return t     groups -> do       grp <- oneOf groups@@ -196,26 +198,32 @@  closeTo :: Kind.Ops TileKind -> Kind.Id TileKind -> Rnd (Kind.Id TileKind) closeTo Kind.Ops{okind, opick} t = do-  let getTo (F.CloseTo grp) acc = grp : acc+  let getTo (TK.CloseTo grp) acc = grp : acc       getTo _ acc = acc-  case foldr getTo [] $ tfeature $ okind t of+  case foldr getTo [] $ TK.tfeature $ okind t of     [] -> return t     groups -> do       grp <- oneOf groups       fmap (fromMaybe $ assert `failure` grp)         $ opick grp (const True) -causeEffects :: Kind.Ops TileKind -> Kind.Id TileKind -> [Effect.Effect Int]-causeEffects Kind.Ops{okind} t = do-  let getTo (F.Cause eff) acc = eff : acc+embedItems :: Kind.Ops TileKind -> Kind.Id TileKind -> [GroupName ItemKind]+embedItems Kind.Ops{okind} t =+  let getTo (TK.Embed eff) acc = eff : acc       getTo _ acc = acc-  foldr getTo [] $ tfeature $ okind t+  in foldr getTo [] $ TK.tfeature $ okind t +causeEffects :: Kind.Ops TileKind -> Kind.Id TileKind -> [IK.Effect]+causeEffects Kind.Ops{okind} t =+  let getTo (TK.Cause eff) acc = eff : acc+      getTo _ acc = acc+  in foldr getTo [] $ TK.tfeature $ okind t+ revealAs :: Kind.Ops TileKind -> Kind.Id TileKind -> Rnd (Kind.Id TileKind) revealAs Kind.Ops{okind, opick} t = do-  let getTo (F.RevealAs grp) acc = grp : acc+  let getTo (TK.RevealAs grp) acc = grp : acc       getTo _ acc = acc-  case foldr getTo [] $ tfeature $ okind t of+  case foldr getTo [] $ TK.tfeature $ okind t of     [] -> return t     groups -> do       grp <- oneOf groups@@ -224,32 +232,38 @@  hideAs :: Kind.Ops TileKind -> Kind.Id TileKind -> Kind.Id TileKind hideAs Kind.Ops{okind, ouniqGroup} t =-  let getTo (F.HideAs grp) _ = Just grp+  let getTo (TK.HideAs grp) _ = Just grp       getTo _ acc = acc-  in case foldr getTo Nothing (tfeature (okind t)) of+  in case foldr getTo Nothing (TK.tfeature (okind t)) of        Nothing    -> t        Just grp -> ouniqGroup grp  -- | Whether a tile kind (specified by its id) has an OpenTo feature. isOpenable :: Kind.Ops TileKind -> Kind.Id TileKind -> Bool isOpenable Kind.Ops{okind} t =-  let getTo F.OpenTo{} = True+  let getTo TK.OpenTo{} = True       getTo _ = False-  in any getTo $ tfeature $ okind t+  in any getTo $ TK.tfeature $ okind t  -- | Whether a tile kind (specified by its id) has a CloseTo feature. isClosable :: Kind.Ops TileKind -> Kind.Id TileKind -> Bool isClosable Kind.Ops{okind} t =-  let getTo F.CloseTo{} = True+  let getTo TK.CloseTo{} = True       getTo _ = False-  in any getTo $ tfeature $ okind t+  in any getTo $ TK.tfeature $ okind t  isEscape :: Kind.Ops TileKind -> Kind.Id TileKind -> Bool-isEscape cotile t = let isEffectEscape Effect.Escape{} = True+isEscape cotile t = let isEffectEscape IK.Escape{} = True                         isEffectEscape _ = False                     in any isEffectEscape $ causeEffects cotile t  isStair :: Kind.Ops TileKind -> Kind.Id TileKind -> Bool-isStair cotile t = let isEffectAscend Effect.Ascend{} = True+isStair cotile t = let isEffectAscend IK.Ascend{} = True                        isEffectAscend _ = False                    in any isEffectAscend $ causeEffects cotile t++ascendTo :: Kind.Ops TileKind -> Kind.Id TileKind -> [Int]+ascendTo cotile t =+  let getTo (IK.Ascend k) acc = k : acc+      getTo _ acc = acc+  in foldr getTo [] (causeEffects cotile t)
Game/LambdaHack/Common/Time.hs view
@@ -6,7 +6,7 @@   , Delta(..), timeShift, timeDeltaToFrom, timeDeltaReverse, timeDeltaScale   , timeDeltaToDigit, ticksPerMeter   , Speed, toSpeed, fromSpeed, speedZero, speedNormal-  , speedScale, speedAdd, speedNegate+  , speedScale, timeDeltaDiv, speedAdd, speedNegate   , speedFromWeight, rangeFromSpeed, rangeFromSpeedAndLinger   ) where @@ -104,6 +104,10 @@ timeDeltaScale :: Delta Time -> Int -> Delta Time timeDeltaScale (Delta (Time t)) s = Delta (Time (t * fromIntegral s)) +-- | Divide a time vector.+timeDeltaDiv :: Delta Time -> Int -> Delta Time+timeDeltaDiv (Delta (Time t)) n = Delta (Time (t `div` fromIntegral n))+ -- | Represent the main 10 thresholds of a time range by digits, -- given the total length of the time range. timeDeltaToDigit :: Delta Time -> Delta Time -> Char@@ -116,7 +120,7 @@  -- | Speed in meters per 1 million seconds (m/Ms). -- Actors at normal speed (2 m/s) take one time turn (0.5 s)--- to move one tile (1 m by 1 m).+-- to make one step (move one tile, which is 1 m by 1 m). newtype Speed = Speed Int64   deriving (Eq, Ord, Binary) @@ -131,6 +135,11 @@ toSpeed :: Int -> Speed 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 fromSpeed (Speed s) = fromIntegral $ s * 10 `div` sInMs@@ -157,7 +166,8 @@  -- | The number of time ticks it takes to walk 1 meter at the given speed. ticksPerMeter :: Speed -> Delta Time-ticksPerMeter (Speed v) = Delta $ Time $ _ticksInSecond * sInMs `divUp` max 1 v+ticksPerMeter (Speed v) =+  Delta $ Time $ _ticksInSecond * sInMs `divUp` max minimalSpeed v  -- | Calculate projectile speed from item weight in grams -- and velocity percent modifier.@@ -170,7 +180,7 @@            | w > 500 && w <= 2000 = sInMs * 16 * 1500 `div` (w + 1000)            | w < 16000 = sInMs * (18000 - w) `div` 1000            | w < 200000 = sInMs  -- half a step per turn is the minimum-           | otherwise = 0  -- unless _very_ heavy+           | otherwise = minimalSpeed  -- unless _very_ heavy                -- TODO: such high weight should also affect moving       v = mpMs * vp `div` 100       -- We round down to the nearest multiple of 2M (unless the speed@@ -179,7 +189,7 @@       multiple2M = sInMs * if v > 2 * sInMs                            then 2 * (v `div` (2 * sInMs))                            else v `div` sInMs-      minimumSpeed = if mpMs == 0 then 0 else sInMs+      minimumSpeed = if mpMs == minimalSpeed then minimalSpeed else sInMs   in Speed $ max minimumSpeed multiple2M  -- | Calculate maximum range in meters of a projectile from its speed.
Game/LambdaHack/Content/CaveKind.hs view
@@ -10,12 +10,16 @@ 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        -- ^ frequency within groups+  , 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@@ -30,18 +34,20 @@   , 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        -- ^ actor groups to consider+  , cactorFreq      :: !(Freqs ItemKind)  -- ^ actor groups to consider   , citemNum        :: !Dice.Dice    -- ^ the number of items in the cave-  , citemFreq       :: !Freqs        -- ^ item groups to consider-  , cplaceFreq      :: !Freqs        -- ^ place groups to consider+  , citemFreq       :: !(Freqs ItemKind)+                                     -- ^ item groups to consider+  , cplaceFreq      :: !(Freqs PlaceKind)+                                     -- ^ place groups to consider   , cpassable       :: !Bool         -- ^ are passable default tiles permitted-  , cdefTile        :: !GroupName    -- ^ the default cave tile-  , cdarkCorTile    :: !GroupName    -- ^ the dark cave corridor tile-  , clitCorTile     :: !GroupName    -- ^ the lit cave corridor tile-  , cfillerTile     :: !GroupName    -- ^ the filler wall-  , couterFenceTile :: !GroupName    -- ^ the outer fence wall-  , clegendDarkTile :: !GroupName    -- ^ the dark place plan legend-  , clegendLitTile  :: !GroupName    -- ^ the lit place plan legend+  , cdefTile        :: !(GroupName TileKind)  -- ^ the default cave tile+  , cdarkCorTile    :: !(GroupName TileKind)  -- ^ the dark cave corridor tile+  , clitCorTile     :: !(GroupName TileKind)  -- ^ the lit cave corridor tile+  , cfillerTile     :: !(GroupName TileKind)  -- ^ the filler wall+  , couterFenceTile :: !(GroupName TileKind)  -- ^ the outer fence wall+  , clegendDarkTile :: !(GroupName TileKind)  -- ^ the dark place plan legend+  , clegendLitTile  :: !(GroupName TileKind)  -- ^ the lit place plan legend   }   deriving Show  -- No Eq and Ord to make extending it logically sound 
Game/LambdaHack/Content/ItemKind.hs view
@@ -1,15 +1,25 @@--- | The type of kinds of weapons, treasure, organs, shrapnel and actors.+{-# LANGUAGE DeriveFunctor, DeriveGeneric #-}+-- | The type of kinds of weapons, treasure, organs, blasts and actors. module Game.LambdaHack.Content.ItemKind-  ( ItemKind(..), toVelocity, toLinger+  ( ItemKind(..)+  , Effect(..), TimerDice(..)+  , Aspect(..), ThrowMod(..)+  , Feature(..), EqpSlot(..)+  , aspectTrav+  , toVelocity, toLinger, toOrganGameTurn, toOrganActorTurn, toOrganNone   , validateSingleItemKind, validateAllItemKind   ) where +import qualified Control.Monad.State as St+import Data.Binary+import Data.Hashable (Hashable) import Data.Text (Text) import qualified Data.Text as T+import GHC.Generics (Generic) import qualified NLP.Miniutter.English as MU +import qualified Game.LambdaHack.Common.Ability as Ability import qualified Game.LambdaHack.Common.Dice as Dice-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Flavour import Game.LambdaHack.Common.Misc @@ -17,33 +27,232 @@ data ItemKind = ItemKind   { isymbol  :: !Char              -- ^ map symbol   , iname    :: !Text              -- ^ generic name-  , ifreq    :: !Freqs             -- ^ frequency within groups+  , 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 for applying and melee   , iweight  :: !Int               -- ^ weight in grams-  , iaspects :: ![Effect.Aspect Dice.Dice]+  , iaspects :: ![Aspect Dice.Dice]                                    -- ^ keep the aspect continuously-  , ieffects :: ![Effect.Effect Dice.Dice]+  , ieffects :: ![Effect]                                    -- ^ cause the effect when triggered-  , ifeature :: ![Effect.Feature]  -- ^ public properties+  , ifeature :: ![Feature]         -- ^ public properties   , idesc    :: !Text              -- ^ description-  , ikit     :: ![(GroupName, CStore)]  -- ^ accompanying organs and items+  , ikit     :: ![(GroupName ItemKind, CStore)]+                                   -- ^ accompanying organs and items   }   deriving Show  -- No Eq and Ord to make extending it logically sound -toVelocity :: Int -> Effect.Feature-toVelocity n = Effect.ToThrow $ Effect.ThrowMod n 100+-- TODO: document each constructor+-- | 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.+data Effect =+    -- Ordinary effects.+    NoEffect !Text+  | Hurt !Dice.Dice+  | Burn !Int  -- Dice.Dice? generalize to other elements? ignite terrain?+  | Explode !(GroupName ItemKind)+                          -- ^ explode, producing this group of blasts+  | RefillHP !Int+  | OverfillHP !Int+  | RefillCalm !Int+  | OverfillCalm !Int+  | Dominate+  | Impress+  | CallFriend !Dice.Dice+  | Summon !(Freqs ItemKind) !Dice.Dice+  | Ascend !Int+  | Escape !Int           -- ^ the Int says if can be placed on last level, etc.+  | Paralyze !Dice.Dice+  | InsertMove !Dice.Dice+  | Teleport !Dice.Dice+  | CreateItem !CStore !(GroupName ItemKind) !TimerDice+                          -- ^ create an item of the group and insert into+                          --   the store with the given random timer+  | DropItem !CStore !(GroupName ItemKind) !Bool+                          -- ^ @DropItem CGround x True@ means stomp on items+  | PolyItem !CStore+  | Identify !CStore+  | 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)+  | Recharging !Effect    -- ^ this effect inactive until timeout passes+  | Temporary !Text       -- ^ the item is temporary, vanishes at even void+                          --   Periodic activation, unless Durable+  deriving (Show, Read, Eq, Ord, Generic) -toLinger :: Int -> Effect.Feature-toLinger n = Effect.ToThrow $ Effect.ThrowMod 100 n+data TimerDice =+    TimerNone+  | TimerGameTurn !Dice.Dice+  | TimerActorTurn !Dice.Dice+  deriving (Read, Eq, Ord, Generic) +instance Show TimerDice where+  show TimerNone = "0"+  show (TimerGameTurn nDm) =+    show nDm ++ " " ++ if nDm == 1 then "turn" else "turns"+  show (TimerActorTurn nDm) =+    show nDm ++ " " ++ if nDm == 1 then "move" else "moves"++-- | Aspects of items. Those that are named @Add*@ are additive+-- (starting at 0) for all items wielded by an actor and they affect the actor.+data Aspect a =+    Periodic           -- ^ in equipment, activate as often as @Timeout@ permits+  | Timeout !a         -- ^ some effects will be disabled until item recharges+  | AddHurtMelee !a    -- ^ percentage damage bonus in melee+  | AddArmorMelee !a   -- ^ percentage armor bonus against melee+  | AddHurtRanged !a   -- ^ percentage damage bonus in ranged+  | AddArmorRanged !a  -- ^ percentage armor bonus against ranged+  | AddMaxHP !a        -- ^ maximal hp+  | AddMaxCalm !a      -- ^ maximal calm+  | AddSpeed !a        -- ^ speed in m/10s+  | AddSkills !Ability.Skills  -- ^ skills in particular abilities+  | AddSight !a        -- ^ FOV radius, where 1 means a single tile+  | AddSmell !a        -- ^ smell radius, where 1 means a single tile+  | AddLight !a        -- ^ light radius, where 1 means a single tile+  deriving (Show, Read, Eq, Ord, Generic, Functor)++-- | Parameters modifying a throw. Not additive and don't start at 0.+data ThrowMod = ThrowMod+  { throwVelocity :: !Int  -- ^ fly with this percentage of base throw speed+  , throwLinger   :: !Int  -- ^ fly for this percentage of 2 turns+  }+  deriving (Show, Read, Eq, Ord, Generic)++-- | Features of item. 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+  | Durable                 -- ^ don't break even when hitting or applying+  | ToThrow !ThrowMod       -- ^ parameters modifying a throw+  | Identified              -- ^ the item starts identified+  | Applicable              -- ^ AI and UI flag: consider applying+  | EqpSlot !EqpSlot !Text  -- ^ AI and UI flag: goes to inventory+  | Precious                -- ^ can't throw or apply if not calm enough;+                            --   AI and UI flag: don't risk identifying by use+  | Tactic !Tactic          -- ^ overrides actor's tactic (TODO)+  deriving (Show, Eq, Ord, Generic)++data EqpSlot =+    EqpSlotPeriodic+  | EqpSlotTimeout+  | EqpSlotAddHurtMelee+  | EqpSlotAddArmorMelee+  | EqpSlotAddHurtRanged+  | EqpSlotAddArmorRanged+  | EqpSlotAddMaxHP+  | EqpSlotAddMaxCalm+  | EqpSlotAddSpeed+  | EqpSlotAddSkills Ability.Ability+  | EqpSlotAddSight+  | EqpSlotAddSmell+  | EqpSlotAddLight+  | EqpSlotWeapon+  deriving (Show, Eq, Ord, Generic)++instance Hashable Effect++instance Hashable TimerDice++instance Hashable a => Hashable (Aspect a)++instance Hashable ThrowMod++instance Hashable Feature++instance Hashable EqpSlot++instance Binary Effect++instance Binary TimerDice++instance Binary a => Binary (Aspect a)++instance Binary ThrowMod++instance Binary Feature++instance Binary EqpSlot++-- TODO: Traversable?+-- | Transform an aspect using a stateful function.+aspectTrav :: Aspect a -> (a -> St.State s b) -> St.State s (Aspect b)+aspectTrav Periodic _ = return Periodic+aspectTrav (Timeout a) f = do+  b <- f a+  return $! Timeout b+aspectTrav (AddMaxHP a) f = do+  b <- f a+  return $! AddMaxHP b+aspectTrav (AddMaxCalm a) f = do+  b <- f a+  return $! AddMaxCalm b+aspectTrav (AddSpeed a) f = do+  b <- f a+  return $! AddSpeed b+aspectTrav (AddSkills as) _ = return $! AddSkills as+aspectTrav (AddHurtMelee a) f = do+  b <- f a+  return $! AddHurtMelee b+aspectTrav (AddHurtRanged a) f = do+  b <- f a+  return $! AddHurtRanged b+aspectTrav (AddArmorMelee a) f = do+  b <- f a+  return $! AddArmorMelee b+aspectTrav (AddArmorRanged a) f = do+  b <- f a+  return $! AddArmorRanged b+aspectTrav (AddSight a) f = do+  b <- f a+  return $! AddSight b+aspectTrav (AddSmell a) f = do+  b <- f a+  return $! AddSmell b+aspectTrav (AddLight a) f = do+  b <- f a+  return $! AddLight b++toVelocity :: Int -> Feature+toVelocity n = ToThrow $ ThrowMod n 100++toLinger :: Int -> Feature+toLinger n = ToThrow $ ThrowMod 100 n++toOrganGameTurn :: GroupName ItemKind -> Dice.Dice -> Effect+toOrganGameTurn grp nDm = CreateItem COrgan grp (TimerGameTurn nDm)++toOrganActorTurn :: GroupName ItemKind -> Dice.Dice -> Effect+toOrganActorTurn grp nDm = CreateItem COrgan grp (TimerActorTurn nDm)++toOrganNone :: GroupName ItemKind -> Effect+toOrganNone grp = CreateItem COrgan grp TimerNone+ -- | Catch invalid item kind definitions. validateSingleItemKind :: ItemKind -> [Text] validateSingleItemKind ItemKind{..} =   [ "iname longer than 23" | T.length iname > 23 ]   ++ validateRarity irarity+  -- Reject duplicate Timeout and Periodic. Otherwise the behaviour+  -- may not agree with the item's in-game description.+  ++ let periodicAspect :: Aspect a -> Bool+         periodicAspect Periodic = True+         periodicAspect _ = False+         ps = filter periodicAspect iaspects+     in if length ps > 1 then ["more than one Periodic specification"] else []+  ++ let timeoutAspect :: Aspect a -> Bool+         timeoutAspect Timeout{} = True+         timeoutAspect _ = False+         ts = filter timeoutAspect iaspects+     in if length ts > 1 then ["more than one Timeout specification"] else []  -- TODO: if "treasure" stays wired-in, assure there are some treasure items -- TODO: (spans multiple contents) check that there is at least one item
Game/LambdaHack/Content/ModeKind.hs view
@@ -1,13 +1,11 @@ {-# LANGUAGE DeriveGeneric #-} -- | The type of kinds of game modes. module Game.LambdaHack.Content.ModeKind-  ( Caves, Roster(..), Player(..), ModeKind(..), Tactic(..)-  , LeaderMode(..), AutoLeader(..)+  ( Caves, Roster(..), Player(..), ModeKind(..), LeaderMode(..), AutoLeader(..)   , validateSingleModeKind, validateAllModeKind   ) where  import Data.Binary-import Data.Hashable (Hashable) import qualified Data.IntMap.Strict as IM import Data.Text (Text) import qualified Data.Text as T@@ -15,14 +13,17 @@ import qualified NLP.Miniutter.English as MU ()  import Game.LambdaHack.Common.Ability+import qualified Game.LambdaHack.Common.Dice as Dice import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg+import Game.LambdaHack.Content.CaveKind+import Game.LambdaHack.Content.ItemKind (ItemKind)  -- | Game mode specification. data ModeKind = ModeKind   { msymbol :: !Char    -- ^ a symbol (matches the keypress, if any)   , mname   :: !Text    -- ^ short description-  , mfreq   :: !Freqs   -- ^ frequency within groups+  , mfreq   :: !(Freqs ModeKind)  -- ^ frequency within groups   , mroster :: !Roster  -- ^ players taking part in the game   , mcaves  :: !Caves   -- ^ arena of the game   , mdesc   :: !Text    -- ^ description@@ -32,64 +33,35 @@ -- | Requested cave groups for particular levels. The second component -- is the @Escape@ feature on the level. @True@ means it's represented -- by @<@, @False@, by @>@.-type Caves = IM.IntMap (GroupName, Maybe Bool)+type Caves = IM.IntMap (GroupName CaveKind, Maybe Bool)  -- | The specification of players for the game mode. data Roster = Roster-  { rosterList  :: ![Player]        -- ^ players in the particular team-  , rosterEnemy :: ![(Text, Text)]  -- ^ the initial enmity matrix-  , rosterAlly  :: ![(Text, Text)]  -- ^ the initial aliance matrix+  { rosterList  :: ![Player Dice.Dice]  -- ^ players in the particular team+  , rosterEnemy :: ![(Text, Text)]      -- ^ the initial enmity matrix+  , rosterAlly  :: ![(Text, Text)]      -- ^ the initial aliance matrix   }   deriving (Show, Eq)  -- | Properties of a particular player.-data Player = Player+data Player a = Player   { fname          :: !Text        -- ^ name of the player-  , fgroup         :: !GroupName   -- ^ name of the monster group to control+  , fgroup         :: !(GroupName ItemKind)  -- ^ name of the monster group to control   , fskillsOther   :: !Skills      -- ^ skills of the other actors   , fcanEscape     :: !Bool        -- ^ the player can escape the dungeon   , fneverEmpty    :: !Bool        -- ^ the faction declared killed if no actors   , fhasNumbers    :: !Bool        -- ^ whether actors have numbers, not symbols   , fhasGender     :: !Bool        -- ^ whether actors have gender   , ftactic        :: !Tactic      -- ^ members behave according to this tactic-  , fentryLevel    :: !Int         -- ^ level where the initial members start-  , finitialActors :: !Int         -- ^ number of initial members+  , fentryLevel    :: !a           -- ^ level where the initial members start+  , finitialActors :: !a           -- ^ number of initial members   , fleaderMode    :: !LeaderMode  -- ^ the mode of switching the leader   , fhasUI         :: !Bool        -- ^ does the faction have a UI client                                    --   (for control or passive observation)   }   deriving (Show, Eq, Generic) -instance Binary Player---- Keep this type here, so that Contents/ is as self-contained as possible.--- TODO: alway shoot, never shoot, etc., but there is too many and this is best--- expressed via skills, and also we risk micromanagement, so let's stop--- and think first; perhaps only have as many tactics as needed for realistic--- AI behaviour in our game modes; perhaps even expose only some of them to UI-data Tactic =-    TBlock    -- ^ always only wait, even if enemy in melee range-  | TFollow   -- ^ always follow leader's target or his position if no target-  | TExplore  -- ^ if enemy nearby, attack, if no items, etc., explore unknown-  | TRoam     -- ^ if enemy nearby, attack, if no items, etc., roam randomly-  | TPatrol   -- ^ find an open and uncrowded area, patrol it according-              --   to sight radius and fallback temporarily to @TRoam@-              --   when enemy is seen by the faction and is within-              --   the actor's sight radius-              --   TODO (currently the same as TExplore; should it chase-              --   targets too (TRoam) and only switch to TPatrol when none?)-  deriving (Eq, Ord, Enum, Bounded, Generic)--instance Show Tactic where-  show TBlock = "block and wait"-  show TFollow = "follow leader's target or position"-  show TExplore = "explore unknown, chase targets"-  show TRoam = "roam freely, chase targets"-  show TPatrol = "find and patrol an area (TODO)"--instance Binary Tactic--instance Hashable Tactic+instance Binary a => Binary (Player a)  -- | If a faction with @LeaderUI@ and @LeaderAI@ has any actor, it has a leader. data LeaderMode =@@ -101,9 +73,22 @@ instance Binary LeaderMode  data AutoLeader = AutoLeader-  { autoDungeon :: !Bool  -- ^ leader change between levels only automatic-  , autoLevel   :: !Bool  -- ^ leader change within a level only automatic-                          --   (for UI, currently no leader change here)+  { autoDungeon :: !Bool+      -- ^ leader switching between levels is automatically done by the server+      --   and client is not permitted to change leaders+      --   (the frequency of leader level switching done by the server+      --   is controlled by @RuleKind.rleadLevelClips@);+      --   if the flag is @False@, server still does a subset+      --   of the automatic switching, e.g., when the old leader dies+      --   and no other actor of the faction resides on his level,+      --   but the client (particularly UI) is expected to do changes as well+  , autoLevel   :: !Bool+      -- ^ leader switching within a level is automatically done by the server+      --   and client is not permitted to change leaders+      --   (server is guaranteed to switch leader within a level very rarely,+      --   e.g., when the old leader dies);+      --   if the flag is @False@, server still does a subset+      --   of the automatic switching, but the client is permitted to do more   }   deriving (Show, Eq, Generic) @@ -135,7 +120,7 @@         ++ concatMap (checkDipl "rosterAlly") rosterAlly  -- Note that @fSkillsOther@ needn't be a subset of @fSkillsLeader@.-validateSinglePlayer :: Caves -> Player -> [Text]+validateSinglePlayer :: Caves -> Player Dice.Dice -> [Text] validateSinglePlayer caves Player{..} =   [ "fname empty:" <+> fname | T.null fname ]   ++ [ "first word of fname longer than 15:" <+> fname@@ -144,8 +129,10 @@      | not fhasUI && case fleaderMode of                        LeaderUI _ -> True                        _ -> False ]-  ++ [ "fentryLevel not among caves:" <+> fname-     | fentryLevel `notElem` IM.keys caves ]+  ++ [ "fentryLevel value not among cave numbers:" <+> fname+     | any (`notElem` IM.keys caves)+           [Dice.minDice fentryLevel+            .. Dice.maxDice fentryLevel] ]  -- simplification  -- | Validate all game mode kinds. Currently always valid. validateAllModeKind :: [ModeKind] -> [Text]
Game/LambdaHack/Content/PlaceKind.hs view
@@ -8,17 +8,18 @@ import qualified Data.Text as T  import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Content.TileKind (TileKind)  -- | Parameters for the generation of small areas within a dungeon level. data PlaceKind = PlaceKind   { psymbol   :: !Char          -- ^ a symbol   , pname     :: !Text          -- ^ short description-  , pfreq     :: !Freqs         -- ^ frequency within groups+  , pfreq     :: !(Freqs PlaceKind)  -- ^ frequency within groups   , prarity   :: !Rarity        -- ^ rarity on given depths   , pcover    :: !Cover         -- ^ how to fill whole place based on the corner   , pfence    :: !Fence         -- ^ whether to fence place with solid border   , ptopLeft  :: ![Text]        -- ^ plan of the top-left corner of the place-  , poverride :: ![(Char, GroupName)]  -- ^ legend override+  , poverride :: ![(Char, GroupName TileKind)]  -- ^ legend override   }   deriving Show  -- No Eq and Ord to make extending it logically sound 
Game/LambdaHack/Content/RuleKind.hs view
@@ -35,7 +35,7 @@ data RuleKind = RuleKind   { rsymbol         :: !Char      -- ^ a symbol   , rname           :: !Text      -- ^ short description-  , rfreq           :: !Freqs     -- ^ frequency within groups+  , rfreq           :: !(Freqs RuleKind)  -- ^ frequency within groups   , raccessible     :: !(Maybe (Point -> Point -> Bool))  -- ^ see above   , raccessibleDoor :: !(Maybe (Point -> Point -> Bool))  -- ^ see above   , rtitle          :: !Text      -- ^ the title of the game@@ -49,7 +49,7 @@                                   --   ends the game   , rfovMode        :: !FovMode   -- ^ FOV calculation mode   , rwriteSaveClips :: !Int       -- ^ game is saved that often-  , rleadLevelClips :: !Int       -- ^ flip AI/spawn leader level that often+  , rleadLevelClips :: !Int       -- ^ server switches leader level that often   , rscoresFile     :: !FilePath  -- ^ name of the scores file   , rsavePrefix     :: !String    -- ^ name of the savefile prefix   , rsharedStash    :: !Bool      -- ^ whether shared stashes are available
Game/LambdaHack/Content/TileKind.hs view
@@ -1,19 +1,24 @@+{-# LANGUAGE DeriveGeneric #-} -- | The type of kinds of terrain tiles. module Game.LambdaHack.Content.TileKind-  ( TileKind(..), validateSingleTileKind, validateAllTileKind, actionFeatures+  ( TileKind(..), Feature(..)+  , validateSingleTileKind, validateAllTileKind, actionFeatures   ) where  import Control.Exception.Assert.Sugar-import Data.Hashable (hash)+import Data.Binary+import Data.Hashable import qualified Data.IntSet as IS import qualified Data.Map.Strict as M import Data.Maybe import Data.Text (Text)+import GHC.Generics (Generic)  import Game.LambdaHack.Common.Color-import qualified Game.LambdaHack.Common.Feature as F import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg+import Game.LambdaHack.Content.ItemKind (ItemKind)+import qualified Game.LambdaHack.Content.ItemKind as IK  -- | The type of kinds of terrain tiles. See @Tile.hs@ for explanation -- of the absence of a corresponding type @Tile@ that would hold@@ -21,20 +26,49 @@ data TileKind = TileKind   { tsymbol  :: !Char         -- ^ map symbol   , tname    :: !Text         -- ^ short description-  , tfreq    :: !Freqs        -- ^ frequency within groups+  , tfreq    :: !(Freqs TileKind)  -- ^ frequency within groups   , tcolor   :: !Color        -- ^ map color   , tcolor2  :: !Color        -- ^ map color when not in FOV-  , tfeature :: ![F.Feature]  -- ^ properties+  , tfeature :: ![Feature]  -- ^ properties   }   deriving Show  -- No Eq and Ord to make extending it logically sound +-- | All possible terrain tile features.+data Feature =+    Embed !(GroupName ItemKind)  -- ^ embed an item of this group, to cause effects (WIP)+  | Cause !IK.Effect             -- ^ causes the effect when triggered;+                                 --   more succint than @Embed@, but will+                                 --   probably get supplanted by @Embed@+  | OpenTo !(GroupName TileKind)    -- ^ goes from a closed to an open tile when altered+  | CloseTo !(GroupName TileKind)   -- ^ goes from an open to a closed tile when altered+  | ChangeTo !(GroupName TileKind)  -- ^ alters tile, but does not change walkability+  | HideAs !(GroupName TileKind)    -- ^ when hidden, looks as a tile of the group+  | RevealAs !(GroupName TileKind)  -- ^ if secret, 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 shine+  | Suspect              -- ^ may not be what it seems (clients only)+  | Impenetrable         -- ^ can never be excavated nor seen through++  | OftenItem            -- ^ initial items often generated there+  | OftenActor           -- ^ initial actors and stairs often generated there+  | NoItem               -- ^ no items ever generated there+  | NoActor              -- ^ no actors nor stairs ever generated there+  | Trail                -- ^ used for visible trails throughout the level+  deriving (Show, Read, Eq, Ord, Generic)++instance Binary Feature++instance Hashable Feature+ -- TODO: (spans multiple contents) check that all posible solid place -- fences have hidden counterparts. -- | Validate a single tile kind. validateSingleTileKind :: TileKind -> [Text] validateSingleTileKind TileKind{..} =-  [ "suspect tile is walkable" | F.Walkable `elem` tfeature-                                 && F.Suspect `elem` tfeature ]+  [ "suspect tile is walkable" | Walkable `elem` tfeature+                                 && Suspect `elem` tfeature ]  -- TODO: verify that OpenTo, CloseTo and ChangeTo are assigned as specified. -- | Validate all tile kinds.@@ -47,7 +81,7 @@ validateAllTileKind :: [TileKind] -> [Text] validateAllTileKind lt =   let listVis f = map (\kt -> ( ( tsymbol kt-                                  , F.Suspect `elem` tfeature kt+                                  , Suspect `elem` tfeature kt                                   , f kt                                   )                                 , [kt] ) ) lt@@ -74,21 +108,21 @@ actionFeatures :: Bool -> TileKind -> IS.IntSet actionFeatures markSuspect t =   let f feat = case feat of-        F.Cause{} -> Just feat-        F.OpenTo{} -> Just $ F.OpenTo ""  -- if needed, remove prefix/suffix-        F.CloseTo{} -> Just $ F.CloseTo ""-        F.ChangeTo{} -> Just $ F.ChangeTo ""-        F.Walkable -> Just feat-        F.Clear -> Just feat-        F.Suspect -> if markSuspect then Just feat else Nothing-        F.Aura{} -> Just feat-        F.Impenetrable -> Just feat-        F.Trail -> Just feat  -- doesn't affect tile behaviour, but important-        F.HideAs{} -> Nothing-        F.RevealAs{} -> Nothing-        F.Dark -> Nothing  -- not important any longer, after FOV computed-        F.OftenItem -> Nothing-        F.OftenActor -> Nothing-        F.NoItem -> Nothing-        F.NoActor -> Nothing+        Embed{} -> Just feat+        Cause{} -> Just feat+        OpenTo{} -> Just $ OpenTo ""  -- if needed, remove prefix/suffix+        CloseTo{} -> Just $ CloseTo ""+        ChangeTo{} -> Just $ ChangeTo ""+        Walkable -> Just feat+        Clear -> Just feat+        Suspect -> if markSuspect then Just feat else Nothing+        Impenetrable -> Just feat+        Trail -> Just feat  -- doesn't affect tile behaviour, but important+        HideAs{} -> Nothing+        RevealAs{} -> Nothing+        Dark -> Nothing  -- not important any longer, after FOV computed+        OftenItem -> Nothing+        OftenActor -> Nothing+        NoItem -> Nothing+        NoActor -> Nothing   in IS.fromList $ map hash $ mapMaybe f $ tfeature t
Game/LambdaHack/Server/Commandline.hs view
@@ -31,9 +31,10 @@         , "  --dumpInitRngs  dump RNG states from the start of the game"         , "  --dbgMsgSer  let the server emit its internal debug messages"         , "  --font fn  use the given font for the main game window"+        , "  --noColorIsBold  don't use bold attribute for colorful characters"         , "  --maxFps n  display at most n frames per second"         , "  --noDelay  don't maintain any requested delays between frames"-        , "  --noMore  auto-answer all prompts"+        , "  --disableAutoYes  never auto-answer all prompts"         , "  --noAnim  don't show any animations"         , "  --savePrefix  prepend the text to all savefile names"         , "  --frontendStd  use the simple stdout/stdin frontend"@@ -43,7 +44,6 @@         , "    Digital"         , "    Permissive"         , "    Shadow"-        , "    Blind"         ]       parseArgs [] = defDebugModeSer       parseArgs ("--knowMap" : rest) =@@ -57,7 +57,7 @@       parseArgs ("--allClear" : rest) =         (parseArgs rest) {sallClear = True}       parseArgs ("--gameMode" : s : rest) =-        (parseArgs rest) {sgameMode = toGroupName (T.pack s)}+        (parseArgs rest) {sgameMode = Just $ toGroupName (T.pack s)}       parseArgs ("--automateAll" : rest) =         (parseArgs rest) {sautomateAll = True}       parseArgs ("--newGame" : rest) =@@ -87,6 +87,10 @@       parseArgs ("--font" : s : rest) =         let debugSer = parseArgs rest         in debugSer {sdebugCli = (sdebugCli debugSer) {sfont = Just s}}+      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 =@@ -94,9 +98,9 @@       parseArgs ("--noDelay" : rest) =         let debugSer = parseArgs rest         in debugSer {sdebugCli = (sdebugCli debugSer) {snoDelay = True}}-      parseArgs ("--noMore" : rest) =+      parseArgs ("--disableAutoYes" : rest) =         let debugSer = parseArgs rest-        in debugSer {sdebugCli = (sdebugCli debugSer) {snoMore = True}}+        in debugSer {sdebugCli = (sdebugCli debugSer) {sdisableAutoYes = True}}       parseArgs ("--noAnim" : rest) =         let debugSer = parseArgs rest         in debugSer {sdebugCli = (sdebugCli debugSer) {snoAnim = Just True}}
Game/LambdaHack/Server/CommonServer.hs view
@@ -11,7 +11,6 @@ import Control.Exception.Assert.Sugar import Control.Monad import qualified Data.EnumMap.Strict as EM-import Data.Int (Int64) import Data.List import Data.Maybe import Data.Text (Text)@@ -22,7 +21,6 @@ import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Color as Color-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Flavour import Game.LambdaHack.Common.Item@@ -40,7 +38,8 @@ import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind+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@@ -57,8 +56,13 @@   body <- getsState $ getActorBody aid   let fid = bfid body       msg = showReqFailure failureSer-  debugPrint $ "execFailure:" <+> msg <> "\n"-               <> tshow body <> "\n" <> tshow req+      impossible = impossibleReqFailure failureSer+      possiblyAlarm = if impossible+                      then debugPossiblyPrintAndExit+                      else debugPossiblyPrint+  possiblyAlarm $+    "execFailure:" <+> msg <> "\n"+    <> tshow body <> "\n" <> tshow req   execSfxAtomic $ SfxMsgFid fid $ "Unexpected problem:" <+> msg <> "."     -- TODO: --more--, but keep in history @@ -94,6 +98,8 @@                               `twith` (lid, fid)) $ EM.lookup lid fper   return $! per +-- We don't provide ActorId, because the actor can be dead and then, e.g.,+-- containers with the ActorId are invalid and lead to crashes. revealItems :: (MonadAtomic m, MonadServer m)             => Maybe FactionId -> Maybe Actor -> m () revealItems mfid mbody = do@@ -117,7 +123,7 @@            => ActorId -> CStore -> CStore -> m () moveStores aid fromStore toStore = do   b <- getsState $ getActorBody aid-  let g iid k = execUpdAtomic $ UpdMoveItem iid k aid fromStore toStore+  let g iid (k, _) = execUpdAtomic $ UpdMoveItem iid k aid fromStore toStore   mapActorCStore_ fromStore g b  quitF :: (MonadAtomic m, MonadServer m)@@ -237,9 +243,9 @@             -> Int        -- ^ digital line parameter             -> ItemId     -- ^ the item to be projected             -> CStore     -- ^ whether the items comes from floor or inventory-            -> Bool       -- ^ whether the item is a shrapnel+            -> Bool       -- ^ whether the item is a blast             -> m (Maybe ReqFailure)-projectFail source tpxy eps iid cstore isShrapnel = do+projectFail source tpxy eps iid cstore isBlast = do   Kind.COps{cotile} <- getsState scops   sb <- getsState $ getActorBody source   let lid = blid sb@@ -250,133 +256,161 @@     Just [] -> assert `failure` "projecting from the edge of level"                       `twith` (spos, tpxy)     Just (pos : restUnlimited) -> do-      item <- getsState $ getItemBody iid-      let fragile = Effect.Fragile `elem` jfeature item-          rest = if fragile-                 then take (chessDist spos tpxy - 1) restUnlimited-                 else restUnlimited-          t = lvl `at` pos-      if not $ Tile.isWalkable cotile t-        then return $ Just ProjectBlockTerrain-        else do-          mab <- getsState $ posToActor pos lid-          actorBlind <- radiusBlind-                        <$> sumOrganEqpServer Effect.EqpSlotAddSight source-          if not $ maybe True (bproj . snd . fst) mab-            then if isShrapnel && bproj sb then do-                   -- Hit the blocking actor.-                   projectBla source spos (pos:rest) iid cstore isShrapnel-                   return Nothing-                 else return $ Just ProjectBlockActor-            else if actorBlind && not (isShrapnel || bproj sb) then-                   return $ Just ProjectBlind-                 else do-                   if isShrapnel && bproj sb && eps `mod` 2 == 0 then-                     -- Make the explosion a bit less regular.-                     projectBla source spos (pos:rest) iid cstore isShrapnel-                   else-                     projectBla source pos rest iid cstore isShrapnel-                   return Nothing+      bag <- getsState $ getActorBag source cstore+      case EM.lookup iid bag of+        Nothing ->  return $ Just ProjectOutOfReach+        Just kit -> do+          itemToF <- itemToFullServer+          activeItems <- activeItemsServer source+          actorSk <- actorSkillsServer source+          let skill = EM.findWithDefault 0 Ability.AbProject actorSk+              itemFull@ItemFull{itemBase} = itemToF iid kit+              forced = isBlast || bproj sb+              legal = permittedProject " " forced skill itemFull sb activeItems+          case legal of+            Left reqFail ->  return $ Just reqFail+            Right _ -> do+              let fragile = IK.Fragile `elem` jfeature itemBase+                  rest = if fragile+                         then take (chessDist spos tpxy - 1) restUnlimited+                         else restUnlimited+                  t = lvl `at` pos+              if not $ Tile.isWalkable cotile t+                then return $ Just ProjectBlockTerrain+                else do+                  mab <- getsState $ posToActor pos lid+                  if not $ maybe True (bproj . snd . fst) mab+                    then if isBlast && bproj sb then do+                           -- Hit the blocking actor.+                           projectBla source spos (pos:rest) iid cstore+                                      isBlast+                           return Nothing+                         else return $ Just ProjectBlockActor+                    else do+                      if isBlast && bproj sb && eps `mod` 2 == 0 then+                        -- Make the explosion a bit less regular.+                        projectBla source spos (pos:rest) iid cstore isBlast+                      else+                        projectBla source pos rest iid cstore isBlast+                      return Nothing + projectBla :: (MonadAtomic m, MonadServer m)            => ActorId    -- ^ actor projecting the item (is on current lvl)            -> Point      -- ^ starting point of the projectile            -> [Point]    -- ^ rest of the trajectory of the projectile            -> ItemId     -- ^ the item to be projected            -> CStore     -- ^ whether the items comes from floor or inventory-           -> Bool       -- ^ whether the item is a shrapnel+           -> Bool       -- ^ whether the item is a blast            -> m ()-projectBla source pos rest iid cstore isShrapnel = do+projectBla source pos rest iid cstore isBlast = do   sb <- getsState $ getActorBody source   item <- getsState $ getItemBody iid   let lid = blid sb   localTime <- getsState $ getLocalTime lid-  unless isShrapnel $ execSfxAtomic $ SfxProject source iid-  addProjectile pos rest iid lid (bfid sb) localTime isShrapnel-  let c = CActor source cstore-  execUpdAtomic $ UpdLoseItem iid item 1 c+  unless isBlast $ execSfxAtomic $ SfxProject source iid cstore+  bag <- getsState $ getActorBag source cstore+  case iid `EM.lookup` bag of+    Nothing -> assert `failure` (source, pos, rest, iid, cstore)+    Just kit@(_, it) -> do+      addProjectile source pos rest iid kit lid (bfid sb) localTime isBlast+      let c = CActor source cstore+      execUpdAtomic $ UpdLoseItem 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)-              => Point -> [Point] -> ItemId -> LevelId -> FactionId+              => ActorId -> Point -> [Point] -> ItemId -> ItemQuant -> LevelId -> FactionId               -> Time -> Bool               -> m ()-addProjectile bpos rest iid blid bfid btime isShrapnel = do+addProjectile source bpos rest iid (_, it) blid bfid btime isBlast = do+  localTime <- getsState $ getLocalTime blid   itemToF <- itemToFullServer-  let itemFull@ItemFull{itemBase} = itemToF iid 1+  let itemFull@ItemFull{itemBase} = itemToF iid (1, take 1 it)       (trajectory, (speed, trange)) = itemTrajectory itemBase (bpos : rest)       adj | trange < 5 = "falling"           | otherwise = "flying"       -- Not much detail about a fast flying item.-      (object1, object2) = partItem CInv $ itemNoDisco (itemBase, 1)+      (object1, object2) = partItem (CActor source CInv) blid localTime $ itemNoDisco (itemBase, 1)       bname = makePhrase [MU.AW $ MU.Text adj, object1, object2]-      tweakBody b = b { bsymbol = if isShrapnel then bsymbol b else '*'-                      , bcolor = if isShrapnel then bcolor b else Color.BrWhite+      tweakBody b = b { bsymbol = if isBlast then bsymbol b else '*'+                      , bcolor = if isBlast then bcolor b else Color.BrWhite                       , bname                       , bhp = 0                       , bproj = True                       , btrajectory = Just (trajectory, speed)-                      , beqp = EM.singleton iid 1+                      , beqp = EM.singleton iid (1, take 1 it)                       , borgan = EM.empty}       bpronoun = "it"   void $ addActorIid iid itemFull-                     bfid bpos blid tweakBody bpronoun btime+                     True bfid bpos blid tweakBody bpronoun btime  addActor :: (MonadAtomic m, MonadServer m)-         => GroupName -> FactionId -> Point -> LevelId+         => GroupName ItemKind -> FactionId -> Point -> LevelId          -> (Actor -> Actor) -> Text -> Time          -> m (Maybe ActorId) addActor actorGroup bfid pos lid tweakBody bpronoun time = do   -- We bootstrap the actor by first creating the trunk of the actor's body   -- contains the constant properties.   let trunkFreq = [(actorGroup, 1)]-  m2 <- rollAndRegisterItem lid trunkFreq (CTrunk bfid lid pos) False+  m2 <- rollAndRegisterItem lid trunkFreq (CTrunk bfid lid pos) False Nothing   case m2 of     Nothing -> return Nothing     Just (trunkId, (trunkFull, _)) ->-      addActorIid trunkId trunkFull bfid pos lid tweakBody bpronoun time+      addActorIid trunkId trunkFull False bfid pos lid tweakBody bpronoun time  addActorIid :: (MonadAtomic m, MonadServer m)-            => ItemId -> ItemFull -> FactionId -> Point -> LevelId+            => ItemId -> ItemFull -> Bool -> FactionId -> Point -> LevelId             -> (Actor -> Actor) -> Text -> Time             -> m (Maybe ActorId)-addActorIid trunkId trunkFull@ItemFull{..}+addActorIid trunkId trunkFull@ItemFull{..} bproj             bfid pos lid tweakBody bpronoun time = do   let trunkKind = case itemDisco of         Just ItemDisco{itemKind} -> itemKind         Nothing -> assert `failure` trunkFull   -- Initial HP and Calm is based only on trunk and ignores organs.-  let hp = xM (max 2 $ sumSlotNoFilter Effect.EqpSlotAddMaxHP [trunkFull])+  let hp = xM (max 2 $ sumSlotNoFilter IK.EqpSlotAddMaxHP [trunkFull])            `div` 2       calm = xM $ max 1-             $ sumSlotNoFilter Effect.EqpSlotAddMaxCalm [trunkFull]+             $ sumSlotNoFilter IK.EqpSlotAddMaxCalm [trunkFull]   -- Create actor.-  Faction{gplayer} <- getsState $ (EM.! bfid) . sfactionD+  factionD <- getsState sfactionD+  let factMine = factionD EM.! bfid   DebugModeSer{sdifficultySer} <- getsServer sdebugSer   nU <- nUI-  -- If no UI factions, the difficulty applies to the escapees (for testing).-  let diffHP | fhasUI gplayer || nU == 0 && fcanEscape gplayer =-        (ceiling :: Double -> Int64)-        $ fromIntegral hp-          * 1.5 ^^ difficultyCoeff sdifficultySer+  -- If difficulty is below standard, HP is added to the UI factions,+  -- otherwise HP is added to their enemies.+  -- If no UI factions, their role is taken by the escapees (for testing).+  let diffBonusCoeff = difficultyCoeff sdifficultySer+      hasUIorEscapes Faction{gplayer} =+        fhasUI gplayer || nU == 0 && fcanEscape gplayer+      boostFact fact = not bproj+                       && if diffBonusCoeff > 0+                          then hasUIorEscapes fact+                          else any hasUIorEscapes+                               $ filter (`isAtWar` bfid) $ EM.elems factionD+      diffHP | boostFact factMine = hp * 2 ^ abs diffBonusCoeff              | otherwise = hp+      bonusHP = fromIntegral $ (diffHP - hp) `divUp` oneM+      healthOrgans = [(Just bonusHP, ("bonus HP", COrgan)) | bonusHP /= 0]       bsymbol = jsymbol itemBase       bname = jname itemBase       bcolor = flavourToColor $ jflavour itemBase       b = actorTemplate trunkId bsymbol bname bpronoun bcolor diffHP calm                         pos lid time bfid       -- Insert the trunk as the actor's organ.-      withTrunk = b {borgan = EM.singleton trunkId itemK}+      withTrunk = b {borgan = EM.singleton trunkId (itemK, itemTimer)}   aid <- getsServer sacounter   modifyServer $ \ser -> ser {sacounter = succ aid}   execUpdAtomic $ UpdCreateActor aid (tweakBody withTrunk) [(trunkId, itemBase)]-  -- Create, register and insert all initial actor items.-  forM_ (ikit trunkKind) $ \(ikText, cstore) -> do+  -- Create, register and insert all initial actor items, including+  -- the bonus health organs from difficulty setting.+  forM_ (healthOrgans ++ map (Nothing,) (IK.ikit trunkKind))+        $ \(mk, (ikText, cstore)) -> do     let container = CActor aid cstore         itemFreq = [(ikText, 1)]-    void $ rollAndRegisterItem lid itemFreq container False+    void $ rollAndRegisterItem lid itemFreq container False mk   return $ Just aid  -- Server has to pick a random weapon or it could leak item discovery@@ -385,16 +419,27 @@ -- unidentified items. pickWeaponServer :: MonadServer m => ActorId -> m (Maybe (ItemId, CStore)) pickWeaponServer source = do-  sb <- getsState $ getActorBody source   eqpAssocs <- fullAssocsServer source [CEqp]   bodyAssocs <- fullAssocsServer source [COrgan]+  actorSk <- actorSkillsServer source+  sb <- getsState $ getActorBody source   -- For projectiles we need to accept even items without any effect,   -- so that the projectile dissapears and NoEffect feedback is produced.   let allAssocs = eqpAssocs ++ bodyAssocs-      strongest | bproj sb = map (1,) eqpAssocs-                | otherwise =-                    strongestSlotNoFilter Effect.EqpSlotWeapon allAssocs-  case strongest of+      calm10 = calmEnough10 sb $ map snd allAssocs+      forced = bproj sb+      permitted = permittedPrecious calm10 forced+      legalPrecious = either (const False) (const True) . permitted+      preferredPrecious = either (const False) id . permitted+      strongest = strongestSlotNoFilter IK.EqpSlotWeapon allAssocs+      strongestLegal = filter (legalPrecious . snd . snd) strongest+      strongestPreferred = filter (preferredPrecious . snd . snd) strongestLegal+      best = case strongestPreferred of+        _ | bproj sb -> map (1,) eqpAssocs+        _ | EM.findWithDefault 0 Ability.AbMelee actorSk <= 0 -> []+        _:_ -> strongestPreferred+        [] -> strongestLegal+  case best of     [] -> return Nothing     iis -> do       let is = map snd iis@@ -404,21 +449,21 @@       return $ Just (iid, cstore)  sumOrganEqpServer :: MonadServer m-                 => Effect.EqpSlot -> ActorId -> m Int+                 => IK.EqpSlot -> ActorId -> m Int sumOrganEqpServer eqpSlot aid = do   activeAssocs <- activeItemsServer aid   return $! sumSlotNoFilter eqpSlot activeAssocs -actorSkillsServer :: MonadServer m-                  => ActorId -> Maybe ActorId -> m Ability.Skills-actorSkillsServer aid mleader = do+actorSkillsServer :: MonadServer m => ActorId -> m Ability.Skills+actorSkillsServer aid  = do   activeItems <- activeItemsServer aid-  getsState $ actorSkills aid mleader activeItems+  body <- getsState $ getActorBody aid+  fact <- getsState $ (EM.! bfid body) . sfactionD+  let mleader = fst <$> gleader fact+  getsState $ actorSkills mleader aid activeItems  maxActorSkillsServer :: MonadServer m                      => ActorId -> m Ability.Skills maxActorSkillsServer aid = do   activeItems <- activeItemsServer aid-  skOther <- getsState $ actorSkills aid Nothing activeItems-  skLeader <- getsState $ actorSkills aid (Just aid) activeItems-  return $! Ability.maxSkills skOther skLeader+  getsState $ maxActorSkills aid activeItems
Game/LambdaHack/Server/DebugServer.hs view
@@ -32,8 +32,8 @@   RespUpdAtomicAI cmdA -> debugPretty cmd cmdA   RespQueryAI aid -> do     d <- debugAid aid "RespQueryAI" cmd-    debugPrint d-  RespPingAI -> debugPrint $ tshow cmd+    serverPrint d+  RespPingAI -> serverPrint $ tshow cmd  debugResponseUI :: MonadServer m => ResponseUI -> m () debugResponseUI cmd = case cmd of@@ -43,29 +43,29 @@   RespUpdAtomicUI cmdA -> debugPretty cmd cmdA   RespSfxAtomicUI sfx -> do     ps <- posSfxAtomic sfx-    debugPrint $ tshow (cmd, ps)-  RespQueryUI -> debugPrint $ "RespQueryUI:" <+> tshow cmd-  RespPingUI -> debugPrint $ tshow cmd+    serverPrint $ tshow (cmd, ps)+  RespQueryUI -> serverPrint $ "RespQueryUI:" <+> tshow cmd+  RespPingUI -> serverPrint $ tshow cmd  debugPretty :: (MonadServer m, Show a) => a -> UpdAtomic -> m () debugPretty cmd cmdA = do   ps <- posUpdAtomic cmdA-  debugPrint $ tshow (cmd, ps)+  serverPrint $ tshow (cmd, ps)  debugPlain :: (MonadServer m, Show a) => a -> UpdAtomic -> m () debugPlain cmd cmdA = do   ps <- posUpdAtomic cmdA-  debugPrint $ T.pack $ show (cmd, ps)  -- too large for pretty show+  serverPrint $ T.pack $ show (cmd, ps)  -- too large for pretty show  debugRequestAI :: MonadServer m => ActorId -> RequestAI -> m () debugRequestAI aid cmd = do   d <- debugAid aid "AI request" cmd-  debugPrint d+  serverPrint d  debugRequestUI :: MonadServer m => ActorId -> RequestUI -> m () debugRequestUI aid cmd = do   d <- debugAid aid "UI request" cmd-  debugPrint d+  serverPrint d  data DebugAid a = DebugAid   { label   :: !Text
Game/LambdaHack/Server/DungeonGen.hs view
@@ -15,8 +15,7 @@ import Data.List import Data.Maybe -import qualified Game.LambdaHack.Common.Effect as Effect-import qualified Game.LambdaHack.Common.Feature as F+import qualified Game.LambdaHack.Content.ItemKind as IK import qualified Game.LambdaHack.Common.Kind as Kind import Game.LambdaHack.Common.Level import Game.LambdaHack.Common.Misc@@ -26,8 +25,10 @@ import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Time import Game.LambdaHack.Content.CaveKind+import Game.LambdaHack.Content.ItemKind (ItemKind) import Game.LambdaHack.Content.ModeKind-import Game.LambdaHack.Content.TileKind+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.Cave import Game.LambdaHack.Server.DungeonGen.Place@@ -92,12 +93,12 @@   let dist cmin l _ = all (\pos -> chessDist l pos > cmin) ps   findPosTry 1000 cmap     (\p t -> Tile.isWalkable cotile t-             && not (Tile.hasFeature cotile F.NoActor t)+             && not (Tile.hasFeature cotile TK.NoActor t)              && dist 0 p t)  -- can't overwrite stairs with other stairs     [ dist $ cminStairDist     , dist $ cminStairDist `div` 2     , dist $ cminStairDist `div` 4-    , const $ Tile.hasFeature cotile F.OftenActor+    , const $ Tile.hasFeature cotile TK.OftenActor     , dist $ cminStairDist `div` 8     ] @@ -113,18 +114,18 @@       fitArea pos = inside pos . fromArea . qarea       findLegend pos = maybe clegendLitTile qlegend                        $ find (fitArea pos) dplaces-      hasEscape p t = Tile.kindHasFeature (F.Cause $ Effect.Escape p) t-      ascendable  = Tile.kindHasFeature $ F.Cause (Effect.Ascend 1)-      descendable = Tile.kindHasFeature $ F.Cause (Effect.Ascend (-1))-      nightCond kt = (not (Tile.kindHasFeature F.Clear kt)+      hasEscape p t = Tile.kindHasFeature (TK.Cause $ IK.Escape p) t+      ascendable  = Tile.kindHasFeature $ TK.Cause (IK.Ascend 1)+      descendable = Tile.kindHasFeature $ TK.Cause (IK.Ascend (-1))+      nightCond kt = (not (Tile.kindHasFeature TK.Clear kt)                       || (if dnight then id else not)-                            (Tile.kindHasFeature F.Dark kt))+                            (Tile.kindHasFeature TK.Dark kt))       dcond kt = (cpassable-                  || not (Tile.kindHasFeature F.Walkable kt))+                  || not (Tile.kindHasFeature TK.Walkable kt))                  && nightCond kt       pickDefTile = fmap (fromMaybe $ assert `failure` cdefTile)                     $ opick cdefTile dcond-      wcond kt = Tile.kindHasFeature F.Walkable kt+      wcond kt = Tile.kindHasFeature TK.Walkable kt                  && nightCond kt       mpickWalkable =         if cpassable@@ -207,7 +208,7 @@ -- | Build rudimentary level from a cave kind. levelFromCaveKind :: Kind.COps                   -> CaveKind -> AbsDepth -> TileMap -> ([Point], [Point])-                  -> Int -> Freqs -> Int -> Freqs -> Int -> Bool+                  -> Int -> Freqs ItemKind -> Int -> Freqs ItemKind -> Int -> Bool                   -> Level levelFromCaveKind Kind.COps{cotile}                   CaveKind{..}@@ -217,6 +218,7 @@         { ldepth         , lprio = EM.empty         , lfloor = EM.empty+        , lembed = EM.empty  -- is populated inside $MonadServer$         , ltile         , lxsize = cxsize         , lysize = cysize@@ -240,7 +242,7 @@   in lvl {lclear}  findGenerator :: Kind.COps -> LevelId -> LevelId -> LevelId -> AbsDepth -> Int-              -> (GroupName, Maybe Bool)+              -> (GroupName CaveKind, Maybe Bool)               -> Rnd Level findGenerator cops ln minD maxD totalDepth nstairUp               (genName, escapeFeature) = do@@ -269,7 +271,7 @@       freshTotalDepth = assert (signum minD == signum maxD)                         $ AbsDepth                         $ max 10 $ max (abs minD) (abs maxD)-  let gen :: (Int, [(LevelId, Level)]) -> (Int, (GroupName, Maybe Bool))+  let gen :: (Int, [(LevelId, Level)]) -> (Int, (GroupName CaveKind, Maybe Bool))           -> Rnd (Int, [(LevelId, Level)])       gen (nstairUp, l) (n, caveTB) = do         let ln = toEnum n
Game/LambdaHack/Server/DungeonGen/Cave.hs view
@@ -20,7 +20,7 @@ import Game.LambdaHack.Common.Vector import Game.LambdaHack.Content.CaveKind import Game.LambdaHack.Content.PlaceKind-import Game.LambdaHack.Content.TileKind+import Game.LambdaHack.Content.TileKind (TileKind) import Game.LambdaHack.Server.DungeonGen.Area import Game.LambdaHack.Server.DungeonGen.AreaRnd import Game.LambdaHack.Server.DungeonGen.Place
Game/LambdaHack/Server/DungeonGen/Place.hs view
@@ -20,7 +20,8 @@ import Game.LambdaHack.Common.Random import Game.LambdaHack.Content.CaveKind import Game.LambdaHack.Content.PlaceKind-import Game.LambdaHack.Content.TileKind+import Game.LambdaHack.Content.TileKind (TileKind)+import qualified Game.LambdaHack.Content.TileKind as TK import Game.LambdaHack.Server.DungeonGen.Area  -- TODO: use more, rewrite as needed, document each field.@@ -30,7 +31,7 @@   { qkind    :: !(Kind.Id PlaceKind)   , qarea    :: !Area   , qseen    :: !Bool-  , qlegend  :: !GroupName+  , qlegend  :: !(GroupName TileKind)   , qFWall   :: !(Kind.Id TileKind)   , qFFloor  :: !(Kind.Id TileKind)   , qFGround :: !(Kind.Id TileKind)@@ -149,22 +150,22 @@   return (tmap, place)  -- | Roll a legend of a place plan: a map from plan symbols to tile kinds.-olegend :: Kind.COps -> GroupName+olegend :: Kind.COps -> GroupName TileKind         -> Rnd (EM.EnumMap Char (Kind.Id TileKind)) olegend Kind.COps{cotile=Kind.Ops{ofoldrWithKey, opick}} cgroup =   let getSymbols _ tk acc =-        maybe acc (const $ ES.insert (tsymbol tk) acc)-          (lookup cgroup $ tfreq tk)+        maybe acc (const $ ES.insert (TK.tsymbol tk) acc)+          (lookup cgroup $ TK.tfreq tk)       symbols = ofoldrWithKey getSymbols ES.empty       getLegend s acc = do         m <- acc         tk <- fmap (fromMaybe $ assert `failure` (cgroup, s))-              $ opick cgroup $ (== s) . tsymbol+              $ opick cgroup $ (== s) . TK.tsymbol         return $! EM.insert s tk m       legend = ES.foldr getLegend (return EM.empty) symbols   in legend -ooverride :: Kind.COps -> [(Char, GroupName)]+ooverride :: Kind.COps -> [(Char, GroupName TileKind)]           -> Rnd (EM.EnumMap Char (Kind.Id TileKind)) ooverride Kind.COps{cotile=Kind.Ops{opick}} poverride =   let getLegend (s, cgroup) acc = do@@ -185,7 +186,7 @@                    | x <- [x0-1..x1+1], y <- [y0-1, y1+1] ]  -- | Construct a fence around an area, with the given tile group.-buildFenceRnd :: Kind.COps -> GroupName -> Area -> Rnd TileMapEM+buildFenceRnd :: Kind.COps -> GroupName TileKind -> Area -> Rnd TileMapEM buildFenceRnd Kind.COps{cotile=Kind.Ops{opick}} couterFenceTile area = do   let (x0, y0, x1, y1) = fromArea area       fenceIdRnd (xf, yf) = do
Game/LambdaHack/Server/EndServer.hs view
@@ -1,7 +1,7 @@ -- | The main loop of the server, processing human and computer player -- moves turn by turn. module Game.LambdaHack.Server.EndServer-  ( endOrLoop, dieSer, dropEqpItems+  ( endOrLoop, dieSer   ) where  import Control.Monad@@ -49,8 +49,9 @@     modifyServer $ \ser -> ser {swriteSave = False}     gameSave   case (quitters, campers) of-    (sgameMode : _, _) -> do-      modifyServer $ \ser -> ser {sdebugNxt = (sdebugNxt ser) {sgameMode}}+    (gameMode : _, _) -> do+      modifyServer $ \ser -> ser {sdebugNxt = (sdebugNxt ser)+                                                {sgameMode = Just gameMode}}       restart     _ | gameOver -> restart     ([], []) -> loop  -- continue current game@@ -61,7 +62,7 @@   -- TODO: clients don't see the death of their last standing actor;   --       modify Draw.hs and Client.hs to handle that   if bproj b then do-    dropEqpItems aid b hit+    dropAllItems aid b hit     b2 <- getsState $ getActorBody aid     execUpdAtomic $ UpdDestroyActor aid b2 []   else do@@ -70,26 +71,22 @@     let ikind = discoKind EM.! jkindIx trunk     execUpdAtomic $ UpdRecordKill aid ikind 1     electLeader (bfid b) (blid b) aid-    equipAllItems aid b-    dropEqpItems aid b False+    dropAllItems aid b False     b2 <- getsState $ getActorBody aid     execUpdAtomic $ UpdDestroyActor aid b2 []     deduceKilled b -equipAllItems :: (MonadAtomic m, MonadServer m)-              => ActorId -> Actor -> m ()-equipAllItems aid b = do+-- | Drop all actor's items.+dropAllItems :: (MonadAtomic m, MonadServer m)+             => ActorId -> Actor -> Bool -> m ()+dropAllItems aid b hit = do   fact <- getsState $ (EM.! bfid b) . sfactionD   -- A faction that is defeated, leaderless or with temporarlity no member   -- drops all items from the faction stash, too.-  when (isNothing $ gleader fact) $ moveStores aid CSha CEqp-  moveStores aid CInv CEqp---- | Drop all actor's items. If the actor hits another actor and this--- collision results in all item being dropped, all items are destroyed.--- If the actor does not hit, but dies, only fragile items are destroyed--- and only if the actor was a projectile (and so died by dropping--- to the ground due to exceeded range or bumping off an obstacle).-dropEqpItems :: (MonadAtomic m, MonadServer m)-             => ActorId -> Actor -> Bool -> m ()-dropEqpItems aid b hit = mapActorCStore_ CEqp (dropEqpItem aid b hit) b+  -- 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 (not (bproj b) && isNothing (gleader fact)) $+    mapActorCStore_ CSha (dropCStoreItem CSha aid b hit) b+  mapActorCStore_ CInv (dropCStoreItem CInv aid b hit) b+  mapActorCStore_ CEqp (dropCStoreItem CEqp aid b hit) b
Game/LambdaHack/Server/Fov.hs view
@@ -16,11 +16,11 @@ import qualified Data.EnumSet as ES import Data.Function import Data.List+import Data.Maybe import Data.Ord  import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemStrongest@@ -29,9 +29,11 @@ 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 qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Vector+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.RuleKind import Game.LambdaHack.Server.Fov.Common import qualified Game.LambdaHack.Server.Fov.Digital as Digital@@ -52,16 +54,19 @@ type ActorEqpBody = [((ActorId, Actor), [ItemFull])]  type PersLit = EML.EnumMap LevelId ( PerceptionLit-                                   , EM.EnumMap FactionId ActorEqpBody )+                                   , EM.EnumMap FactionId ActorEqpBody+                                   , PointArray.Array Bool )  -- | Calculate faction's perception of a level.-levelPerception :: Kind.COps -> PerceptionLit -> ActorEqpBody+levelPerception :: Kind.COps+                -> PerceptionLit -> ActorEqpBody -> PointArray.Array Bool                 -> FovMode -> Level                 -> Perception-levelPerception cops litHere actorEqpBody fovMode lvl@Level{lxsize, lysize} =+levelPerception cops litHere actorEqpBody blockers+                fovMode lvl@Level{lxsize, lysize} =   let -- Dying actors included, to let them see their own demise.       ours = filter (not . bproj . snd . fst) actorEqpBody-      ourR = preachable . reachableFromActor cops fovMode lvl+      ourR = preachable . reachableFromActor blockers fovMode       totalReachable = PerceptionReachable $ concatMap ourR ours       pAndVicinity p = p : vicinity lxsize lysize p       -- All actors feel adjacent positions, even dark (for easy exploration).@@ -69,7 +74,7 @@       nocto = concat $ map fst noctoBodies       ptotal = visibleOnLevel cops totalReachable litHere nocto lvl       canSmellAround (_, allAssocs) =-        let radius = sumSlotNoFilter Effect.EqpSlotAddSmell allAssocs+        let radius = sumSlotNoFilter IK.EqpSlotAddSmell allAssocs         in radius >= 2       -- TODO: handle smell radius < 2, that is only under the actor       -- TODO: filter out tiles that are solid and so can't hold smell.@@ -77,14 +82,14 @@                $ concat $ map fst $ filter (canSmellAround . snd) noctoBodies   in Perception ptotal psmell --- | Calculate faction's perception of a level based on the lit tiles cache..+-- | Calculate faction's perception of a level based on the lit tiles cache. fidLidPerception :: Kind.COps -> FovMode -> PersLit                  -> FactionId -> LevelId -> Level                  -> Perception fidLidPerception cops fovMode persLit fid lid lvl =-  let (litHere, bodyMap) = persLit EML.! lid+  let (litHere, bodyMap, blockers) = persLit EML.! lid       actorEqpBody = EM.findWithDefault [] fid bodyMap-  in levelPerception cops litHere actorEqpBody fovMode lvl+  in levelPerception cops litHere actorEqpBody blockers fovMode lvl  -- | Calculate perception of a faction. factionPerception :: FovMode -> PersLit -> FactionId -> State -> FactionPers@@ -109,30 +114,32 @@ visibleOnLevel Kind.COps{cotile}                PerceptionReachable{preachable} PerceptionLit{plit}                nocto lvl =+  -- TODO: costly+  -- TODO: make a vector mask from isVisible; possibly of PerceptionVisible, too   let isVisible pos = Tile.isLit cotile (lvl `at` pos) || pos `ES.member` plit   in PerceptionVisible $ ES.fromList $ nocto ++ filter isVisible preachable  -- | Compute positions reachable by the actor. Reachable are all fields -- on a visually unblocked path from the actor position.-reachableFromActor :: Kind.COps -> FovMode -> Level+reachableFromActor :: PointArray.Array Bool -> FovMode                    -> ((ActorId, Actor), [ItemFull])                    -> PerceptionReachable-reachableFromActor cops fovMode lvl ((_, body), allItems) =-  let sumSight = sumSlotNoFilter Effect.EqpSlotAddSight allItems+reachableFromActor blockers fovMode ((_, body), allItems) =+  let sumSight = sumSlotNoFilter IK.EqpSlotAddSight allItems       radius = min (fromIntegral $ bcalm body `div` (5 * oneM)) sumSight-  in PerceptionReachable $ fullscan cops fovMode radius (bpos body) lvl+  in PerceptionReachable $ fullscan blockers fovMode radius (bpos body)  -- | Compute all lit positions on a level, whether lit by actors or floor items. -- Note that an actor can be blind or a projectile, in which case he doesn't see -- his own light (but others, from his or other factions, possibly do).-litByItems :: Kind.COps -> FovMode -> Level+litByItems :: Kind.COps -> PointArray.Array Bool -> FovMode -> Level            -> [(Point, [ItemFull])]            -> PerceptionLit-litByItems cops@Kind.COps{cotile} fovMode lvl allItems =+litByItems Kind.COps{cotile} blockers fovMode lvl allItems =   let litPos :: (Point, [ItemFull]) -> [Point]       litPos (p, is) =-        let radius = sumSlotNoFilter Effect.EqpSlotAddLight is-            scan = fullscan cops fovMode radius p lvl+        let radius = sumSlotNoFilter IK.EqpSlotAddLight is+            scan = fullscan blockers fovMode radius p             -- Optimization: filter out positions already having ambient light.             opt = filter (\pos -> not $ Tile.isLit cotile $ lvl `at` pos) scan         in opt@@ -142,7 +149,7 @@ -- | Compute all lit positions in the dungeon litInDungeon :: FovMode -> State -> StateServer -> PersLit litInDungeon fovMode s ser =-  let cops = scops s+  let cops@Kind.COps{cotile} = scops s       itemsInActors :: Level -> EM.EnumMap FactionId ActorEqpBody       itemsInActors lvl =         let asLid = map (\aid -> (aid, getActorBody aid s))@@ -161,23 +168,31 @@         in EM.fromDistinctAscList $ map bodyFid asGrouped       itemsOnFloor :: Level -> [(Point, [ItemFull])]       itemsOnFloor lvl =-        let iToFull (iid, (item, k)) =-              itemToFull cops (sdiscoKind ser) (sdiscoEffect ser) iid item k+        let iToFull (iid, (item, kit)) =+              itemToFull cops (sdiscoKind ser) (sdiscoEffect ser) iid item kit             processPos (p, bag) =               (p, map iToFull $ bagAssocsK s bag)-        in map processPos $ EM.assocs $ lfloor lvl+        in map processPos $ EM.assocs $ lfloor lvl  -- lembed are covered       -- Note that an actor can be blind or a projectile,       -- in which case he doesn't see his own light       -- (but others, from his or other factions, possibly do).       litOnLevel :: Level -> ( PerceptionLit-                             , EM.EnumMap FactionId ActorEqpBody )-      litOnLevel lvl =+                             , EM.EnumMap FactionId ActorEqpBody+                             , PointArray.Array Bool )+      litOnLevel lvl@Level{ltile} =         let bodyMap = itemsInActors lvl             allBodies = concat $ EM.elems bodyMap+            blockingTiles = PointArray.mapA (Tile.isClear cotile) ltile+            blockFromBody ((_, b), _) =+              if bproj b then Nothing else Just (bpos b, False)+            -- TODO: keep it in server state and update when tiles change+            -- and actors are born/move/die. Actually, do this for PersLit.+            blockingActors = mapMaybe blockFromBody allBodies+            blockers = blockingTiles PointArray.// blockingActors             actorItems = map (\((_, b), iis) -> (bpos b, iis)) allBodies             floorItems = itemsOnFloor lvl             allItems = floorItems ++ actorItems-        in (litByItems cops fovMode lvl allItems, bodyMap)+        in (litByItems cops blockers fovMode lvl allItems, bodyMap, blockers)       litLvl (lid, lvl) = (lid, litOnLevel lvl)   in EML.fromDistinctAscList $ map litLvl $ EM.assocs $ sdungeon s @@ -185,13 +200,12 @@ -- that are currently in the field of view. The Field of View -- algorithm to use is passed in the second argument. -- The actor's own position is considred reachable by him.-fullscan :: Kind.COps  -- ^ tile content, determines clear tiles+fullscan :: PointArray.Array Bool  -- ^ the array with non-clear points          -> FovMode    -- ^ scanning mode          -> Int        -- ^ scanning radius          -> Point      -- ^ position of the spectator-         -> Level      -- ^ the map that is scanned          -> [Point]-fullscan Kind.COps{cotile} fovMode radius spectatorPos lvl =+fullscan blockers fovMode radius spectatorPos =   if radius <= 0 then []   else if radius == 1 then [spectatorPos]   else spectatorPos : case fovMode of@@ -203,7 +217,8 @@       concatMap (\tr -> map tr (Digital.scan (radius - 1) (isCl . tr))) tr4  where   isCl :: Point -> Bool-  isCl = Tile.isClear cotile . (lvl `at`)+  {-# INLINE isCl #-}+  isCl = (blockers PointArray.!)    -- This function is cheap, so no problem it's called twice   -- for each point: once with @isCl@, once via @concatMap@.@@ -213,6 +228,7 @@    -- | The translation, rotation and symmetry functions for octants.   tr8 :: [(Distance, Progress) -> Point]+  {-# INLINE tr8 #-}   tr8 =     [ \(p, d) -> trV   p    d     , \(p, d) -> trV (-p)   d@@ -226,6 +242,7 @@    -- | The translation and rotation functions for quadrants.   tr4 :: [Bump -> Point]+  {-# INLINE tr4 #-}   tr4 =     [ \B{..} -> trV   bx  (-by)  -- quadrant I     , \B{..} -> trV   by    bx   -- II (we rotate counter-clockwise)
Game/LambdaHack/Server/Fov/Digital.hs view
@@ -15,6 +15,7 @@ scan :: Distance        -- ^ visiblity distance      -> (Bump -> Bool)  -- ^ clear tile predicate      -> [Bump]+{-# INLINE scan #-} scan r isClear = assert (r > 0 `blame` r) $   -- The scanned area is a square, which is a sphere in the chessboard metric.   dscan 1 ( (Line (B 1 0) (B (-r) r), [B 0 0])@@ -24,14 +25,14 @@   dscan d ( s0@(sl{-shallow line-}, sHull0)           , e@(el{-steep line-}, eHull) ) = -    let ps0 = let (n, k) = intersect sl d  -- minimal progress to consider-              in n `div` k-        pe = let (n, k) = intersect el d   -- maximal progress to consider-               -- Corners obstruct view, so the steep line, constructed-               -- from corners, is itself not a part of the view,-               -- so if its intersection with the line of diagonals is only-               -- at a corner, choose the diamond leading to a smaller view.-             in -1 + n `divUp` k+    let !ps0 = let (n, k) = intersect sl d  -- minimal progress to consider+               in n `div` k+        !pe = let (n, k) = intersect el d   -- maximal progress to consider+                -- Corners obstruct view, so the steep line, constructed+                -- from corners, is itself not a part of the view,+                -- so if its intersection with the line of diagonals is only+                -- at a corner, choose the diamond leading to a smaller view.+              in -1 + n `divUp` k         inside = [B p d | p <- [ps0..pe]]         outside           | d >= r = []@@ -40,17 +41,21 @@          -- We're in a visible interval.         mscanVisible :: Edge -> Progress -> [Bump]-        mscanVisible s@(_, sHull) ps-          | ps > pe = dscan (d+1) (s, e)       -- reached end, scan next-          | not $ isClear steepBump =          -- entering shadow-              mscanShadowed (ps+1)-              ++ dscan (d+1) (s, (dline nep steepBump, neHull))-          | otherwise = mscanVisible s (ps+1)  -- continue in visible area+        {-# INLINE mscanVisible #-}+        mscanVisible s = go          where-          steepBump = B ps d-          gte = dsteeper steepBump-          nep = maximal gte sHull-          neHull = addHull gte steepBump eHull+          go ps | ps > pe = dscan (d+1) (s, e)       -- reached end, scan next+                | not $ isClear steepBump =          -- entering shadow+                    mscanShadowed (ps+1)+                    ++ dscan (d+1) (s, (dline nep steepBump, neHull))+                | otherwise = go (ps+1)  -- continue in visible area+           where+            steepBump = B ps d+            gte :: Bump -> Bump -> Bool+            {-# INLINE gte #-}+            gte = dsteeper steepBump+            nep = maximal gte (snd s)+            neHull = addHull gte steepBump eHull          -- We're in a shadowed interval.         mscanShadowed :: Progress -> [Bump]@@ -61,6 +66,8 @@           | otherwise = mscanShadowed (ps+1)   -- continue in shadow          where           shallowBump = B ps d+          gte :: Bump -> Bump -> Bool+          {-# INLINE gte #-}           gte = flip $ dsteeper shallowBump           nsp = maximal gte eHull           nsHull = addHull gte shallowBump sHull0
Game/LambdaHack/Server/HandleEffectServer.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE TupleSections #-} -- | Handle effects (most often caused by requests sent by clients). module Game.LambdaHack.Server.HandleEffectServer-  ( applyItem, itemEffect, itemEffectAndDestroy, effectsSem-  , dropEqpItem, armorHurtBonus+  ( applyItem, itemEffectAndDestroy, effectAndDestroy, itemEffectCause+  , dropCStoreItem, armorHurtBonus   ) where  import Control.Applicative@@ -10,7 +10,9 @@ import Control.Monad import Data.Bits (xor) import qualified Data.EnumMap.Strict as EM+import qualified Data.HashMap.Strict as HM import Data.Key (mapWithKeyM_)+import Data.List import Data.Maybe import qualified NLP.Miniutter.English as MU @@ -18,9 +20,7 @@ import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Dice as Dice-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction-import qualified Game.LambdaHack.Common.Feature as F import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemDescription import Game.LambdaHack.Common.ItemStrongest@@ -36,8 +36,10 @@ import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Time import Game.LambdaHack.Common.Vector-import Game.LambdaHack.Content.ItemKind+import Game.LambdaHack.Content.ItemKind (ItemKind)+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.ModeKind+import qualified Game.LambdaHack.Content.TileKind as TK import Game.LambdaHack.Server.CommonServer import Game.LambdaHack.Server.ItemServer import Game.LambdaHack.Server.MonadServer@@ -50,50 +52,118 @@ applyItem :: (MonadAtomic m, MonadServer m)           => ActorId -> ItemId -> CStore -> m () applyItem aid iid cstore = do-  itemToF <- itemToFullServer-  bag <- getsState $ getActorBag aid cstore-  let k = bag EM.! iid-      itemFull = itemToF iid k-  execSfxAtomic $ SfxActivate aid iid 1-  itemEffectAndDestroy aid aid iid itemFull cstore+  execSfxAtomic $ SfxActivate aid iid cstore+  let c = CActor aid cstore+  itemEffectAndDestroy aid aid iid c  itemEffectAndDestroy :: (MonadAtomic m, MonadServer m)-                     => ActorId -> ActorId -> ItemId -> ItemFull -> CStore+                     => ActorId -> ActorId -> ItemId -> Container                      -> m ()-itemEffectAndDestroy source target iid itemFull cstore = do-  -- We have to destroy the item before the effect affects the item-  -- or the actor holding it or standing on it (later on we could-  -- lose track of the item and wouldn't be able to destroy it) .-  -- This is OK, because we don't remove the item type from various-  -- item dictionaries, just an individual copy from the container,-  -- so, e.g., the item can be identified after it's removed.-  let item = itemBase itemFull-      durable = Effect.Durable `elem` jfeature item-      periodic = isJust $ strengthFromEqpSlot Effect.EqpSlotPeriodic itemFull-      c = CActor source cstore-  unless (durable && periodic) $ do-    when (not durable) $-      execUpdAtomic $ UpdLoseItem iid item 1 c-    triggered <- itemEffect source target iid itemFull False False+itemEffectAndDestroy source target iid c = do+  discoEffect <- getsServer sdiscoEffect+  case EM.lookup iid discoEffect of+    Just ItemAspectEffect{jeffects, jaspects} -> do+      bag <- getsState $ getCBag c+      case iid `EM.lookup` bag of+        Nothing -> assert `failure` (source, target, iid, c)+        Just kit ->+          effectAndDestroy source target iid c False jeffects jaspects kit+    _ -> assert `failure` (source, target, iid, c)++effectAndDestroy :: (MonadAtomic m, MonadServer m)+                 => ActorId -> ActorId -> ItemId -> Container -> Bool+                 -> [IK.Effect] -> [IK.Aspect Int] -> ItemQuant+                 -> m ()+effectAndDestroy source target iid c periodic effs aspects kitK@(k, it) = do+  let mtimeout = let timeoutAspect :: IK.Aspect a -> Bool+                     timeoutAspect IK.Timeout{} = True+                     timeoutAspect _ = False+                 in find timeoutAspect aspects+  lid <- getsState $ lidFromC c+  localTime <- getsState $ getLocalTime lid+  let it1 = case mtimeout of+        Just (IK.Timeout timeout) ->+          let timeoutTurns = timeDeltaScale (Delta timeTurn) timeout+              pending startT = timeShift startT timeoutTurns > localTime+          in filter pending it+        _ -> []+      len = length it1+      recharged = len < k+  assert (len <= k `blame` (kitK, source, target, iid, c)) skip+  -- If there is no Timeout, but there are Recharging,+  -- then such effects are disabled whenever the item is affected+  -- by a Discharge attack (TODO).+  it2 <- case mtimeout of+    Just (IK.Timeout _) | recharged ->+      return $ localTime : it1+    _ ->+      -- TODO: if has timeout and not recharged, report failure+      return it1+  when (it /= it2 && mtimeout /= Just (IK.Timeout 0)) $+    execUpdAtomic $ UpdTimeItem iid c it it2+  -- If the activation is not periodic, trigger at least the effects+  -- that are not recharging and so don't depend on @recharged@.+  when (not periodic || recharged) $ do+    -- We have to destroy the item before the effect affects the item+    -- or the actor holding it or standing on it (later on we could+    -- lose track of the item and wouldn't be able to destroy it) .+    -- This is OK, because we don't remove the item type from various+    -- item dictionaries, just an individual copy from the container,+    -- so, e.g., the item can be identified after it's removed.+    let mtmp = let tmpEffect :: IK.Effect -> Bool+                   tmpEffect IK.Temporary{} = True+                   tmpEffect (IK.Recharging IK.Temporary{}) = True+                   tmpEffect (IK.OnSmash IK.Temporary{}) = True+                   tmpEffect _ = False+               in find tmpEffect effs+    item <- getsState $ getItemBody iid+    let durable = IK.Durable `elem` jfeature item+        imperishable = durable || periodic && isNothing mtmp+        kit = if isNothing mtmp || periodic then (1, take 1 it2) else (k, it2)+    unless imperishable $+      execUpdAtomic $ UpdLoseItem iid item kit c+    -- At this point, the item is potentially no longer in container @c@,+    -- so we don't pass @c@ along.+    triggered <- itemEffectDisco source target iid recharged periodic effs     -- If none of item's effects was performed, we try to recreate the item.     -- Regardless, we don't rewind the time, because some info is gained     -- (that the item does not exhibit any effects in the given context).-    when (not triggered && not durable) $-      execUpdAtomic $ UpdSpotItem iid item 1 c+    unless (triggered || imperishable) $+      execUpdAtomic $ UpdSpotItem iid item kit c +itemEffectCause :: (MonadAtomic m, MonadServer m)+                => ActorId -> Point -> IK.Effect+                -> m Bool+itemEffectCause aid tpos ef = do+  sb <- getsState $ getActorBody aid+  let c = CEmbed (blid sb) tpos+  bag <- getsState $ getCBag c+  case EM.assocs bag of+    [(iid, kit)] -> do+      -- No block against tile, hence unconditional.+      discoEffect <- getsServer sdiscoEffect+      let aspects = case EM.lookup iid discoEffect of+            Just ItemAspectEffect{jaspects} -> jaspects+            _ -> assert `failure` (aid, tpos, ef, iid)+      execSfxAtomic $ SfxTrigger aid tpos $ TK.Cause ef+      effectAndDestroy aid aid iid c False [ef] aspects kit+      return True+    ab -> assert `failure` (aid, tpos, ab)+ -- | The source actor affects the target actor, with a given item.--- If any of the effect effect fires up, the item gets identified. This function+-- If any of the effects fires up, the item gets identified. This function -- is mutually recursive with @effect@ and so it's a part of @Effect@ -- semantics.-itemEffect :: (MonadAtomic m, MonadServer m)-           => ActorId -> ActorId -> ItemId -> ItemFull -> Bool -> Bool-           -> m Bool-itemEffect source target iid itemFull onSmash periodic = do-  case itemDisco itemFull of-    Just ItemDisco{itemKindId, itemAE=Just ItemAspectEffect{jeffects}} -> do-      let effs | onSmash = strengthOnSmash itemFull-               | otherwise = jeffects-      triggered <- effectsSem effs source target periodic+itemEffectDisco :: (MonadAtomic m, MonadServer m)+                => ActorId -> ActorId -> ItemId -> Bool -> Bool+                -> [IK.Effect]+                -> m Bool+itemEffectDisco source target iid recharged periodic effs = do+  discoKind <- getsServer sdiscoKind+  item <- getsState $ getItemBody iid+  case EM.lookup (jkindIx item) discoKind of+    Just itemKindId -> do+      triggered <- itemEffect source target iid recharged periodic effs       -- The effect fires up, so the item gets identified, if seen       -- (the item was at the source actor's position, so his old position       -- is given, since the actor and/or the item may be moved by the effect;@@ -102,16 +172,19 @@       when triggered $ do         postb <- getsState $ getActorBody source         seed <- getsServer $ (EM.! iid) . sitemSeedD+        -- Not giving a container to UpdDiscover, because the actor+        -- from the container can be dead, etc.         execUpdAtomic $ UpdDiscover (blid postb) (bpos postb)                                     iid itemKindId seed       return triggered-    _ -> assert `failure` (source, target, iid, itemFull)+    _ -> assert `failure` (source, target, iid, item) -effectsSem :: (MonadAtomic m, MonadServer m)-           => [Effect.Effect Int] -> ActorId -> ActorId -> Bool+itemEffect :: (MonadAtomic m, MonadServer m)+           => ActorId -> ActorId -> ItemId -> Bool -> Bool+           -> [IK.Effect]            -> m Bool-effectsSem effects source target periodic = do-  trs <- mapM (\ef -> effectSem ef source target) effects+itemEffect source target iid recharged periodic effects = do+  trs <- mapM (effectSem source target iid recharged) effects   let triggered = or trs   sb <- getsState $ getActorBody source   -- Announce no effect, which is rare and wastes time, so noteworthy.@@ -119,110 +192,89 @@           || null effects  -- no effects present, no feedback needed           || periodic  -- don't spam from fizzled periodic effects           || bproj sb) $  -- don't spam, projectiles can be very numerous-    execSfxAtomic $ SfxEffect (bfid sb) target $ Effect.NoEffect ""+    execSfxAtomic $ SfxEffect (bfid sb) target $ IK.NoEffect ""   return triggered  -- | The source actor affects the target actor, with a given effect and power. -- Both actors are on the current level and can be the same actor.+-- 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)-          => Effect.Effect Int -> ActorId -> ActorId+          => ActorId -> ActorId -> ItemId -> Bool -> IK.Effect           -> m Bool-effectSem effect source target = do+effectSem source target iid recharged effect = do+  let recursiveCall = effectSem source target iid recharged   sb <- getsState $ getActorBody source   -- @execSfx@ usually comes last in effect semantics, but not always   -- and we are likely to introduce more variety.   let execSfx = execSfxAtomic $ SfxEffect (bfid sb) target effect   case effect of-    Effect.NoEffect _ -> return False-    Effect.RefillHP p -> effectRefillHP execSfx p source target-    Effect.Hurt nDm -> effectHurt nDm source target-    Effect.RefillCalm p -> effectRefillCalm execSfx p target-    Effect.Dominate -> effectDominate source target-    Effect.Impress -> effectImpress execSfx source target-    Effect.CallFriend p -> effectCallFriend p source target-    Effect.Summon freqs p -> effectSummon freqs p source target-    Effect.CreateItem p -> effectCreateItem p target-    Effect.ApplyPerfume -> effectApplyPerfume execSfx target-    Effect.Burn p -> effectBurn execSfx p source target-    Effect.Ascend p -> effectAscend execSfx p source target-    Effect.Escape{} -> effectEscape target-    Effect.Paralyze p -> effectParalyze execSfx p target-    Effect.InsertMove p -> effectInsertMove execSfx p target-    Effect.DropBestWeapon -> effectDropBestWeapon execSfx target-    Effect.DropEqp symbol hit -> effectDropEqp execSfx hit target symbol-    Effect.SendFlying tmod ->+    IK.NoEffect _ -> return False+    IK.Hurt nDm -> effectHurt nDm source target False+    IK.Burn p -> effectBurn execSfx p source target+    IK.Explode t -> effectExplode execSfx t target+    IK.RefillHP p -> effectRefillHP False execSfx p source target+    IK.OverfillHP p -> effectRefillHP True execSfx p source target+    IK.RefillCalm p -> effectRefillCalm False execSfx p target+    IK.OverfillCalm p -> effectRefillCalm True execSfx p target+    IK.Dominate -> effectDominate recursiveCall source target+    IK.Impress -> effectImpress execSfx source target+    IK.CallFriend p -> effectCallFriend p source target+    IK.Summon freqs p -> effectSummon recursiveCall freqs p source target+    IK.Ascend p -> effectAscend recursiveCall execSfx p target+    IK.Escape{} -> effectEscape target+    IK.Paralyze p -> effectParalyze execSfx p target+    IK.InsertMove p -> effectInsertMove execSfx p target+    IK.Teleport p -> effectTeleport execSfx p target+    IK.CreateItem store grp tim -> effectCreateItem target store grp tim+    IK.DropItem store grp hit -> effectDropItem execSfx store grp hit target+    IK.PolyItem cstore -> effectPolyItem execSfx cstore target+    IK.Identify cstore -> effectIdentify iid (bfid sb) cstore target+    IK.SendFlying tmod ->       effectSendFlying execSfx tmod source target Nothing-    Effect.PushActor tmod ->+    IK.PushActor tmod ->       effectSendFlying execSfx tmod source target (Just True)-    Effect.PullActor tmod ->+    IK.PullActor tmod ->       effectSendFlying execSfx tmod source target (Just False)-    Effect.Teleport p -> effectTeleport execSfx p target-    Effect.PolyItem cstore -> effectPolyItem execSfx cstore target-    Effect.Identify cstore -> effectIdentify execSfx cstore target-    Effect.ActivateInv symbol -> effectActivateInv execSfx target symbol-    Effect.Explode t -> effectExplode execSfx t target-    Effect.OneOf l -> effectOneOf l source target-    Effect.OnSmash _ -> return False  -- ignored under normal circumstances-    Effect.TimedAspect{} -> return False  -- TODO+    IK.DropBestWeapon -> effectDropBestWeapon execSfx target+    IK.ActivateInv symbol -> effectActivateInv execSfx target symbol+    IK.ApplyPerfume -> effectApplyPerfume execSfx target+    IK.OneOf l -> effectOneOf recursiveCall l+    IK.OnSmash _ -> return False  -- ignored under normal circumstances+    IK.Recharging e -> effectRecharging recursiveCall e recharged+    IK.Temporary _ -> effectTemporary execSfx source iid  -- + Individual semantic functions for effects --- ** RefillHP--effectRefillHP :: (MonadAtomic m, MonadServer m)-           => m () -> Int -> ActorId -> ActorId -> m Bool-effectRefillHP execSfx power source target = do-  tb <- getsState $ getActorBody target-  hpMax <- sumOrganEqpServer Effect.EqpSlotAddMaxHP target-  let deltaHP = min (xM power) (max 0 $ xM hpMax - bhp tb)-  if deltaHP == 0-    then return False-    else do-      execUpdAtomic $ UpdRefillHP target deltaHP-      when (deltaHP < 0 && source /= target && not (bproj tb)) $-        halveCalm target-      execSfx-      return True--halveCalm :: (MonadAtomic m, MonadServer m)-          => ActorId -> m ()-halveCalm target = do-  tb <- getsState $ getActorBody target-  activeItems <- activeItemsServer target-  let calmMax = sumSlotNoFilter Effect.EqpSlotAddMaxCalm activeItems-      calmUpperBound = if hpTooLow tb activeItems-                       then 0  -- to trigger domination, etc.-                       else xM calmMax `div` 2-      deltaCalm = min minusTwoM (calmUpperBound - bcalm tb)-  -- HP loss decreases Calm by at least minusTwoM, to overcome Calm regen,-  -- when far from shooting foe and to avoid "hears something",-  -- which is emitted for decrease minusM.-  execUpdAtomic $ UpdRefillCalm target deltaCalm- -- ** Hurt +-- Modified by armor. Can, exceptionally, add HP. effectHurt :: (MonadAtomic m, MonadServer m)-           => Dice.Dice -> ActorId -> ActorId+           => Dice.Dice -> ActorId -> ActorId -> Bool            -> m Bool-effectHurt nDm source target = do+effectHurt nDm source target silent = do   sb <- getsState $ getActorBody source   tb <- getsState $ getActorBody target+  hpMax <- sumOrganEqpServer IK.EqpSlotAddMaxHP target   n <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm   hurtBonus <- armorHurtBonus source target   let block = braced tb       mult = (100 + hurtBonus) * (if block then 50 else 100)-      deltaHP = - (max oneM  -- at least 1 HP taken-                   $ fromIntegral mult * xM n `divUp` (100 * 100))+      rawDeltaHP = - (max oneM  -- at least 1 HP taken+                          (fromIntegral mult * xM n `divUp` (100 * 100)))+      serious = 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       deltaDiv = fromIntegral $ deltaHP `divUp` oneM   -- Damage the target.   execUpdAtomic $ UpdRefillHP target deltaHP-  when (source /= target && not (bproj tb)) $ halveCalm target-  execSfxAtomic $ SfxEffect (bfid sb) target $+  when serious $ halveCalm target+  unless silent $ execSfxAtomic $ SfxEffect (bfid sb) target $     if source == target-    then Effect.RefillHP deltaDiv  -- no SfxStrike, so treat as any heal/wound-    else Effect.Hurt (Dice.intToDice deltaDiv)  -- avoid spam; SfxStrike sent+    then IK.RefillHP deltaDiv  -- no SfxStrike, so treat as any heal/wound+    else IK.Hurt (Dice.intToDice deltaDiv)  -- SfxStrike sent, avoid spam   return True  armorHurtBonus :: (MonadAtomic m, MonadServer m)@@ -233,19 +285,125 @@   tactiveItems <- activeItemsServer target   sb <- getsState $ getActorBody source   return $! if bproj sb-            then sumSlotNoFilter Effect.EqpSlotAddHurtRanged sactiveItems-                 - sumSlotNoFilter Effect.EqpSlotAddArmorRanged tactiveItems-            else sumSlotNoFilter Effect.EqpSlotAddHurtMelee sactiveItems-                 - sumSlotNoFilter Effect.EqpSlotAddArmorMelee tactiveItems+            then sumSlotNoFilter IK.EqpSlotAddHurtRanged sactiveItems+                 - sumSlotNoFilter IK.EqpSlotAddArmorRanged tactiveItems+            else sumSlotNoFilter IK.EqpSlotAddHurtMelee sactiveItems+                 - sumSlotNoFilter IK.EqpSlotAddArmorMelee tactiveItems +halveCalm :: (MonadAtomic m, MonadServer m)+          => ActorId -> m ()+halveCalm target = do+  tb <- getsState $ getActorBody target+  activeItems <- activeItemsServer target+  let calmMax = sumSlotNoFilter IK.EqpSlotAddMaxCalm activeItems+      upperBound = if hpTooLow tb activeItems+                   then 0  -- to trigger domination, etc.+                   else max (xM calmMax) (bcalm tb) `div` 2+      deltaCalm = min minusTwoM (upperBound - bcalm tb)+  -- HP loss decreases Calm by at least minusTwoM, to overcome Calm regen,+  -- when far from shooting foe and to avoid "hears something",+  -- which is emitted for decrease @minusM@.+  execUpdAtomic $ UpdRefillCalm target deltaCalm++-- ** Burn++-- Damage from both impact and fire. Modified by armor.+effectBurn :: (MonadAtomic m, MonadServer m)+           => m () -> Int -> ActorId -> ActorId+           -> m Bool+effectBurn execSfx power source target = assert (power > 0) $ do+  void $ effectHurt (Dice.intToDice power) source target True+  execSfx+  return True++-- ** Explode++effectExplode :: (MonadAtomic m, MonadServer m)+              => m () -> GroupName ItemKind -> ActorId -> m Bool+effectExplode execSfx cgroup target = do+  tb <- getsState $ getActorBody target+  let itemFreq = [(cgroup, 1)]+      container = CActor target CEqp+  m2 <- rollAndRegisterItem (blid tb) itemFreq container False Nothing+  let (iid, (ItemFull{..}, _)) = fromMaybe (assert `failure` cgroup) m2+      Point x y = bpos tb+      projectN k100 (n, _) = do+        -- We pick a point at the border, not inside, to have a uniform+        -- distribution for the points the line goes through at each distance+        -- from the source. Otherwise, e.g., the points on cardinal+        -- and diagonal lines from the source would be more common.+        let fuzz = 2 + (k100 `xor` (itemK * n)) `mod` 9+            k = if itemK >= 8 && n < 8 then 0+                else if n < 8 && n >= 4 then 4 else n+            psAll =+              [ Point (x - 12) $ y + fuzz+              , Point (x + 12) $ y - fuzz+              , Point (x - 12) $ y - fuzz+              , Point (x + 12) $ y + fuzz+              , flip Point (y - 12) $ x + fuzz+              , flip Point (y + 12) $ x - fuzz+              , flip Point (y - 12) $ x - fuzz+              , flip Point (y + 12) $ x + fuzz+              ]+            -- Keep full symmetry, but only if enough projectiles. Fall back+            -- to random, on average, symmetry.+            ps = take k $+              if k >= 4 then psAll+              else drop ((n + x + y + fromEnum iid * 7) `mod` 16)+                   $ cycle $ psAll ++ reverse psAll+        forM_ ps $ \tpxy -> do+          let req = ReqProject tpxy k100 iid CEqp+          mfail <- projectFail target tpxy k100 iid CEqp True+          case mfail of+            Nothing -> return ()+            Just ProjectBlockTerrain -> return ()+            Just ProjectBlockActor | not $ bproj tb -> return ()+            Just failMsg -> execFailure target req failMsg+  -- All blasts bounce off obstacles many times before they destruct.+  forM_ [101..201] $ \k100 -> do+    bag2 <- getsState $ beqp . getActorBody target+    let mn2 = EM.lookup iid bag2+    maybe skip (projectN k100) mn2+  bag3 <- getsState $ beqp . getActorBody target+  let mn3 = EM.lookup iid bag3+  maybe skip (\kit -> execUpdAtomic+                      $ UpdLoseItem iid itemBase kit container) mn3+  execSfx+  return True  -- we avoid verifying that at least one projectile got off++-- ** RefillHP++-- Unaffected by armor.+effectRefillHP :: (MonadAtomic m, MonadServer m)+               => Bool -> m () -> Int -> ActorId -> ActorId -> m Bool+effectRefillHP overfill execSfx power source target = do+  tb <- getsState $ getActorBody target+  hpMax <- sumOrganEqpServer IK.EqpSlotAddMaxHP target+  let overMax | overfill = xM hpMax * 10  -- arbitrary limit to scumming+              | otherwise = xM hpMax+      serious = overfill && source /= target && not (bproj tb)+      deltaHP | power > 0 = min (xM power) (max 0 $ overMax - bhp tb)+              | serious = -- if HP overfull, at least cut back to max HP+                          min (xM power) (xM hpMax - bhp tb)+              | otherwise = xM power+  if deltaHP == 0+    then return False+    else do+      execUpdAtomic $ UpdRefillHP target deltaHP+      when (deltaHP < 0 && serious) $ halveCalm target+      execSfx+      return True+ -- ** RefillCalm  effectRefillCalm ::  (MonadAtomic m, MonadServer m)-           => m () -> Int -> ActorId -> m Bool-effectRefillCalm execSfx power target = do+           => Bool -> m () -> Int -> ActorId -> m Bool+effectRefillCalm overfill execSfx power target = do   tb <- getsState $ getActorBody target-  calmMax <- sumOrganEqpServer Effect.EqpSlotAddMaxCalm target-  let deltaCalm = min (xM power) (max 0 $ xM calmMax - bcalm tb)+  calmMax <- sumOrganEqpServer IK.EqpSlotAddMaxCalm target+  let overMax | overfill = xM calmMax * 10  -- arbitrary limit to scumming+              | otherwise = xM calmMax+  let deltaCalm = min (xM power) (max 0 $ overMax - bcalm tb)   if deltaCalm == 0     then return False     else do@@ -256,14 +414,16 @@ -- ** Dominate  effectDominate :: (MonadAtomic m, MonadServer m)-               => ActorId -> ActorId -> m Bool-effectDominate source target = do+               => (IK.Effect -> m Bool)+               -> ActorId -> ActorId+               -> m Bool+effectDominate recursiveCall source target = do   sb <- getsState $ getActorBody source   tb <- getsState $ getActorBody target   if bproj tb then     return False   else if bfid tb == bfid sb then-    effectSem Effect.Impress source target+    recursiveCall IK.Impress   else     dominateFidSfx (bfid sb) target @@ -281,25 +441,25 @@     execUpdAtomic $ UpdOldFidActor target (boldfid tb) (bfid sb)     return True --- ** SummonFriend+-- ** CallFriend  effectCallFriend :: (MonadAtomic m, MonadServer m)-                   => Int -> ActorId -> ActorId+                   => Dice.Dice -> ActorId -> ActorId                    -> m Bool-effectCallFriend power source target = assert (power > 0) $ do+effectCallFriend nDm source target = do   -- Obvious effect, nothing announced.   Kind.COps{cotile} <- getsState scops+  power <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm   sb <- getsState $ getActorBody source   activeItems <- activeItemsServer source   let legal = source == target-              && hpEnough sb activeItems-              && bhp sb >= xM 10  -- prevent spam from regenerating wimps+              && hpEnough10 sb activeItems  -- prevent spam from regen wimps   if not legal then return False   else do-    let hpMax = max 1 $ sumSlotNoFilter Effect.EqpSlotAddMaxHP activeItems+    let hpMax = max 1 $ sumSlotNoFilter IK.EqpSlotAddMaxHP activeItems         deltaHP = - xM hpMax `div` 3     execUpdAtomic $ UpdRefillHP source deltaHP-    let validTile t = not $ Tile.hasFeature cotile F.NoActor t+    let validTile t = not $ Tile.hasFeature cotile TK.NoActor t         lid = blid sb     ps <- getsState $ nearbyFreePoints validTile (bpos sb) lid     time <- getsState $ getLocalTime lid@@ -308,22 +468,23 @@ -- ** Summon  effectSummon :: (MonadAtomic m, MonadServer m)-             => Freqs -> Int -> ActorId -> ActorId -> m Bool-effectSummon actorFreq power source target = assert (power > 0) $ do+             => (IK.Effect -> m Bool)+             -> Freqs ItemKind -> Dice.Dice+             -> ActorId -> ActorId+             -> m Bool+effectSummon recursiveCall actorFreq nDm source target = do   -- Obvious effect, nothing announced.   Kind.COps{cotile} <- getsState scops+  power <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm   sb <- getsState $ getActorBody source   activeItems <- activeItemsServer source-  let legal = source == target-              && (bproj sb-                  || calmEnough sb activeItems-                     && bcalm sb >= xM 10)+  let legal = source == target && (bproj sb || calmEnough10 sb activeItems)   if not legal then return False   else do-    let calmMax = max 1 $ sumSlotNoFilter Effect.EqpSlotAddMaxCalm activeItems+    let calmMax = max 1 $ sumSlotNoFilter IK.EqpSlotAddMaxCalm activeItems         deltaCalm = - xM calmMax `div` 3     unless (bproj sb) $ execUpdAtomic $ UpdRefillCalm source deltaCalm-    let validTile t = not $ Tile.hasFeature cotile F.NoActor t+    let validTile t = not $ Tile.hasFeature cotile TK.NoActor t     ps <- getsState $ nearbyFreePoints validTile (bpos sb) (blid sb)     localTime <- getsState $ getLocalTime (blid sb)     -- Make sure summoned actors start acting after the summoner.@@ -334,7 +495,7 @@       case maid of         Nothing ->           -- Don't make this item useless.-          effectSem (Effect.CallFriend 1) source target+          recursiveCall (IK.CallFriend 1)         Just aid -> do           b <- getsState $ getActorBody aid           mleader <- getsState $ gleader . (EM.! bfid b) . sfactionD@@ -344,46 +505,14 @@           return True     return $! or bs --- ** CreateItem--effectCreateItem :: (MonadAtomic m, MonadServer m)-                 => Int -> ActorId -> m Bool-effectCreateItem power target = assert (power > 0) $ do-  -- Obvious effect, nothing announced.-  tb <- getsState $ getActorBody target-  void $ createItems power (bpos tb) (blid tb)-  return True---- ** ApplyPerfume--effectApplyPerfume :: (MonadAtomic m, MonadServer m)-                   => m () -> ActorId -> m Bool-effectApplyPerfume execSfx target = do-  tb <- getsState $ getActorBody target-  Level{lsmell} <- getLevel $ blid tb-  let f p fromSm =-        execUpdAtomic $ UpdAlterSmell (blid tb) p (Just fromSm) Nothing-  mapWithKeyM_ f lsmell-  execSfx-  return True---- ** Burn--effectBurn :: (MonadAtomic m, MonadServer m)-           => m () -> Int -> ActorId -> ActorId-           -> m Bool-effectBurn execSfx power source target = do-  -- Damage from both impact and fire.-  void $ effectHurt (Dice.intToDice $ 2 * power) source target-  execSfx-  return True- -- ** Ascend  -- Note that projectiles can be teleported, too, for extra fun. effectAscend :: (MonadAtomic m, MonadServer m)-             => m () -> Int -> ActorId -> ActorId -> m Bool-effectAscend execSfx k source aid = do+             => (IK.Effect -> m Bool)+             -> m () -> Int -> ActorId+             -> m Bool+effectAscend recursiveCall execSfx k aid = do   b1 <- getsState $ getActorBody aid   ais1 <- getsState $ getCarriedAssocs b1   let lid1 = blid b1@@ -391,8 +520,7 @@   (lid2, pos2) <- getsState $ whereTo lid1 pos1 k . sdungeon   if lid2 == lid1 && pos2 == pos1 then do     execSfxAtomic $ SfxMsgFid (bfid b1) "No more levels in this direction."-    let effect = Effect.Teleport 30  -- powerful teleport-    effectSem effect source aid+    recursiveCall $ IK.Teleport 30  -- powerful teleport   else do     let switch1 = void $ switchLevels1 ((aid, b1), ais1)         switch2 = do@@ -455,7 +583,7 @@   execUpdAtomic $ UpdLoseActor aid bOld ais   return mlead -switchLevels2 :: MonadAtomic m+switchLevels2 ::(MonadAtomic m, MonadServer m)               => LevelId -> Point               -> ((ActorId, Actor), [(ItemId, Item)]) -> Maybe ActorId               -> m ()@@ -465,16 +593,23 @@   assert (lidNew /= lidOld `blame` "stairs looped" `twith` lidNew) skip   -- Sync the actor time with the level time.   timeOld <- getsState $ getLocalTime lidOld-  timeLastVisited <- getsState $ getLocalTime lidNew+  timeLastActive <- getsState $ getLocalTime lidNew   -- This time calculation may cause a double move of a foe of the same   -- speed, but this is OK --- the foe didn't have a chance to move   -- before, because the arena went inactive, so he moves now one more time.-  let delta = btime bOld `timeDeltaToFrom` timeOld+  let delta = timeLastActive `timeDeltaToFrom` timeOld+      shiftByDelta = (`timeShift` delta)+      computeNewTimeout :: ItemQuant -> ItemQuant+      computeNewTimeout (k, it) = (k, map shiftByDelta it)+      setTimeout :: ItemBag -> ItemBag+      setTimeout bag = EM.map computeNewTimeout bag       bNew = bOld { blid = lidNew-                  , btime = timeShift timeLastVisited delta+                  , btime = shiftByDelta $ btime bOld                   , bpos = posNew                   , boldpos = posNew  -- new level, new direction-                  , boldlid = lidOld }  -- record old level+                  , boldlid = lidOld  -- record old level+                  , borgan = setTimeout $ borgan bOld+                  , beqp = setTimeout $ beqp bOld }   -- Materialize the actor at the new location.   -- Onlookers see somebody appear suddenly. The actor himself   -- sees new surroundings and has to reset his perception.@@ -504,8 +639,9 @@ -- | Advance target actor time by this many time clips. Not by actor moves, -- to hurt fast actors more. effectParalyze :: (MonadAtomic m, MonadServer m)-               => m () -> Int -> ActorId -> m Bool-effectParalyze execSfx p target = assert (p > 0) $ do+               => m () -> Dice.Dice -> ActorId -> m Bool+effectParalyze execSfx nDm target = do+  p <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm   b <- getsState $ getActorBody target   if bproj b || bhp b <= 0     then return False@@ -517,13 +653,12 @@  -- ** InsertMove --- TODO: Replace with SpeedBurst that lasts just 1 turn,--- but make sure the cost of this item activation is vs previous speed -- | 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)-                 => m () -> Int -> ActorId -> m Bool-effectInsertMove execSfx p target = assert (p > 0) $ do+                 => m () -> Dice.Dice -> ActorId -> m Bool+effectInsertMove execSfx nDm target = do+  p <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm   b <- getsState $ getActorBody target   activeItems <- activeItemsServer target   let tpm = ticksPerMeter $ bspeed b activeItems@@ -532,77 +667,224 @@   execSfx   return True --- ** DropBestWeapon+-- ** Teleport --- | Make the target actor drop his best weapon (stack).-effectDropBestWeapon :: (MonadAtomic m, MonadServer m)-                     => m () -> ActorId -> m Bool-effectDropBestWeapon execSfx target = do-  allAssocs <- fullAssocsServer target [CEqp]-  case strongestSlotNoFilter Effect.EqpSlotWeapon allAssocs of-    (_, (iid, _)) : _ -> do-      b <- getsState $ getActorBody target-      let k = beqp b EM.! iid-      dropEqpItem target b False iid k-      execSfx+-- | Teleport the target actor.+-- Note that projectiles can be teleported, too, for extra fun.+effectTeleport :: (MonadAtomic m, MonadServer m)+               => m () -> Dice.Dice -> ActorId -> m Bool+effectTeleport execSfx nDm target = do+  Kind.COps{cotile} <- getsState scops+  range <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm+  b <- getsState $ getActorBody target+  Level{ltile} <- getLevel (blid b)+  as <- getsState $ actorList (const True) (blid b)+  let spos = bpos b+      dMinMax delta pos =+        let d = chessDist spos pos+        in d >= range - delta && d <= range + delta+      dist delta pos _ = dMinMax delta pos+  tpos <- rndToAction $ findPosTry 200 ltile+    (\p t -> Tile.isWalkable cotile t+             && (not (dMinMax 9 p)  -- don't loop, very rare+                 || not (Tile.hasFeature cotile TK.NoActor t)+                    && unoccupied as p))+    [ dist $ 1+    , dist $ 1 + range `div` 9+    , dist $ 1 + range `div` 7+    , dist $ 1 + range `div` 5+    , dist $ 5+    , dist $ 7+    ]+  if not (dMinMax 9 tpos) then+    return False  -- very rare+  else do+    execUpdAtomic $ UpdMoveActor target spos tpos+    execSfx+    return True++-- ** CreateItem++effectCreateItem :: (MonadAtomic m, MonadServer m)+                  => ActorId -> CStore -> GroupName ItemKind -> IK.TimerDice+                  -> m Bool+effectCreateItem target store grp tim = do+  tb <- getsState $ getActorBody target+  delta <- case tim of+    IK.TimerNone -> return $ Delta timeZero+    IK.TimerGameTurn nDm -> do+      k <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm+      assert (k >= 0) skip+      return $! timeDeltaScale (Delta timeTurn) k+    IK.TimerActorTurn nDm -> do+      k <- rndToAction $ castDice (AbsDepth 0) (AbsDepth 0) nDm+      assert (k >= 0) skip+      activeItems <- activeItemsServer target+      let actorTurn = ticksPerMeter $ bspeed tb activeItems+      return $! timeDeltaScale actorTurn k+  let c = CActor target store+  bagBefore <- getsState $ getCBag c+  let litemFreq = [(grp, 1)]+  m4 <- rollItem (blid tb) litemFreq+  let (itemKnown, itemFull, seed, _) = case m4 of+        Nothing -> assert `failure` (blid tb, litemFreq, c)+        Just i4 -> i4+  itemRev <- getsServer sitemRev+  let mquant = case HM.lookup itemKnown itemRev of+        Nothing -> Nothing+        Just iid -> (iid,) <$> iid `EM.lookup` bagBefore+  case mquant of+    Just (iid, (1, afterIt@(timer : rest))) | tim /= IK.TimerNone -> do+      -- Already has such an item, so only increase the timer by half delta.+      let newIt = let halfTurns = delta `timeDeltaDiv` 2+                      newTimer = timer `timeShift` halfTurns+                  in newTimer : rest+      when (afterIt /= newIt) $+        execUpdAtomic $ UpdTimeItem iid c afterIt newIt  -- TODO: announce+    _ -> do+      -- Multiple such items, so it's a periodic poison, etc., so just stack,+      -- or no such items at all, so create some.+      iid <- registerItem (itemBase itemFull) itemKnown seed+                          (itemK itemFull) c True+      unless (tim == IK.TimerNone) $ do+        bagAfter <- getsState $ getCBag c+        localTime <- getsState $ getLocalTime (blid tb)+        let newTimer = localTime `timeShift` delta+            (afterK, afterIt) = case iid `EM.lookup` bagAfter of+              Nothing -> assert `failure` (iid, bagAfter, c)+              Just kit -> kit+            newIt = replicate afterK newTimer+        when (afterIt /= newIt) $+          execUpdAtomic $ UpdTimeItem iid c afterIt newIt+  return True++-- ** DropItem++-- | Make the target actor drop all items in his equiment with the given symbol+-- (not just a random single item, or cluttering equipment with rubbish+-- would be beneficial).+effectDropItem :: (MonadAtomic m, MonadServer m)+               => m () -> CStore -> GroupName ItemKind -> Bool -> ActorId+               -> m Bool+effectDropItem execSfx store grp hit target = do+  Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops+  discoKind <- getsServer sdiscoKind+  b <- getsState $ getActorBody target+  let hasGroup (iid, _) = do+        item <- getsState $ getItemBody iid+        case EM.lookup (jkindIx item) discoKind of+          Just kindId ->+            return $! maybe False (> 0) $ lookup grp $ IK.ifreq (okind kindId)+          Nothing ->+            assert `failure` (target, grp, iid, item)+  assocsCStore <- getsState $ EM.assocs . getActorBag target store+  is <- filterM hasGroup assocsCStore+  if null is+    then return False+    else do+      mapM_ (\(iid, kit) -> dropCStoreItem store target b hit iid kit) is+      unless (store == COrgan) execSfx       return True-    [] ->-      return False --- | Drop a single actor's item. Note that if there multiple copies,+-- | Drop a single actor's item. Note that if there are multiple copies, -- 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).-dropEqpItem :: (MonadAtomic m, MonadServer m)-            => ActorId -> Actor -> Bool -> ItemId -> Int -> m ()-dropEqpItem aid b hit iid k = do+dropCStoreItem :: (MonadAtomic m, MonadServer m)+               => CStore -> ActorId -> Actor -> Bool -> ItemId -> ItemQuant+               -> m ()+dropCStoreItem store aid b hit iid kit@(k, _) = do   item <- getsState $ getItemBody iid-  itemToF <- itemToFullServer-  let container = CActor aid CEqp-      fragile = Effect.Fragile `elem` jfeature item-      durable = Effect.Durable `elem` jfeature item+  let c = CActor aid store+      fragile = IK.Fragile `elem` jfeature item+      durable = IK.Durable `elem` jfeature item       isDestroyed = hit && not durable || bproj b && fragile-      itemFull = itemToF iid k   if isDestroyed then do-    -- Feedback from hit, or it's shrapnel, so no @UpdDestroyItem@.-    execUpdAtomic $ UpdLoseItem iid item k container-    void $ itemEffect aid aid iid itemFull True False+    discoEffect <- getsServer sdiscoEffect+    let aspects = case EM.lookup iid discoEffect of+          Just ItemAspectEffect{jaspects} -> jaspects+          _ -> assert `failure` (aid, iid)+    itemToF <- itemToFullServer+    let itemFull = itemToF iid kit+        effs = strengthOnSmash itemFull+    effectAndDestroy aid aid iid c False effs aspects kit   else do-    mvCmd <- generalMoveItem iid k (CActor aid CEqp)+    mvCmd <- generalMoveItem iid k (CActor aid store)                                    (CActor aid CGround)     mapM_ execUpdAtomic mvCmd --- ** DropEqp---- | Make the target actor drop all items in his equiment with the given symbol--- (not just a random one, or cluttering equipment with rubbish--- would be beneficial).-effectDropEqp :: (MonadAtomic m, MonadServer m)-              => m () -> Bool -> ActorId -> Char -> m Bool-effectDropEqp execSfx hit target symbol = do-  b <- getsState $ getActorBody target-  effectTransformEqp execSfx target symbol CEqp $-    dropEqpItem target b hit+-- ** PolyItem -effectTransformEqp :: forall m. (MonadAtomic m, MonadServer m)-                   => m () -> ActorId -> Char -> CStore-                   -> (ItemId -> Int -> m ())-                   -> m Bool-effectTransformEqp execSfx target symbol cstore m = do-  let hasSymbol (iid, _) = do-        item <- getsState $ getItemBody iid-        return $! jsymbol item == symbol-  assocsCStore <- getsState $ EM.assocs . getActorBag target cstore-  is <- if symbol == ' '-        then return assocsCStore-        else filterM hasSymbol assocsCStore-  if null is-    then return False-    else do-      mapM_ (uncurry m) is-      execSfx+effectPolyItem :: (MonadAtomic m, MonadServer m)+               => m () -> CStore -> ActorId -> m Bool+effectPolyItem execSfx cstore target = do+  allAssocs <- fullAssocsServer target [cstore]+  case allAssocs of+    [] -> do+      tb <- getsState $ getActorBody target+      execSfxAtomic $ SfxMsgFid (bfid tb) $+        "The purpose of repurpose cannot be availed without an item"+        <+> ppCStore cstore <> "."+      -- TODO: identify the scroll, but don't use up.       return True+    (iid, itemFull@ItemFull{..}) : _ -> case itemDisco of+      Just ItemDisco{itemKind} -> do+        let maxCount = Dice.maxDice $ IK.icount itemKind+        if itemK >= maxCount+        then do+          let c = CActor target cstore+              kit = (maxCount, take maxCount itemTimer)+          execUpdAtomic $ UpdDestroyItem iid itemBase kit c+          execSfx+          effectCreateItem target cstore "useful" IK.TimerNone+        else do+          tb <- getsState $ getActorBody target+          execSfxAtomic $ SfxMsgFid (bfid tb) $+            "The purpose of repurpose is served by" <+> tshow maxCount+            <+> "pieces of this item, not by" <+> tshow itemK <> "."+          -- TODO: identify the scroll, but don't use up.+          return True+      _ -> assert `failure` (cstore, target, iid, itemFull) +-- ** Identify++-- TODO: ask player for an item, because server doesn't know which+-- is already identified, it only knows which cannot ever be.+effectIdentify :: (MonadAtomic m, MonadServer m)+               => ItemId -> FactionId -> CStore -> ActorId -> m Bool+effectIdentify iidId fid storeInitial target = do+  let tryFull store as = case as of+        [] -> return False+        (iid, itemFull@ItemFull{..}) : rest -> case itemDisco of+          _ | iid == iidId -> tryFull store rest  -- don't id itself+          Just ItemDisco{..} -> do+            -- TODO: use this (but faster, via traversing effects with 999)+            -- also to prevent sending any other UpdDiscover.+            let ided = IK.Identified `elem` IK.ifeature itemKind+                itemSecret = itemNoAE itemFull+                c = CActor target store+                statsObvious = textAllAE False c itemFull+                               == textAllAE False c itemSecret+            if ided && statsObvious+              then tryFull store rest+              else do+                let effect = IK.Identify store  -- the real store, not initial+                execSfxAtomic $ SfxEffect fid target effect  -- a cheat+                tb <- getsState $ getActorBody target+                seed <- getsServer $ (EM.! iid) . sitemSeedD+                execUpdAtomic $+                  UpdDiscover (blid tb) (bpos tb) iid itemKindId seed+                return True+          _ -> assert `failure` (store, target, iid, itemFull)+      tryStore stores = case stores of+        [] -> return False+        store : rest -> do+          allAssocs <- fullAssocsServer target [store]+          go <- tryFull store allAssocs+          if go then return True else tryStore rest+      storesSorted = storeInitial : delete storeInitial [CGround, CInv, CEqp]+  tryStore storesSorted+ -- ** SendFlying  -- | Shend the target actor flying like a projectile. The arguments correspond@@ -611,10 +893,10 @@ -- boldpos is used, if it can't, a random outward vector of length 10 -- is picked. effectSendFlying :: (MonadAtomic m, MonadServer m)-                 => m () -> Effect.ThrowMod+                 => m () -> IK.ThrowMod                  -> ActorId -> ActorId -> Maybe Bool                  -> m Bool-effectSendFlying execSfx Effect.ThrowMod{..} source target modePush = do+effectSendFlying execSfx IK.ThrowMod{..} source target modePush = do   v <- sendFlyingVector source target modePush   Kind.COps{cotile} <- getsState scops   tb <- getsState $ getActorBody target@@ -668,91 +950,22 @@                 Nothing | adjacent (bpos sb) (bpos tb) -> pushV                 Nothing -> pullV --- ** Teleport---- | Teleport the target actor.--- Note that projectiles can be teleported, too, for extra fun.-effectTeleport :: (MonadAtomic m, MonadServer m)-               => m () -> Int -> ActorId -> m Bool-effectTeleport execSfx range target = do-  Kind.COps{cotile} <- getsState scops-  b <- getsState $ getActorBody target-  Level{ltile} <- getLevel (blid b)-  as <- getsState $ actorList (const True) (blid b)-  let spos = bpos b-      dMinMax delta pos =-        let d = chessDist spos pos-        in d >= range - delta && d <= range + delta-      dist delta pos _ = dMinMax delta pos-  tpos <- rndToAction $ findPosTry 200 ltile-    (\p t -> Tile.isWalkable cotile t-             && (not (dMinMax 9 p)  -- don't loop, very rare-                 || not (Tile.hasFeature cotile F.NoActor t)-                    && unoccupied as p))-    [ dist $ 1-    , dist $ 1 + range `div` 9-    , dist $ 1 + range `div` 7-    , dist $ 1 + range `div` 5-    , dist $ 5-    , dist $ 7-    ]-  if not (dMinMax 9 tpos) then-    return False  -- very rare-  else do-    execUpdAtomic $ UpdMoveActor target spos tpos-    execSfx-    return True---- ** PolyItem--effectPolyItem :: (MonadAtomic m, MonadServer m)-               => m () -> CStore -> ActorId -> m Bool-effectPolyItem execSfx cstore target = do-  allAssocs <- fullAssocsServer target [cstore]-  case allAssocs of-    [] -> return False-    (iid, itemFull@ItemFull{..}) : _ -> case itemDisco of-      Just ItemDisco{itemKind} -> do-        let maxCount = Dice.maxDice $ icount itemKind-        if itemK >= maxCount-        then do-          let c = CActor target cstore-          execUpdAtomic $ UpdDestroyItem iid itemBase maxCount c-          execSfx-          effectCreateItem 1 target-        else do-          tb <- getsState $ getActorBody target-          execSfxAtomic $ SfxMsgFid (bfid tb) $-            "The purpose is served by" <+> tshow maxCount-            <+> "pieces of this item, not by" <+> tshow itemK <> "."-          return False-      _ -> assert `failure` (cstore, target, iid, itemFull)---- ** Identify+-- ** DropBestWeapon -effectIdentify :: (MonadAtomic m, MonadServer m)-               => m () -> CStore -> ActorId -> m Bool-effectIdentify execSfx cstore target = do-  allAssocs <- fullAssocsServer target [cstore]-  case allAssocs of-    [] -> return False-    (iid, itemFull@ItemFull{..}) : _ -> case itemDisco of-      Just ItemDisco{..} -> do-        -- TODO: use this (but faster, via traversing effects with 999)-        -- also to prevent sending any other UpdDiscover.-        let ided = Effect.Identified `elem` ifeature itemKind-            itemSecret = itemNoAE itemFull-            statsObvious = textAllAE False cstore itemFull-                           == textAllAE False cstore itemSecret-        if ided && statsObvious-          then return False-          else do-            execSfx-            tb <- getsState $ getActorBody target-            seed <- getsServer $ (EM.! iid) . sitemSeedD-            execUpdAtomic $ UpdDiscover (blid tb) (bpos tb) iid itemKindId seed-            return True-      _ -> assert `failure` (cstore, target, iid, itemFull)+-- | Make the target actor drop his best weapon (stack).+effectDropBestWeapon :: (MonadAtomic m, MonadServer m)+                     => m () -> ActorId -> m Bool+effectDropBestWeapon execSfx target = do+  allAssocs <- fullAssocsServer target [CEqp]+  case strongestSlotNoFilter IK.EqpSlotWeapon allAssocs of+    (_, (iid, _)) : _ -> do+      b <- getsState $ getActorBody target+      let kit = beqp b EM.! iid+      dropCStoreItem CEqp target b False iid kit+      execSfx+      return True+    [] ->+      return False  -- ** ActivateInv @@ -766,59 +979,67 @@   effectTransformEqp execSfx target symbol CInv $ \iid _ ->     applyItem target iid CInv --- ** Explode+effectTransformEqp :: forall m. (MonadAtomic m, MonadServer m)+                   => m () -> ActorId -> Char -> CStore+                   -> (ItemId -> ItemQuant -> m ())+                   -> m Bool+effectTransformEqp execSfx target symbol cstore m = do+  let hasSymbol (iid, _) = do+        item <- getsState $ getItemBody iid+        return $! jsymbol item == symbol+  assocsCStore <- getsState $ EM.assocs . getActorBag target cstore+  is <- if symbol == ' '+        then return assocsCStore+        else filterM hasSymbol assocsCStore+  if null is+    then return False+    else do+      mapM_ (uncurry m) is+      execSfx+      return True -effectExplode :: (MonadAtomic m, MonadServer m)-              => m () -> GroupName -> ActorId -> m Bool-effectExplode execSfx cgroup target = do+-- ** ApplyPerfume++effectApplyPerfume :: (MonadAtomic m, MonadServer m)+                   => m () -> ActorId -> m Bool+effectApplyPerfume execSfx target = do   tb <- getsState $ getActorBody target-  let itemFreq = [(cgroup, 1)]-      container = CActor target CEqp-  m2 <- rollAndRegisterItem (blid tb) itemFreq container False-  let (iid, (ItemFull{..}, _)) = fromMaybe (assert `failure` cgroup) m2-      Point x y = bpos tb-      projectN k100 n = do-        -- We pick a point at the border, not inside, to have a uniform-        -- distribution for the points the line goes through at each distance-        -- from the source. Otherwise, e.g., the points on cardinal-        -- and diagonal lines from the source would be more common.-        let fuzz = 2 + (k100 `xor` (itemK * n)) `mod` 9-            k = if itemK >= 8 && n < 8 then 0-                else if n < 8 && n >= 4 then 4 else n-            ps = take k $-              [ Point (x - 12) $ y + fuzz-              , Point (x - 12) $ y - fuzz-              , Point (x + 12) $ y + fuzz-              , Point (x + 12) $ y - fuzz-              , flip Point (y - 12) $ x + fuzz-              , flip Point (y - 12) $ x - fuzz-              , flip Point (y + 12) $ x + fuzz-              , flip Point (y + 12) $ x - fuzz-              ]-        forM_ ps $ \tpxy -> do-          let req = ReqProject tpxy k100 iid CEqp-          mfail <- projectFail target tpxy k100 iid CEqp True-          case mfail of-            Nothing -> return ()-            Just ProjectBlockTerrain -> return ()-            Just ProjectBlockActor | not $ bproj tb -> return ()-            Just failMsg -> execFailure target req failMsg-  -- All shrapnels bounce off obstacles many times before they destruct.-  forM_ [101..201] $ \k100 -> do-    bag2 <- getsState $ beqp . getActorBody target-    let mn2 = EM.lookup iid bag2-    maybe skip (projectN k100) mn2-  bag3 <- getsState $ beqp . getActorBody target-  let mn3 = EM.lookup iid bag3-  maybe skip (\k -> execUpdAtomic-                    $ UpdLoseItem iid itemBase k container) mn3+  Level{lsmell} <- getLevel $ blid tb+  let f p fromSm =+        execUpdAtomic $ UpdAlterSmell (blid tb) p (Just fromSm) Nothing+  mapWithKeyM_ f lsmell   execSfx-  return True  -- we avoid verifying that at least one projectile got off+  return True  -- ** OneOf  effectOneOf :: (MonadAtomic m, MonadServer m)-            => [Effect.Effect Int] -> ActorId -> ActorId -> m Bool-effectOneOf l source target = do-  ef <-  rndToAction $ oneOf l-  effectSem ef source target+            => (IK.Effect -> m Bool)+            -> [IK.Effect]+            -> m Bool+effectOneOf recursiveCall l = do+  ef <- rndToAction $ oneOf l+  recursiveCall ef++-- ** Recharging++effectRecharging :: (MonadAtomic m, MonadServer m)+                 => (IK.Effect -> m Bool)+                 -> IK.Effect -> Bool+                 -> m Bool+effectRecharging recursiveCall e recharged =+  if recharged+  then recursiveCall e+  else return False++-- ** Temporary++effectTemporary :: (MonadAtomic m, MonadServer m)+                => m () -> ActorId -> ItemId+                -> m Bool+effectTemporary execSfx source iid = do+  bag <- getsState $ getCBag $ CActor source COrgan+  case iid `EM.lookup` bag of+    Just _ -> skip  -- still some copies left of a multi-copy tmp item+    Nothing -> execSfx  -- last copy just destroyed+  return True
Game/LambdaHack/Server/HandleRequestServer.hs view
@@ -12,7 +12,6 @@   ( handleRequestAI, handleRequestUI, reqMove   ) where -import Control.Applicative import Control.Exception.Assert.Sugar import Control.Monad import qualified Data.EnumMap.Strict as EM@@ -20,25 +19,26 @@ import Data.Text (Text)  import Game.LambdaHack.Atomic+import qualified Game.LambdaHack.Common.Ability as Ability import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.ClientOptions-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction-import qualified Game.LambdaHack.Common.Feature as F 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 qualified Game.LambdaHack.Common.Tile as Tile 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 as TileKind+import qualified Game.LambdaHack.Content.TileKind as TK import Game.LambdaHack.Server.CommonServer import Game.LambdaHack.Server.HandleEffectServer import Game.LambdaHack.Server.ItemServer@@ -48,34 +48,34 @@ -- | The semantics of server commands. The resulting actor id -- is of the actor that carried out the request. handleRequestAI :: (MonadAtomic m, MonadServer m)-                => FactionId -> ActorId -> RequestAI -> m ActorId+                => FactionId -> ActorId -> RequestAI -> m (ActorId, m ()) handleRequestAI fid aid cmd = case cmd of-  ReqAITimed cmdT -> handleRequestTimed aid cmdT >> return aid+  ReqAITimed cmdT -> return (aid, handleRequestTimed aid cmdT)   ReqAILeader aidNew mtgtNew cmd2 -> do     switchLeader fid aidNew mtgtNew     handleRequestAI fid aidNew cmd2-  ReqAIPong -> return aid+  ReqAIPong -> return (aid, skip)  -- | The semantics of server commands. The resulting actor id -- is of the actor that carried out the request. @Nothing@ means -- the command took no time. handleRequestUI :: (MonadAtomic m, MonadServer m)-                => FactionId -> RequestUI -> m (Maybe ActorId)+                => FactionId -> RequestUI -> m (Maybe ActorId, m ()) handleRequestUI fid cmd = case cmd of   ReqUITimed cmdT -> do     fact <- getsState $ (EM.! fid) . sfactionD     let (aid, _) = fromMaybe (assert `failure` fact) $ gleader fact-    handleRequestTimed aid cmdT >> return (Just aid)+    return (Just aid, handleRequestTimed aid cmdT)   ReqUILeader aidNew mtgtNew cmd2 -> do     switchLeader fid aidNew mtgtNew     handleRequestUI fid cmd2   ReqUIGameRestart aid t d names ->-    reqGameRestart aid t d names >> return Nothing-  ReqUIGameExit aid d -> reqGameExit aid d >> return Nothing-  ReqUIGameSave -> reqGameSave >> return Nothing-  ReqUITactic toT -> reqTactic fid toT >> return Nothing-  ReqUIAutomate -> reqAutomate fid >> return Nothing-  ReqUIPong _ -> return Nothing+    return (Nothing, reqGameRestart aid t d names)+  ReqUIGameExit aid d -> return (Nothing, reqGameExit aid d)+  ReqUIGameSave -> return (Nothing, reqGameSave)+  ReqUITactic toT -> return (Nothing, reqTactic fid toT)+  ReqUIAutomate -> return (Nothing, reqAutomate fid)+  ReqUIPong _ -> return (Nothing, skip)  handleRequestTimed :: (MonadAtomic m, MonadServer m)                    => ActorId -> RequestTimed a -> m ()@@ -126,7 +126,7 @@ addSmell aid = do   b <- getsState $ getActorBody aid   fact <- getsState $ (EM.! bfid b) . sfactionD-  smellRadius <- sumOrganEqpServer Effect.EqpSlotAddSmell aid+  smellRadius <- sumOrganEqpServer IK.EqpSlotAddSmell aid   -- TODO: right now only humans leave smell and content should not   -- give humans the ability to smell (dominated monsters are rare enough).   -- In the future smells should be marked by the faction that left them@@ -184,7 +184,6 @@ reqMelee :: (MonadAtomic m, MonadServer m)          => ActorId -> ActorId -> ItemId -> CStore -> m () reqMelee source target iid cstore = do-  itemToF <- itemToFullServer   sb <- getsState $ getActorBody source   tb <- getsState $ getActorBody target   let adj = checkAdjacent sb tb@@ -214,8 +213,9 @@           -- Non-projectiles can't pierce, so terminate their flight.           execUpdAtomic           $ UpdTrajectory source (btrajectory sb) (Just ([], speed))+    let c = CActor source cstore     -- Msgs inside itemEffect describe the target part.-    itemEffectAndDestroy source target iid (itemToF iid 1) cstore+    itemEffectAndDestroy source target iid c     -- The only way to start a war is to slap an enemy. Being hit by     -- and hitting projectiles count as unintentional friendly fire.     let friendlyFire = bproj sb || bproj tb@@ -234,7 +234,6 @@   cops <- getsState scops   sb <- getsState $ getActorBody source   tb <- getsState $ getActorBody target-  sfact <- getsState $ (EM.! bfid sb) . sfactionD   tfact <- getsState $ (EM.! bfid tb) . sfactionD   let spos = bpos sb       tpos = bpos tb@@ -242,8 +241,7 @@       atWar = isAtWar tfact (bfid sb)       req = ReqDisplace target   activeItems <- activeItemsServer target-  dEnemy <--    getsState $ dispEnemy source (fst <$> gleader sfact) target activeItems+  dEnemy <- getsState $ dispEnemy source target activeItems   if not adj then execFailure source req DisplaceDistant   else if atWar && not dEnemy   then do@@ -276,7 +274,7 @@ -- Note that if @serverTile /= freshClientTile@, @freshClientTile@ -- should not be alterable (but @serverTile@ may be). reqAlter :: (MonadAtomic m, MonadServer m)-         => ActorId -> Point -> Maybe F.Feature -> m ()+         => ActorId -> Point -> Maybe TK.Feature -> m () reqAlter source tpos mfeat = do   cops@Kind.COps{cotile=cotile@Kind.Ops{okind, opick}} <- getsState scops   sb <- getsState $ getActorBody source@@ -300,14 +298,14 @@               (True, False) -> execUpdAtomic $ UpdAlterClear lid (-1)               _ -> return ()         feats = case mfeat of-          Nothing -> TileKind.tfeature $ okind serverTile+          Nothing -> TK.tfeature $ okind serverTile           Just feat2 | Tile.hasFeature cotile feat2 serverTile -> [feat2]           Just _ -> []         toAlter feat =           case feat of-            F.OpenTo tgroup -> Just tgroup-            F.CloseTo tgroup -> Just tgroup-            F.ChangeTo tgroup -> Just tgroup+            TK.OpenTo tgroup -> Just tgroup+            TK.CloseTo tgroup -> Just tgroup+            TK.ChangeTo tgroup -> Just tgroup             _ -> Nothing         groupsToAlterTo = mapMaybe toAlter feats     as <- getsState $ actorList (const True) lid@@ -315,7 +313,7 @@       -- Neither searching nor altering possible; silly client.       execFailure source req AlterNothing     else do-      if EM.null $ lvl `atI` tpos then+      if EM.notMember tpos $ lfloor lvl then         if unoccupied as tpos then do           when (serverTile /= freshClientTile) $ do             -- Search, in case some actors (of other factions?)@@ -324,7 +322,7 @@           maybe skip changeTo $ listToMaybe groupsToAlterTo             -- TODO: pick another, if the first one void           -- Perform an effect, if any permitted.-          void $ triggerEffect source feats+          void $ triggerEffect source tpos feats         else execFailure source req AlterBlockActor       else execFailure source req AlterBlockItem @@ -343,12 +341,14 @@ reqMoveItem aid iid k fromCStore toCStore = do   b <- getsState $ getActorBody aid   activeItems <- activeItemsServer aid+  let fromC = CActor aid fromCStore+      toC = CActor aid toCStore+  bagBefore <- getsState $ getCBag toC   let moveItem = do         when (fromCStore == CGround) $ do           seed <- getsServer $ (EM.! iid) . sitemSeedD           execUpdAtomic $ UpdDiscoverSeed (blid b) (bpos b) iid seed-        upds <- generalMoveItem iid k (CActor aid fromCStore)-                                      (CActor aid toCStore)+        upds <- generalMoveItem iid k fromC toC         mapM_ execUpdAtomic upds       req = ReqMoveItem iid k fromCStore toCStore   if k < 1 || fromCStore == toCStore then execFailure aid req ItemNothing@@ -358,7 +358,45 @@   else do     if calmEnough b activeItems then moveItem     else execFailure aid req ItemNotCalm+  -- Reset timeout for equipped periodic items.+  when (toCStore `elem` [CEqp, COrgan]+        && fromCStore `notElem` [CEqp, COrgan]) $ do+    localTime <- getsState $ getLocalTime (blid b)+    discoEffect <- getsServer sdiscoEffect+    mrndTimeout <- rndToAction $ computeRndTimeout localTime discoEffect iid+    let beforeIt = case iid `EM.lookup` bagBefore of+          Nothing -> []  -- no such items before move+          Just (_, it2) -> it2+    -- The moved item set (not the whole stack) has its timeout+    -- reset to a random value between timeout and twice timeout.+    -- This prevents micromanagement via swapping items in and out of eqp+    -- and via exact prediction of first timeout after equip.+    case mrndTimeout of+      Just rndT -> do+        bagAfter <- getsState $ getCBag toC+        let afterIt = case iid `EM.lookup` bagAfter of+              Nothing -> assert `failure` (iid, bagAfter, toC)+              Just (_, it2) -> it2+            resetIt = beforeIt ++ replicate k rndT+        when (afterIt /= resetIt) $+          execUpdAtomic $ UpdTimeItem iid toC afterIt resetIt+      Nothing -> return ()  -- no Periodic or Timeout aspect; don't touch +computeRndTimeout :: Time -> DiscoveryEffect -> ItemId -> Rnd (Maybe Time)+computeRndTimeout localTime discoEffect iid = do+  let timeoutAspect :: IK.Aspect Int -> Maybe Int+      timeoutAspect (IK.Timeout t) = Just t+      timeoutAspect _ = Nothing+  case EM.lookup iid discoEffect of+    Just ItemAspectEffect{jaspects} -> do+      case mapMaybe timeoutAspect jaspects of+        [t] | IK.Periodic `elem` jaspects -> do+          rndT <- randomR (0, t)+          let rndTurns = timeDeltaScale (Delta timeTurn) rndT+          return $ Just $ timeShift localTime rndTurns+        _ -> return Nothing+    _ -> assert `failure` (iid, discoEffect)+ -- * ReqProject  reqProject :: (MonadAtomic m, MonadServer m)@@ -381,24 +419,27 @@          -> CStore   -- ^ the location of the item          -> m () reqApply aid iid cstore = assert (cstore /= CSha) $ do-  bag <- getsState $ getActorBag aid cstore   let req = ReqApply iid cstore-  if EM.notMember iid bag-    then execFailure aid req ApplyOutOfReach-    else do-      actorBlind <- radiusBlind-                    <$> sumOrganEqpServer Effect.EqpSlotAddSight aid-      item <- getsState $ getItemBody iid-      let blindScroll = jsymbol item == '?' && actorBlind-      if blindScroll-        then execFailure aid req ApplyBlind-        else applyItem aid iid cstore+  bag <- getsState $ getActorBag aid cstore+  case EM.lookup iid bag of+    Nothing -> execFailure aid req ApplyOutOfReach+    Just kit -> do+      itemToF <- itemToFullServer+      b <- getsState $ getActorBody aid+      activeItems <- activeItemsServer aid+      actorSk <- actorSkillsServer aid+      let skill = EM.findWithDefault 0 Ability.AbProject actorSk+          itemFull = itemToF iid kit+          legal = permittedApply " " skill itemFull b activeItems+      case legal of+        Left reqFail -> execFailure aid req reqFail+        Right _ -> applyItem aid iid cstore  -- * ReqTrigger  -- | Perform the effect specified for the tile in case it's triggered. reqTrigger :: (MonadAtomic m, MonadServer m)-           => ActorId -> Maybe F.Feature -> m ()+           => ActorId -> Maybe TK.Feature -> m () reqTrigger aid mfeat = do   Kind.COps{cotile=cotile@Kind.Ops{okind}} <- getsState scops   sb <- getsState $ getActorBody aid@@ -407,25 +448,19 @@   let tpos = bpos sb       serverTile = lvl `at` tpos       feats = case mfeat of-        Nothing -> TileKind.tfeature $ okind serverTile+        Nothing -> TK.tfeature $ okind serverTile         Just feat2 | Tile.hasFeature cotile feat2 serverTile -> [feat2]         Just _ -> []       req = ReqTrigger mfeat-  go <- triggerEffect aid feats+  go <- triggerEffect aid tpos feats   unless go $ execFailure aid req TriggerNothing  triggerEffect :: (MonadAtomic m, MonadServer m)-              => ActorId -> [F.Feature] -> m Bool-triggerEffect aid feats = do-  sb <- getsState $ getActorBody aid-  let tpos = bpos sb-      triggerFeat feat =+              => ActorId -> Point -> [TK.Feature] -> m Bool+triggerEffect aid tpos feats = do+  let triggerFeat feat =         case feat of-          F.Cause ef -> do-            -- No block against tile, hence unconditional.-            execSfxAtomic $ SfxTrigger aid tpos feat-            void $ effectsSem [ef] aid aid False-            return True+          TK.Cause ef -> itemEffectCause aid tpos ef           _ -> return False   goes <- mapM triggerFeat feats   return $! or goes@@ -436,7 +471,7 @@ -- so that they are available in the first game too, -- not only in subsequent, restarted, games. reqGameRestart :: (MonadAtomic m, MonadServer m)-               => ActorId -> GroupName -> Int -> [(Int, (Text, Text))] -> m ()+               => ActorId -> GroupName ModeKind -> Int -> [(Int, (Text, Text))] -> m () reqGameRestart aid groupName d configHeroNames = do   modifyServer $ \ser ->     ser {sdebugNxt = (sdebugNxt ser) { sdifficultySer = d
Game/LambdaHack/Server/ItemRev.hs view
@@ -25,7 +25,8 @@ import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg import Game.LambdaHack.Common.Random-import Game.LambdaHack.Content.ItemKind+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@@ -55,20 +56,20 @@           -> Item buildItem (FlavourMap flavour) discoRev ikChosen kind jlid =   let jkindIx  = discoRev EM.! ikChosen-      jsymbol  = isymbol kind-      jname    = iname kind+      jsymbol  = IK.isymbol kind+      jname    = IK.iname kind       jflavour =-        case iflavour kind of+        case IK.iflavour kind of           [fl] -> fl           _ -> flavour EM.! ikChosen-      jfeature = ifeature kind-      jweight = iweight kind+      jfeature = IK.ifeature kind+      jweight = IK.iweight kind   in Item{..}  -- | Generate an item based on level. newItem :: Kind.COps -> FlavourMap -> DiscoveryKindRev-        -> Freqs -> LevelId -> AbsDepth -> AbsDepth-        -> Rnd (Maybe (ItemKnown, ItemFull, ItemSeed, Int, GroupName))+        -> Freqs ItemKind -> LevelId -> AbsDepth -> AbsDepth+        -> Rnd (Maybe (ItemKnown, ItemFull, ItemSeed, GroupName ItemKind)) newItem Kind.COps{coitem=Kind.Ops{ofoldrGroup}}         flavour discoRev itemFreq jlid         ldepth@(AbsDepth ld) totalDepth@(AbsDepth depth) = do@@ -83,7 +84,7 @@         in y1 + (y2 - y1) * (ld * 10 - x1 * depth)            `divUp` ((x2 - x1) * depth)       f itemGroup q p ik kind acc =-        let rarity = linearInterpolation (irarity kind)+        let rarity = linearInterpolation (IK.irarity kind)         in (q * p * rarity, ((ik, kind), itemGroup)) : acc       g (itemGroup, q) = ofoldrGroup itemGroup (f itemGroup q) []       freqDepth = concatMap g itemFreq@@ -91,17 +92,17 @@   if nullFreq freq then return Nothing   else do     ((itemKindId, itemKind), itemGroup) <- frequency freq-    itemN <- castDice ldepth totalDepth (icount itemKind)+    itemN <- castDice ldepth totalDepth (IK.icount itemKind)     seed <- fmap toEnum random     let itemBase = buildItem flavour discoRev itemKindId itemKind jlid         itemK = max 1 itemN+        itemTimer = []+        itemDisco = Just ItemDisco {itemKindId, itemKind, itemAE = Just iae}         iae = seedToAspectsEffects seed itemKind ldepth totalDepth-        itemFull = ItemFull {itemBase, itemK, itemDisco = Just itemDisco}-        itemDisco = ItemDisco {itemKindId, itemKind, itemAE = Just iae}+        itemFull = ItemFull {..}     return $ Just ( (jkindIx itemBase, iae)                   , itemFull                   , seed-                  , itemK                   , itemGroup )  -- | Flavours assigned by the server to item kinds, in this particular game.@@ -111,20 +112,21 @@ emptyFlavourMap :: FlavourMap emptyFlavourMap = FlavourMap EM.empty --- | Assigns flavours to item kinds. Assures no flavor is repeated,--- except for items with only one permitted flavour.+-- | Assigns flavours to item kinds. Assures no flavor is repeated for the same+-- symbol, except for items with only one permitted flavour. rollFlavourMap :: S.Set Flavour -> Kind.Id ItemKind -> ItemKind                -> Rnd ( EM.EnumMap (Kind.Id ItemKind) Flavour                       , EM.EnumMap Char (S.Set Flavour) )                -> Rnd ( EM.EnumMap (Kind.Id ItemKind) Flavour                       , EM.EnumMap Char (S.Set Flavour) ) rollFlavourMap fullFlavSet key ik rnd =-  let flavours = iflavour ik+  let flavours = IK.iflavour ik   in if length flavours == 1      then rnd      else do        (assocs, availableMap) <- rnd-       let available = EM.findWithDefault fullFlavSet (isymbol ik) availableMap+       let available =+             EM.findWithDefault fullFlavSet (IK.isymbol ik) availableMap            proper = S.fromList flavours `S.intersection` available        assert (not (S.null proper)                `blame` "not enough flavours for items"@@ -132,7 +134,7 @@          flavour <- oneOf (S.toList proper)          let availableReduced = S.delete flavour available          return ( EM.insert key flavour assocs-                , EM.insert (isymbol ik) availableReduced availableMap)+                , EM.insert (IK.isymbol ik) availableReduced availableMap)  -- | Randomly chooses flavour for all item kinds for this game. dungeonFlavourMap :: Kind.COps -> Rnd FlavourMap
Game/LambdaHack/Server/ItemServer.hs view
@@ -1,26 +1,31 @@ -- | Server operations for items. module Game.LambdaHack.Server.ItemServer-  ( rollAndRegisterItem, registerItem, createItems, placeItemsInDungeon-  , fullAssocsServer, activeItemsServer, itemToFullServer, mapActorCStore_+  ( rollItem, rollAndRegisterItem, registerItem+  , placeItemsInDungeon, embedItemsInDungeon, fullAssocsServer+  , activeItemsServer, itemToFullServer, mapActorCStore_   ) where  import Control.Monad import qualified Data.EnumMap.Strict as EM import qualified Data.HashMap.Strict as HM import Data.Key (mapWithKeyM_)+import Data.Maybe  import Game.LambdaHack.Atomic import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState-import qualified Game.LambdaHack.Common.Feature as F 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 qualified Game.LambdaHack.Common.PointArray as PointArray import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile+import Game.LambdaHack.Content.ItemKind (ItemKind)+import Game.LambdaHack.Content.TileKind (TileKind)+import qualified Game.LambdaHack.Content.TileKind as TK import Game.LambdaHack.Server.ItemRev import Game.LambdaHack.Server.MonadServer import Game.LambdaHack.Server.State@@ -35,7 +40,7 @@     Just iid -> do       -- TODO: try to avoid this case for createItems,       -- to make items more interesting-      execUpdAtomic $ cmd iid item k container+      execUpdAtomic $ cmd iid item (k, []) container       return iid     Nothing -> do       icounter <- getsServer sicounter@@ -44,66 +49,98 @@             , sitemRev = HM.insert itemKnown icounter (sitemRev ser)             , sitemSeedD = EM.insert icounter seed (sitemSeedD ser)             , sdiscoEffect = EM.insert icounter iae (sdiscoEffect ser)}-      execUpdAtomic $ cmd icounter item k container+      execUpdAtomic $ cmd icounter item (k, []) container       return $! icounter -createItems :: (MonadAtomic m, MonadServer m)-            => Int -> Point -> LevelId -> m ()-createItems n pos lid = do+createLevelItem :: (MonadAtomic m, MonadServer m)+                => Point -> LevelId -> m ()+createLevelItem pos lid = do   Level{litemFreq} <- getLevel lid   let container = CFloor lid pos-  replicateM_ n $ void $ rollAndRegisterItem lid litemFreq container True+  void $ rollAndRegisterItem lid litemFreq container True Nothing -rollAndRegisterItem :: (MonadAtomic m, MonadServer m)-                    => LevelId -> Freqs -> Container -> Bool-                    -> m (Maybe (ItemId, (ItemFull, GroupName)))-rollAndRegisterItem lid itemFreq container verbose = do+embedItem :: (MonadAtomic m, MonadServer m)+          => LevelId -> Point -> Kind.Id TileKind -> m ()+embedItem lid pos tk = do+  Kind.COps{cotile} <- getsState scops+  let embeds = Tile.embedItems cotile tk+      causes = Tile.causeEffects cotile tk+      -- TODO: unack this, e.g., by turning each Cause into Embed+      itemFreq = zip embeds (repeat 1)+                 ++ if not (null causes) && null embeds+                    then [("hero", 1)]  -- hack: the bag, not item, is relevant+                    else []+      container = CEmbed lid pos+  void $ rollAndRegisterItem lid itemFreq container False Nothing++rollItem :: (MonadAtomic m, MonadServer m)+         => LevelId -> Freqs ItemKind+         -> m (Maybe (ItemKnown, ItemFull, ItemSeed, GroupName ItemKind))+rollItem lid itemFreq = do   cops <- getsState scops   flavour <- getsServer sflavour   discoRev <- getsServer sdiscoKindRev   totalDepth <- getsState stotalDepth   Level{ldepth} <- getLevel lid-  m4 <- rndToAction-        $ newItem cops flavour discoRev itemFreq lid ldepth totalDepth+  rndToAction $ newItem cops flavour discoRev itemFreq lid ldepth totalDepth++rollAndRegisterItem :: (MonadAtomic m, MonadServer m)+                    => LevelId -> Freqs ItemKind -> Container -> Bool+                    -> Maybe Int+                    -> m (Maybe (ItemId, (ItemFull, GroupName ItemKind)))+rollAndRegisterItem lid itemFreq container verbose mk = do+  m4 <- rollItem lid itemFreq   case m4 of     Nothing -> return Nothing-    Just (itemKnown, itemFull, seed, k, itemGroup) -> do-      iid <- registerItem (itemBase itemFull) itemKnown seed k container verbose+    Just (itemKnown, itemFullRaw, seed, itemGroup) -> do+      let itemFull = itemFullRaw {itemK = fromMaybe (itemK itemFullRaw) mk}+      iid <- registerItem (itemBase itemFull) itemKnown seed+                          (itemK itemFull) container verbose       return $ Just (iid, (itemFull, itemGroup)) -placeItemsInDungeon :: (MonadAtomic m, MonadServer m) => m ()+placeItemsInDungeon :: forall m. (MonadAtomic m, MonadServer m) => m () placeItemsInDungeon = do   Kind.COps{cotile} <- getsState scops-  let initialItems lid (Level{ltile, litemNum, lxsize, lysize}) = do+  let initialItems lid (Level{lfloor, ltile, litemNum, lxsize, lysize}) = do         let factionDist = max lxsize lysize - 5-        replicateM litemNum $ do-          Level{lfloor} <- getLevel lid-          let dist p = minimum $ maxBound : map (chessDist p) (EM.keys lfloor)-          pos <- rndToAction $ findPosTry 100 ltile+            placeItems :: [Point] -> Int -> m ()+            placeItems _ 0 = return ()+            placeItems lfloorKeys n = do+              let dist p = minimum $ maxBound : map (chessDist p) lfloorKeys+              pos <- rndToAction $ findPosTry 100 ltile                    (\_ t -> Tile.isWalkable cotile t-                            && (not $ Tile.hasFeature cotile F.NoItem t))-                   [ \p t -> Tile.hasFeature cotile F.OftenItem t+                            && (not $ Tile.hasFeature cotile TK.NoItem t))+                   [ \p t -> Tile.hasFeature cotile TK.OftenItem t                              && dist p > factionDist `div` 5-                   , \p t -> Tile.hasFeature cotile F.OftenItem t+                   , \p t -> Tile.hasFeature cotile TK.OftenItem t                              && dist p > factionDist `div` 7-                   , \p t -> Tile.hasFeature cotile F.OftenItem t+                   , \p t -> Tile.hasFeature cotile TK.OftenItem t                              && dist p > factionDist `div` 9-                   , \p t -> Tile.hasFeature cotile F.OftenItem t+                   , \p t -> Tile.hasFeature cotile TK.OftenItem t                              && dist p > factionDist `div` 12                    , \p _ -> dist p > factionDist `div` 5-                   , \p t -> Tile.hasFeature cotile F.OftenItem t+                   , \p t -> Tile.hasFeature cotile TK.OftenItem t                              || dist p > factionDist `div` 7-                   , \p t -> Tile.hasFeature cotile F.OftenItem t+                   , \p t -> Tile.hasFeature cotile TK.OftenItem t                              || dist p > factionDist `div` 9-                   , \p t -> Tile.hasFeature cotile F.OftenItem t+                   , \p t -> Tile.hasFeature cotile TK.OftenItem t                              || dist p > factionDist `div` 12                    , \p _ -> dist p > 1-                   , \p _ -> EM.notMember p lfloor+                   , \p _ -> dist p > 0                    ]-          createItems 1 pos lid+              createLevelItem pos lid+              placeItems (pos : lfloorKeys) (n - 1)+        placeItems (EM.keys lfloor) litemNum   dungeon <- getsState sdungeon   mapWithKeyM_ initialItems dungeon +embedItemsInDungeon :: (MonadAtomic m, MonadServer m) => m ()+embedItemsInDungeon = do+  let embedItems lid (Level{ltile}) =+        PointArray.mapWithKeyM_A (embedItem lid) ltile+  dungeon <- getsState sdungeon+  mapWithKeyM_ embedItems dungeon+ fullAssocsServer :: MonadServer m                  => ActorId -> [CStore] -> m [(ItemId, ItemFull)] fullAssocsServer aid cstores = do@@ -117,7 +154,7 @@   activeAssocs <- fullAssocsServer aid [CEqp, COrgan]   return $! map snd activeAssocs -itemToFullServer :: MonadServer m => m (ItemId -> Int -> ItemFull)+itemToFullServer :: MonadServer m => m (ItemId -> ItemQuant -> ItemFull) itemToFullServer = do   cops <- getsState scops   discoKind <- getsServer sdiscoKind@@ -128,7 +165,7 @@  -- | Mapping over actor's items from a give store. mapActorCStore_ :: MonadServer m-                => CStore -> (ItemId -> Int -> m a) -> Actor ->  m ()+                => CStore -> (ItemId -> ItemQuant -> m a) -> Actor ->  m () mapActorCStore_ cstore f b = do   bag <- getsState $ getBodyActorBag b cstore   mapM_ (uncurry f) $ EM.assocs bag
Game/LambdaHack/Server/LoopServer.hs view
@@ -18,8 +18,8 @@ import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.ClientOptions import qualified Game.LambdaHack.Common.Color as Color-import qualified Game.LambdaHack.Common.Effect as Effect 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@@ -30,13 +30,13 @@ import Game.LambdaHack.Common.Response import Game.LambdaHack.Common.State 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.Server.EndServer import Game.LambdaHack.Server.Fov import Game.LambdaHack.Server.HandleEffectServer import Game.LambdaHack.Server.HandleRequestServer-import Game.LambdaHack.Server.ItemServer import Game.LambdaHack.Server.MonadServer import Game.LambdaHack.Server.PeriodicServer import Game.LambdaHack.Server.ProtocolServer@@ -142,14 +142,16 @@   when (clipN `mod` writeSaveClips == 0) $ do     modifyServer $ \ser -> ser {swriteSave = False}     writeSaveAll False-  when (clipN `mod` leadLevelClips == 0) leadLevelFlip-  -- Add monsters each turn, not each clip.-  -- Do this on only one of the arenas to prevent micromanagement,-  -- e.g., spreading leaders across levels to bump monster generation.+  when (clipN `mod` leadLevelClips == 0) leadLevelSwitch   if clipMod == 1 then do+    -- Periodic activation only once per turn, for speed, but on all arenas.+    mapM_ activatePeriodicLevel arenas+    -- Add monsters each turn, not each clip.+    -- Do this on only one of the arenas to prevent micromanagement,+    -- e.g., spreading leaders across levels to bump monster generation.     arena <- rndToAction $ oneOf arenas-    activatePeriodicLevel arena     spawnMonster arena+    -- Check, once per turn, for benchmark game stop, after a set time.     stopAfter <- getsServer $ sstopAfter . sdebugSer     case stopAfter of       Nothing -> return True@@ -163,21 +165,31 @@   else return True  -- | Trigger periodic items for all actors on the given level.--- This is done each game turn, not player turn, not to overpower--- fast actors (assuming the effects are positive). activatePeriodicLevel :: (MonadAtomic m, MonadServer m) => LevelId -> m () activatePeriodicLevel lid = do-  time <- getsState $ getLocalTime lid-  let turnN = time `timeFit` timeTurn-      activatePeriodicItem aid (iid, itemFull) = do-        case strengthFromEqpSlot Effect.EqpSlotPeriodic itemFull of-          Nothing -> return ()-          Just n -> when (turnN `mod` (100 `div` n) == 0) $-                      void $ itemEffect aid aid iid itemFull False True-            -- periodic activation doesn't destroy items, even non-Durable+  discoEffect <- getsServer sdiscoEffect+  let activatePeriodicItem c aid iid =+        case EM.lookup iid discoEffect of+          Just ItemAspectEffect{jeffects, jaspects} ->+            if IK.Periodic `elem` jaspects then do+              -- Check if the item is still in the bag (previous items act!).+              bag <- getsState $ getCBag c+              case iid `EM.lookup` bag of+                Nothing -> return ()  -- item dropped+                Just kit ->+                  -- In periodic activation, consider *only* recharging effects.+                  effectAndDestroy aid aid iid c True+                                   (allRecharging jeffects) jaspects kit+            else+              return ()+          _ -> assert `failure` (lid, aid, c, iid)+      activatePeriodicCStore aid cstore = do+        let c = CActor aid cstore+        bag <- getsState $ getCBag c+        mapM_ (activatePeriodicItem c aid) $ EM.keys bag       activatePeriodicActor aid = do-        allItems <- fullAssocsServer aid [COrgan, CEqp]-        mapM_ (activatePeriodicItem aid) allItems+        activatePeriodicCStore aid COrgan+        activatePeriodicCStore aid CEqp   allActors <- getsState $ actorRegularAssocs (const True) lid   mapM_ (\(aid, _) -> activatePeriodicActor aid) allActors @@ -196,8 +208,9 @@   let -- Actors of the same faction move together.       -- TODO: insert wrt the order, instead of sorting       isLeader (aid, b) = Just aid /= fmap fst (gleader (factionD EM.! bfid b))+      notDead (_, b) = if bproj b then bhp b >= 0 else bhp b > 0       order = Ord.comparing $-        ((>= 0) . bhp . snd) &&& bfid . snd &&& isLeader &&& bsymbol . snd+        notDead &&& bfid . snd &&& isLeader &&& bsymbol . snd       (atime, as) = EM.findMin lprio       ams = map (\a -> (a, getActorBody a s)) as       mnext | EM.null lprio = Nothing  -- no actor alive, wait until it spawns@@ -216,10 +229,6 @@       handleActors lid     Just (aid, b) | bhp b < 0 && bproj b -> do       -- A projectile hits an actor. The carried item is destroyed.-      -- TODO: perhaps don't destroy if no effect (NoEffect),-      -- to help testing items. But OTOH, we want most items to have-      -- some effect, even silly, for flavour. Anyway, if the silly-      -- effect identifies an item, the hit is not wasted, so this makes sense.       startActor aid       dieSer aid b True       handleActors lid@@ -262,20 +271,20 @@         cmdS <- sendQueryUI side aid         -- TODO: check that the command is legal first, report and reject,         -- but do not crash (currently server asserts things and crashes)-        aidNew <- handleRequestUI side cmdS+        (aidNew, action) <- handleRequestUI side cmdS         let hasWait (ReqUITimed ReqWait{}) = True             hasWait (ReqUILeader _ _ cmd) = hasWait cmd             hasWait _ = False         maybe skip (setBWait (hasWait cmdS)) aidNew         -- Advance time once, after the leader switched perhaps many times.-        -- TODO: this is correct only when all heroes have the same-        -- speed and can't switch leaders by, e.g., aiming a wand-        -- of domination. We need to generalize by displaying-        -- "(next move in .3s [RET]" when switching leaders.-        -- RET waits .3s and gives back control,-        -- Any other key does the .3s wait and the action from the key-        -- at once.+        -- Sometimes this may result in a double move of the new leader,+        -- followed by a double pause. Or a fractional variant of that.+        -- In this setup, reading a scroll of Previous Leader is a free action+        -- for the old leader, but otherwise his time is undisturbed.+        -- He is able to move normally in the same turn, immediately+        -- after the new leader completes his move.         maybe skip advanceTime aidNew+        action       else do         -- Clear messages in the UI client (if any), if the actor         -- is a leader (which happens when a UI client is fully@@ -284,13 +293,14 @@         -- but we'd have to add -more- prompts.         when mainUIactor $ execUpdAtomic $ UpdRecordHistory side         cmdS <- sendQueryAI side aid-        aidNew <- handleRequestAI side aid cmdS+        (aidNew, action) <- handleRequestAI side aid cmdS         let hasWait (ReqAITimed ReqWait{}) = True             hasWait (ReqAILeader _ _ cmd) = hasWait cmd             hasWait _ = False         setBWait (hasWait cmdS) aidNew         -- AI always takes time and so doesn't loop.         advanceTime aidNew+        action       handleActors lid  gameExit :: (MonadAtomic m, MonadServerReadRequest m) => m ()
Game/LambdaHack/Server/MonadServer.hs view
@@ -9,7 +9,8 @@                , liftIO  -- exposed only to be implemented, not used                )     -- * Assorted primitives-  , debugPrint, saveServer, saveName, dumpRngs+  , debugPossiblyPrint, debugPossiblyPrintAndExit+  , serverPrint, saveServer, saveName, dumpRngs   , restoreScore, registerScore   , resetSessionStart, resetGameStart, elapsedSessionTimeGT   , tellAllClipPS, tellGameClipPS@@ -30,6 +31,7 @@ import System.IO import qualified System.Random as R import System.Time+import System.Exit (exitFailure)  import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState@@ -61,13 +63,26 @@   liftIO         :: IO a -> m a   saveChanServer :: m (Save.ChanSave (State, StateServer)) -debugPrint :: MonadServer m => Text -> m ()-debugPrint t = do+debugPossiblyPrint :: MonadServer m => Text -> m ()+debugPossiblyPrint t = do   debug <- getsServer $ sdbgMsgSer . sdebugSer   when debug $ liftIO $ do     T.hPutStrLn stderr t     hFlush stderr +debugPossiblyPrintAndExit :: MonadServer m => Text -> m ()+debugPossiblyPrintAndExit t = do+  debug <- getsServer $ sdbgMsgSer . sdebugSer+  when debug $ liftIO $ do+    T.hPutStrLn stderr t+    hFlush stderr+    exitFailure++serverPrint :: MonadServer m => Text -> m ()+serverPrint t = liftIO $ do+  T.hPutStrLn stderr t+  hFlush stderr+ saveServer :: MonadServer m => m () saveServer = do   s <- getState@@ -138,7 +153,7 @@       outputScore (worthMentioning, (ntable, pos)) =         -- If not human, probably debugging, so dump instead of registering.         if bench || isAIFact fact then-          debugPrint $ T.intercalate "\n"+          debugPossiblyPrint $ T.intercalate "\n"           $ HighScore.showScore (pos, HighScore.getRecord pos ntable)         else           if worthMentioning then@@ -191,8 +206,9 @@     let diff = fromIntegral sCur + fromIntegral pCur / 10e12                - fromIntegral s - fromIntegral p / 10e12         cps = fromIntegral (timeFit time timeClip) / diff :: Double-    debugPrint $ "Session time:" <+> tshow diff <> "s."-                 <+> "Average clips per second:" <+> tshow cps <> "."+    debugPossiblyPrint $+      "Session time:" <+> tshow diff <> "s."+      <+> "Average clips per second:" <+> tshow cps <> "."  tellGameClipPS :: MonadServer m => m () tellGameClipPS = do@@ -205,8 +221,9 @@       let diff = fromIntegral sCur + fromIntegral pCur / 10e12                  - fromIntegral s - fromIntegral p / 10e12           cps = fromIntegral (timeFit time timeClip) / diff :: Double-      debugPrint $ "Game time:" <+> tshow diff <> "s."-                   <+> "Average clips per second:" <+> tshow cps <> "."+      debugPossiblyPrint $+        "Game time:" <+> tshow diff <> "s."+        <+> "Average clips per second:" <+> tshow cps <> "."  tryRestore :: MonadServer m            => Kind.COps -> DebugModeSer -> m (Maybe (State, StateServer))
Game/LambdaHack/Server/PeriodicServer.hs view
@@ -1,7 +1,7 @@ -- | Server operations performed periodically in the game loop -- and related operations. module Game.LambdaHack.Server.PeriodicServer-  ( spawnMonster, addAnyActor, dominateFidSfx, advanceTime, leadLevelFlip+  ( spawnMonster, addAnyActor, dominateFidSfx, advanceTime, leadLevelSwitch   ) where  import Control.Exception.Assert.Sugar@@ -15,9 +15,7 @@ import qualified Game.LambdaHack.Common.Ability as Ability import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction-import qualified Game.LambdaHack.Common.Feature as F import Game.LambdaHack.Common.Frequency import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.Kind as Kind@@ -30,8 +28,10 @@ import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind+import Game.LambdaHack.Content.ItemKind (ItemKind)+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.ModeKind+import qualified Game.LambdaHack.Content.TileKind as TK import Game.LambdaHack.Server.CommonServer import Game.LambdaHack.Server.ItemRev import Game.LambdaHack.Server.ItemServer@@ -48,7 +48,8 @@   f <- getsState $ \s fid -> not $ fcanEscape $ gplayer $ sfactionD s EM.! fid   spawns <- getsState $ actorRegularList f lid   totalDepth <- getsState stotalDepth-  -- We do not check @playerSpawn@ of any faction, but just take @lactorFreq@.+  -- TODO: eliminate the defeated and victorious faction from lactorFreq;+  -- then fcanEscape and fneverEmpty make sense for spawning factions   Level{ldepth, lactorCoeff, lactorFreq} <- getLevel lid   rc <- rndToAction         $ monsterGenChance ldepth totalDepth (length spawns) lactorCoeff@@ -64,7 +65,7 @@           execUpdAtomic $ UpdLeadFaction (bfid b) Nothing (Just (aid, Nothing))  addAnyActor :: (MonadAtomic m, MonadServer m)-            => Freqs -> LevelId -> Time -> Maybe Point+            => Freqs ItemKind -> LevelId -> Time -> Maybe Point             -> m (Maybe ActorId) addAnyActor actorFreq lid time mpos = do   -- We bootstrap the actor by first creating the trunk of the actor's body@@ -79,9 +80,9 @@         $ newItem cops flavour discoRev actorFreq lid ldepth totalDepth   case m4 of     Nothing -> return Nothing-    Just (itemKnown, trunkFull, seed, k, _) -> do+    Just (itemKnown, trunkFull, seed, _) -> do       let ik = maybe (assert `failure` trunkFull) itemKind $ itemDisco trunkFull-          freqNames = map fst $ ifreq ik+          freqNames = map fst $ IK.ifreq ik           f fact = fgroup (gplayer fact)           factNames = map f $ EM.elems factionD           fidName = case freqNames `intersect` factNames of@@ -100,8 +101,9 @@           rndToAction rollPos       let container = (CTrunk fid lid pos)       trunkId <--        registerItem (itemBase trunkFull) itemKnown seed k container False-      addActorIid trunkId trunkFull fid pos lid id "it" time+        registerItem (itemBase trunkFull) itemKnown seed+                     (itemK trunkFull) container False+      addActorIid trunkId trunkFull False fid pos lid id "it" time  rollSpawnPos :: Kind.COps -> ES.EnumSet Point              -> LevelId -> Level -> FactionId -> State@@ -114,11 +116,11 @@       isLit = Tile.isLit cotile       distantAtLeast d p _ =         all (\b -> chessDist (bpos b) p > d) inhabitants-  -- Not considering F.OftenActor, because monsters emerge from hidden ducts,+  -- Not considering TK.OftenActor, because monsters emerge from hidden ducts,   -- which are easier to hide in crampy corridors that lit halls.   findPosTry 100 ltile     ( \p t -> Tile.isWalkable cotile t-              && not (Tile.hasFeature cotile F.NoActor t)+              && not (Tile.hasFeature cotile TK.NoActor t)               && unoccupied as p)     [ \_ t -> not (isLit t)  -- no such tiles on some maps     , distantAtLeast factionDist@@ -128,6 +130,7 @@     , \p _ -> not $ p `ES.member` visible     , distantAtLeast 3  -- otherwise a fast actor can walk and hit in one turn     ]+ dominateFidSfx :: (MonadAtomic m, MonadServer m)                => FactionId -> ActorId -> m Bool dominateFidSfx fid target = do@@ -138,7 +141,7 @@     then do       tb <- getsState $ getActorBody target       let execSfx = execSfxAtomic-                    $ SfxEffect (boldfid tb) target Effect.Dominate+                    $ SfxEffect (boldfid tb) target IK.Dominate       execSfx       dominateFid fid target       execSfx@@ -163,7 +166,7 @@   tb <- getsState $ getActorBody target   deduceKilled tb   ais <- getsState $ getCarriedAssocs tb-  calmMax <- sumOrganEqpServer Effect.EqpSlotAddMaxCalm target+  calmMax <- sumOrganEqpServer IK.EqpSlotAddMaxCalm target   execUpdAtomic $ UpdLoseActor target tb ais   let bNew = tb { bfid = fid                 , boldfid = bfid tb@@ -217,14 +220,16 @@         -- Clear delta for the next player turn.         execUpdAtomic $ UpdRefillHP aid clearMark -leadLevelFlip :: (MonadAtomic m, MonadServer m) => m ()-leadLevelFlip = do+leadLevelSwitch :: (MonadAtomic m, MonadServer m) => m ()+leadLevelSwitch = do   Kind.COps{cotile} <- getsState scops-  let canFlip fact = case fleaderMode (gplayer fact) of-                       LeaderNull -> True-                       LeaderAI _ -> True-                       LeaderUI AutoLeader{autoDungeon} -> autoDungeon-      flipFaction fact | not $ canFlip fact = return ()+  let canSwitch fact = fst (autoDungeonLevel fact)+                       -- a hack to help AI, until AI client can switch levels+                       || case fleaderMode (gplayer fact) of+                            LeaderNull -> False+                            LeaderAI _ -> True+                            LeaderUI _ -> False+      flipFaction fact | not $ canSwitch fact = return ()       flipFaction fact = do         case gleader fact of           Nothing -> return ()
Game/LambdaHack/Server/StartServer.hs view
@@ -22,9 +22,7 @@ import Game.LambdaHack.Common.ActorState import Game.LambdaHack.Common.ClientOptions import qualified Game.LambdaHack.Common.Color as Color-import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction-import qualified Game.LambdaHack.Common.Feature as F import Game.LambdaHack.Common.Flavour import qualified Game.LambdaHack.Common.HighScore as HighScore import Game.LambdaHack.Common.Item@@ -38,9 +36,11 @@ import Game.LambdaHack.Common.State import qualified Game.LambdaHack.Common.Tile as Tile import Game.LambdaHack.Common.Time-import Game.LambdaHack.Content.ItemKind+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 qualified Game.LambdaHack.Content.TileKind as TK import Game.LambdaHack.Server.CommonServer import qualified Game.LambdaHack.Server.DungeonGen as DungeonGen import Game.LambdaHack.Server.Fov@@ -68,12 +68,13 @@   let defLocal | knowMap = s                | otherwise = localFromGlobal s   discoS <- getsServer sdiscoKind-  let sdiscoKind = let f ik = Effect.Identified `elem` ifeature (okind ik)+  let sdiscoKind = let f ik = IK.Identified `elem` IK.ifeature (okind ik)                in EM.filter f discoS   sdebugCli <- getsServer $ sdebugCli . sdebugSer   modeName <- getsServer $ sgameMode . sdebugSer+  let gameMode = fromMaybe "starting" modeName   broadcastUpdAtomic-    $ \fid -> UpdRestart fid sdiscoKind (pers EM.! fid) defLocal sdebugCli modeName+    $ \fid -> UpdRestart fid sdiscoKind (pers EM.! fid) defLocal sdebugCli gameMode   populateDungeon  mapFromFuns :: (Bounded a, Enum a, Ord b) => [a -> b] -> M.Map b a@@ -87,10 +88,16 @@ lowercase :: Text -> Text lowercase = T.pack . map Char.toLower . T.unpack -createFactions :: Roster -> Rnd FactionDict-createFactions players = do-  let rawCreate gplayer@Player{..} = do-        let cmap = mapFromFuns+createFactions :: AbsDepth -> Roster -> Rnd FactionDict+createFactions totalDepth players = do+  let rawCreate Player{..} = do+        entryLevel <- castDice (AbsDepth 0) (AbsDepth 0) fentryLevel+        initialActors <- castDice (AbsDepth $ abs entryLevel) totalDepth+                                  finitialActors+        let gplayer = Player{ fentryLevel = entryLevel+                            , finitialActors = initialActors+                            , ..}+            cmap = mapFromFuns                      [colorToTeamName, colorToPlainName, colorToFancyName]             nameoc = lowercase $ head $ T.words fname             fisAI = case fleaderMode of@@ -136,7 +143,7 @@  gameReset :: MonadServer m           => Kind.COps -> DebugModeSer -> Maybe R.StdGen -> m State-gameReset cops@Kind.COps{comode=Kind.Ops{opick, okind}}+gameReset cops@Kind.COps{comode=Kind.Ops{opick, okind, ouniqGroup}}           sdebug mrandom = do   dungeonSeed <- getSetGen $ sdungeonRng sdebug `mplus` mrandom   srandom <- getSetGen $ smainRng sdebug `mplus` mrandom@@ -147,22 +154,22 @@   sstart <- getsServer sstart  -- copy over from previous game   sallTime <- getsServer sallTime  -- copy over from previous game   sheroNames <- getsServer sheroNames  -- copy over from previous game-  let smode = sgameMode sdebug+  let gameMode = fromMaybe "starting" $ sgameMode sdebug       rnd :: Rnd (FactionDict, FlavourMap, DiscoveryKind, DiscoveryKindRev,                   DungeonGen.FreshDungeon)       rnd = do-        modeKind <- fmap (fromMaybe $ assert `failure` smode)-                    $ opick smode (const True)+        modeKind <- fmap (fromMaybe $ ouniqGroup "campaign")  -- the fallback+                    $ opick gameMode (const True)         let mode = okind modeKind             automatePS ps = ps {rosterList =                                   map (automatePlayer True) $ rosterList ps}             players = if sautomateAll sdebug                       then automatePS $ mroster mode                       else mroster mode-        faction <- createFactions players         sflavour <- dungeonFlavourMap cops         (sdiscoKind, sdiscoKindRev) <- serverDiscos cops         freshDng <- DungeonGen.dungeonGen cops $ mcaves mode+        faction <- createFactions (DungeonGen.freshTotalDepth freshDng) players         return (faction, sflavour, sdiscoKind, sdiscoKindRev, freshDng)   let (faction, sflavour, sdiscoKind, sdiscoKindRev, DungeonGen.FreshDungeon{..}) =         St.evalState rnd dungeonSeed@@ -182,6 +189,7 @@ populateDungeon = do   cops@Kind.COps{cotile} <- getsState scops   placeItemsInDungeon+  embedItemsInDungeon   dungeon <- getsState sdungeon   factionD <- getsState sfactionD   sheroNames <- getsServer sheroNames@@ -213,7 +221,7 @@         time <- getsState $ getLocalTime lid         let nmult = 1 + fromEnum fid3 `mod` 4  -- always positive             ntime = timeShift time (timeDeltaScale (Delta timeClip) nmult)-            validTile t = not $ Tile.hasFeature cotile F.NoActor t+            validTile t = not $ Tile.hasFeature cotile TK.NoActor t         psFree <- getsState $ nearbyFreePoints validTile ppos lid         let ps = take (finitialActors $ gplayer fact3) $ zip [0..] psFree         forM_ ps $ \ (n, p) -> do@@ -258,7 +266,7 @@ -- | Create a new monster on the level, at a given position -- and with a given actor kind and HP. addMonster :: (MonadAtomic m, MonadServer m)-           => GroupName -> FactionId -> Point -> LevelId -> Time+           => GroupName ItemKind -> FactionId -> Point -> LevelId -> Time            -> m (Maybe ActorId) addMonster groupName bfid ppos lid time = do   fact <- getsState $ (EM.! bfid) . sfactionD@@ -300,10 +308,10 @@       tryFind ps n = do         np <- findPosTry 1000 ltile  -- try really hard, for skirmish fairness                 (\_ t -> Tile.isWalkable cotile t-                         && (not $ Tile.hasFeature cotile F.NoActor t))+                         && (not $ Tile.hasFeature cotile TK.NoActor t))                 [ dist ps $ factionDist `div` 2                 , dist ps $ factionDist `div` 3-                , const (Tile.hasFeature cotile F.OftenActor)+                , const (Tile.hasFeature cotile TK.OftenActor)                 , dist ps $ factionDist `div` 3                 , dist ps $ factionDist `div` 4                 , dist ps $ factionDist `div` 5
Game/LambdaHack/Server/State.hs view
@@ -22,6 +22,7 @@ import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Perception import Game.LambdaHack.Common.Time+import Game.LambdaHack.Content.ModeKind import Game.LambdaHack.Content.RuleKind import Game.LambdaHack.Server.ItemRev @@ -60,7 +61,7 @@   , sniffIn        :: !Bool   , sniffOut       :: !Bool   , sallClear      :: !Bool-  , sgameMode      :: !GroupName+  , sgameMode      :: !(Maybe (GroupName ModeKind))   , sautomateAll   :: !Bool   , sstopAfter     :: !(Maybe Int)   , sdungeonRng    :: !(Maybe R.StdGen)@@ -122,7 +123,7 @@                                , sniffIn = False                                , sniffOut = False                                , sallClear = False-                               , sgameMode = "campaign"+                               , sgameMode = Nothing                                , sautomateAll = False                                , sstopAfter = Nothing                                , sdungeonRng = Nothing
GameDefinition/Client/UI/Content/KeyKind.hs view
@@ -7,9 +7,9 @@ import qualified Game.LambdaHack.Client.Key as K import Game.LambdaHack.Client.UI.Content.KeyKind import Game.LambdaHack.Client.UI.HumanCmd-import qualified Game.LambdaHack.Common.Effect as Effect-import qualified Game.LambdaHack.Common.Feature as F import Game.LambdaHack.Common.Misc+import qualified Game.LambdaHack.Content.ItemKind as IK+import qualified Game.LambdaHack.Content.TileKind as TK  standardKeys :: KeyKind standardKeys = KeyKind@@ -34,25 +34,25 @@       , ("less", ([CmdMove, CmdMinimal], TriggerTile            [ TriggerFeature { verb = "ascend"                             , object = "a level"-                            , feature = F.Cause (Effect.Ascend 1) }+                            , feature = TK.Cause (IK.Ascend 1) }            , TriggerFeature { verb = "escape"                             , object = "dungeon"-                            , feature = F.Cause (Effect.Escape 1) } ]))+                            , feature = TK.Cause (IK.Escape 1) } ]))       , ("CTRL-less", ([CmdMove], TriggerTile            [ TriggerFeature { verb = "ascend"                             , object = "10 levels"-                            , feature = F.Cause (Effect.Ascend 10) } ]))+                            , feature = TK.Cause (IK.Ascend 10) } ]))       , ("greater", ([CmdMove, CmdMinimal], TriggerTile            [ TriggerFeature { verb = "descend"                             , object = "a level"-                            , feature = F.Cause (Effect.Ascend (-1)) }+                            , feature = TK.Cause (IK.Ascend (-1)) }            , TriggerFeature { verb = "escape"                             , object = "dungeon"-                            , feature = F.Cause (Effect.Escape (-1)) } ]))+                            , feature = TK.Cause (IK.Escape (-1)) } ]))       , ("CTRL-greater", ([CmdMove], TriggerTile            [ TriggerFeature { verb = "descend"                             , object = "10 levels"-                            , feature = F.Cause (Effect.Ascend (-10)) } ]))+                            , feature = TK.Cause (IK.Ascend (-10)) } ]))       , ("semicolon", ([CmdMove], StepToTarget))       , ("colon", ([CmdMove], Macro "go to target for 100 steps"                                     ["semicolon", "V"]))@@ -76,18 +76,21 @@       , ("c", ([CmdMove], AlterDir            [ AlterFeature { verb = "close"                           , object = "door"-                          , feature = F.CloseTo "vertical closed door Lit" }+                          , feature = TK.CloseTo "vertical closed door Lit" }            , AlterFeature { verb = "close"                           , object = "door"-                          , feature = F.CloseTo "horizontal closed door Lit" }+                          , feature = TK.CloseTo "horizontal closed door Lit" }            , AlterFeature { verb = "close"                           , object = "door"-                          , feature = F.CloseTo "vertical closed door Dark" }+                          , feature = TK.CloseTo "vertical closed door Dark" }            , AlterFeature { verb = "close"                           , object = "door"-                          , feature = F.CloseTo "horizontal closed door Dark" }+                          , feature = TK.CloseTo "horizontal closed door Dark" }            ]))+      , ("CTRL-KP_Begin", ([CmdMove], Macro "" ["KP_Begin"]))       , ("period", ([CmdMove], Macro "" ["KP_Begin"]))+      , ("KP_5", ([CmdMove], Macro "" ["KP_Begin"]))+      , ("CTRL-KP_5", ([CmdMove], Macro "" ["KP_Begin"]))       , ("i", ([CmdMove], Macro "" ["KP_Begin"]))        -- Item use@@ -97,16 +100,16 @@       , ("G", ([CmdItem], DescribeItem CGround))       , ("A", ([CmdItem], AllOwned))       , ("g", ([CmdItem, CmdMinimal],-               MoveItem [CGround] CEqp "get" "an item" True))+               MoveItem [CGround] CEqp (Just "get") "an item" True))       , ("d", ([CmdItem], MoveItem [CEqp, CInv, CSha] CGround-                                   "drop" "an item" False))+                                   Nothing "an item" False))       , ("e", ([CmdItem], MoveItem [CInv, CSha] CEqp-                                   "equip" "an item" False))+                                   Nothing "an item" False))       , ("p", ([CmdItem], MoveItem [CEqp, CSha] CInv-                                   "pack" "an item into inventory backpack"+                                   Nothing "an item into inventory backpack"                                    False))       , ("s", ([CmdItem], MoveItem [CInv, CEqp] CSha-                                   "stash" "and share an item" False))+                                   Nothing "and share an item" False))       , ("a", ([CmdItem, CmdMinimal], Apply            [ ApplyItem { verb = "activate"                        , object = "applicable item"@@ -174,5 +177,6 @@       , ("CTRL-s", ([CmdDebug], GameSave))       , ("CTRL-f", ([CmdDebug], GameRestart "safari"))       , ("CTRL-e", ([CmdDebug], GameRestart "defense"))+      , ("CTRL-O", ([CmdDebug], DescribeItem COrgan))       ]   }
GameDefinition/Content/ItemKind.hs view
@@ -4,12 +4,12 @@ import Data.List  import Content.ItemKindActor+import Content.ItemKindBlast import Content.ItemKindOrgan-import Content.ItemKindShrapnel+import Content.ItemKindTemporary import Game.LambdaHack.Common.Color import Game.LambdaHack.Common.ContentDef import Game.LambdaHack.Common.Dice-import Game.LambdaHack.Common.Effect import Game.LambdaHack.Common.Flavour import Game.LambdaHack.Common.Misc import Game.LambdaHack.Content.ItemKind@@ -21,21 +21,47 @@   , getFreq = ifreq   , validateSingle = validateSingleItemKind   , validateAll = validateAllItemKind-  , content = items ++ organs ++ shrapnels ++ actors+  , content = items ++ organs ++ blasts ++ actors ++ temporaries   }  items :: [ItemKind] items =-  [bolas, brassLantern, buckler, dart, dart200, gem1, gem2, gem3, gloveFencing, gloveGauntlet, gloveJousting, currency, gorget, harpoon, jumpingPole, monocle, necklace1, necklace2, necklace3, necklace4, necklace5, necklace6, necklace7, net, oilLamp, potion1, potion2, potion3, potion4, potion5, potion6, potion7, potion8, potion9, potion10, potion11, ring1, ring2, ring3, ring4, ring5, scroll1, scroll2, scroll3, scroll4, scroll5, scroll6, scroll7, scroll8, scroll9, shield, dagger, hammer, sword, halberd, wand1, wand2, woodenTorch, armorLeather, armorMail, whetstone]+  [dart, dart200, bolas, harpoon, net, jumpingPole, whetstone, woodenTorch, oilLamp, brassLantern, gorget, necklace1, necklace2, necklace3, necklace4, necklace5, necklace6, necklace7, monocle, ring1, ring2, ring3, ring4, ring5, potion1, potion2, potion3, potion4, potion5, potion6, potion7, potion8, flask1, flask2, flask3, flask4, flask5, flask6, flask7, flask8, flask9, flask10, flask11, flask12, flask13, flask14, scroll1, scroll2, scroll3, scroll4, scroll5, scroll6, scroll7, scroll8, scroll9, scroll10, armorLeather, armorMail, gloveFencing, gloveGauntlet, gloveJousting, buckler, shield, dagger, daggerDropBestWeapon, hammer, hammerParalyze, hammerSpark, sword, swordImpress, swordNullify, halberd, halberdPushActor, wand1, wand2, gem1, gem2, gem3, currency] -bolas,    brassLantern, buckler, dart, dart200, gem1, gem2, gem3, gloveFencing, gloveGauntlet, gloveJousting, currency, gorget, harpoon, jumpingPole, monocle, necklace1, necklace2, necklace3, necklace4, necklace5, necklace6, necklace7, net, oilLamp, potion1, potion2, potion3, potion4, potion5, potion6, potion7, potion8, potion9, potion10, potion11, ring1, ring2, ring3, ring4, ring5, scroll1, scroll2, scroll3, scroll4, scroll5, scroll6, scroll7, scroll8, scroll9, shield, dagger, hammer, sword, halberd, wand1, wand2, woodenTorch, armorLeather, armorMail, whetstone :: ItemKind+dart,    dart200, bolas, harpoon, net, jumpingPole, whetstone, woodenTorch, oilLamp, brassLantern, gorget, necklace1, necklace2, necklace3, necklace4, necklace5, necklace6, necklace7, monocle, ring1, ring2, ring3, ring4, ring5, potion1, potion2, potion3, potion4, potion5, potion6, potion7, potion8, flask1, flask2, flask3, flask4, flask5, flask6, flask7, flask8, flask9, flask10, flask11, flask12, flask13, flask14, scroll1, scroll2, scroll3, scroll4, scroll5, scroll6, scroll7, scroll8, scroll9, scroll10, armorLeather, armorMail, gloveFencing, gloveGauntlet, gloveJousting, buckler, shield, dagger, daggerDropBestWeapon, hammer, hammerParalyze, hammerSpark, sword, swordImpress, swordNullify, halberd, halberdPushActor, wand1, wand2, gem1, gem2, gem3, currency :: ItemKind -gem, necklace, potion, ring, scroll, wand :: ItemKind  -- generic templates+necklace, ring, potion, flask, scroll, wand, gem :: ItemKind  -- generic templates +-- * Item group symbols, partially from Nethack++symbolProjectile, _symbolLauncher, symbolLight, symbolTool, symbolGem, symbolGold, symbolNecklace, symbolRing, symbolPotion, symbolFlask, symbolScroll, symbolTorsoArmor, symbolMiscArmor, _symbolClothes, symbolShield, symbolPolearm, symbolEdged, symbolHafted, symbolWand, _symbolStaff, _symbolFood :: Char++symbolProjectile = '|'+_symbolLauncher  = '}'+symbolLight      = '('+symbolTool       = '('+symbolGem        = '*'+symbolGold       = '$'+symbolNecklace   = '"'+symbolRing       = '='+symbolPotion     = '!'  -- concoction, bottle, jar, vial, canister+symbolFlask      = '!'+symbolScroll     = '?'  -- book, note, tablet, remote+symbolTorsoArmor = '['+symbolMiscArmor  = '['+_symbolClothes   = '('+symbolShield     = '['+symbolPolearm    = ')'+symbolEdged      = ')'+symbolHafted     = ')'+symbolWand       = '/'  -- magical rod, transmitter, pistol, rifle+_symbolStaff     = '_'  -- scanner+_symbolFood      = ','+ -- * Thrown weapons  dart = ItemKind-  { isymbol  = '|'+  { isymbol  = symbolProjectile   , iname    = "dart"   , ifreq    = [("useful", 100), ("any arrow", 100)]   , iflavour = zipPlain [Cyan]@@ -45,12 +71,12 @@   , iweight  = 50   , iaspects = [AddHurtRanged ((d 6 + dl 6) |*| 10)]   , ieffects = [Hurt (3 * d 1)]-  , ifeature = []+  , ifeature = [Identified]   , idesc    = "Little, but sharp and sturdy."  -- "Much inferior to arrows though, especially given the contravariance problems."  --- funny, but destroy the suspension of disbelief; this is supposed to be a Lovecraftian horror and any hilarity must ensue from the failures in making it so and not from actively trying to be funny; also, mundane objects are not supposed to be scary or transcendental; the scare is in horrors from the abstract dimension visiting our ordinary reality; without the contrast there's no horror and no wonder, so also the magical items must be contrasted with ordinary XIX century and antique items   , ikit     = []   } dart200 = ItemKind-  { isymbol  = '|'+  { isymbol  = symbolProjectile   , iname    = "fine dart"   , ifreq    = [("useful", 100), ("any arrow", 50)]  -- TODO: until arrows added   , iflavour = zipPlain [BrRed]@@ -60,7 +86,7 @@   , iweight  = 50   , iaspects = [AddHurtRanged ((d 6 + dl 6) |*| 10)]   , ieffects = [Hurt (2 * d 1)]-  , ifeature = [toVelocity 200]+  , ifeature = [toVelocity 200, Identified]   , idesc    = "Finely balanced for throws of great speed."   , ikit     = []   }@@ -68,7 +94,7 @@ -- * Exotic thrown weapons  bolas = ItemKind-  { isymbol  = '|'+  { isymbol  = symbolProjectile   , iname    = "bolas set"   , ifreq    = [("useful", 100)]   , iflavour = zipPlain [BrYellow]@@ -77,13 +103,13 @@   , iverbHit = "entangle"   , iweight  = 500   , iaspects = []-  , ieffects = [Hurt (2 * d 1), Paralyze (5 + d 5), ActivateInv '!']-  , ifeature = []-  , idesc    = "Wood balls tied with hemp rope for tripping, entangling and bringing down crashing."+  , ieffects = [Hurt (2 * d 1), Paralyze (5 + d 5), DropBestWeapon]+  , ifeature = [Identified]+  , idesc    = "Wood balls tied with hemp rope. The target enemy is tripped and bound to drop its weapon, while recovering balance."   , ikit     = []   } harpoon = ItemKind-  { isymbol  = '|'+  { isymbol  = symbolProjectile   , iname    = "harpoon"   , ifreq    = [("useful", 100)]   , iflavour = zipPlain [Brown]@@ -98,7 +124,7 @@   , ikit     = []   } net = ItemKind-  { isymbol  = '|'+  { isymbol  = symbolProjectile   , iname    = "net"   , ifreq    = [("useful", 100)]   , iflavour = zipPlain [White]@@ -107,17 +133,50 @@   , iverbHit = "entangle"   , iweight  = 1000   , iaspects = []-  , ieffects = [ Paralyze (5 + d 5)-               , DropBestWeapon, DropEqp ']' False ]-  , ifeature = []-  , idesc    = "A wide net with weights along the edges. Entangles weapon and armor alike."+  , ieffects = [ toOrganGameTurn "slow 10" (3 + d 3)+               , DropItem CEqp "torso armor" False ]+  , ifeature = [Identified]+  , idesc    = "A wide net with weights along the edges. Entangles armor and restricts movement."   , ikit     = []   } +-- * Assorted tools++jumpingPole = ItemKind+  { isymbol  = symbolTool+  , iname    = "jumping pole"+  , ifreq    = [("useful", 100)]+  , iflavour = zipPlain [White]+  , icount   = 1+  , irarity  = [(1, 2), (10, 1)]+  , iverbHit = "prod"+  , iweight  = 10000+  , iaspects = [Timeout $ (d 2 + 2 - dl 2) |*| 10]+  , ieffects = [Recharging (toOrganActorTurn "fast 20" 1)]+  , ifeature = [Durable, Applicable, Identified]+  , idesc    = "Makes you vulnerable at take-off, but then you are free like a bird."+  , ikit     = []+  }+whetstone = ItemKind+  { isymbol  = symbolTool+  , iname    = "whetstone"+  , ifreq    = [("useful", 100)]+  , iflavour = zipPlain [Blue]+  , icount   = 1+  , irarity  = [(10, 10)]+  , iverbHit = "smack"+  , iweight  = 400+  , iaspects = [AddHurtMelee $ d 10 |*| 3]+  , ieffects = []+  , ifeature = [EqpSlot EqpSlotAddHurtMelee "", Identified]+  , 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."+  , ikit     = []+  }+ -- * Lights  woodenTorch = ItemKind-  { isymbol  = '('+  { isymbol  = symbolLight   , iname    = "wooden torch"   , ifreq    = [("useful", 100), ("light source", 100)]   , iflavour = zipPlain [Brown]@@ -133,7 +192,7 @@   , ikit     = []   } oilLamp = ItemKind-  { isymbol  = '('+  { isymbol  = symbolLight   , iname    = "oil lamp"   , ifreq    = [("useful", 100), ("light source", 100)]   , iflavour = zipPlain [BrYellow]@@ -149,7 +208,7 @@   , ikit     = []   } brassLantern = ItemKind-  { isymbol  = '('+  { isymbol  = symbolLight   , iname    = "brass lantern"   , ifreq    = [("useful", 100), ("light source", 100)]   , iflavour = zipPlain [BrWhite]@@ -165,77 +224,37 @@   , ikit     = []   } --- * Treasure--gem = ItemKind-  { isymbol  = '*'-  , iname    = "gem"-  , ifreq    = [("treasure", 100)]  -- x3, but rare on shallow levels-  , iflavour = zipPlain $ delete BrYellow brightCol  -- natural, so not fancy-  , icount   = 1-  , irarity  = []-  , iverbHit = "tap"-  , iweight  = 50-  , iaspects = [AddLight 1, AddSpeed (-1)]  -- reflects strongly, distracts-  , ieffects = []-  , ifeature = [ Durable  -- prevent destruction by evil monsters-               , Precious ]-  , 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     = []-  }-gem1 = gem-  { irarity  = [(2, 0), (10, 10)]-  }-gem2 = gem-  { irarity  = [(5, 0), (10, 10)]-  }-gem3 = gem-  { irarity  = [(8, 0), (10, 10)]-  }-currency = ItemKind-  { isymbol  = '$'-  , iname    = "gold piece"-  , ifreq    = [("treasure", 100), ("currency", 1)]-  , iflavour = zipPlain [BrYellow]-  , icount   = 10 + d 20 + dl 20-  , irarity  = [(1, 0), (2, 15), (5, 25), (10, 10)]-  , iverbHit = "tap"-  , iweight  = 31-  , iaspects = []-  , ieffects = []-  , ifeature = [Durable, Identified, Precious]-  , idesc    = "Reliably valuable in every civilized plane of existence."-  , ikit     = []-  }- -- * Periodic jewelry  gorget = ItemKind-  { isymbol  = '"'+  { isymbol  = symbolNecklace   , iname    = "gorget"   , ifreq    = [("useful", 100)]   , iflavour = zipFancy [BrCyan]-  , irarity  = [(4, 1), (10, 2)]   , icount   = 1+  , irarity  = [(4, 1), (10, 2)]   , iverbHit = "whip"   , iweight  = 30-  , iaspects = [Periodic $ d 4 + dl 4, AddArmorMelee 1, AddArmorRanged 1]-  , ieffects = [RefillCalm 1]-  , ifeature = [ Precious, EqpSlot EqpSlotPeriodic "", Identified+  , iaspects = [ Periodic+               , Timeout $ (d 3 + 3 - dl 3) |*| 10+               , AddArmorMelee 1+               , AddArmorRanged 1 ]+  , ieffects = [Recharging (RefillCalm 1)]+  , ifeature = [ Durable, Precious, EqpSlot EqpSlotPeriodic "", Identified                , toVelocity 50 ]  -- not dense enough   , idesc    = "Highly ornamental, cold, large, steel medallion on a chain. Unlikely to offer much protection as an armor piece, but the old, worn engraving reassures you."   , ikit     = []   } necklace = ItemKind-  { isymbol  = '"'+  { isymbol  = symbolNecklace   , iname    = "necklace"   , ifreq    = [("useful", 100)]   , iflavour = zipFancy stdCol ++ zipPlain brightCol-  , irarity  = [(10, 3)]   , icount   = 1+  , irarity  = [(10, 3)]   , iverbHit = "whip"   , iweight  = 30-  , iaspects = []+  , iaspects = [Periodic]   , ieffects = []   , ifeature = [ Precious, EqpSlot EqpSlotPeriodic ""                , toVelocity 50 ]  -- not dense enough@@ -243,46 +262,45 @@   , ikit     = []   } necklace1 = necklace-  { iaspects = [Periodic $ d 2 + dl 2]-  , ieffects = [RefillHP 1]+  { iaspects = (Timeout $ (d 3 + 4 - dl 3) |*| 10) : iaspects necklace+  , ieffects = [Recharging (RefillHP 1)]   , idesc    = "A cord of dried herbs and healing berries."   } necklace2 = necklace   { irarity  = [(2, 0), (10, 1)]-  , iaspects = [Periodic $ d 4 + dl 2]-  , ieffects = [ Impress-               , Summon [("mobile animal", 1)] $ 1 + dl 2, Explode "waste" ]+  , iaspects = (Timeout $ (d 3 + 3 - dl 3) |*| 10) : iaspects necklace+  , ieffects = [ Recharging (Impress)+               , Recharging (DropItem COrgan "temporary conditions" True)+               , Recharging (Summon [("mobile animal", 1)] $ 1 + dl 2)+               , Recharging (Explode "waste") ]   } necklace3 = necklace-  { iaspects = [Periodic $ d 4 + dl 2]-  , ieffects = [Paralyze $ 5 + d 5 + dl 5, RefillCalm 999]+  { iaspects = (Timeout $ (d 3 + 3 - dl 3) |*| 10) : iaspects necklace+  , ieffects = [Recharging (Paralyze $ 5 + d 5 + dl 5)]   } necklace4 = necklace-  { iaspects = [Periodic $ 2 * d 10 + dl 10]-  , ieffects = [Teleport $ d 3 |*| 3]+  { iaspects = (Timeout $ (d 4 + 4 - dl 4) |*| 2) : iaspects necklace+  , ieffects = [Recharging (Teleport $ d 3 |*| 3)]   } necklace5 = necklace-  { iaspects = [Periodic $ d 4 + dl 2]-  , ieffects = [Teleport $ 12 + d 3 |*| 3]+  { iaspects = (Timeout $ (d 3 + 4 - dl 3) |*| 10) : iaspects necklace+  , ieffects = [Recharging (Teleport $ 12 + d 3 |*| 3)]   } necklace6 = necklace-  { iaspects = [Periodic $ 2 * d 5 + dl 5]-  , ieffects = [PushActor (ThrowMod 100 50)]+  { iaspects = (Timeout $ d 4 |*| 10) : iaspects necklace+  , ieffects = [Recharging (PushActor (ThrowMod 100 50))]   }-necklace7 = necklace+necklace7 = necklace  -- TODO: teach AI to wear only for fight   { irarity  = [(4, 0), (10, 2)]-  , iaspects = [Periodic $ 2 * d 5 + dl 15]-  , ieffects = [InsertMove 1, RefillHP (-1)]-  , ifeature = ifeature necklace ++ [Durable]-      -- evil players would throw before death, to destroy-      -- TODO: teach AI to wear only for fight; prevent players from meleeing-      -- allies with that (Durable)+  , iaspects = [AddSpeed $ d 2, Timeout $ (d 3 + 3 + dl 3) |*| 2]+               ++ iaspects necklace+  , ieffects = [Recharging (RefillHP (-1))]   }  -- * Non-periodic jewelry  monocle = ItemKind-  { isymbol  = '='+  { isymbol  = symbolRing   , iname    = "monocle"   , ifreq    = [("useful", 100)]   , iflavour = zipPlain [White]@@ -296,8 +314,11 @@   , idesc    = "Let's you better focus your weaker eye."   , ikit     = []   }+-- Don't add standard effects to rings, because they go in and out+-- of eqp and so activating them would require UI tedium: looking for+-- them in eqp and inv or even activating a wrong item via letter by mistake. ring = ItemKind-  { isymbol  = '='+  { isymbol  = symbolRing   , iname    = "ring"   , ifreq    = [("useful", 100)]   , iflavour = zipPlain stdCol ++ zipFancy darkCol@@ -306,15 +327,16 @@   , iverbHit = "knock"   , iweight  = 15   , iaspects = []-  , ieffects = []+  , ieffects = [Explode "blast 2"]   , ifeature = [Precious, Identified]-  , idesc    = "It looks like an ordinary object, but it's in fact a generator of exceptional effects: adding to some of your natural abilities and subtracting from others. You'd profit enormously if you could find a way to multiply such generators..."  -- TODO: merge rings: do not add effects though, because it would make the ring too powerful (only one eqp slot taken); define correct, but not overpowered multiplication, if possible+  , idesc    = "It looks like an ordinary object, but it's in fact a generator of exceptional effects: adding to some of your natural abilities and subtracting from others. You'd profit enormously if you could find a way to multiply such generators."   , ikit     = []   } ring1 = ring-  { irarity  = [(2, 0), (10, 2)]+  { irarity  = [(10, 2)]   , iaspects = [AddSpeed $ d 2, AddMaxHP $ dl 3 - 5 - d 3]-  , ifeature = ifeature ring ++ [Durable, EqpSlot EqpSlotAddSpeed ""]+  , ieffects = [Explode "distortion"]  -- strong magic+  , ifeature = ifeature ring ++ [EqpSlot EqpSlotAddSpeed ""]   } ring2 = ring   { iaspects = [AddMaxHP $ 3 + dl 5, AddMaxCalm $ dl 6 - 15 - d 6]@@ -325,14 +347,15 @@   , ifeature = ifeature ring ++ [EqpSlot EqpSlotAddMaxCalm ""]   , idesc    = "Cold, solid to the touch, perfectly round, engraved with solemn, strangely comforting, worn out words."   }-ring4 = ring  -- TODO: move to level-ups and to timed effects-  { irarity  = [(3, 12), (10, 12)]+ring4 = ring+  { irarity  = [(3, 6), (10, 6)]   , iaspects = [AddHurtMelee $ (d 5 + dl 5) |*| 3, AddMaxHP $ dl 3 - 4 - d 2]-  , ifeature = ifeature ring ++ [Durable, EqpSlot EqpSlotAddHurtMelee ""]+  , ifeature = ifeature ring ++ [EqpSlot EqpSlotAddHurtMelee ""]   } ring5 = ring  -- by the time it's found, probably no space in eqp   { irarity  = [(5, 0)]   , iaspects = [AddLight $ d 2]+  , ieffects = [Explode "distortion"]  -- strong magic   , ifeature = ifeature ring ++ [EqpSlot EqpSlotAddLight ""]   , idesc    = "A sturdy ring with a large, shining stone."   }@@ -340,19 +363,19 @@ -- * Exploding consumables, often intended to be thrown  potion = ItemKind-  { isymbol  = '!'+  { isymbol  = symbolPotion   , iname    = "potion"   , ifreq    = [("useful", 100)]-  , iflavour = zipPlain stdCol ++ zipFancy brightCol+  , iflavour = zipLiquid brightCol ++ zipPlain brightCol ++ zipFancy brightCol   , icount   = 1-  , irarity  = [(1, 15), (10, 12)]+  , irarity  = [(1, 12), (10, 9)]   , iverbHit = "splash"   , iweight  = 200   , iaspects = []   , ieffects = []   , ifeature = [ toVelocity 50  -- oily, bad grip                , Applicable, Fragile ]-  , idesc    = "A flask of bubbly, slightly oily liquid of a suspect color."  -- purely natural; no maths, no magic  -- TODO: move distortion to a special flask item or when some precious magical item is destroyed (jewelry?)?+  , idesc    = "A vial of bright, frothing concoction."  -- purely natural; no maths, no magic   , ikit     = []   } potion1 = potion@@ -360,67 +383,147 @@                , OnSmash (ApplyPerfume), OnSmash (Explode "fragrance") ]   } potion2 = potion-  { ifreq    = [("useful", 1)]  -- extremely rare+  { ifreq    = [("useful", 10)]  -- extremely rare   , irarity  = [(1, 1)]-  , ieffects = [ NoEffect "of musky concoction", Impress, DropBestWeapon-               , OnSmash (Explode "pheromone")]+  , ieffects = [ NoEffect "of attraction", OnSmash (Explode "pheromone")]   } potion3 = potion-  { ieffects = [RefillHP 5, OnSmash (Explode "healing mist")]+  { irarity  = [(1, 5), (10, 5)]+  , ieffects = [RefillHP 5, OnSmash (Explode "healing mist")]   }-potion4 = potion  -- TODO: a bit boring-  { irarity  = [(1, 7)]-  , ieffects = [RefillHP (-5), OnSmash (Explode "wounding mist")]+potion4 = potion+  { irarity  = [(10, 5)]+  , ieffects = [RefillHP 10, OnSmash (Explode "healing mist 2")]   } potion5 = potion-  { ieffects = [ Explode "explosion blast 10", Impress-               , PushActor (ThrowMod 200 75)-               , OnSmash (Explode "explosion blast 10") ]-  }-potion6 = potion-  { irarity  = [(10, 2)]-  , ieffects = [ NoEffect "of distortion", Impress-               , OnSmash (Explode "distortion")]-  }-potion7 = potion-  { ieffects = [ NoEffect "of bait cocktail", Impress-               , OnSmash (Summon [("mobile animal", 1)] $ 1 + dl 2)-               , OnSmash (Explode "waste") ]-  }-potion8 = potion   { ieffects = [ OneOf [Impress, DropBestWeapon, RefillHP 5, Burn 3]                , OnSmash (OneOf [ Explode "healing mist"                                 , Explode "wounding mist"                                 , Explode "fragrance"-                                , Explode "explosion blast 10" ]) ]+                                , Explode "blast 10" ]) ]   }-potion9 = potion+potion6 = potion   { irarity  = [(3, 3), (10, 6)]   , ieffects = [ OneOf [ Dominate, DropBestWeapon, RefillHP 20, Burn 9-                       , InsertMove 2]+                       , InsertMove 4 ]                , OnSmash (OneOf [ Explode "healing mist 2"                                 , Explode "healing mist 2"                                 , Explode "pheromone"-                                , Explode "distortion"-                                , Explode "explosion blast 20" ]) ]+                                , Explode "distortion"  -- outlier, OK+                                , Explode "blast 20" ]) ]   }-potion10 = potion  -- used only as initial equipmnt; count betray identity-  { ifreq    = [("useful", 100), ("potion of glue", 1)]-  , irarity  = [(1, 1)]-  , icount   = 1 + d 2-  , ieffects = [ NoEffect "of glue", Paralyze (5 + d 5)-               , OnSmash (Explode "glue")]-  , ifeature = [Identified]+potion7 = potion+  { ieffects = [ DropItem COrgan "poisoned" True, RefillHP 1+               , OnSmash (Explode "antidote mist") ]   }-potion11 = potion+potion8 = potion+  { ieffects = [ DropItem COrgan "temporary conditions" True, RefillHP 2+               , OnSmash (Explode "blast 10") ]+  }++-- * Exploding consumables, with temporary aspects+-- TODO: dip projectiles in those+-- TODO: add flavour and realism as in, e.g., "flask of whiskey",+-- which is more flavourful and believable than "flask of strength"++flask = ItemKind+  { isymbol  = symbolFlask+  , iname    = "flask"+  , ifreq    = [("useful", 100)]+  , iflavour = zipLiquid darkCol ++ zipPlain darkCol ++ zipFancy darkCol+  , icount   = 1+  , irarity  = [(1, 9), (10, 6)]+  , iverbHit = "splash"+  , iweight  = 500+  , iaspects = []+  , ieffects = []+  , ifeature = [ toVelocity 50  -- oily, bad grip+               , Applicable, Fragile ]+  , idesc    = "A flask of oily liquid of a suspect color."+  , ikit     = []+  }+flask1 = flask   { irarity  = [(10, 5)]-  , ieffects = [RefillHP 10, OnSmash (Explode "healing mist 2")]+  , ieffects = [ NoEffect "of strength brew"+               , toOrganActorTurn "strengthened" (20 + d 5)+               , OnSmash (Explode "strength mist") ]   }+flask2 = flask+  { ieffects = [ NoEffect "of weakness brew"+               , toOrganGameTurn "weakened" (20 + d 5)+               , OnSmash (Explode "weakness mist") ]+  }+flask3 = flask+  { ieffects = [ NoEffect "of protecting balm"+               , toOrganActorTurn "protected" (20 + d 5)+               , OnSmash (Explode "protecting balm") ]+  }+flask4 = flask+  { ieffects = [ NoEffect "of red paint"+               , toOrganGameTurn "painted red" (20 + d 5)+               , OnSmash (Explode "red paint") ]+  }+flask5 = flask+  { irarity  = [(10, 5)]+  , ieffects = [ NoEffect "of haste brew"+               , toOrganActorTurn "fast 20" (20 + d 5)+               , OnSmash (Explode "haste spray") ]+  }+flask6 = flask+  { ieffects = [ NoEffect "of lethargy brew"+               , toOrganGameTurn "slow 10" (20 + d 5)+               , toOrganNone "regenerating"+               , OnSmash (Explode "slowness spray") ]+  }+flask7 = flask  -- sight can be reduced from Calm, drunk, etc.+  { irarity  = [(10, 7)]+  , ieffects = [ NoEffect "of eye drops"+               , toOrganActorTurn "far-sighted" (20 + d 5)+               , OnSmash (Explode "eye drop") ]+  }+flask8 = flask+  { irarity  = [(10, 3)]+  , ieffects = [ NoEffect "of smelly concoction"+               , toOrganActorTurn "keen-smelling" (20 + d 5)+               , OnSmash (Explode "smelly droplet") ]+  }+flask9 = flask+  { ieffects = [ NoEffect "of bait cocktail", toOrganActorTurn "drunk" (5 + d 5)+               , OnSmash (Summon [("mobile animal", 1)] $ 1 + dl 2)+               , OnSmash (Explode "waste") ]+  }+flask10 = flask+  { ieffects = [ NoEffect "of whiskey", toOrganActorTurn "drunk" (20 + d 5)+               , Burn 2, RefillHP 4, OnSmash (Explode "whiskey spray") ]+  }+flask11 = flask+  { irarity  = [(1, 20), (10, 6)]+  , ieffects = [ NoEffect "of regeneration brew"+               , toOrganNone "regenerating"+               , OnSmash (Explode "healing mist") ]+  }+flask12 = flask  -- but not flask of Calm depletion, since Calm reduced often+  { ieffects = [ NoEffect "of poison"+               , toOrganNone "poisoned"+               , OnSmash (Explode "wounding mist") ]+  }+flask13 = flask+  { irarity  = [(10, 5)]+  , ieffects = [ NoEffect "of slow resistance"+               , toOrganNone "slow resistant"+               , OnSmash (Explode "anti-slow mist") ]+  }+flask14 = flask  -- but not flask of Calm depletion, since Calm reduced often+  { irarity  = [(10, 5)]+  , ieffects = [ NoEffect "of poison resistance"+               , toOrganNone "poison resistant"+               , OnSmash (Explode "antidote mist") ]+  }  -- * Non-exploding consumables, not specifically designed for throwing  scroll = ItemKind-  { isymbol  = '?'+  { isymbol  = symbolScroll   , iname    = "scroll"   , ifreq    = [("useful", 100), ("any scroll", 100)]   , iflavour = zipFancy stdCol ++ zipPlain darkCol  -- arcane and old@@ -436,7 +539,7 @@   , ikit     = []   } scroll1 = scroll-  { irarity  = [(1, 2), (10, 3)]+  { irarity  = [(3, 6), (10, 6)]   , ieffects = [CallFriend 1]   } scroll2 = scroll@@ -448,50 +551,54 @@   , ieffects = [Ascend (-1)]   } scroll4 = scroll-  { ieffects = [ OneOf [ Teleport $ d 3 |*| 3, RefillCalm 10, RefillCalm (-10)-                       , InsertMove 4, Paralyze 10, Identify CGround ] ]+  { ieffects = [OneOf [ Teleport $ d 3 |*| 3, RefillCalm 10, RefillCalm (-10)+                      , InsertMove 3, Paralyze 10, Identify CGround ]]   } scroll5 = scroll-  { irarity  = [(3, 3), (10, 6)]-  , ieffects = [ OneOf [ Summon standardSummon $ d 2-                       , CallFriend 1, Ascend (-1), Ascend 1-                       , RefillCalm 30, RefillCalm (-30), CreateItem $ d 2-                       , PolyItem CGround ] ]+  { irarity  = [(10, 15)]+  , ieffects = [OneOf [ Summon standardSummon $ d 2+                      , CallFriend (d 2), Ascend (-1), Ascend 1+                      , RefillCalm 30, RefillCalm (-30)+                      , CreateItem CGround "useful" TimerNone+                      , PolyItem CGround ]]                -- TODO: ask player: Escape 1   } scroll6 = scroll-  { ieffects = [Teleport $ d 3 |*| 3]+  { ieffects = [Teleport 5]   } scroll7 = scroll-  { irarity  = [(10, 3)]-  , ieffects = [InsertMove $ d 2 + dl 2]+  { ieffects = [Teleport 20]   } scroll8 = scroll-  { irarity  = [(3, 8), (10, 4)]-  , ieffects = [Identify CGround]  -- TODO: ask player: AskPlayer cstore eff?+  { irarity  = [(10, 3)]+  , ieffects = [InsertMove $ 1 + d 2 + dl 2]   } scroll9 = scroll+  { irarity  = [(1, 15)]+  , ieffects = [Identify CGround]  -- TODO: ask player: AskPlayer cstore eff?+  }+scroll10 = scroll   { irarity  = [(10, 10)]   , ieffects = [PolyItem CGround]   } -standardSummon :: Freqs+standardSummon :: Freqs ItemKind standardSummon = [("monster", 30), ("mobile animal", 70)]  -- * Armor  armorLeather = ItemKind-  { isymbol  = '['+  { isymbol  = symbolTorsoArmor   , iname    = "leather armor"-  , ifreq    = [("useful", 100)]+  , ifreq    = [("useful", 100), ("torso armor", 1)]   , iflavour = zipPlain [Brown]   , icount   = 1-  , irarity  = [(3, 6), (10, 3)]+  , irarity  = [(1, 9), (10, 3)]   , iverbHit = "thud"   , iweight  = 7000   , iaspects = [ AddHurtMelee (-3)-               , AddArmorMelee $ (1 + dl 3) |*| 5-               , AddArmorRanged $ (1 + dl 3) |*| 5 ]+               , AddArmorMelee $ (d 2 + dl 3) |*| 5+               , AddArmorRanged $ (d 2 + dl 3) |*| 5 ]   , ieffects = []   , ifeature = [ toVelocity 30  -- unwieldy to throw and blunt                , Durable, EqpSlot EqpSlotAddArmorMelee "", Identified ]@@ -501,20 +608,20 @@ armorMail = armorLeather   { iname    = "mail armor"   , iflavour = zipPlain [Cyan]-  , irarity  = [(6, 6), (10, 6)]+  , irarity  = [(6, 9), (10, 3)]   , iweight  = 12000   , iaspects = [ AddHurtMelee (-3)-               , AddArmorMelee $ (2 + dl 4) |*| 5-               , AddArmorRanged $ (2 + dl 4) |*| 5 ]+               , AddArmorMelee $ (1 + d 2 + dl 4) |*| 5+               , AddArmorRanged $ (1 + d 2 + dl 4) |*| 5 ]   , idesc    = "A long shirt woven from iron rings. Discourages foes from attacking your torso, making it harder for them to land a blow."   } gloveFencing = ItemKind-  { isymbol  = '['+  { isymbol  = symbolMiscArmor   , iname    = "leather gauntlet"   , ifreq    = [("useful", 100)]   , iflavour = zipPlain [BrYellow]   , icount   = 1-  , irarity  = [(5, 8), (10, 8)]+  , irarity  = [(5, 9), (10, 9)]   , iverbHit = "flap"   , iweight  = 100   , iaspects = [ AddHurtMelee $ (d 2 + dl 10) * 3@@ -527,8 +634,8 @@   } gloveGauntlet = gloveFencing   { iname    = "steel gauntlet"-  , irarity  = [(6, 12)]   , iflavour = zipPlain [BrCyan]+  , irarity  = [(1, 9), (10, 3)]   , iweight  = 300   , iaspects = [ AddArmorMelee $ (1 + dl 2) |*| 5                , AddArmorRanged $ (1 + dl 2) |*| 5 ]@@ -536,18 +643,21 @@   } gloveJousting = gloveFencing   { iname    = "jousting gauntlet"-  , irarity  = [(6, 6)]   , iflavour = zipFancy [BrRed]+  , irarity  = [(1, 3), (10, 3)]   , iweight  = 500   , iaspects = [ AddHurtMelee $ (dl 4 - 6) |*| 3                , AddArmorMelee $ (2 + dl 2) |*| 5                , AddArmorRanged $ (2 + dl 2) |*| 5 ]   , idesc    = "Rigid, steel, jousting handgear. If only you had a lance. And a horse."   }++-- * Shields+ -- Shield doesn't protect against ranged attacks to prevent -- micromanagement: walking with shield, melee without. buckler = ItemKind-  { isymbol  = '['+  { isymbol  = symbolShield   , iname    = "buckler"   , ifreq    = [("useful", 100)]   , iflavour = zipPlain [Blue]@@ -555,8 +665,10 @@   , irarity  = [(4, 6)]   , iverbHit = "bash"   , iweight  = 2000-  , iaspects = [AddArmorMelee 40, AddHurtMelee (-30)]-  , ieffects = []+  , iaspects = [ AddArmorMelee 40+               , AddHurtMelee (-30)+               , Timeout $ (d 3 + 3 - dl 3) |*| 2 ]+  , ieffects = []  -- [Recharging (PushActor (ThrowMod 200 50))]   , ifeature = [ toVelocity 30  -- unwieldy to throw and blunt                , Durable, EqpSlot EqpSlotAddArmorMelee "", Identified ]   , idesc    = "Heavy and unwieldy. Absorbs a percentage of melee damage, both dealt and sustained. Too small to intercept projectiles with."@@ -567,7 +679,10 @@   , irarity  = [(7, 5)]   , iflavour = zipPlain [Green]   , iweight  = 3000-  , iaspects = [AddArmorMelee 80, AddHurtMelee (-70)]+  , iaspects = [ AddArmorMelee 80+               , AddHurtMelee (-70)+               , Timeout $ (d 3 + 6 - dl 3) |*| 2 ]+  , ieffects = []  -- [Recharging (PushActor (ThrowMod 400 50))]   , ifeature = [ toVelocity 20  -- unwieldy to throw and blunt                , Durable, EqpSlot EqpSlotAddArmorMelee "", Identified ]   , idesc    = "Large and unwieldy. Absorbs a percentage of melee damage, both dealt and sustained. Too heavy to intercept projectiles with."@@ -576,7 +691,7 @@ -- * Weapons  dagger = ItemKind-  { isymbol  = ')'+  { isymbol  = symbolEdged   , iname    = "dagger"   , ifreq    = [("useful", 100), ("starting weapon", 100)]   , iflavour = zipPlain [BrCyan]@@ -591,8 +706,23 @@   , idesc    = "A short dagger for thrusting and parrying blows. Does not penetrate deeply, but is hard to block. Especially useful in conjunction with a larger weapon."   , ikit     = []   }+daggerDropBestWeapon = dagger+  { ifreq    = [("useful", 30)]+  , irarity  = [(1, 1), (10, 2)]+  -- The timeout has to be small, so that the player can count on the effect+  -- occuring consistently in any longer fight. Otherwise, the effect will be+  -- absent in some important fights, leading to the feeling of bad luck,+  -- but will manifest sometimes in fights where it doesn't matter,+  -- leading to the feeling of wasted power.+  -- 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]+  , ieffects = ieffects dagger ++ [Recharging DropBestWeapon]+  , idesc    = "A double dagger that a focused fencer can use to catch and twist an opponent's blade occasionally."+  } hammer = ItemKind-  { isymbol  = ')'+  { isymbol  = symbolHafted   , iname    = "war hammer"   , ifreq    = [("useful", 100), ("starting weapon", 100)]   , iflavour = zipPlain [BrMagenta]@@ -607,8 +737,21 @@   , idesc    = "It may not cause grave wounds, but neither does it glance off nor ricochet. Great sidearm for opportunistic blows against armored foes."   , ikit     = []   }+hammerParalyze = hammer+  { ifreq    = [("useful", 30)]+  , irarity  = [(4, 1), (10, 2)]+  , iaspects = iaspects hammer ++ [Timeout $ (d 2 + 3 - dl 2) |*| 2]+  , ieffects = ieffects hammer ++ [Recharging $ Paralyze 5]+  }+hammerSpark = hammer+  { iname    = "smithhammer"+  , ifreq    = [("useful", 30)]+  , irarity  = [(4, 1), (10, 2)]+  , iaspects = iaspects hammer ++ [Timeout $ (d 4 + 4 - dl 4) |*| 2]+  , ieffects = ieffects hammer ++ [Recharging $ Explode "spark"]+  } sword = ItemKind-  { isymbol  = ')'+  { isymbol  = symbolEdged   , iname    = "sword"   , ifreq    = [("useful", 100), ("starting weapon", 100)]   , iflavour = zipPlain [BrBlue]@@ -623,9 +766,24 @@   , idesc    = "Difficult to master; deadly when used effectively. The steel is particularly hard and keen, but rusts quickly without regular maintenance."   , ikit     = []   }+swordImpress = sword+  { ifreq    = [("useful", 30)]+  , irarity  = [(3, 1), (10, 2)]+  , iaspects = iaspects sword ++ [Timeout $ (d 4 + 5 - dl 4) |*| 2]+  , ieffects = ieffects sword ++ [Recharging Impress]+  , idesc    = "A particularly well-balance blade, lending itself to impressive shows of fencing skill."+  }+swordNullify = sword+  { ifreq    = [("useful", 30)]+  , irarity  = [(3, 0), (10, 1)]+  , iaspects = iaspects sword ++ [Timeout $ (d 4 + 5 - dl 4) |*| 2]+  , ieffects = ieffects sword+               ++ [Recharging $ DropItem COrgan "temporary conditions" True]+  , idesc    = "Cold, thin blade that pierces deeply and sends its victim into abrupt, sobering shock."+  } halberd = ItemKind-  { isymbol  = ')'-  , iname    = "halberd"+  { isymbol  = symbolPolearm+  , iname    = "war scythe"   , ifreq    = [("useful", 100), ("starting weapon", 1)]   , iflavour = zipPlain [BrYellow]   , icount   = 1@@ -633,17 +791,25 @@   , iverbHit = "impale"   , iweight  = 3000   , iaspects = [AddArmorMelee $ (1 + dl 3) |*| 5]-  , ieffects = [Hurt (14 * d 1)]+  , ieffects = [Hurt (12 * d 1)]   , ifeature = [ toVelocity 20  -- not balanced                , Durable, EqpSlot EqpSlotWeapon "", Identified ]-  , idesc    = "Versatile, with great reach and leverage. Foes are held at a distance."+  , idesc    = "An improvised but deadly weapon made of a blade from a scythe attached to a long pole."   , ikit     = []   }+halberdPushActor = halberd+  { iname    = "halberd"+  , ifreq    = [("useful", 30)]+  , irarity  = [(7, 1), (10, 2)]+  , iaspects = iaspects halberd ++ [Timeout $ (d 5 + 5 - dl 5) |*| 2]+  , ieffects = ieffects halberd ++ [Recharging (PushActor (ThrowMod 400 25))]+  , idesc    = "A versatile polearm, with great reach and leverage. Foes are held at a distance."+  }  -- * Wands  wand = ItemKind-  { isymbol  = '/'+  { isymbol  = symbolWand   , iname    = "wand"   , ifreq    = [("useful", 100)]   , iflavour = zipFancy brightCol@@ -665,37 +831,44 @@   { ieffects = []   } --- * Assorted tools+-- * Treasure -jumpingPole = ItemKind-  { isymbol  = '('-  , iname    = "jumping pole"-  , ifreq    = [("useful", 100)]-  , iflavour = zipPlain [White]+gem = ItemKind+  { isymbol  = symbolGem+  , iname    = "gem"+  , ifreq    = [("treasure", 100)]+  , iflavour = zipPlain $ delete BrYellow brightCol  -- natural, so not fancy   , icount   = 1-  , irarity  = [(1, 4), (10, 2)]-  , iverbHit = "prod"-  , iweight  = 10000-  , iaspects = []-  , ieffects = [InsertMove 2]  -- TODO: implement with timed speed instead-                               -- and then make Durable, freq 2, and just trade-                               -- taken turn now for a free turn later-  , ifeature = [Applicable, Identified]-  , idesc    = "Makes you vulnerable at take-off, but then you are free like a bird."+  , irarity  = []+  , iverbHit = "tap"+  , iweight  = 50+  , iaspects = [AddLight 1, AddSpeed (-1)]  -- reflects strongly, distracts+  , ieffects = []+  , ifeature = [Precious]+  , 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     = []   }-whetstone = ItemKind-  { isymbol  = '~'-  , iname    = "whetstone"-  , ifreq    = [("useful", 100)]-  , iflavour = zipPlain [Blue]-  , icount   = 1-  , irarity  = [(10, 10)]-  , iverbHit = "smack"-  , iweight  = 400-  , iaspects = [AddHurtMelee $ d 10 |*| 3]+gem1 = gem+  { irarity  = [(2, 0), (10, 10)]+  }+gem2 = gem+  { irarity  = [(4, 0), (10, 15)]+  }+gem3 = gem+  { irarity  = [(6, 0), (10, 20)]+  }+currency = ItemKind+  { isymbol  = symbolGold+  , iname    = "gold piece"+  , ifreq    = [("treasure", 100), ("currency", 1)]+  , iflavour = zipPlain [BrYellow]+  , icount   = 10 + d 20 + dl 20+  , irarity  = [(1, 0), (2, 15), (5, 25), (10, 10)]+  , iverbHit = "tap"+  , iweight  = 31+  , iaspects = []   , ieffects = []-  , ifeature = [EqpSlot EqpSlotAddHurtMelee "", Identified]-  , 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."+  , ifeature = [Identified, Precious]+  , idesc    = "Reliably valuable in every civilized plane of existence."   , ikit     = []   }
GameDefinition/Content/ItemKindActor.hs view
@@ -5,16 +5,15 @@  import Game.LambdaHack.Common.Ability import Game.LambdaHack.Common.Color-import Game.LambdaHack.Common.Effect import Game.LambdaHack.Common.Flavour import Game.LambdaHack.Common.Misc import Game.LambdaHack.Content.ItemKind  actors :: [ItemKind] actors =-  [warrior, adventurer, blacksmith, forester, scientist, soldier, clerk, hairdresser, lawyer, peddler, taxCollector, eye, fastEye, nose, elbow, armadillo, gilaMonster, komodoDragon, hyena, alligator, hornetSwarm, thornbush, geyser]+  [warrior, adventurer, blacksmith, forester, scientist, soldier, clerk, hairdresser, lawyer, peddler, taxCollector, eye, fastEye, nose, elbow, armadillo, gilaMonster, rattlesnake, komodoDragon, hyena, alligator, hornetSwarm, thornbush, geyser] -warrior,    adventurer, blacksmith, forester, scientist, soldier, clerk, hairdresser, lawyer, peddler, taxCollector, eye, fastEye, nose, elbow, armadillo, gilaMonster, komodoDragon, hyena, alligator, hornetSwarm, thornbush, geyser :: ItemKind+warrior,    adventurer, blacksmith, forester, scientist, soldier, clerk, hairdresser, lawyer, peddler, taxCollector, eye, fastEye, nose, elbow, armadillo, gilaMonster, rattlesnake, komodoDragon, hyena, alligator, hornetSwarm, thornbush, geyser :: ItemKind  -- * Hunams @@ -27,8 +26,11 @@   , irarity  = [(1, 5)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 50, AddMaxCalm 60, AddSpeed 20-               , AddSight 3 ]  -- no via eyes, but feel, hearing, etc.+  , iaspects = [ AddMaxHP 60  -- partially from clothes and assumed first aid+               , AddMaxCalm 60, AddSpeed 20+               , AddSkills $ EM.fromList [(AbProject, 1), (AbApply, 1)]+                   -- TODO: on a ring?+               , AddSight 3 ]  -- not via eyes, but feel, hearing, etc.   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = ""@@ -72,8 +74,9 @@   , irarity  = [(1, 10), (10, 6)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 20, AddMaxCalm 60, AddSpeed 20-               , AddSight 4 ]  -- can shoot for as long as lives+  , iaspects = [ AddMaxHP 10, AddMaxCalm 60, AddSpeed 20+               , AddSkills $ EM.fromList [(AbProject, 1), (AbApply, 1)]+               , AddSight 4 ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = "Under your stare, it reduces to the bits that define its essence. Under introspection, the bits slow down and solidify into an arbitrary form again. It must be huge inside, for holographic principle to manifest so overtly."  -- holographic principle is an anachronism for XIX or most of XX century, but "the cosmological scale effects" is too weak@@ -85,11 +88,11 @@   , ifreq    = [("monster", 100), ("horror", 100)]   , iflavour = zipPlain [BrBlue]   , icount   = 1-  , irarity  = [(10, 5)]+  , irarity  = [(1, 3), (10, 5)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 6, AddMaxCalm 60, AddSpeed 30-               , AddSight 7 ]  -- can shoot for as long as lives+  , iaspects = [ AddMaxHP 5, AddMaxCalm 60, AddSpeed 30+               , AddSight 7 ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = "Hungers but never eats. Bites but never swallows. Burrows its own image through, but never carries anything back."  -- rather weak: not about injective objects, but puny, concrete, injective functions  --- where's the madness in that?@@ -102,10 +105,11 @@   , ifreq    = [("monster", 100), ("horror", 100)]   , iflavour = zipPlain [BrGreen]   , icount   = 1-  , irarity  = [(1, 6), (10, 4)]+  , irarity  = [(1, 10), (3, 0), (4, 5), (10, 5)]  -- ensure explorers at lvl 3   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 40, AddMaxCalm 30, AddSpeed 18+  , iaspects = [ AddMaxHP 20, AddMaxCalm 30, AddSpeed 18+               , AddSkills $ EM.fromList [(AbProject, -1), (AbApply, -1)]                , AddSmell 3 ]  -- depends solely on smell   , ieffects = []   , ifeature = [Durable, Identified]@@ -121,9 +125,10 @@   , irarity  = [(6, 1), (10, 5)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 10, AddMaxCalm 80, AddSpeed 26-               , AddSkills $ EM.singleton AbMelee (-1)-               , AddSight 15 ]  -- can shoot for as long as lives+  , iaspects = [ AddMaxHP 8, AddMaxCalm 90, AddSpeed 26+               , AddSkills+                 $ EM.fromList [(AbProject, 1), (AbApply, 1), (AbMelee, -1)]+               , AddSight 15 ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = "An arm strung like a bow. A few edges, but none keen enough. A few points, but none piercing. Deadly objects zip out of the void."@@ -138,6 +143,13 @@  -- * Animals +-- They need rather strong melee, because they don't use items.+-- Unless/until they level up.++animalSkillMalus :: Skills+animalSkillMalus =+  EM.fromList $ zip [AbDisplace, AbMoveItem, AbProject, AbApply] [-1, -1..]+ armadillo = ItemKind   { isymbol  = 'a'   , iname    = "giant armadillo"@@ -148,7 +160,7 @@   , iverbHit = "thud"   , iweight  = 80000   , iaspects = [ AddMaxHP 30, AddMaxCalm 30, AddSpeed 18-               , AddSkills $ EM.singleton AbAlter (-1)+               , AddSkills $ EM.insert AbAlter (-1) animalSkillMalus                , AddSight 3 ]   , ieffects = []   , ifeature = [Durable, Identified]@@ -166,7 +178,7 @@   , iverbHit = "thud"   , iweight  = 80000   , iaspects = [ AddMaxHP 15, AddMaxCalm 60, AddSpeed 15-               , AddSkills $ EM.singleton AbAlter (-1)+               , AddSkills $ EM.insert AbAlter (-1) animalSkillMalus                , AddSight 3 ]   , ieffects = []   , ifeature = [Durable, Identified]@@ -174,6 +186,24 @@   , ikit     = [ ("venom tooth", COrgan), ("small claw", COrgan)                , ("eye 4", COrgan), ("nostril", COrgan) ]   }+rattlesnake = ItemKind+  { isymbol  = 'r'+  , iname    = "rattlesnake"+  , ifreq    = [("animal", 100), ("horror", 100), ("mobile animal", 100)]+  , iflavour = zipPlain [Brown]+  , icount   = 1+  , irarity  = [(3, 2), (10, 4)]+  , iverbHit = "thud"+  , iweight  = 80000+  , iaspects = [ AddMaxHP 25, AddMaxCalm 60, AddSpeed 15+               , AddSkills $ EM.insert AbAlter (-1) animalSkillMalus+               , AddSight 3 ]+  , ieffects = []+  , ifeature = [Durable, Identified]+  , idesc    = ""+  , ikit     = [ ("venom fang", COrgan)+               , ("eye 4", COrgan), ("nostril", COrgan) ]+  } komodoDragon = ItemKind  -- bad hearing   { isymbol  = 'k'   , iname    = "Komodo dragon"@@ -183,8 +213,8 @@   , irarity  = [(5, 5), (10, 7)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 40, AddMaxCalm 60, AddSpeed 18-               , AddSight 3 ]+  , iaspects = [ AddMaxHP 40, AddMaxCalm 60, AddSpeed 16+               , AddSkills animalSkillMalus, AddSight 3 ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = ""@@ -201,8 +231,8 @@   , irarity  = [(4, 6), (10, 6)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 30, AddMaxCalm 60, AddSpeed 35-               , AddSight 3 ]+  , iaspects = [ AddMaxHP 20, AddMaxCalm 60, AddSpeed 30+               , AddSkills animalSkillMalus, AddSight 3 ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = ""@@ -219,7 +249,7 @@   , iweight  = 80000   , iaspects = [ AddMaxHP 30, AddMaxCalm 60, AddSpeed 17                , AddArmorMelee 30, AddArmorRanged 30-               , AddSight 3 ]+               , AddSkills animalSkillMalus, AddSight 3 ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = ""@@ -239,7 +269,7 @@   , iverbHit = "thud"   , iweight  = 1000   , iaspects = [ AddMaxHP 5, AddMaxCalm 60, AddSpeed 30, AddSight 2-               , AddSkills $ EM.singleton AbAlter (-1)+               , AddSkills $ EM.insert AbAlter (-1) animalSkillMalus                , AddArmorMelee 90, AddArmorRanged 90 ]   , ieffects = []   , ifeature = [Durable, Identified]@@ -255,7 +285,7 @@   , irarity  = [(3, 2), (10, 1)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 30, AddMaxCalm 999, AddSpeed 20+  , iaspects = [ AddMaxHP 20, AddMaxCalm 999, AddSpeed 20                , AddSkills                  $ EM.fromDistinctAscList (zip [minBound..maxBound] [-1, -1..])                    `addSkills` EM.fromList (zip [AbWait, AbMelee] [1, 1..])@@ -274,10 +304,11 @@   , irarity  = [(5, 2), (10, 1)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 100, AddMaxCalm 999, AddSpeed 5+  , iaspects = [ AddMaxHP 10, AddMaxCalm 999, AddSpeed 5                , AddSkills                  $ EM.fromDistinctAscList (zip [minBound..maxBound] [-1, -1..])-                   `addSkills` EM.fromList (zip [AbWait, AbMelee] [1, 1..]) ]+                   `addSkills` EM.fromList (zip [AbWait, AbMelee] [1, 1..])+               , AddArmorMelee 80, AddArmorRanged 80 ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = ""
+ GameDefinition/Content/ItemKindBlast.hs view
@@ -0,0 +1,455 @@+-- | Blast definitions.+module Content.ItemKindBlast ( blasts ) where++import Game.LambdaHack.Common.Color+import Game.LambdaHack.Common.Dice+import Game.LambdaHack.Common.Flavour+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Msg+import Game.LambdaHack.Content.ItemKind++blasts :: [ItemKind]+blasts =+  [burningOil2, burningOil3, burningOil4, explosionBlast2, explosionBlast10, explosionBlast20, firecracker2, firecracker3, firecracker4, firecracker5, firecracker6, firecracker7, fragrance, pheromone, mistHealing, mistHealing2, mistWounding, distortion, waste, glassPiece, smoke, boilingWater, glue, spark, mistAntiSlow, mistAntidote, mistStrength, mistWeakness, protectingBalm, redPaint, hasteSpray, slownessSpray, eyeDrop, smellyDroplet, whiskeySpray]++burningOil2,    burningOil3, burningOil4, explosionBlast2, explosionBlast10, explosionBlast20, firecracker2, firecracker3, firecracker4, firecracker5, firecracker6, firecracker7, fragrance, pheromone, mistHealing, mistHealing2, mistWounding, distortion, waste, glassPiece, smoke, boilingWater, glue, spark, mistAntiSlow, mistAntidote, mistStrength, mistWeakness, protectingBalm, redPaint, hasteSpray, slownessSpray, eyeDrop, smellyDroplet, whiskeySpray :: ItemKind++-- * Parameterized immediate effect blasts++burningOil :: Int -> ItemKind+burningOil n = ItemKind+  { isymbol  = '*'+  , iname    = "burning oil"+  , ifreq    = [(toGroupName $ "burning oil" <+> tshow n, 1)]+  , iflavour = zipFancy [BrYellow]+  , icount   = intToDice (n * 4)+  , irarity  = [(1, 1)]+  , iverbHit = "burn"+  , iweight  = 1+  , iaspects = [AddLight 2]+  , ieffects = [ Burn (n `div` 2)+               , Paralyze (intToDice $ n `div` 2) ]  -- tripping on oil+  , ifeature = [ toVelocity (min 100 $ n * 7)+               , Fragile, Identified ]+  , idesc    = "Sticky oil, burning brightly."+  , ikit     = []+  }+burningOil2 = burningOil 2+burningOil3 = burningOil 3+burningOil4 = burningOil 4+explosionBlast :: Int -> ItemKind+explosionBlast n = ItemKind+  { isymbol  = '*'+  , iname    = "blast"+  , ifreq    = [(toGroupName $ "blast" <+> tshow n, 1)]+  , iflavour = zipPlain [BrRed]+  , icount   = 12  -- strong, but few, so not always hits target+  , irarity  = [(1, 1)]+  , iverbHit = "tear apart"+  , iweight  = 1+  , iaspects = [AddLight $ intToDice n]+  , ieffects = [RefillHP (- n `div` 2)]+               ++ [DropBestWeapon | n > 2]+               ++ [DropItem COrgan "temporary conditions" True | n >= 10]+  , ifeature = [Fragile, toLinger 10, Identified]+  , idesc    = ""+  , ikit     = []+  }+explosionBlast2 = explosionBlast 2+explosionBlast10 = explosionBlast 10+explosionBlast20 = explosionBlast 20+firecracker :: Int -> ItemKind+firecracker n = ItemKind+  { isymbol  = '*'+  , iname    = "firecracker"+  , ifreq    = [(toGroupName $ "firecracker" <+> tshow n, 1)]+  , iflavour = zipPlain [brightCol !! (n `mod` length brightCol)]+  , icount   = intToDice (n `div` 6) + d (n `div` 2)+  , irarity  = [(1, 1)]+  , iverbHit = "crack"+  , iweight  = 1+  , iaspects = [AddLight $ intToDice $ n `div` 2]+  , ieffects = [ Burn 1 | n > 5 ]+               ++ [ OnSmash (Explode $ toGroupName+                             $ "firecracker" <+> tshow (n - 1))+                  | n > 2 ]+  , ifeature = [ ToThrow $ ThrowMod (10 + 3 * n) (10 + 100 `div` n)+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+firecracker7 = firecracker 7+firecracker6 = firecracker 6+firecracker5 = firecracker 5+firecracker4 = firecracker 4+firecracker3 = firecracker 3+firecracker2 = firecracker 2++-- * Assorted immediate effect blasts++fragrance = ItemKind+  { isymbol  = '\''+  , iname    = "fragrance"+  , ifreq    = [("fragrance", 1)]+  , iflavour = zipFancy [Magenta]+  , icount   = 15+  , irarity  = [(1, 1)]+  , iverbHit = "engulf"+  , iweight  = 1+  , iaspects = []+  , ieffects = [Impress]+  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+pheromone = ItemKind+  { isymbol  = '\''+  , iname    = "musky whiff"+  , ifreq    = [("pheromone", 1)]+  , iflavour = zipFancy [BrMagenta]+  , icount   = 8+  , irarity  = [(1, 1)]+  , iverbHit = "tempt"+  , iweight  = 1+  , iaspects = []+  , ieffects = [Dominate]+  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+mistHealing = ItemKind+  { isymbol  = '\''+  , iname    = "mist"+  , ifreq    = [("healing mist", 1)]+  , iflavour = zipFancy [White]+  , icount   = 11+  , irarity  = [(1, 1)]+  , iverbHit = "revitalize"+  , iweight  = 1+  , iaspects = [AddLight 1]+  , ieffects = [RefillHP 2]+  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+mistHealing2 = ItemKind+  { isymbol  = '\''+  , iname    = "mist"+  , ifreq    = [("healing mist 2", 1)]+  , iflavour = zipFancy [White]+  , icount   = 12+  , irarity  = [(1, 1)]+  , iverbHit = "revitalize"+  , iweight  = 1+  , iaspects = [AddLight 2]+  , ieffects = [RefillHP 4]+  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+mistWounding = ItemKind+  { isymbol  = '\''+  , iname    = "mist"+  , ifreq    = [("wounding mist", 1)]+  , iflavour = zipFancy [White]+  , icount   = 13+  , irarity  = [(1, 1)]+  , iverbHit = "devitalize"+  , iweight  = 1+  , iaspects = []+  , ieffects = [RefillHP (-2)]+  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+distortion = ItemKind+  { isymbol  = 'v'+  , iname    = "vortex"+  , ifreq    = [("distortion", 1)]+  , iflavour = zipFancy [White]+  , icount   = 4+  , irarity  = [(1, 1)]+  , iverbHit = "engulf"+  , iweight  = 1+  , iaspects = []+  , ieffects = [Teleport $ 15 + d 10]+  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+waste = ItemKind+  { isymbol  = '*'+  , iname    = "waste"+  , ifreq    = [("waste", 1)]+  , iflavour = zipPlain [Brown]+  , icount   = 10+  , irarity  = [(1, 1)]+  , iverbHit = "splosh"+  , iweight  = 50+  , iaspects = []+  , ieffects = [RefillHP (-1)]+  , ifeature = [ ToThrow $ ThrowMod 28 50+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+glassPiece = ItemKind  -- when blowing up windows+  { isymbol  = '*'+  , iname    = "glass piece"+  , ifreq    = [("glass piece", 1)]+  , iflavour = zipPlain [BrBlue]+  , icount   = 17+  , irarity  = [(1, 1)]+  , iverbHit = "cut"+  , iweight  = 10+  , iaspects = []+  , ieffects = [Hurt (1 * d 1)]+  , ifeature = [toLinger 20, Fragile, Identified]+  , idesc    = ""+  , ikit     = []+  }+smoke = ItemKind  -- when stuff burns out+  { isymbol  = '\''+  , iname    = "smoke"+  , ifreq    = [("smoke", 1)]+  , iflavour = zipPlain [BrBlack]+  , icount   = 19+  , irarity  = [(1, 1)]+  , iverbHit = "choke"+  , iweight  = 1+  , iaspects = []+  , ieffects = []+  , ifeature = [ toVelocity 21, Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+boilingWater = ItemKind+  { isymbol  = '*'+  , iname    = "boiling water"+  , ifreq    = [("boiling water", 1)]+  , iflavour = zipPlain [BrWhite]+  , icount   = 10+  , irarity  = [(1, 1)]+  , iverbHit = "boil"+  , iweight  = 5+  , iaspects = []+  , ieffects = [Burn 1]+  , ifeature = [toVelocity 50, Fragile, Identified]+  , idesc    = ""+  , ikit     = []+  }+glue = ItemKind+  { isymbol  = '*'+  , iname    = "hoof glue"+  , ifreq    = [("glue", 1)]+  , iflavour = zipPlain [BrYellow]+  , icount   = 14+  , irarity  = [(1, 1)]+  , iverbHit = "glue"+  , iweight  = 20+  , iaspects = []+  , ieffects = [Paralyze (3 + d 3)]+  , ifeature = [toVelocity 40, Fragile, Identified]+  , idesc    = ""+  , ikit     = []+  }+spark = ItemKind+  { isymbol  = '\''+  , iname    = "spark"+  , ifreq    = [("spark", 1)]+  , iflavour = zipPlain [BrYellow]+  , icount   = 18+  , irarity  = [(1, 1)]+  , iverbHit = "burn"+  , iweight  = 1+  , iaspects = [AddLight 4]+  , ieffects = [Burn 1]+  , ifeature = [Fragile, toLinger 10, Identified]+  , idesc    = ""+  , ikit     = []+  }+mistAntiSlow = ItemKind+  { isymbol  = '\''+  , iname    = "mist"+  , ifreq    = [("anti-slow mist", 1)]+  , iflavour = zipPlain [BrRed]+  , icount   = 11+  , irarity  = [(1, 1)]+  , iverbHit = "propel"+  , iweight  = 1+  , iaspects = []+  , ieffects = [DropItem COrgan "slow 10" True]+  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+mistAntidote = ItemKind+  { isymbol  = '\''+  , iname    = "mist"+  , ifreq    = [("antidote mist", 1)]+  , iflavour = zipPlain [BrBlue]+  , icount   = 11+  , irarity  = [(1, 1)]+  , iverbHit = "cure"+  , iweight  = 1+  , iaspects = []+  , ieffects = [DropItem COrgan "poisoned" True]+  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }++-- * Assorted temporary condition blasts++mistStrength = ItemKind+  { isymbol  = '\''+  , iname    = "mist"+  , ifreq    = [("strength mist", 1)]+  , iflavour = zipFancy [Red]+  , icount   = 11+  , irarity  = [(1, 1)]+  , iverbHit = "strengthen"+  , iweight  = 1+  , iaspects = []+  , ieffects = [toOrganActorTurn "strengthened" (3 + d 3)]+  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+mistWeakness = ItemKind+  { isymbol  = '\''+  , iname    = "mist"+  , ifreq    = [("weakness mist", 1)]+  , iflavour = zipFancy [Blue]+  , icount   = 12+  , irarity  = [(1, 1)]+  , iverbHit = "weaken"+  , iweight  = 1+  , iaspects = []+  , ieffects = [toOrganGameTurn "weakened" (3 + d 3)]+  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+protectingBalm = ItemKind+  { isymbol  = '\''+  , iname    = "balm droplet"+  , ifreq    = [("protecting balm", 1)]+  , iflavour = zipPlain [Brown]+  , icount   = 13+  , irarity  = [(1, 1)]+  , iverbHit = "balm"+  , iweight  = 1+  , iaspects = []+  , ieffects = [toOrganActorTurn "protected" (3 + d 3)]+  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+redPaint = ItemKind+  { isymbol  = '\''+  , iname    = "red paint"+  , ifreq    = [("red paint", 1)]+  , iflavour = zipPlain [BrRed]+  , icount   = 14+  , irarity  = [(1, 1)]+  , iverbHit = "paint"+  , iweight  = 1+  , iaspects = []+  , ieffects = [toOrganGameTurn "painted red" (3 + d 3)]+  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+hasteSpray = ItemKind+  { isymbol  = '\''+  , iname    = "haste spray"+  , ifreq    = [("haste spray", 1)]+  , iflavour = zipPlain [BrRed]+  , icount   = 15+  , irarity  = [(1, 1)]+  , iverbHit = "haste"+  , iweight  = 1+  , iaspects = []+  , ieffects = [toOrganActorTurn "fast 20" (3 + d 3)]+  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+slownessSpray = ItemKind+  { isymbol  = '\''+  , iname    = "slowness spray"+  , ifreq    = [("slowness spray", 1)]+  , iflavour = zipPlain [BrBlue]+  , icount   = 16+  , irarity  = [(1, 1)]+  , iverbHit = "slow"+  , iweight  = 1+  , iaspects = []+  , ieffects = [toOrganGameTurn "slow 10" (3 + d 3)]+  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+eyeDrop = ItemKind+  { isymbol  = '\''+  , iname    = "eye drop"+  , ifreq    = [("eye drop", 1)]+  , iflavour = zipPlain [BrGreen]+  , icount   = 17+  , irarity  = [(1, 1)]+  , iverbHit = "cleanse"+  , iweight  = 1+  , iaspects = []+  , ieffects = [toOrganActorTurn "far-sighted" (3 + d 3)]+  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+smellyDroplet = ItemKind+  { isymbol  = '\''+  , iname    = "smelly droplet"+  , ifreq    = [("smelly droplet", 1)]+  , iflavour = zipPlain [Blue]+  , icount   = 18+  , irarity  = [(1, 1)]+  , iverbHit = "sensitize"+  , iweight  = 1+  , iaspects = []+  , ieffects = [toOrganActorTurn "keen-smelling" (3 + d 3)]+  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }+whiskeySpray = ItemKind+  { isymbol  = '\''+  , iname    = "whiskey spray"+  , ifreq    = [("whiskey spray", 1)]+  , iflavour = zipPlain [Brown]+  , icount   = 19+  , irarity  = [(1, 1)]+  , iverbHit = "inebriate"+  , iweight  = 1+  , iaspects = []+  , ieffects = [toOrganActorTurn "drunk" (3 + d 3)]+  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps+               , Fragile, Identified ]+  , idesc    = ""+  , ikit     = []+  }
GameDefinition/Content/ItemKindOrgan.hs view
@@ -3,7 +3,6 @@  import Game.LambdaHack.Common.Color import Game.LambdaHack.Common.Dice-import Game.LambdaHack.Common.Effect import Game.LambdaHack.Common.Flavour import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg@@ -11,43 +10,11 @@  organs :: [ItemKind] organs =-  [fist, foot, tentacle, lash, noseTip, lip, claw, smallClaw, snout, sting, venomTooth, venomFang, largeTail, jaw, largeJaw, tooth, pupil, armoredSkin, speedGland2, speedGland4, speedGland6, speedGland8, speedGland10, eye2, eye3, eye4, eye5, nostril, thorn, vent, fissure]--fist,    foot, tentacle, lash, noseTip, lip, claw, smallClaw, snout, sting, venomTooth, venomFang, largeTail, jaw, largeJaw, tooth, pupil, armoredSkin, speedGland2, speedGland4, speedGland6, speedGland8, speedGland10, eye2, eye3, eye4, eye5, nostril, thorn, vent, fissure :: ItemKind+  [fist, foot, claw, smallClaw, snout, jaw, largeJaw, tooth, tentacle, lash, noseTip, lip, thorn, fissure, sting, venomTooth, venomFang, largeTail, pupil, armoredSkin, eye2, eye3, eye4, eye5, nostril, speedGland2, speedGland4, speedGland6, speedGland8, speedGland10, vent, bonusHP] --- * Parameterized organs+fist,    foot, claw, smallClaw, snout, jaw, largeJaw, tooth, tentacle, lash, noseTip, lip, thorn, fissure, sting, venomTooth, venomFang, largeTail, pupil, armoredSkin, eye2, eye3, eye4, eye5, nostril, speedGland2, speedGland4, speedGland6, speedGland8, speedGland10, vent, bonusHP :: ItemKind -speedGland :: Int -> ItemKind-speedGland n = fist-  { iname    = "speed gland"-  , ifreq    = [(toGroupName $ "speed gland" <+> tshow n, 100)]-  , icount   = 1-  , iverbHit = "spit at"-  , iaspects = [AddSpeed $ intToDice n, Periodic $ intToDice n]-  , ieffects = [RefillHP 1]-  , ifeature = [Durable, Identified]-  , idesc    = ""-  }-speedGland2 = speedGland 2-speedGland4 = speedGland 4-speedGland6 = speedGland 6-speedGland8 = speedGland 8-speedGland10 = speedGland 10-eye :: Int -> ItemKind-eye n = fist-  { iname    = "eye"-  , ifreq    = [(toGroupName $ "eye" <+> tshow n, 100)]-  , icount   = 2-  , iverbHit = "glare at"-  , iaspects = [AddSight (intToDice n)]-  , ieffects = []-  , ifeature = [Durable, Identified]-  , idesc    = ""-  }-eye2 = eye 2-eye3 = eye 3-eye4 = eye 4-eye5 = eye 5+-- Weapons  -- * Human weapon organs @@ -55,7 +22,7 @@   { isymbol  = '%'   , iname    = "fist"   , ifreq    = [("fist", 100)]-  , iflavour = zipPlain [BrRed]+  , iflavour = zipPlain [Red]   , icount   = 2   , irarity  = [(1, 1)]   , iverbHit = "punch"@@ -105,7 +72,7 @@   , ifreq    = [("jaw", 20)]   , icount   = 1   , iverbHit = "rip"-  , ieffects = [Hurt (5 * d 1)]+  , ieffects = [Hurt (4 * d 1)]   , idesc    = ""   } largeJaw = fist@@ -121,7 +88,7 @@   , ifreq    = [("tooth", 20)]   , icount   = 3   , iverbHit = "nail"-  , ieffects = [Hurt (3 * d 1)]+  , ieffects = [Hurt (2 * d 1)]   , idesc    = ""   } @@ -140,7 +107,7 @@   , ifreq    = [("lash", 100)]   , icount   = 1   , iverbHit = "lash"-  , ieffects = [Hurt (4 * d 1)]+  , ieffects = [Hurt (3 * d 1), DropItem COrgan "far-sighted" True]   , idesc    = ""   } noseTip = fist@@ -156,7 +123,7 @@   , ifreq    = [("lip", 10)]   , icount   = 2   , iverbHit = "lap"-  , ieffects = [Hurt (2 * d 1)]  -- TODO: decrease Hurt, but use+  , ieffects = [Hurt (2 * d 1), DropItem COrgan "keen-smelling" True]  -- TODO: decrease Hurt, but use   , idesc    = ""   } @@ -168,6 +135,7 @@   , icount   = 7   , iverbHit = "impale"   , ieffects = [Hurt (2 * d 1)]+  , ifeature = [EqpSlot EqpSlotWeapon "", Identified]  -- not Durable   , idesc    = ""   } fissure = fist@@ -183,7 +151,8 @@   , ifreq    = [("sting", 100)]   , icount   = 1   , iverbHit = "sting"-  , ieffects = [Burn 1, Paralyze 2]+  , iaspects = [Timeout $ 1 + d 5]+  , ieffects = [Burn 1, Recharging (Paralyze 3)]   , idesc    = ""   } venomTooth = fist@@ -191,15 +160,21 @@   , ifreq    = [("venom tooth", 100)]   , icount   = 2   , iverbHit = "bite"-  , ieffects = [Hurt (3 * d 1), Paralyze 3]+  , iaspects = [Timeout $ 5 + d 3]+  , ieffects = [ Hurt (3 * d 1)+               , Recharging (toOrganGameTurn "slow 10" (3 + d 3)) ]   , idesc    = ""   }+-- TODO: should also confer poison resistance, but current implementation+-- is too costly (poison removal each turn) venomFang = fist   { iname    = "venom fang"   , ifreq    = [("venom fang", 100)]   , icount   = 2   , iverbHit = "bite"-  , ieffects = [Hurt (3 * d 1)]  -- TODO: +12 damage or poison effect+  , iaspects = [Timeout $ 7 + d 5]+  , ieffects = [ Hurt (3 * d 1)+               , Recharging (toOrganNone "poisoned") ]   , idesc    = ""   } largeTail = fist@@ -207,7 +182,8 @@   , ifreq    = [("large tail", 50)]   , icount   = 1   , iverbHit = "knock"-  , ieffects = [Hurt (8 * d 1), PushActor (ThrowMod 400 25)]+  , iaspects = [Timeout $ 1 + d 3]+  , ieffects = [Hurt (8 * d 1), Recharging (PushActor (ThrowMod 400 25))]   , idesc    = ""   } pupil = fist@@ -215,46 +191,89 @@   , ifreq    = [("pupil", 100)]   , icount   = 1   , iverbHit = "gaze at"-  , iaspects = [AddSight 7]-  , ieffects = [Hurt (4 * d 1), Paralyze 1]  -- TODO: decrease Hurt, but use+  , iaspects = [AddSight 7, Timeout $ 5 + d 5]+  , ieffects = [Hurt (3 * d 1), Recharging (DropItem COrgan "temporary conditions" True)]  -- TODO: decrease Hurt, but use   , idesc    = ""   } +-- Non-weapons+ -- * Armor organs -armoredSkin = fist-  { iname    = "armored skin"+armoredSkin = ItemKind+  { isymbol  = '%'+  , iname    = "armored skin"   , ifreq    = [("armored skin", 100)]+  , iflavour = zipPlain [Red]   , icount   = 1+  , irarity  = [(1, 1)]   , iverbHit = "bash"+  , iweight  = 2000   , iaspects = [AddArmorMelee 30, AddArmorRanged 30]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = ""+  , ikit     = []   }  -- * Sense organs -nostril = fist+eye :: Int -> ItemKind+eye n = armoredSkin+  { iname    = "eye"+  , ifreq    = [(toGroupName $ "eye" <+> tshow n, 100)]+  , icount   = 2+  , iverbHit = "glare at"+  , iaspects = [AddSight (intToDice n)]+  , idesc    = ""+  }+eye2 = eye 2+eye3 = eye 3+eye4 = eye 4+eye5 = eye 5+nostril = armoredSkin   { iname    = "nostril"   , ifreq    = [("nostril", 100)]   , icount   = 2   , iverbHit = "snuff"   , iaspects = [AddSmell 1]-  , ieffects = []-  , ifeature = [Durable, Identified]   , idesc    = ""   }  -- * Assorted -vent = fist+speedGland :: Int -> ItemKind+speedGland n = armoredSkin+  { iname    = "speed gland"+  , ifreq    = [(toGroupName $ "speed gland" <+> tshow n, 100)]+  , icount   = 1+  , iverbHit = "spit at"+  , iaspects = [ AddSpeed $ intToDice n+               , Periodic+               , Timeout $ intToDice $ 100 `div` n ]+  , ieffects = [Recharging (RefillHP 1)]+  , idesc    = ""+  }+speedGland2 = speedGland 2+speedGland4 = speedGland 4+speedGland6 = speedGland 6+speedGland8 = speedGland 8+speedGland10 = speedGland 10+vent = armoredSkin   { iname    = "vent"   , ifreq    = [("vent", 100)]   , icount   = 1   , iverbHit = "menace"-  , iaspects = [Periodic $ 1 + d 2]-  , ieffects = [Explode "boiling water"]-  , ifeature = [Durable, Identified]+  , iaspects = [Periodic, Timeout $ (2 + d 4) |*| 5]+  , ieffects = [Recharging (Explode "boiling water")]+  , idesc    = ""+  }+bonusHP = armoredSkin+  { iname    = "bonus HP"+  , ifreq    = [("bonus HP", 100)]+  , icount   = 1+  , iverbHit = "intimidate"+  , iweight  = 0+  , iaspects = [AddMaxHP 1]   , idesc    = ""   }
− GameDefinition/Content/ItemKindShrapnel.hs
@@ -1,256 +0,0 @@--- | Shrapnel definitions.-module Content.ItemKindShrapnel ( shrapnels ) where--import Game.LambdaHack.Common.Color-import Game.LambdaHack.Common.Dice-import Game.LambdaHack.Common.Effect-import Game.LambdaHack.Common.Flavour-import Game.LambdaHack.Common.Misc-import Game.LambdaHack.Common.Msg-import Game.LambdaHack.Content.ItemKind--shrapnels :: [ItemKind]-shrapnels =-  [fragrance, pheromone, firecracker2, firecracker3, firecracker4, firecracker5, firecracker6, firecracker7, mistHealing, mistHealing2, mistWounding, distortion, waste, burningOil2, burningOil3, burningOil4, explosionBlast10, explosionBlast20, glassPiece, smoke, boilingWater, glue]--fragrance,    pheromone, firecracker2, firecracker3, firecracker4, firecracker5, firecracker6, firecracker7, mistHealing, mistHealing2, mistWounding, distortion, waste, burningOil2, burningOil3, burningOil4, explosionBlast10, explosionBlast20, glassPiece, smoke, boilingWater, glue :: ItemKind---- * Parameterized shrapnel--burningOil :: Int -> ItemKind-burningOil n = ItemKind-  { isymbol  = '*'-  , iname    = "burning oil"-  , ifreq    = [(toGroupName $ "burning oil" <+> tshow n, 1)]-  , iflavour = zipFancy [BrYellow]-  , icount   = intToDice (n * 4)-  , irarity  = [(1, 1)]-  , iverbHit = "burn"-  , iweight  = 1-  , iaspects = [AddLight 2]-  , ieffects = [ Burn (n `div` 2)-               , Paralyze (intToDice $ n `div` 2) ]  -- tripping on oil-  , ifeature = [ toVelocity (min 100 $ n * 7)-               , Fragile, Identified ]-  , idesc    = "Sticky oil, burning brightly."-  , ikit     = []-  }-burningOil2 = burningOil 2-burningOil3 = burningOil 3-burningOil4 = burningOil 4-explosionBlast :: Int -> ItemKind-explosionBlast n = ItemKind-  { isymbol  = '*'-  , iname    = "explosion blast"-  , ifreq    = [(toGroupName $ "explosion blast" <+> tshow n, 1)]-  , iflavour = zipPlain [BrRed]-  , icount   = 12  -- strong, but few, so not always hits target-  , irarity  = [(1, 1)]-  , iverbHit = "tear apart"-  , iweight  = 1-  , iaspects = [AddLight $ intToDice n]-  , ieffects = [RefillHP (- n `div` 2), DropBestWeapon]-  , ifeature = [Fragile, toLinger 10, Identified]-  , idesc    = ""-  , ikit     = []-  }-explosionBlast10 = explosionBlast 10-explosionBlast20 = explosionBlast 20-firecracker :: Int -> ItemKind-firecracker n = ItemKind-  { isymbol  = '*'-  , iname    = "firecracker"-  , ifreq    = [(toGroupName $ "firecracker" <+> tshow n, 1)]-  , iflavour = zipPlain [stdCol !! (n `mod` length stdCol)]-  , icount   = intToDice $ 2 * n-  , irarity  = [(1, 1)]-  , iverbHit = "crack"-  , iweight  = 1-  , iaspects = [AddLight $ intToDice $ n `div` 2]-  , ieffects = [Burn 1, Explode $ toGroupName $ "firecracker" <+> tshow (n - 1)]-  , ifeature = [ ToThrow $ ThrowMod (n * 10) 20-               , Fragile, Identified ]-  , idesc    = ""-  , ikit     = []-  }-firecracker7 = firecracker 7-firecracker6 = firecracker 6-firecracker5 = firecracker 5-firecracker4 = firecracker 4-firecracker3 = firecracker 3-firecracker2 = firecracker 2---- * Assorted--fragrance = ItemKind-  { isymbol  = '\''-  , iname    = "fragrance"-  , ifreq    = [("fragrance", 1)]-  , iflavour = zipFancy [Magenta]-  , icount   = 15-  , irarity  = [(1, 1)]-  , iverbHit = "engulf"-  , iweight  = 1-  , iaspects = []-  , ieffects = [Impress]-  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps-               , Fragile, Identified ]-  , idesc    = ""-  , ikit     = []-  }-pheromone = ItemKind-  { isymbol  = '\''-  , iname    = "musky whiff"-  , ifreq    = [("pheromone", 1)]-  , iflavour = zipFancy [BrMagenta]-  , icount   = 8-  , irarity  = [(1, 1)]-  , iverbHit = "tempt"-  , iweight  = 1-  , iaspects = []-  , ieffects = [Dominate]-  , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps-               , Fragile, Identified ]-  , idesc    = ""-  , ikit     = []-  }-mistHealing = ItemKind-  { isymbol  = '\''-  , iname    = "mist"-  , ifreq    = [("healing mist", 1)]-  , iflavour = zipFancy [White]-  , icount   = 11-  , irarity  = [(1, 1)]-  , iverbHit = "revitalize"-  , iweight  = 1-  , iaspects = [AddLight 1]-  , ieffects = [RefillHP 2]-  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)-               , Fragile, Identified ]-  , idesc    = ""-  , ikit     = []-  }-mistHealing2 = ItemKind-  { isymbol  = '\''-  , iname    = "mist"-  , ifreq    = [("healing mist 2", 1)]-  , iflavour = zipFancy [White]-  , icount   = 12-  , irarity  = [(1, 1)]-  , iverbHit = "revitalize"-  , iweight  = 1-  , iaspects = [AddLight 2]-  , ieffects = [RefillHP 4]-  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)-               , Fragile, Identified ]-  , idesc    = ""-  , ikit     = []-  }-mistWounding = ItemKind-  { isymbol  = '\''-  , iname    = "mist"-  , ifreq    = [("wounding mist", 1)]-  , iflavour = zipFancy [White]-  , icount   = 13-  , irarity  = [(1, 1)]-  , iverbHit = "devitalize"-  , iweight  = 1-  , iaspects = []-  , ieffects = [RefillHP (-2)]-  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)-               , Fragile, Identified ]-  , idesc    = ""-  , ikit     = []-  }-distortion = ItemKind-  { isymbol  = 'v'-  , iname    = "vortex"-  , ifreq    = [("distortion", 1)]-  , iflavour = zipFancy [White]-  , icount   = 4-  , irarity  = [(1, 1)]-  , iverbHit = "engulf"-  , iweight  = 1-  , iaspects = []-  , ieffects = [Teleport $ 15 + d 10]-  , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)-               , Fragile, Identified ]-  , idesc    = ""-  , ikit     = []-  }-waste = ItemKind-  { isymbol  = '*'-  , iname    = "waste"-  , ifreq    = [("waste", 1)]-  , iflavour = zipPlain [Brown]-  , icount   = 10-  , irarity  = [(1, 1)]-  , iverbHit = "splosh"-  , iweight  = 50-  , iaspects = []-  , ieffects = [RefillHP (-1)]-  , ifeature = [ ToThrow $ ThrowMod 28 50-               , Fragile, Identified ]-  , idesc    = ""-  , ikit     = []-  }-glassPiece = ItemKind  -- when blowing up windows-  { isymbol  = '*'-  , iname    = "glass piece"-  , ifreq    = [("glass piece", 1)]-  , iflavour = zipPlain [BrBlue]-  , icount   = 17-  , irarity  = [(1, 1)]-  , iverbHit = "cut"-  , iweight  = 10-  , iaspects = []-  , ieffects = [Hurt (1 * d 1)]-  , ifeature = [toLinger 20, Fragile, Identified]-  , idesc    = ""-  , ikit     = []-  }-smoke = ItemKind  -- when stuff burns out-  { isymbol  = '\''-  , iname    = "smoke"-  , ifreq    = [("smoke", 1), ("firecracker 1", 1)]-  , iflavour = zipPlain [BrBlack]-  , icount   = 19-  , irarity  = [(1, 1)]-  , iverbHit = "choke"-  , iweight  = 1-  , iaspects = []-  , ieffects = []-  , ifeature = [ toVelocity 21, Fragile, Identified ]-  , idesc    = ""-  , ikit     = []-  }-boilingWater = ItemKind-  { isymbol  = '*'-  , iname    = "boiling water"-  , ifreq    = [("boiling water", 1)]-  , iflavour = zipPlain [BrWhite]-  , icount   = 9-  , irarity  = [(1, 1)]-  , iverbHit = "boil"-  , iweight  = 5-  , iaspects = []-  , ieffects = [Burn 1]-  , ifeature = [toVelocity 50, Fragile, Identified]-  , idesc    = ""-  , ikit     = []-  }-glue = ItemKind-  { isymbol  = '*'-  , iname    = "hoof glue"-  , ifreq    = [("glue", 1)]-  , iflavour = zipPlain [BrYellow]-  , icount   = 14-  , irarity  = [(1, 1)]-  , iverbHit = "glue"-  , iweight  = 20-  , iaspects = []-  , ieffects = [Paralyze (3 + d 3)]-  , ifeature = [toVelocity 40, Fragile, Identified]-  , idesc    = ""-  , ikit     = []-  }
+ GameDefinition/Content/ItemKindTemporary.hs view
@@ -0,0 +1,75 @@+-- | Temporary aspect pseudo-item definitions.+module Content.ItemKindTemporary ( temporaries ) where++import Data.Text (Text)++import Game.LambdaHack.Common.Color+import Game.LambdaHack.Common.Dice+import Game.LambdaHack.Common.Flavour+import Game.LambdaHack.Common.Misc+import Game.LambdaHack.Common.Msg+import Game.LambdaHack.Content.ItemKind++temporaries :: [ItemKind]+temporaries =+  [tmpStrengthened, tmpWeakened, tmpProtected, tmpPaintedRed, tmpFast20, tmpSlow10, tmpFarSighted, tmpKeenSmelling, tmpDrunk, tmpRegenerating, tmpPoisoned, tmpSlow10Resistant, tmpPoisonResitant]++tmpStrengthened,    tmpWeakened, tmpProtected, tmpPaintedRed, tmpFast20, tmpSlow10, tmpFarSighted, tmpKeenSmelling, tmpDrunk, tmpRegenerating, tmpPoisoned, tmpSlow10Resistant, tmpPoisonResitant :: ItemKind++-- The @name@ is be used in item description, so it should be an adjective+-- describing the temporary set of aspects.+tmpAs :: Text -> [Aspect Dice] -> ItemKind+tmpAs name aspects = ItemKind+  { isymbol  = '.'+  , iname    = name+  , ifreq    = [(toGroupName name, 1), ("temporary conditions", 1)]+  , iflavour = zipPlain [BrWhite]+  , icount   = 1+  , irarity  = [(1, 1)]+  , iverbHit = "affect"+  , iweight  = 1+  , iaspects = [Periodic, Timeout 0]  -- activates and vanishes soon,+                                      -- depending on initial timer setting+               ++ aspects+  , ieffects = let tmp = Temporary $ "be no longer" <+> name+               in [Recharging tmp, OnSmash tmp]+  , ifeature = [Identified]+  , idesc    = ""+  , ikit     = []+  }++tmpStrengthened = tmpAs "strengthened" [AddHurtMelee 20]+tmpWeakened = tmpAs "weakened" [AddHurtMelee (-20)]+tmpProtected = tmpAs "protected" [ AddArmorMelee 30+                                 , AddArmorRanged 30 ]+tmpPaintedRed = tmpAs "painted red" [ AddArmorMelee (-30)+                                    , AddArmorRanged (-30) ]+tmpFast20 = tmpAs "fast 20" [AddSpeed 20]+tmpSlow10 = tmpAs "slow 10" [AddSpeed (-10)]+tmpFarSighted = tmpAs "far-sighted" [AddSight 5]+tmpKeenSmelling = tmpAs "keen-smelling" [AddSmell 1]+tmpDrunk = tmpAs "drunk" [ AddHurtMelee 30  -- fury+                         , AddArmorMelee (-20)+                         , AddArmorRanged (-20)+                         , AddSight (-7)+                         ]+tmpRegenerating =+  let tmp = tmpAs "regenerating" []+  in tmp { icount = 7 + d 5+         , ieffects = [Recharging (RefillHP 1)] ++ ieffects tmp+         }+tmpPoisoned =+  let tmp = tmpAs "poisoned" []+  in tmp { icount = 7 + d 5+         , ieffects = [Recharging (RefillHP (-1))] ++ ieffects tmp+         }+tmpSlow10Resistant =+  let tmp = tmpAs "slow resistant" []+  in tmp { icount = 7 + d 5+         , ieffects = [Recharging (DropItem COrgan "slow 10" True)] ++ ieffects tmp+         }+tmpPoisonResitant =+  let tmp = tmpAs "poison resistant" []+  in tmp { icount = 7 + d 5+         , ieffects = [Recharging (DropItem COrgan "poisoned" True)] ++ ieffects tmp+         }
GameDefinition/Content/ModeKind.hs view
@@ -5,6 +5,7 @@  import Content.ModeKindPlayer import Game.LambdaHack.Common.ContentDef+import Game.LambdaHack.Common.Misc import Game.LambdaHack.Content.ModeKind  cdefs :: ContentDef ModeKind@@ -15,9 +16,9 @@   , validateSingle = validateSingleModeKind   , validateAll = validateAllModeKind   , content =-      [campaign, duel, skirmish, ambush, battle, safari, pvp, coop, defense]+      [campaign, duel, skirmish, ambush, battle, safari, pvp, coop, defense, screensaver]   }-campaign,        duel, skirmish, ambush, battle, safari, pvp, coop, defense :: ModeKind+campaign,        duel, skirmish, ambush, battle, safari, pvp, coop, defense, screensaver :: ModeKind  campaign = ModeKind   { msymbol = 'a'@@ -100,7 +101,17 @@   , mdesc   = "Don't let the humans defile your abstract secrets and flee, like the vulgar, literal, base scoundrels that they are!"   } +screensaver = safari+  { mname   = "safari screensaver"+  , mfreq   = [("screensaver", 1), ("starting", 1)]+  , mroster = rosterSafari+      { rosterList = (head (rosterList rosterSafari))+                       {fleaderMode = LeaderAI $ AutoLeader False False }+                     : tail (rosterList rosterSafari)+      }+  } + rosterCampaign, rosterDuel, rosterSkirmish, rosterAmbush, rosterBattle, rosterSafari, rosterPvP, rosterCoop, rosterDefense :: Roster  rosterCampaign = Roster@@ -147,10 +158,10 @@ rosterBattle = Roster   { rosterList = [ playerSoldier { finitialActors = 5                                  , fentryLevel = -5 }-                 , playerMobileMonster { finitialActors = 30+                 , playerMobileMonster { finitialActors = 35                                        , fentryLevel = -5                                        , fneverEmpty = True }-                 , playerMobileAnimal { finitialActors = 20+                 , playerMobileAnimal { finitialActors = 30                                       , fentryLevel = -5                                       , fneverEmpty = True } ]   , rosterEnemy = [ ("Armed Adventurer Party", "Monster Hive")@@ -166,6 +177,7 @@                                      , fentryLevel = -4                                      , finitialActors = 15                                      , fleaderMode =+                                         -- no spawning and TFollow                                          LeaderUI $ AutoLeader False False }                  , playerCivilian { fname = "Hunam Convict Pack"                                   , fentryLevel = -4 }@@ -178,7 +190,7 @@                                           "Animal Exquisite Herds and Packs"                                       , fneverEmpty = True                                       , fentryLevel = -10-                                      , finitialActors = 25 } ]+                                      , finitialActors = 30 } ]   , rosterEnemy = [ ("Monster Tourist Office", "Hunam Convict Pack")                   , ("Monster Tourist Office",                      "Animal Magnificent Specimen Variety")@@ -218,9 +230,8 @@                  , ("Green", "Leaderless Monster Hive") ] }  rosterDefense = Roster-  { rosterList = [ playerAntiMonster { finitialActors = 1 }-                 , playerAntiHero { fname = "Yellow"-                                  , finitialActors = 10 }+  { rosterList = [ playerAntiMonster+                 , playerAntiHero {fname = "Yellow"}                  , playerAnimal ]   , rosterEnemy = [ ("Yellow", "Monster Hive")                   , ("Yellow", "Animal Kingdom") ]
GameDefinition/Content/ModeKindPlayer.hs view
@@ -8,9 +8,11 @@ import qualified Data.EnumMap.Strict as EM  import Game.LambdaHack.Common.Ability+import Game.LambdaHack.Common.Dice+import Game.LambdaHack.Common.Misc import Game.LambdaHack.Content.ModeKind -playerHero, playerSoldier, playerAntiHero, playerCivilian, playerMonster, playerMobileMonster, playerAntiMonster, playerAnimal, playerMobileAnimal, playerHorror :: Player+playerHero, playerSoldier, playerAntiHero, playerCivilian, playerMonster, playerMobileMonster, playerAntiMonster, playerAnimal, playerMobileAnimal, playerHorror :: Player Dice  playerHero = Player   { fname = "Adventurer Party"@@ -33,7 +35,7 @@   }  playerAntiHero = playerHero-  { fleaderMode = LeaderAI $ AutoLeader False False+  { fleaderMode = LeaderAI $ AutoLeader True False   , fhasUI = False   } @@ -47,7 +49,7 @@   , fhasGender = True   , ftactic = TPatrol   , fentryLevel = -1-  , finitialActors = 3+  , finitialActors = d 2 + 1   , fleaderMode = LeaderNull  -- unorganized   , fhasUI = False   }@@ -62,8 +64,8 @@   , fhasGender = False   , ftactic = TExplore   , fentryLevel = -3-  , finitialActors = 5-  , fleaderMode = LeaderAI $ AutoLeader True False+  , finitialActors = 1  -- an explorer, not a nose, ensured via irarity+  , fleaderMode = LeaderAI $ AutoLeader True True   , fhasUI = False   } @@ -71,19 +73,20 @@  playerAntiMonster = playerMonster   { fhasUI = True+  , fleaderMode = LeaderUI $ AutoLeader True True   }  playerAnimal = Player   { fname = "Animal Kingdom"   , fgroup = "animal"-  , fskillsOther = animalSkills+  , fskillsOther = unitSkills   , fcanEscape = False   , fneverEmpty = False   , fhasNumbers = False   , fhasGender = False   , ftactic = TRoam  -- can't pick up, so no point exploring-  , fentryLevel = -2-  , finitialActors = 3+  , fentryLevel = -1  -- fun from the start; don't cumulate with monsters+  , finitialActors = 1 + d 2   , fleaderMode = LeaderNull   , fhasUI = False   }@@ -112,12 +115,14 @@   , fhasUI = False   } -meleeAdjacent, _meleeAndRanged, animalSkills :: Skills+minusHundred, meleeAdjacent, _meleeAndRanged :: Skills -meleeAdjacent = EM.fromList $ zip [AbWait, AbMelee] [1, 1..]+-- To make sure weak items can't override move-only-leader, etc.+minusHundred = EM.fromList $ zip [minBound..maxBound] [-100, -100..] --- Melee and reaction fire.-_meleeAndRanged = EM.fromList $ zip [AbWait, AbMelee, AbProject] [1, 1..]+meleeAdjacent = addSkills minusHundred+                $ EM.fromList $ zip [AbWait, AbMelee] [101, 101..] -animalSkills =-  EM.fromList $ zip [AbMove, AbMelee, AbAlter, AbWait, AbTrigger] [1, 1..]+-- Melee and reaction fire.+_meleeAndRanged = addSkills minusHundred+                  $ EM.fromList $ zip [AbWait, AbMelee, AbProject] [101, 101..]
GameDefinition/Content/TileKind.hs view
@@ -7,10 +7,9 @@  import Game.LambdaHack.Common.Color import Game.LambdaHack.Common.ContentDef-import qualified Game.LambdaHack.Common.Effect as Effect-import Game.LambdaHack.Common.Feature import Game.LambdaHack.Common.Misc import Game.LambdaHack.Common.Msg+import qualified Game.LambdaHack.Content.ItemKind as IK import Game.LambdaHack.Content.TileKind  cdefs :: ContentDef TileKind@@ -73,7 +72,8 @@                , ("legendLit", 100), ("legendDark", 100) ]   , tcolor   = BrWhite   , tcolor2  = defFG-  , tfeature = [Cause $ Effect.CreateItem 1, ChangeTo "cachable"]+  , tfeature = [ Cause $ IK.CreateItem CGround "useful" IK.TimerNone+               , ChangeTo "cachable" ]   } lampPost = TileKind   { tsymbol  = 'O'@@ -185,7 +185,7 @@   , tfreq    = [("legendLit", 100)]   , tcolor   = BrWhite   , tcolor2  = defFG-  , tfeature = [Walkable, Clear, NoItem, NoActor, Cause $ Effect.Ascend 1]+  , tfeature = [Walkable, Clear, NoItem, NoActor, Cause $ IK.Ascend 1]   } stairsLit = TileKind   { tsymbol  = '>'@@ -194,8 +194,8 @@   , tcolor   = BrCyan   , tcolor2  = Cyan  -- TODO   , tfeature = [ Walkable, Clear, NoItem, NoActor-               , Cause $ Effect.Ascend 1-               , Cause $ Effect.Ascend (-1) ]+               , Cause $ IK.Ascend 1+               , Cause $ IK.Ascend (-1) ]   } stairsDownLit = TileKind   { tsymbol  = '>'@@ -203,7 +203,7 @@   , tfreq    = [("legendLit", 100)]   , tcolor   = BrWhite   , tcolor2  = defFG-  , tfeature = [Walkable, Clear, NoItem, NoActor, Cause $ Effect.Ascend (-1)]+  , tfeature = [Walkable, Clear, NoItem, NoActor, Cause $ IK.Ascend (-1)]   } escapeUpLit = TileKind   { tsymbol  = '<'@@ -211,7 +211,7 @@   , tfreq    = [("legendLit", 100)]   , tcolor   = BrYellow   , tcolor2  = BrYellow-  , tfeature = [Walkable, Clear, NoItem, NoActor, Cause $ Effect.Escape 1]+  , tfeature = [Walkable, Clear, NoItem, NoActor, Cause $ IK.Escape 1]   } escapeDownLit = TileKind   { tsymbol  = '>'@@ -219,7 +219,7 @@   , tfreq    = [("legendLit", 100)]   , tcolor   = BrYellow   , tcolor2  = BrYellow-  , tfeature = [Walkable, Clear, NoItem, NoActor, Cause $ Effect.Escape (-1)]+  , tfeature = [Walkable, Clear, NoItem, NoActor, Cause $ IK.Escape (-1)]   } unknown = TileKind   { tsymbol  = ' '@@ -290,7 +290,7 @@   }  makeDark :: TileKind -> TileKind-makeDark k = let darkText :: GroupName -> GroupName+makeDark k = let darkText :: GroupName TileKind -> GroupName TileKind                  darkText t = maybe t (toGroupName . (<> "Dark"))                               $ T.stripSuffix "Lit" $ tshow t                  darkFrequency = map (first darkText) $ tfreq k
GameDefinition/PLAYING.md view
@@ -13,7 +13,7 @@ Once the few basic command keys and on-screen symbols are learned, mastery and enjoyment of the game is the matter of tactical skill and literary imagination. To be honest, a lot of imagination is required-for this rudimentary game, but it's playable and winnable.+for this rudimentary game, even though it is playable and winnable. Contributions are welcome.  @@ -38,8 +38,13 @@ HP (hit points, health). At the end, the personal target of the leader is described, in this case a basilisk monster, with hit points drawn as a bar. -The other status line describes the current location in relation to the party.+Weapon damage and other item stats are displayed using the dice notation `XdY`.+which means X rolls of Y-sided dice. A variant denoted 'XdsY' is additionally+scaled by the level depth in proportion to the maximal dungeon depth. +The second status line describes the current dungeon location in relation+to the party.+     5  Lofty hall   [33% seen] Cursor: exact spot (71,12)  p15 l10  First comes the depth of the current level and its name.@@ -146,9 +151,9 @@ automatically as soon as a monster comes into view). Once in targeting mode, you can move the targeting cursor with arrow keys and switch focus among enemies with `*` (or among friends, projectiles and enemies, depending-on targeting mode set by `/`). The details of the shared cursor position-and of the personal target are described at the bottom of the screen.-All targeting keys are listed below.+on targeting mode toggled by `/`). The details of the shared cursor position+and of the personal target are described in the status lines, at the bottom+of the screen. All targeting keys are listed below.                  keys           command                 KEYPAD_* and \ target enemy@@ -206,17 +211,7 @@                 CTRL-a         new Campaign game                 CTRL-d         cycle next game difficulty -There are also some debug, testing and cheat options and game modes-that can be specified on the command line when starting the game server.-Use at your own peril! :) Of these, you may find the screensaver game modes-the least spoilery and the most fun, e.g.: -    LambdaHack --savePrefix test --newGame --noMore --maxFps 60 --automateAll --gameMode campaign --difficulty 1--The `--automateAll` option strictly corresponds to the `CTRL-A` command,-but most of the debug options have no corresponding commands.-- Monsters -------- @@ -236,12 +231,13 @@ This gives the opponent a free blow, but can improve the tactical situation or aid escape. -Slinging a missile at a target wounds it, consuming the weapon in the process.+Flinging a missile at a target wounds it, consuming the weapon in the process. You may propel any item in your equipment, inventory and on the ground-(press `?` to choose an item and press it again for a non-standard choice).-Only items of a few kinds inflict any damage, but some have other effects.-Whenever the monster's or hero's hit points reach zero, the combatant dies.-When the last hero dies, the game ends in defeat.+(press `?` to see appropriate items in a menu and press it again+for a non-standard choice). Only items of a few kinds inflict any damage,+but some have other effects. Whenever the monster's or hero's hit points+reach zero, the combatant dies. When the last hero dies, the game ends+in defeat.   On Winning and Dying
GameDefinition/config.ui.default view
@@ -1,12 +1,22 @@ ; ; This is a commented out copy of the default UI settings config file-; ; that is embedded in the binary.-; ; A user config file can overrides these options. The game looks for it at-; ; ~/.LambdaHack/config.ui.ini (or a similar path, depending on the OS).-; ; Warning: options are case-sensitive and only ';' for comments is permitted.+; ; that is embedded in the game binary. A user config file can override+; ; these options. Option names are case-sensitive and only ';' for comments+; ; is permitted.+; ;+; ; The game looks for the config file at the same path where saved games+; ; directory is located. E.g. on Linux the file is at+; ; ~/.LambdaHack/config.ui.ini+; ; and on Windows it can be at+; ; C:\Documents And Settings\user\Application Data\LambdaHack\config.ui.ini+; ; or at+; ; C:\Users\<username>\AppData\Roaming\LambdaHack\config.ui.ini+; ; or elsewhere.  ; [extra_commands]-; ; A handy shorthand with Vi keys:+; ; A handy shorthand with Vi keys ; Macro_1 = ("comma", ([CmdItem], Macro "" ["g"]))+; ; Angband compatibility (accept target)+; Macro_2 = ("KP_Insert", ([CmdMeta], Macro "" ["Return"]))  ; [hero_names] ; HeroName_0 = ("Haskell Alvin", "he")@@ -20,7 +30,9 @@ ; [ui] ; movementViKeys_hjklyubn = False ; movementLaptopKeys_uk8o79jl = True-; font = "Terminus,Monospace normal normal normal normal 12"+; ; Monospace fonts that have fixed size regardless of boldness (on some OSes)+; font = "Terminus,DejaVu Sans Mono,Consolas,Courier New,Liberation Mono,Courier,FreeMono,Monospace normal normal normal normal 16"+; colorIsBold = True ; historyMax = 5000 ; maxFps = 15 ; noAnim = False
LambdaHack.cabal view
@@ -4,8 +4,8 @@ -- http://www.haskell.org/haskellwiki/Package_versioning_policy -- PVP summary: +-+------- breaking API changes --              | | +----- non-breaking API additions---              | | |  +--- code changes with no API change-version:        0.4.99.0+--              | | |   +--- code changes with no API change+version:        0.4.100.0 synopsis:       A game engine library for roguelike dungeon crawlers description:   LambdaHack is a game engine library for roguelike games                of arbitrary theme, size and complexity,@@ -71,14 +71,22 @@ flag vty   description:        switch to the vty frontend   default:            False+  manual:             True  flag curses   description:        switch to the curses frontend (not fully supported)   default:            False+  manual:             True  flag expose_internal-  description:        expose internal functions and types (True for release, False for development)+  description:        expose internal functions and types, but don't switch on any other release mode options+  default:            False+  manual:             True++flag release+  description:        prepare for a release (expose, optimize, etc.)   default:            True+  manual:             True  library   exposed-modules:    Game.LambdaHack.Atomic@@ -134,10 +142,8 @@                       Game.LambdaHack.Common.Color,                       Game.LambdaHack.Common.ContentDef,                       Game.LambdaHack.Common.Dice,-                      Game.LambdaHack.Common.Effect,                       Game.LambdaHack.Common.EffectDescription,                       Game.LambdaHack.Common.Faction,-                      Game.LambdaHack.Common.Feature,                       Game.LambdaHack.Common.File,                       Game.LambdaHack.Common.Flavour,                       Game.LambdaHack.Common.Frequency,@@ -204,6 +210,7 @@                       binary     >= 0.7     && < 1,                       bytestring >= 0.9.2   && < 1,                       containers >= 0.5     && < 1,+                      data-default,                       deepseq    >= 1.3     && < 2,                       directory  >= 1.1.0.1 && < 2,                       enummapset-th >= 0.6.0.0 && < 1,@@ -212,7 +219,7 @@                       hashable   >= 1.1.2.5 && < 2,                       hsini      >= 0.2     && < 2,                       keys       >= 3       && < 4,-                      miniutter  >= 0.4.1   && < 2,+                      miniutter  >= 0.4.4   && < 2,                       mtl        >= 2.0.1   && < 3,                       old-time   >= 1.0.0.7 && < 2,                       pretty-show >= 1.6    && < 2,@@ -247,22 +254,28 @@     cpp-options:      -DCURSES   } else { if flag(vty) {     other-modules:    Game.LambdaHack.Client.UI.Frontend.Vty-    build-depends:    vty >= 4.7.0.6 && < 5+    build-depends:    vty >= 5 && < 6     cpp-options:      -DVTY   } else {     if impl(ghc > 7.8) {       other-modules:    Game.LambdaHack.Client.UI.Frontend.Gtk       build-depends:    gtk >= 0.12.1 && < 0.14-      pkgconfig-depends: gtk+-2.0, x11+      pkgconfig-depends: gtk+-2.0     } else {       other-modules:    Game.LambdaHack.Client.UI.Frontend.Gtk       build-depends:    gtk >= 0.12.1 && < 0.13-      pkgconfig-depends: gtk+-2.0, x11+      pkgconfig-depends: gtk+-2.0     }   } } -  if flag(expose_internal) {+  if flag(expose_internal)     cpp-options:      -DEXPOSE_INTERNAL++  if flag(release) {+    cpp-options:      -DEXPOSE_INTERNAL+-- 7.6.3 has broken -O2, apparently+    if impl(ghc > 7.8)+      ghc-options:      -O2   }  executable LambdaHack@@ -273,7 +286,8 @@                       Content.ItemKind,                       Content.ItemKindActor,                       Content.ItemKindOrgan,-                      Content.ItemKindShrapnel,+                      Content.ItemKindBlast,+                      Content.ItemKindTemporary,                       Content.ModeKind,                       Content.ModeKindPlayer,                       Content.PlaceKind,@@ -291,6 +305,7 @@                       binary     >= 0.7     && < 1,                       bytestring >= 0.9.2   && < 1,                       containers >= 0.5     && < 1,+                      data-default,                       deepseq    >= 1.3     && < 2,                       directory  >= 1.1.0.1 && < 2,                       enummapset-th >= 0.6.0.0 && < 1,@@ -299,7 +314,7 @@                       hashable   >= 1.1.2.5 && < 2,                       hsini      >= 0.2     && < 2,                       keys       >= 3       && < 4,-                      miniutter  >= 0.4.1   && < 2,+                      miniutter  >= 0.4.4   && < 2,                       mtl        >= 2.0.1   && < 3,                       old-time   >= 1.0.0.7 && < 2,                       pretty-show >= 1.6    && < 2,@@ -319,9 +334,12 @@   ghc-options:        -Wall -fwarn-orphans -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates -fwarn-monomorphism-restriction -fwarn-unrecognised-pragmas   ghc-options:        -fno-warn-auto-orphans -fno-warn-implicit-prelude   ghc-options:        -fno-ignore-asserts -funbox-strict-fields-  ghc-options:        -threaded -with-rtsopts=-C0.005 -rtsopts--- ghc-options:       -with-rtsopts=-N  -- eats all cores+  ghc-options:        -threaded "-with-rtsopts=-C0.005" -rtsopts +  if flag(release)+    ghc-options:      -O2 "-with-rtsopts=-N1"+-- TODO: -N+ test-suite test   type:               exitcode-stdio-1.0   hs-source-dirs:     GameDefinition, test@@ -336,6 +354,7 @@                       binary     >= 0.7     && < 1,                       bytestring >= 0.9.2   && < 1,                       containers >= 0.5     && < 1,+                      data-default,                       deepseq    >= 1.3     && < 2,                       directory  >= 1.1.0.1 && < 2,                       enummapset-th >= 0.6.0.0 && < 1,@@ -344,7 +363,7 @@                       hashable   >= 1.1.2.5 && < 2,                       hsini      >= 0.2     && < 2,                       keys       >= 3       && < 4,-                      miniutter  >= 0.4.1   && < 2,+                      miniutter  >= 0.4.4   && < 2,                       mtl        >= 2.0.1   && < 3,                       old-time   >= 1.0.0.7 && < 2,                       pretty-show >= 1.6    && < 2,@@ -364,4 +383,8 @@   ghc-options:        -Wall -fwarn-orphans -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates -fwarn-monomorphism-restriction -fwarn-unrecognised-pragmas   ghc-options:        -fno-warn-auto-orphans -fno-warn-implicit-prelude   ghc-options:        -fno-ignore-asserts -funbox-strict-fields-  ghc-options:        -threaded -with-rtsopts=-C0.005 -rtsopts+  ghc-options:        -threaded "-with-rtsopts=-C0.005" -rtsopts++  if flag(release)+    ghc-options:      -O2 "-with-rtsopts=-N1"+-- TODO: -N
Makefile view
@@ -12,250 +12,307 @@ 	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --dumpInitRngs  xcfrontendCampaign:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode campaign --difficulty 1+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode campaign --difficulty 2  xcfrontendSkirmish:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode skirmish+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode skirmish  xcfrontendAmbush:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode ambush+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode ambush  xcfrontendBattle:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode battle --difficulty 1+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode battle --difficulty 2  xcfrontendSafari:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode safari --difficulty 1+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode safari --difficulty 2  xcfrontendDefense:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode defense --difficulty 9+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode defense --difficulty 8  xcbenchCampaign:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 60 --automateAll --gameMode campaign --difficulty 1 --setDungeonRng 42 --setMainRng 42+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 60 --automateAll --gameMode campaign --difficulty 2 --setDungeonRng 42 --setMainRng 42 +RTS -N1 -RTS  xcbenchBattle:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 60 --automateAll --gameMode battle --difficulty 1 --setDungeonRng 42 --setMainRng 42+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 60 --automateAll --gameMode battle --difficulty 2 --setDungeonRng 42 --setMainRng 42 +RTS -N1 -RTS  xcbenchFrontendCampaign:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 100000 --benchmark --stopAfter 60 --automateAll --gameMode campaign --difficulty 1 --setDungeonRng 42 --setMainRng 42+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 100000 --benchmark --stopAfter 60 --automateAll --gameMode campaign --difficulty 2 --setDungeonRng 42 --setMainRng 42 +RTS -N1 -RTS  xcbenchFrontendBattle:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 100000 --benchmark --stopAfter 60 --automateAll --gameMode battle --difficulty 1 --setDungeonRng 42 --setMainRng 42+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 100000 --benchmark --stopAfter 60 --automateAll --gameMode battle --difficulty 2 --setDungeonRng 42 --setMainRng 42 +RTS -N1 -RTS  xcbenchNull: xcbenchCampaign xcbenchBattle  xcbench: xcbenchCampaign xcbenchFrontendCampaign xcbenchBattle xcbenchFrontendBattle  -xctest-travis-short: xctest-short xcbenchNull+xctest-travis-short: xctest-short -xctest-travis: xctest-short xctest-medium xcbenchNull+xctest-travis: xctest-short xctest-medium -xctest-travis-long: xctest-short xctest-long xcbenchNull+xctest-travis-long: xctest-short xctest-long +xctest-travis-long-no-safari: xctest-short xctest-long-no-safari+ xctest: xctest-short xctest-medium xctest-long  xctest-short: xctest-short-new xctest-short-load  xctest-medium: xctestCampaign-medium xctestSkirmish-medium xctestAmbush-medium xctestBattle-medium xctestSafari-medium xctestPvP-medium xctestCoop-medium xctestDefense-medium -xctest-long: xctestCampaign-long xctestSkirmish-long xctestAmbush-long xctestBattle-long xctestSafari-long xctestPvP-long xctestCoop-long xctestDefense-long+xctest-long: xctestCampaign-long xctestSkirmish-medium xctestAmbush-medium xctestBattle-long xctestSafari-long xctestPvP-medium xctestDefense-long +xctest-long-no-safari: xctestCampaign-long xctestSkirmish-medium xctestAmbush-medium xctestBattle-long xctestPvP-medium xctestDefense-long+ xctestCampaign-long:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode campaign --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 500 --dumpInitRngs --automateAll --gameMode campaign --difficulty 2 > /tmp/stdtest.log  xctestCampaign-medium:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 250 --dumpInitRngs --automateAll --gameMode campaign --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode campaign --difficulty 2 > /tmp/stdtest.log  xctestSkirmish-long:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode skirmish > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode skirmish > /tmp/stdtest.log  xctestSkirmish-medium:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode skirmish > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode skirmish > /tmp/stdtest.log  xctestAmbush-long:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode ambush > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode ambush > /tmp/stdtest.log  xctestAmbush-medium:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode ambush > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode ambush > /tmp/stdtest.log  xctestBattle-long:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode battle --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 200 --dumpInitRngs --automateAll --gameMode battle --difficulty 2 > /tmp/stdtest.log  xctestBattle-medium:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 150 --dumpInitRngs --automateAll --gameMode battle --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 100 --dumpInitRngs --automateAll --gameMode battle --difficulty 2 > /tmp/stdtest.log  xctestSafari-long:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode safari --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 500 --dumpInitRngs --automateAll --gameMode safari --difficulty 2 > /tmp/stdtest.log  xctestSafari-medium:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 250 --dumpInitRngs --automateAll --gameMode safari --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode safari --difficulty 2 > /tmp/stdtest.log  xctestPvP-long:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode PvP > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode PvP > /tmp/stdtest.log  xctestPvP-medium:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode PvP > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode PvP > /tmp/stdtest.log  xctestCoop-long:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode Coop --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 500 --dumpInitRngs --automateAll --gameMode Coop --difficulty 2 > /tmp/stdtest.log  xctestCoop-medium:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 250 --dumpInitRngs --automateAll --gameMode Coop --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 300 --dumpInitRngs --automateAll --gameMode Coop --difficulty 2 > /tmp/stdtest.log  xctestDefense-long:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode defense --difficulty 9 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 500 --dumpInitRngs --automateAll --gameMode defense --difficulty 8 > /tmp/stdtest.log  xctestDefense-medium:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 250 --dumpInitRngs --automateAll --gameMode defense --difficulty 9 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 300 --dumpInitRngs --automateAll --gameMode defense --difficulty 8 > /tmp/stdtest.log  xctest-short-new:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --noMore --savePrefix campaign --dumpInitRngs --automateAll --gameMode campaign --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --noMore --savePrefix skirmish --dumpInitRngs --automateAll --gameMode skirmish --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --noMore --savePrefix ambush --dumpInitRngs --automateAll --gameMode ambush --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --noMore --savePrefix battle --dumpInitRngs --automateAll --gameMode battle --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --noMore --savePrefix safari --dumpInitRngs --automateAll --gameMode safari --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --noMore --savePrefix PvP --dumpInitRngs --automateAll --gameMode PvP --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --noMore --savePrefix Coop --dumpInitRngs --automateAll --gameMode Coop --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --noMore --savePrefix defense --dumpInitRngs --automateAll --gameMode defense --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --savePrefix campaign --dumpInitRngs --automateAll --gameMode campaign --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --savePrefix skirmish --dumpInitRngs --automateAll --gameMode skirmish --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --savePrefix ambush --dumpInitRngs --automateAll --gameMode ambush --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --savePrefix battle --dumpInitRngs --automateAll --gameMode battle --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --savePrefix safari --dumpInitRngs --automateAll --gameMode safari --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --savePrefix PvP --dumpInitRngs --automateAll --gameMode PvP --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --savePrefix Coop --dumpInitRngs --automateAll --gameMode Coop --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --newGame --savePrefix defense --dumpInitRngs --automateAll --gameMode defense --frontendStd --stopAfter 2 > /tmp/stdtest.log  xctest-short-load:-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --noMore --savePrefix campaign --dumpInitRngs --automateAll --gameMode campaign --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --noMore --savePrefix skirmish --dumpInitRngs --automateAll --gameMode skirmish --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --noMore --savePrefix ambush --dumpInitRngs --automateAll --gameMode ambush --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --noMore --savePrefix battle --dumpInitRngs --automateAll --gameMode battle --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --noMore --savePrefix safari --dumpInitRngs --automateAll --gameMode safari --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --noMore --savePrefix PvP --dumpInitRngs --automateAll --gameMode PvP --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --noMore --savePrefix Coop --dumpInitRngs --automateAll --gameMode Coop --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --noMore --savePrefix defense --dumpInitRngs --automateAll --gameMode defense --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix campaign --dumpInitRngs --automateAll --gameMode campaign --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix skirmish --dumpInitRngs --automateAll --gameMode skirmish --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix ambush --dumpInitRngs --automateAll --gameMode ambush --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix battle --dumpInitRngs --automateAll --gameMode battle --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix safari --dumpInitRngs --automateAll --gameMode safari --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix PvP --dumpInitRngs --automateAll --gameMode PvP --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix Coop --dumpInitRngs --automateAll --gameMode Coop --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack +RTS -xc -RTS --dbgMsgSer --savePrefix defense --dumpInitRngs --automateAll --gameMode defense --frontendStd --stopAfter 2 > /tmp/stdtest.log   play: 	dist/build/LambdaHack/LambdaHack --dbgMsgSer --dumpInitRngs  frontendCampaign:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode campaign --difficulty 1+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode campaign --difficulty 2  frontendSkirmish:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode skirmish+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode skirmish  frontendAmbush:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode ambush+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode ambush  frontendBattle:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode battle --difficulty 1+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode battle --difficulty 2  frontendSafari:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode safari --difficulty 1+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode safari --difficulty 2  frontendDefense:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 60 --dumpInitRngs --automateAll --gameMode defense --difficulty 9+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 60 --dumpInitRngs --automateAll --gameMode defense --difficulty 8  benchCampaign:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 60 --automateAll --gameMode campaign --difficulty 1 --setDungeonRng 42 --setMainRng 42+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 60 --automateAll --gameMode campaign --difficulty 2 --setDungeonRng 42 --setMainRng 42 +RTS -N1 -RTS  benchBattle:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 60 --automateAll --gameMode battle --difficulty 1 --setDungeonRng 42 --setMainRng 42+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 60 --automateAll --gameMode battle --difficulty 2 --setDungeonRng 42 --setMainRng 42 +RTS -N1 -RTS  benchFrontendCampaign:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 100000 --benchmark --stopAfter 60 --automateAll --gameMode campaign --difficulty 1 --setDungeonRng 42 --setMainRng 42+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 100000 --benchmark --stopAfter 60 --automateAll --gameMode campaign --difficulty 2 --setDungeonRng 42 --setMainRng 42 +RTS -N1 -RTS  benchFrontendBattle:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 100000 --benchmark --stopAfter 60 --automateAll --gameMode battle --difficulty 1 --setDungeonRng 42 --setMainRng 42+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 100000 --benchmark --stopAfter 60 --automateAll --gameMode battle --difficulty 2 --setDungeonRng 42 --setMainRng 42 +RTS -N1 -RTS  benchNull: benchCampaign benchBattle  bench: benchCampaign benchFrontendCampaign benchBattle benchFrontendBattle  -test-travis-short: test-short benchNull+test-travis-short: test-short -test-travis: test-short test-medium benchNull+test-travis: test-short test-medium -test-travis-long: test-short test-long benchNull+test-travis-long: test-short test-long +test-travis-long-no-safari: test-short test-long-no-safari+ test: test-short test-medium test-long  test-short: test-short-new test-short-load  test-medium: testCampaign-medium testSkirmish-medium testAmbush-medium testBattle-medium testSafari-medium testPvP-medium testCoop-medium testDefense-medium -test-long: testCampaign-long testSkirmish-long testAmbush-long testBattle-long testSafari-long testPvP-long testCoop-long testDefense-long+test-long: testCampaign-long testSkirmish-medium testAmbush-medium testBattle-long testSafari-long testPvP-medium testDefense-long +test-long-no-safari: testCampaign-long testSkirmish-medium testAmbush-medium testBattle-long testPvP-medium testDefense-long+ testCampaign-long:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode campaign --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 500 --dumpInitRngs --automateAll --gameMode campaign --difficulty 2 > /tmp/stdtest.log  testCampaign-medium:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 250 --dumpInitRngs --automateAll --gameMode campaign --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode campaign --difficulty 2 > /tmp/stdtest.log  testSkirmish-long:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode skirmish > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode skirmish > /tmp/stdtest.log  testSkirmish-medium:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode skirmish > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode skirmish > /tmp/stdtest.log  testAmbush-long:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode ambush > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode ambush > /tmp/stdtest.log  testAmbush-medium:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode ambush > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode ambush > /tmp/stdtest.log  testBattle-long:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode battle --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 200 --dumpInitRngs --automateAll --gameMode battle --difficulty 2 > /tmp/stdtest.log  testBattle-medium:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 150 --dumpInitRngs --automateAll --gameMode battle --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 100 --dumpInitRngs --automateAll --gameMode battle --difficulty 2 > /tmp/stdtest.log  testSafari-long:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode safari --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 500 --dumpInitRngs --automateAll --gameMode safari --difficulty 2 > /tmp/stdtest.log  testSafari-medium:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 250 --dumpInitRngs --automateAll --gameMode safari --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode safari --difficulty 2 > /tmp/stdtest.log  testPvP-long:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode PvP > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 60 --dumpInitRngs --automateAll --gameMode PvP > /tmp/stdtest.log  testPvP-medium:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode PvP > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 30 --dumpInitRngs --automateAll --gameMode PvP > /tmp/stdtest.log  testCoop-long:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode Coop --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 500 --dumpInitRngs --automateAll --gameMode Coop --difficulty 2 > /tmp/stdtest.log  testCoop-medium:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 250 --dumpInitRngs --automateAll --gameMode Coop --difficulty 1 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 300 --dumpInitRngs --automateAll --gameMode Coop --difficulty 2 > /tmp/stdtest.log  testDefense-long:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 400 --dumpInitRngs --automateAll --gameMode defense --difficulty 9 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 500 --dumpInitRngs --automateAll --gameMode defense --difficulty 8 > /tmp/stdtest.log  testDefense-medium:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 250 --dumpInitRngs --automateAll --gameMode defense --difficulty 9 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendStd --benchmark --stopAfter 300 --dumpInitRngs --automateAll --gameMode defense --difficulty 8 > /tmp/stdtest.log  test-short-new:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --noMore --savePrefix campaign --dumpInitRngs --automateAll --gameMode campaign --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --noMore --savePrefix skirmish --dumpInitRngs --automateAll --gameMode skirmish --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --noMore --savePrefix ambush --dumpInitRngs --automateAll --gameMode ambush --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --noMore --savePrefix battle --dumpInitRngs --automateAll --gameMode battle --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --noMore --savePrefix safari --dumpInitRngs --automateAll --gameMode safari --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --noMore --savePrefix PvP --dumpInitRngs --automateAll --gameMode PvP --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --noMore --savePrefix Coop --dumpInitRngs --automateAll --gameMode Coop --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --noMore --savePrefix defense --dumpInitRngs --automateAll --gameMode defense --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --savePrefix campaign --dumpInitRngs --automateAll --gameMode campaign --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --savePrefix skirmish --dumpInitRngs --automateAll --gameMode skirmish --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --savePrefix ambush --dumpInitRngs --automateAll --gameMode ambush --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --savePrefix battle --dumpInitRngs --automateAll --gameMode battle --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --savePrefix safari --dumpInitRngs --automateAll --gameMode safari --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --savePrefix PvP --dumpInitRngs --automateAll --gameMode PvP --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --savePrefix Coop --dumpInitRngs --automateAll --gameMode Coop --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --newGame --savePrefix defense --dumpInitRngs --automateAll --gameMode defense --frontendStd --stopAfter 2 > /tmp/stdtest.log  test-short-load:-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --noMore --savePrefix campaign --dumpInitRngs --automateAll --gameMode campaign --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --noMore --savePrefix skirmish --dumpInitRngs --automateAll --gameMode skirmish --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --noMore --savePrefix ambush --dumpInitRngs --automateAll --gameMode ambush --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --noMore --savePrefix battle --dumpInitRngs --automateAll --gameMode battle --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --noMore --savePrefix safari --dumpInitRngs --automateAll --gameMode safari --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --noMore --savePrefix PvP --dumpInitRngs --automateAll --gameMode PvP --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --noMore --savePrefix Coop --dumpInitRngs --automateAll --gameMode Coop --frontendStd --stopAfter 2 > /tmp/stdtest.log-	dist/build/LambdaHack/LambdaHack --dbgMsgSer --noMore --savePrefix defense --dumpInitRngs --automateAll --gameMode defense --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix campaign --dumpInitRngs --automateAll --gameMode campaign --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix skirmish --dumpInitRngs --automateAll --gameMode skirmish --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix ambush --dumpInitRngs --automateAll --gameMode ambush --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix battle --dumpInitRngs --automateAll --gameMode battle --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix safari --dumpInitRngs --automateAll --gameMode safari --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix PvP --dumpInitRngs --automateAll --gameMode PvP --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix Coop --dumpInitRngs --automateAll --gameMode Coop --frontendStd --stopAfter 2 > /tmp/stdtest.log+	dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix defense --dumpInitRngs --automateAll --gameMode defense --frontendStd --stopAfter 2 > /tmp/stdtest.log   build-binary:-	cabal configure --prefix=/usr/local+	cabal configure -frelease --prefix=/ 	cabal build exe:LambdaHack-	rm -rf /tmp/LambdaHack-	cabal copy --destdir=/tmp/LambdaHack-	tar -czf /tmp/LambdaHack.tar.gz -C /tmp --exclude=LambdaHack/usr/local/lib --exclude=LambdaHack/usr/local/share/doc LambdaHack+	rm -rf /tmp/LambdaHack_x_ubuntu-12.04-amd64.tar.gz+	rm -rf /tmp/LambdaHackTheGameInstall+	rm -rf /tmp/LambdaHackTheGame+	mkdir -p /tmp/LambdaHackTheGame/GameDefinition+	cabal copy --destdir=/tmp/LambdaHackTheGameInstall+	cp /tmp/LambdaHackTheGameInstall/bin/LambdaHack /tmp/LambdaHackTheGame+	cp GameDefinition/PLAYING.md /tmp/LambdaHackTheGame/GameDefinition+	cp GameDefinition/scores /tmp/LambdaHackTheGame/GameDefinition+	cp GameDefinition/config.ui.default /tmp/LambdaHackTheGame/GameDefinition+	cp CHANGELOG.md /tmp/LambdaHackTheGame+	cp CREDITS /tmp/LambdaHackTheGame+	cp LICENSE /tmp/LambdaHackTheGame+	cp README.md /tmp/LambdaHackTheGame+	tar -czf /tmp/LambdaHack_x_ubuntu-12.04-amd64.tar.gz -C /tmp LambdaHackTheGame++build-binary-i386:+	cabal configure -frelease --prefix=/ --ghc-option="-optc-m32" --ghc-option="-opta-m32" --ghc-option="-optl-m32" --ld-option="-melf_i386"+	cabal build exe:LambdaHack+	rm -rf /tmp/LambdaHack_x_ubuntu-12.04-i386.tar.gz+	rm -rf /tmp/LambdaHackTheGameInstall+	rm -rf /tmp/LambdaHackTheGame+	mkdir -p /tmp/LambdaHackTheGame/GameDefinition+	cabal copy --destdir=/tmp/LambdaHackTheGameInstall+	cp /tmp/LambdaHackTheGameInstall/bin/LambdaHack /tmp/LambdaHackTheGame+	cp GameDefinition/PLAYING.md /tmp/LambdaHackTheGame/GameDefinition+	cp GameDefinition/scores /tmp/LambdaHackTheGame/GameDefinition+	cp GameDefinition/config.ui.default /tmp/LambdaHackTheGame/GameDefinition+	cp CHANGELOG.md /tmp/LambdaHackTheGame+	cp CREDITS /tmp/LambdaHackTheGame+	cp LICENSE /tmp/LambdaHackTheGame+	cp README.md /tmp/LambdaHackTheGame+	tar -czf /tmp/LambdaHack_x_ubuntu-12.04-i386.tar.gz -C /tmp LambdaHackTheGame++# TODO: figure out, whey this must be so different from Linux+build-binary-windows-i386:+	wine cabal configure -frelease+	wine cabal build exe:LambdaHack+	rm -rf /tmp/LambdaHack_x_windows-i386.zip+	rm -rf /tmp/LambdaHackTheGameInstall+	rm -rf /tmp/LambdaHackTheGame+	mkdir -p /tmp/LambdaHackTheGame/GameDefinition+	wine cabal copy --destdir=Z:/tmp/LambdaHackTheGameInstall+	cp /tmp/LambdaHackTheGameInstall/users/mikolaj/Application\ Data/cabal/bin/LambdaHack.exe /tmp/LambdaHackTheGame+	cp GameDefinition/PLAYING.md /tmp/LambdaHackTheGame/GameDefinition+	cp GameDefinition/scores /tmp/LambdaHackTheGame/GameDefinition+	cp GameDefinition/config.ui.default /tmp/LambdaHackTheGame/GameDefinition+	cp CHANGELOG.md /tmp/LambdaHackTheGame+	cp CREDITS /tmp/LambdaHackTheGame+	cp LICENSE /tmp/LambdaHackTheGame+	cp README.md /tmp/LambdaHackTheGame+	cp /home/mikolaj/.wine/drive_c/users/mikolaj/gtk/bin/zlib1.dll /tmp/LambdaHackTheGame+	wine Z:/home/mikolaj/.local/share/wineprefixes/7zip/drive_c/Program\ Files/7-Zip/7z.exe a -ssc -sfx Z:/tmp/LambdaHack_x_windows-i386.exe Z:/tmp/LambdaHackTheGame   # The rest of the makefile is unmaintained at the moment.
README.md view
@@ -1,13 +1,11 @@ LambdaHack [![Build Status](https://travis-ci.org/LambdaHack/LambdaHack.svg?branch=master)](https://travis-ci.org/LambdaHack/LambdaHack)[![Build Status](https://drone.io/github.com/LambdaHack/LambdaHack/status.png)](https://drone.io/github.com/LambdaHack/LambdaHack/latest) ========== -LambdaHack is a [Haskell] [1] game engine-library for [roguelike] [2] games of arbitrary theme, size and complexity,-packaged together with a small example dungeon crawler. When completed,-the engine will let you specify content to be procedurally generated,-define the AI behaviour on top of the generic content-independent rules-and compile a ready-to-play game binary, using either the supplied-or a custom-made main loop. Several frontends are available+LambdaHack is a [Haskell] [1] game engine library for [roguelike] [2]+games of arbitrary theme, size and complexity. You specify the content+to be procedurally generated, including game rules and AI behaviour.+The library lets you compile a ready-to-play game binary, using either+the supplied or a custom-made main loop. Several frontends are available (GTK is the default) and many other generic engine components are easily overridden, but the fundamental source of flexibility lies in the strict and type-safe separation of code and content and of clients@@ -20,36 +18,84 @@  ![gameplay screenshot](GameDefinition/screenshot.png?raw=true) -The engine and the example game are bundled together in a single [Hackage] [3]-package released under the permissive `BSD3` license. You are welcome-to create your own games by forking and modifying the single package,-but please consider eventually splitting your changes into a separate-content-only package that depends on the upstream engine library. This will-help us exchange ideas and share improvements to the common codebase.-Alternatively, you can already start the development in separation-by cloning and rewriting [Allure of the Stars] [10] or any other pure-game content package and mix and merge with the example LambdaHack game-rules at will. Note that the LambdaHack sample game derives from the-[Hack/Nethack visual and narrative tradition] [9], while Allure of the Stars-uses the more free-form Moria/Angband style (it also uses the `AGPL` license,-and `BSD3 + AGPL = AGPL`, so make sure you want to liberate your code-and content to such an extent).--Games known to use the LambdaHack library:+Other games known to use the LambdaHack library:  * [Allure of the Stars] [6], a near-future Sci-Fi game * [Space Privateers] [8], an adventure game set in far future +Note: the engine and the example game are bundled together in a single+[Hackage] [3] package released under the permissive `BSD3` license.+You are welcome to create your own games by forking and modifying+the single package, but please consider eventually splitting your changes+into a separate content-only package that depends on the upstream+engine library. This will help us exchange ideas and share improvements+to the common codebase. Alternatively, you can already start the development+in separation by cloning and rewriting [Allure of the Stars] [10]+or any other pure game content package and mix and merge with the example+LambdaHack game rules at will. Note that the LambdaHack sample game+derives from the [Hack/Nethack visual and narrative tradition] [9],+while Allure of the Stars uses the more free-form Moria/Angband style+(it also uses the `AGPL` license, and `BSD3 + AGPL = AGPL`,+so make sure you want to liberate your code and content to such an extent). -Compilation and installation----------------------------- +Installation from binary archives+---------------------------------++Pre-compiled game binaries for some platforms are available through+the [release page] [11] and from the [Nix Packages Collection] [12].+To manually install a binary archive, make sure you have the GTK+libraries suite on your system, unpack the LambdaHack archive+and run the executable in the unpacked directory.++On Windows, if you don't already have GTK installed (e.g., for the GIMP+picture editor) please download and run (with default settings)+the GTK installer from++http://sourceforge.net/projects/gtk-win/+++Screen and keyboard configuration+---------------------------------++The game UI can be configured via a config file.+The commented out version of this file with the default settings is in+[GameDefinition/config.ui.default](GameDefinition/config.ui.default).+When the game is run for the first time, the file is copied to the official+location, which is `~/.LambdaHack/config.ui.ini` on Linux and+`C:\Users\<username>\AppData\Roaming\LambdaHack\config.ui.ini`+(or `C:\Documents And Settings\user\Application Data\LambdaHack\config.ui.ini`+or something else altogether) on Windows.++Screen font can be changed and enlarged by uncommenting and editing+the config file at its official location or by right-clicking+on the game window.++If you use the numeric keypad, use the NumLock key on your keyboard+to toggle the game keyboard mode. With NumLock off, you walk with the numeric+keys and run with Shift (or Control) and the keys. When you turn NumLock on,+the reversed key setup enforces good playing habits by setting as the default+the run command (which automatically stops at threats, keeping you safe)+and requiring Shift for the error-prone step by step walking.++If you don't have a numeric keypad, you can use laptop keys (uk8o79jl)+or you can enable the Vi keys (aka roguelike keys) in the config file.+++Compilation from source+-----------------------+ The library is best compiled and installed via Cabal (already a part of your OS distribution, or available within [The Haskell Platform] [7]),-which also takes care of all the dependencies. The latest official-version of the library can be downloaded, compiled and installed-automatically by Cabal from [Hackage] [3] as follows+which also takes care of all the dependencies. You also need+the GTK libraries for your OS. On Linux, remember to install the -dev+versions as well. 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]. +The latest official version of the library can be downloaded,+compiled and installed automatically by Cabal from [Hackage] [3] as follows+     cabal install gtk2hs-buildtools     cabal install LambdaHack @@ -63,17 +109,14 @@ is the default gtk. To compile with one of the terminal frontends, use Cabal flags, e.g, -    cabal install gtk2hs-buildtools     cabal install -fvty -Some pre-compiled game binaries may be available from the [releases] [11] page. - Compatibility notes -------------------  The current code was tested with GHC 7.6 and 7.8,-but should also work with other GHC versions.+but should also work with other GHC versions, with minor modifications.  If you are using the terminal frontends, numerical keypad may not work correctly depending on versions of the libraries, terminfo and terminal@@ -88,11 +131,11 @@ ---------------------  The [Makefile](Makefile) contains many sample test commands.-All commands that use the screensaver game modes (AI vs. AI)- and the dumb `stdout` frontend are gathered in `make test`.+Many tests that use the screensaver game modes (AI vs. AI)+and the dumb `stdout` frontend are gathered in `make test`. Of these, travis runs `test-travis-*` on each push to the repo. Test commands with prefix `frontend` start AI vs. AI games-with the standard, user-friendly frontend.+with the standard, user-friendly gtk frontend.  Run `LambdaHack --help` to see a brief description of all debug options. Of these, `--sniffIn` and `--sniffOut` are very useful (though verbose@@ -136,5 +179,9 @@ [7]: http://www.haskell.org/platform [8]: https://github.com/tuturto/space-privateers [9]: https://github.com/LambdaHack/LambdaHack/wiki/Sample-dungeon-crawler+ [10]: https://github.com/AllureOfTheStars/Allure-[11]: https://github.com/LambdaHack/LambdaHack/releases+[11]: https://github.com/LambdaHack/LambdaHack/releases/latest+[12]: http://hydra.cryp.to/search?query=LambdaHack+[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
test/test.hs view
@@ -2,5 +2,5 @@  main :: IO () main = do-  tieKnot $ tail $ words "dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 6 --automateAll --gameMode campaign --difficulty 1 --setDungeonRng 42 --setMainRng 42"-  -- tieKnot $ tail $ words "dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noMore --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 6 --automateAll --gameMode battle --difficulty 1 --setDungeonRng 42 --setMainRng 42"+  tieKnot $ tail $ words "dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 6 --automateAll --gameMode campaign --setDungeonRng 42 --setMainRng 42"+  -- tieKnot $ tail $ words "dist/build/LambdaHack/LambdaHack --dbgMsgSer --savePrefix test --newGame --noDelay --noAnim --maxFps 100000 --frontendNull --benchmark --stopAfter 6 --automateAll --gameMode battle --setDungeonRng 42 --setMainRng 42"