packages feed

LambdaHack 0.4.101.0 → 0.4.101.1

raw patch · 31 files changed

+338/−243 lines, 31 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Game.LambdaHack.Common.ActorState: goesIntoEqp :: ItemFull -> Bool
+ Game.LambdaHack.Common.MonadStateRead: getEntryArena :: MonadStateRead m => Faction -> m LevelId
+ Game.LambdaHack.Common.Time: timeDeltaSubtract :: Delta Time -> Delta Time -> Delta Time
+ Game.LambdaHack.Server.PeriodicServer: swapTime :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> m ()
+ Game.LambdaHack.Server.PeriodicServer: udpateCalm :: (MonadAtomic m, MonadServer m) => ActorId -> Int64 -> m ()
- Game.LambdaHack.Atomic: UpdCover :: !Container -> !ItemId -> !(Id ItemKind) -> !ItemSeed -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdCover :: !Container -> !ItemId -> !(Id ItemKind) -> !ItemSeed -> !AbsDepth -> UpdAtomic
- Game.LambdaHack.Atomic: UpdCoverSeed :: !Container -> !ItemId -> !ItemSeed -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdCoverSeed :: !Container -> !ItemId -> !ItemSeed -> !AbsDepth -> UpdAtomic
- Game.LambdaHack.Atomic: UpdDiscover :: !Container -> !ItemId -> !(Id ItemKind) -> !ItemSeed -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdDiscover :: !Container -> !ItemId -> !(Id ItemKind) -> !ItemSeed -> !AbsDepth -> UpdAtomic
- Game.LambdaHack.Atomic: UpdDiscoverSeed :: !Container -> !ItemId -> !ItemSeed -> UpdAtomic
+ Game.LambdaHack.Atomic: UpdDiscoverSeed :: !Container -> !ItemId -> !ItemSeed -> !AbsDepth -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdCover :: !Container -> !ItemId -> !(Id ItemKind) -> !ItemSeed -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdCover :: !Container -> !ItemId -> !(Id ItemKind) -> !ItemSeed -> !AbsDepth -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdCoverSeed :: !Container -> !ItemId -> !ItemSeed -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdCoverSeed :: !Container -> !ItemId -> !ItemSeed -> !AbsDepth -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdDiscover :: !Container -> !ItemId -> !(Id ItemKind) -> !ItemSeed -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdDiscover :: !Container -> !ItemId -> !(Id ItemKind) -> !ItemSeed -> !AbsDepth -> UpdAtomic
- Game.LambdaHack.Atomic.CmdAtomic: UpdDiscoverSeed :: !Container -> !ItemId -> !ItemSeed -> UpdAtomic
+ Game.LambdaHack.Atomic.CmdAtomic: UpdDiscoverSeed :: !Container -> !ItemId -> !ItemSeed -> !AbsDepth -> UpdAtomic
- Game.LambdaHack.Client.UI.InventoryClient: getAnyItems :: MonadClientUI m => Part -> [CStore] -> [CStore] -> Bool -> Bool -> m (SlideOrCmd ([(ItemId, ItemFull)], ItemDialogMode))
+ Game.LambdaHack.Client.UI.InventoryClient: getAnyItems :: MonadClientUI m => m Suitability -> Text -> Text -> [CStore] -> [CStore] -> Bool -> Bool -> m (SlideOrCmd ([(ItemId, ItemFull)], ItemDialogMode))

Files

CHANGELOG.md view
@@ -12,7 +12,7 @@ - make braced actors immune to translocation effects - use mouse for movement, actor selection, aiming - don't run straight with selected actors, but go-to cross-hair with them-- speed up default frame rate, slot down projectiles visually+- speed up default frame rate, slow down projectiles visually - rework item manipulation UI - you can pick up many items at once and it costs only one turn - allow actors to apply and project from the shared stash
Game/LambdaHack/Atomic/CmdAtomic.hs view
@@ -97,12 +97,12 @@   -- Assorted.   | UpdTimeItem !ItemId !Container !ItemTimer !ItemTimer   | UpdAgeGame !(Delta Time) ![LevelId]-  | UpdDiscover !Container !ItemId !(Kind.Id ItemKind) !ItemSeed-  | UpdCover !Container !ItemId !(Kind.Id ItemKind) !ItemSeed+  | UpdDiscover !Container !ItemId !(Kind.Id ItemKind) !ItemSeed !AbsDepth+  | UpdCover !Container !ItemId !(Kind.Id ItemKind) !ItemSeed !AbsDepth   | UpdDiscoverKind !Container !ItemId !(Kind.Id ItemKind)   | UpdCoverKind !Container !ItemId !(Kind.Id ItemKind)-  | UpdDiscoverSeed !Container !ItemId !ItemSeed-  | UpdCoverSeed !Container !ItemId !ItemSeed+  | UpdDiscoverSeed !Container !ItemId !ItemSeed !AbsDepth+  | UpdCoverSeed !Container !ItemId !ItemSeed !AbsDepth   | UpdPerception !LevelId !Perception !Perception   | UpdRestart !FactionId !DiscoveryKind !FactionPers !State !DebugModeCli   | UpdRestartServer !State@@ -182,12 +182,12 @@   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 c iid ik seed -> Just $ UpdCover c iid ik seed-  UpdCover c iid ik seed -> Just $ UpdDiscover c iid ik seed+  UpdDiscover c iid ik seed ldepth -> Just $ UpdCover c iid ik seed ldepth+  UpdCover c iid ik seed ldepth -> Just $ UpdDiscover c iid ik seed ldepth   UpdDiscoverKind c iid ik -> Just $ UpdCoverKind c iid ik   UpdCoverKind c iid ik -> Just $ UpdDiscoverKind c iid ik-  UpdDiscoverSeed c iid seed -> Just $ UpdCoverSeed c iid seed-  UpdCoverSeed c iid seed -> Just $ UpdDiscoverSeed c iid seed+  UpdDiscoverSeed c iid seed ldepth -> Just $ UpdCoverSeed c iid seed ldepth+  UpdCoverSeed c iid seed ldepth -> Just $ UpdDiscoverSeed c iid seed ldepth   UpdPerception lid outPer inPer -> Just $ UpdPerception lid inPer outPer   UpdRestart{} -> Just cmd  -- here history ends; change direction   UpdRestartServer{} -> Just cmd  -- here history ends; change direction
Game/LambdaHack/Atomic/PosAtomicRead.hs view
@@ -141,12 +141,12 @@     return $! PosSmell lid ps   UpdTimeItem _ c _ _ -> singleContainer c   UpdAgeGame _ _ -> return PosAll-  UpdDiscover c _ _ _ -> singleContainer c-  UpdCover c _ _ _ -> singleContainer c+  UpdDiscover c _ _ _ _ -> singleContainer c+  UpdCover c _ _ _ _ -> singleContainer c   UpdDiscoverKind c _ _ -> singleContainer c   UpdCoverKind c _ _ -> singleContainer c-  UpdDiscoverSeed c _ _ -> singleContainer c-  UpdCoverSeed c _ _ -> singleContainer c+  UpdDiscoverSeed c _ _ _ -> singleContainer c+  UpdCoverSeed c _ _ _ -> singleContainer c   UpdPerception{} -> return PosNone   UpdRestart fid _ _ _ _ -> return $! PosFid fid   UpdRestartServer _ -> return PosSer@@ -302,7 +302,7 @@       -- Death of a party member does not need to be heard,       -- because it's seen.       | not $ fid == bfid body || bproj body -> return $ Just "shriek"-    UpdCreateItem{} -> return $ Just "clatter"+    UpdCreateItem _ _ _ (CActor _ CGround) -> return $ Just "clatter"     UpdAlterTile _ _ fromTile _ -> do       Kind.COps{cotile} <- getsState scops       if Tile.isDoor cotile fromTile
Game/LambdaHack/Client/AI/HandleAbilityClient.hs view
@@ -265,11 +265,10 @@       filterWeapon | onlyWeapon = filter isWeapon                    | otherwise = id       prepareOne (oldN, l4) ((_, (k, _)), (iid, itemFull)) =-        -- TODO: instead of pickup to eqp and then move to inv, pickup to inv         let n = oldN + k             (newN, toCStore)               | calmE && goesIntoSha itemFull = (oldN, CSha)-              | goesIntoInv itemFull || eqpOverfull b n = (oldN, CInv)+              | not (goesIntoEqp itemFull) || eqpOverfull b n = (oldN, CInv)               | otherwise = (n, CEqp)         in (newN, (iid, k, CGround, toCStore) : l4)       (_, prepared) = foldl' prepareOne (0, []) $ filterWeapon benItemL@@ -676,10 +675,15 @@   actorSk <- actorSkillsClient aid   b <- getsState $ getActorBody aid   localTime <- getsState $ getLocalTime (blid b)-  let skill = EM.findWithDefault 0 AbProject actorSk+  let skill = EM.findWithDefault 0 AbApply actorSk       q _ itemFull _ activeItems =-        either (const False) id-        $ permittedApply " " localTime skill itemFull b activeItems+        -- TODO: terrible hack to prevent the use of identified healing gems+        let freq = case itemDisco itemFull of+              Nothing -> []+              Just ItemDisco{itemKind} -> IK.ifreq itemKind+        in maybe True (<= 0) (lookup "gem" freq)+           && either (const False) id+                (permittedApply " " localTime skill itemFull b activeItems)   benList <- benAvailableItems aid q [CEqp, CInv, CGround]   organs <- mapM (getsState . getItemBody) $ EM.keys $ borgan b   let itemLegal itemFull = case applyGroup of
Game/LambdaHack/Client/AI/Preferences.hs view
@@ -156,7 +156,7 @@             isWeapon = isMeleeEqp itemFull             totalSum               | isWeapon && effSum < 0 = - effSum + selfSum-              | goesIntoInv itemFull = effSum+              | not $ goesIntoEqp itemFull = effSum               | mixedBlessing =                   0  -- significant mixed blessings out of AI control               | otherwise = selfSum  -- if the weapon heals the enemy, it
Game/LambdaHack/Client/HandleAtomicClient.hs view
@@ -93,7 +93,7 @@                                  || t /= Tile.hideAs cotile tClient)         newTs = filter notKnown ts     return $! if null newTs then [] else [UpdSpotTile lid newTs]-  UpdDiscover c iid _ seed -> do+  UpdDiscover c iid _ seed ldepth -> do     itemD <- getsState sitemD     case EM.lookup iid itemD of       Nothing -> return []@@ -104,9 +104,9 @@             discoEffect <- getsClient sdiscoEffect             if iid `EM.member` discoEffect               then return []-              else return [UpdDiscoverSeed c iid seed]+              else return [UpdDiscoverSeed c iid seed ldepth]           else return [cmd]-  UpdCover c iid ik _ -> do+  UpdCover c iid ik _ _ -> do     itemD <- getsState sitemD     case EM.lookup iid itemD of       Nothing -> return []@@ -137,7 +137,7 @@         if jkindIx item `EM.notMember` discoKind         then return []         else return [cmd]-  UpdDiscoverSeed _ iid _ -> do+  UpdDiscoverSeed _ iid _ _ -> do     itemD <- getsState sitemD     case EM.lookup iid itemD of       Nothing -> return []@@ -150,7 +150,7 @@           if iid `EM.member` discoEffect             then return []             else return [cmd]-  UpdCoverSeed _ iid _ -> do+  UpdCoverSeed _ iid _ _ -> do     itemD <- getsState sitemD     case EM.lookup iid itemD of       Nothing -> return []@@ -255,16 +255,16 @@       cli { stargetD = case (mtgt, mleader) of               (Just tgt, Just leader) -> EM.singleton leader tgt               _ -> EM.empty }-  UpdDiscover c iid ik seed -> do+  UpdDiscover c iid ik seed ldepth -> do     discoverKind c iid ik-    discoverSeed c iid seed-  UpdCover c iid ik seed -> do+    discoverSeed c iid seed ldepth+  UpdCover c iid ik seed _ldepth -> do     coverSeed c iid seed     coverKind c iid ik   UpdDiscoverKind c iid ik -> discoverKind c iid ik   UpdCoverKind c iid ik -> coverKind c iid ik-  UpdDiscoverSeed c iid seed -> discoverSeed c iid seed-  UpdCoverSeed c iid seed -> coverSeed c iid seed+  UpdDiscoverSeed c iid seed  ldepth -> discoverSeed c iid seed ldepth+  UpdCoverSeed c iid seed _ldepth -> coverSeed c iid seed   UpdPerception lid outPer inPer -> perception lid outPer inPer   UpdRestart side sdiscoKind sfper _ sdebugCli -> do     shistory <- getsClient shistory@@ -351,12 +351,11 @@   modifyClient $ \cli -> cli {sdiscoKind = EM.alter f (jkindIx item) (sdiscoKind cli)}  discoverSeed :: MonadClient m-             => Container -> ItemId -> ItemSeed -> m ()-discoverSeed c iid seed = do+             => Container -> ItemId -> ItemSeed -> AbsDepth -> m ()+discoverSeed c iid seed ldepth = do   Kind.COps{coitem=Kind.Ops{okind}} <- getsState scops   discoKind <- getsClient sdiscoKind   item <- getsState $ getItemBody iid-  Level{ldepth} <- getLevel (jlid item)   totalDepth <- getsState stotalDepth   case EM.lookup (jkindIx item) discoKind of     Nothing -> assert `failure` "kind not known"@@ -370,8 +369,8 @@  coverSeed :: MonadClient m           => Container -> ItemId -> ItemSeed -> m ()-coverSeed c iid ik = do-  let f Nothing = assert `failure` "already covered" `twith` (c, iid, ik)+coverSeed c iid seed = do+  let f Nothing = assert `failure` "already covered" `twith` (c, iid, seed)       f Just{} = Nothing  -- checking that old and new agree is too much work   modifyClient $ \cli -> cli {sdiscoEffect = EM.alter f iid (sdiscoEffect cli)} 
Game/LambdaHack/Client/Key.hs view
@@ -173,6 +173,9 @@                 ++ dirKeypadShiftKey                 ++ map Char dirKeypadShiftChar +dirRunShift :: [Key]+dirRunShift = dirRunControl+ dirAllKey :: Bool -> Bool -> [Key] dirAllKey configVi configLaptop =   dirMoveNoModifier configVi configLaptop@@ -199,6 +202,7 @@   in mapMove NoModifier (dirMoveNoModifier configVi configLaptop)      ++ mapRun NoModifier (dirRunNoModifier configVi configLaptop)      ++ mapRun Control dirRunControl+     ++ mapRun Shift dirRunShift  mkKM :: String -> KM mkKM s = let mkKey sk =
Game/LambdaHack/Client/UI/DisplayAtomicClient.hs view
@@ -59,7 +59,10 @@                        => Bool -> State -> StateClient -> UpdAtomic -> m () displayRespUpdAtomicUI verbose oldState oldStateClient cmd = case cmd of   -- Create/destroy actors and items.-  UpdCreateActor aid body _ -> createActorUI aid body verbose "appear"+  UpdCreateActor aid body _ -> do+    side <- getsClient sside+    let verb = "appear" <+> if bfid body == side then "" else "suddenly"+    createActorUI aid body verbose (MU.Text verb)   UpdDestroyActor aid body _ -> do     destroyActorUI aid body "die" "be destroyed" verbose     side <- getsClient sside@@ -147,7 +150,18 @@         when (null closeFoes) $ do  -- obvious where the feeling comes from           aidVerbMU aid "hear something"           msgDuplicateScrap-  UpdFidImpressedActor{} -> return ()+  UpdFidImpressedActor aid _fidOld fidNew -> do+    b <- getsState $ getActorBody aid+    actorVerbMU aid b $+      if fidNew == bfid b then+        "get calmed and refocused"+-- TODO: only show for liquids; for others say 'flash', etc.+--              "get refocused by the fragrant moisture"+      else if fidNew == bfidOriginal b then+        "remember forgone allegiance suddenly"+      else+        "experience anxiety that weakens resolve and erodes loyalty"+-- TODO     "inhale the sweet smell that weakens resolve and erodes loyalty"   UpdTrajectory{} -> return ()   UpdColorActor{} -> return ()   -- Change faction attributes.@@ -218,11 +232,11 @@   -- Assorted.   UpdTimeItem{} -> return ()   UpdAgeGame{} -> return ()-  UpdDiscover c iid _ _ -> discover c oldStateClient iid+  UpdDiscover c iid _ _ _ -> discover c oldStateClient iid   UpdCover{} -> return ()  -- don't spam when doing undo   UpdDiscoverKind c iid _ -> discover c oldStateClient iid   UpdCoverKind{} -> return ()  -- don't spam when doing undo-  UpdDiscoverSeed c iid _ -> discover c oldStateClient iid+  UpdDiscoverSeed c iid _ _ -> discover c oldStateClient iid   UpdCoverSeed{} -> return ()  -- don't spam when doing undo   UpdPerception{} -> return ()   UpdRestart fid _ _ _ _ -> do@@ -740,16 +754,7 @@             msgAdd $ makeSentence               [MU.SubjectVerbSg subject verb, MU.Text fidSourceName, "control"]           stopPlayBack-        IK.Impress ->-          actorVerbMU aid b-          $ if bfidImpressed b /= bfid b-            then-              "get calmed and refocused"--- TODO: only show for liquids; for others say "='flash', etc.---              "get refocused by the fragrant moisture"-            else-              "experience anxiety that weakens resolve and erodes loyalty"--- TODO:        "inhale the sweet smell that weakens resolve and erodes loyalty"+        IK.Impress -> return ()         IK.CallFriend{} -> return ()         IK.Summon{} -> return ()         IK.Ascend k | k > 0 -> actorVerbMU aid b "find a way upstairs"@@ -843,7 +848,8 @@             displayDelay           let ageDisp = EM.insert arena localTime           modifyClient $ \cli -> cli {sdisplayed = ageDisp $ sdisplayed cli}-          displayPush ""+          when (not (bproj b)) $  -- projectiles display animations instead+            displayPush ""  setLastSlot :: MonadClientUI m => ActorId -> ItemId -> CStore -> m () setLastSlot aid iid cstore = do
Game/LambdaHack/Client/UI/HandleHumanGlobalClient.hs view
@@ -23,6 +23,7 @@ import Data.List import Data.Maybe import Data.Monoid+import Data.Text (Text) import qualified NLP.Miniutter.English as MU  import Game.LambdaHack.Client.BfsClient@@ -261,7 +262,7 @@  -- * MoveItem -moveItemHuman :: MonadClientUI m+moveItemHuman :: forall m. MonadClientUI m               => [CStore] -> CStore -> Maybe MU.Part -> Bool               -> m (SlideOrCmd (RequestTimed 'AbMoveItem)) moveItemHuman cLegalRaw destCStore mverb auto = do@@ -295,7 +296,7 @@         then case destCStore of           CEqp | calmE && goesIntoSha itemFull ->             retRec CSha-          CEqp | goesIntoInv itemFull ->+          CEqp | not $ goesIntoEqp itemFull ->             retRec CInv           CEqp | eqpOverfull b (oldN + k) -> do             msgAdd $ "Warning:" <+> showReqFailure EqpOverfull <> "."@@ -305,9 +306,17 @@         else case destCStore of           CEqp | eqpOverfull b (oldN + k) -> failSer EqpOverfull           _ -> retRec destCStore-  ggi <- if auto-         then getAnyItems verb cLegalRaw cLegal False False-         else getAnyItems verb cLegalRaw cLegal True True+      prompt = makePhrase ["What to", verb]+      promptEqp = makePhrase ["What consumable to", verb]+      p :: CStore -> (Text, m Suitability)+      p cstore = if cstore `elem` [CEqp, CSha] && cLegalRaw /= [CGround]+                 then (promptEqp, return $ SuitsSomething goesIntoEqp)+                 else (prompt, return SuitsEverything)+      (promptGeneric, psuit) = p destCStore+  ggi <-+    if auto+    then getAnyItems psuit prompt promptGeneric cLegalRaw cLegal False False+    else getAnyItems psuit prompt promptGeneric cLegalRaw cLegal True True   case ggi of     Right (l, MStore fromCStore) -> do       leader2 <- getLeaderUI@@ -466,7 +475,7 @@   leader <- getLeaderUI   b <- getsState $ getActorBody leader   actorSk <- actorSkillsClient leader-  let skill = EM.findWithDefault 0 AbProject actorSk+  let skill = EM.findWithDefault 0 AbApply actorSk   activeItems <- activeItemsClient leader   localTime <- getsState $ getLocalTime (blid b)   let cLegal = [CGround, CInv, CEqp, CSha]@@ -674,7 +683,7 @@     b <- getsState $ getActorBody leader     cursorPos <- cursorToPos     case cursorPos of-      Nothing -> failWith "no leader"+      Nothing -> failWith "crosshair position invalid"       Just c | c == bpos b ->         if initialStep         then return $ Right $ RequestAnyAbility ReqWait
Game/LambdaHack/Client/UI/InventoryClient.hs view
@@ -109,19 +109,21 @@ -- and let him specify the number of items. -- Used, e.g., for picking up and inventory manipulation. getAnyItems :: MonadClientUI m-            => MU.Part   -- ^ the verb describing the action+            => m Suitability+                         -- ^ which items to consider suitable+            -> Text      -- ^ specific prompt for only suitable items+            -> Text      -- ^ generic prompt             -> [CStore]  -- ^ initial legal modes             -> [CStore]  -- ^ legal modes after Calm taken into account             -> Bool      -- ^ whether to ask, when the only item                          --   in the starting mode is suitable             -> Bool      -- ^ whether to ask for the number of items             -> m (SlideOrCmd ([(ItemId, ItemFull)], ItemDialogMode))-getAnyItems verb cLegalRaw cLegalAfterCalm askWhenLone askNumber = do-  let prompt _ _ cCur =-        makePhrase ["What to", verb, MU.Text $ ppItemDialogModeFrom cCur]-  soc <- getFull (return SuitsEverything)-                 prompt prompt False-                 cLegalRaw cLegalAfterCalm+getAnyItems psuit prompt promptGeneric cLegalRaw cLegalAfterCalm askWhenLone askNumber = do+  soc <- getFull psuit+                 (\_ _ cCur -> prompt <+> ppItemDialogModeFrom cCur)+                 (\_ _ cCur -> promptGeneric <+> ppItemDialogModeFrom cCur)+                 False cLegalRaw cLegalAfterCalm                  askWhenLone True ISuitable   case soc of     Left _ -> return soc
Game/LambdaHack/Client/UI/KeyBindings.hs view
@@ -75,7 +75,7 @@       , "                /|\\            /|\\            /|\\"       , "               1 2 3          j k l          b j n"       , ""-      , "In aiming mode the same keys (or mouse) move the crosshair (the white box)."+      , "In aiming mode (KEYPAD_* or \\) the same keys (or mouse) move the crosshair."       , "Press 'KEYPAD_5' (or 'i' or '.') to wait, bracing for blows, which reduces"       , "any damage taken and makes it impossible for foes to displace you."       , "You displace enemies or friends by bumping into them with SHIFT (or CTRL)."@@ -108,7 +108,7 @@       , "Press PGUP to return to previous pages or ESC to see the map again."       ]     pickLeaderDescription =-      [ fmt 16 "0, 1, ... 6" "pick a particular actor as the new leader"+      [ fmt 16 "0, 1 ... 6" "pick a particular actor as the new leader"       ]     casualDescription = "Minimal cheat sheet for casual play"     fmt n k h = T.justifyRight 72 ' '@@ -125,7 +125,7 @@                          | (from, (_, cats, Macro _ [to])) <- bcmdList                          , K.mkKM to == k                          , any (`notElem` [CmdDebug, CmdInternal]) cats ]-    disp k = T.concat $ intersperse " and " $ map K.showKM $ coImage k+    disp k = T.concat $ intersperse " or " $ map K.showKM $ coImage k     keysN n cat = [ fmt n (disp k) h                   | (k, (h, cats, _)) <- bcmdList, cat `elem` cats, h /= "" ]     -- TODO: measure the longest key sequence and set the caption automatically
Game/LambdaHack/Client/UI/MonadClientUI.hs view
@@ -295,13 +295,7 @@       fact <- getsState $ (EM.! side) . sfactionD       case gquit fact of         Just Status{stDepth} -> return $! toEnum stDepth-        Nothing -> do-          dungeon <- getsState sdungeon-          let (minD, maxD) =-                case (EM.minViewWithKey dungeon, EM.maxViewWithKey dungeon) of-                  (Just ((s, _), _), Just ((e, _), _)) -> (s, e)-                  _ -> assert `failure` "empty dungeon" `twith` dungeon-          return $! max minD $ min maxD $ toEnum $ fentryLevel $ gplayer fact+        Nothing -> getEntryArena fact  viewedLevel :: MonadClientUI m => m LevelId viewedLevel = do@@ -319,12 +313,11 @@       b <- getsState $ getActorBody aid       maxHP <- sumOrganEqpClient IK.EqpSlotAddMaxHP aid       let percentage = 100 * bhp b `div` xM (max 5 maxHP)-          stars | percentage < 20  = "[_____]"-                | percentage < 40  = "[*____]"-                | percentage < 60  = "[**___]"-                | percentage < 80  = "[***__]"-                | percentage < 100 = "[****_]"-                | otherwise        = "[*****]"+          stars | percentage < 20  = "[____]"+                | percentage < 40  = "[*___]"+                | percentage < 60  = "[**__]"+                | percentage < 80  = "[***_]"+                | otherwise        = "[****]"           hpIndicator = if bfid b == side then Nothing else Just stars       return (bname b, hpIndicator)     Just (TEnemyPos _ lid p _) -> do
Game/LambdaHack/Common/Actor.hs view
@@ -184,7 +184,7 @@   in bhp b <= oneM || 5 * bhp b < xM maxHP && bhp b <= xM 10  hpHuge :: Actor -> Bool-hpHuge b = bhp b > xM 30+hpHuge b = bhp b > xM 40  calmEnough :: Actor -> [ItemFull] -> Bool calmEnough b activeItems =
Game/LambdaHack/Common/ActorState.hs view
@@ -12,8 +12,8 @@   , nearbyFreePoints, whereTo, getCarriedAssocs, getCarriedIidCStore   , posToActors, posToActor, getItemBody, memActor, getActorBody   , tryFindHeroK, getLocalTime, itemPrice, regenCalmDelta-  , actorInAmbient, actorSkills, dispEnemy-  , fullAssocs, itemToFull, goesIntoInv, goesIntoSha, eqpOverfull+  , actorInAmbient, actorSkills, dispEnemy, fullAssocs, itemToFull+  , goesIntoEqp, goesIntoInv, goesIntoSha, eqpOverfull   , storeFromC, lidFromC, aidFromC, hasCharge   , strongestMelee, isMelee, isMeleeEqp   ) where@@ -380,13 +380,17 @@ -- Non-durable item that hurts doesn't go into equipment by default, -- but if it is in equipment or among organs, it's used for melee -- nevertheless, e.g., thorns.-goesIntoInv :: ItemFull -> Bool-goesIntoInv itemFull = not (isJust (strengthEqpSlot $ itemBase itemFull))+goesIntoEqp :: ItemFull -> Bool+goesIntoEqp itemFull = isJust (strengthEqpSlot $ itemBase itemFull) -- TODO: not needed if EqpSlotWeapon stays         || isMeleeEqp itemFull) +goesIntoInv :: ItemFull -> Bool+goesIntoInv itemFull = IK.Precious `notElem` jfeature (itemBase itemFull)+                       && not (goesIntoEqp itemFull)+ goesIntoSha :: ItemFull -> Bool goesIntoSha itemFull = IK.Precious `elem` jfeature (itemBase itemFull)-                       && goesIntoInv itemFull+                       && not (goesIntoEqp itemFull)  eqpOverfull :: Actor -> Int -> Bool eqpOverfull b n = let size = sum $ map fst $ EM.elems $ beqp b
Game/LambdaHack/Common/MonadStateRead.hs view
@@ -2,9 +2,11 @@ -- player actions. Has no access to the the main action type. module Game.LambdaHack.Common.MonadStateRead   ( MonadStateRead(..)-  , getLevel, nUI, posOfAid, factionCanEscape, factionLoots, getGameMode+  , getLevel, nUI, posOfAid, factionCanEscape, factionLoots+  , getGameMode, getEntryArena   ) where +import Control.Exception.Assert.Sugar import qualified Data.EnumMap.Strict as EM  import Game.LambdaHack.Common.Actor@@ -55,3 +57,12 @@   Kind.COps{comode=Kind.Ops{okind}} <- getsState scops   t <- getsState sgameModeId   return $! okind t++getEntryArena :: MonadStateRead m => Faction -> m LevelId+getEntryArena fact = do+  dungeon <- getsState sdungeon+  let (minD, maxD) =+        case (EM.minViewWithKey dungeon, EM.maxViewWithKey dungeon) of+          (Just ((s, _), _), Just ((e, _), _)) -> (s, e)+          _ -> assert `failure` "empty dungeon" `twith` dungeon+  return $! max minD $ min maxD $ toEnum $ fentryLevel $ gplayer fact
Game/LambdaHack/Common/Time.hs view
@@ -3,7 +3,8 @@ module Game.LambdaHack.Common.Time   ( Time, timeZero, timeClip, timeTurn, timeEpsilon   , absoluteTimeAdd, absoluteTimeNegate, timeFit, timeFitUp-  , Delta(..), timeShift, timeDeltaToFrom, timeDeltaReverse, timeDeltaScale+  , Delta(..), timeShift, timeDeltaToFrom+  , timeDeltaSubtract, timeDeltaReverse, timeDeltaScale   , timeDeltaToDigit, ticksPerMeter   , Speed, toSpeed, fromSpeed, speedZero, speedNormal   , speedScale, timeDeltaDiv, speedAdd, speedNegate@@ -99,6 +100,11 @@ -- The arguments are in the same order as in the underlying scalar subtraction. timeDeltaToFrom :: Time -> Time -> Delta Time timeDeltaToFrom (Time t1) (Time t2) = Delta $ Time (t1 - t2)++-- | Time time vector between the second and the first absolute times.+-- The arguments are in the same order as in the underlying scalar subtraction.+timeDeltaSubtract :: Delta Time -> Delta Time -> Delta Time+timeDeltaSubtract (Delta (Time t1)) (Delta (Time t2)) = Delta $ Time (t1 - t2)  -- | Scale the time vector by an @Int@ scalar value. timeDeltaScale :: Delta Time -> Int -> Delta Time
Game/LambdaHack/Server/CommonServer.hs view
@@ -3,8 +3,8 @@ module Game.LambdaHack.Server.CommonServer   ( execFailure, resetFidPerception, resetLitInDungeon, getPerFid   , revealItems, moveStores, deduceQuits, deduceKilled, electLeader-  , addActor, addActorIid, projectFail, pickWeaponServer, sumOrganEqpServer-  , actorSkillsServer+  , addActor, addActorIid, projectFail+  , pickWeaponServer, sumOrganEqpServer, actorSkillsServer   ) where  import Control.Applicative@@ -101,11 +101,10 @@       per = EM.findWithDefault failLvl 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 (ActorId, Actor) -> m () revealItems mfid mbody = do+  let !_A = assert (maybe True (not . bproj . snd) mbody) ()   itemToF <- itemToFullServer   dungeon <- getsState sdungeon   let discover aid store iid k =@@ -114,12 +113,14 @@         in case itemDisco itemFull of           Just ItemDisco{itemKindId} -> do             seed <- getsServer $ (EM.! iid) . sitemSeedD-            execUpdAtomic $ UpdDiscover c iid itemKindId seed+            Level{ldepth} <- getLevel $ jlid $ itemBase itemFull+            execUpdAtomic $ UpdDiscover c iid itemKindId seed ldepth           _ -> assert `failure` (mfid, mbody, c, iid, itemFull)       f aid = do         b <- getsState $ getActorBody aid         let ourSide = maybe True (== bfid b) mfid-        when ourSide $+        -- Don't ID projectiles, because client may not see them.+        when (not (bproj b) && ourSide) $           -- CSha is IDed for each actor of each faction, which is OK,           -- even though it may introduce a slight lag.           -- AI clients being sent this is a bigger waste anyway.@@ -401,12 +402,14 @@   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+      boostFact = not bproj+                  && if diffBonusCoeff > 0+                     then hasUIorEscapes factMine+                          || any hasUIorEscapes+                                 (filter (`isAllied` bfid) $ EM.elems factionD)+                     else any hasUIorEscapes+                              (filter (`isAtWar` bfid) $ EM.elems factionD)+      diffHP | boostFact = hp * 2 ^ abs diffBonusCoeff              | otherwise = hp       bonusHP = fromIntegral $ (diffHP - hp) `divUp` oneM       healthOrgans = [(Just bonusHP, ("bonus HP", COrgan)) | bonusHP /= 0]@@ -433,9 +436,10 @@                   Just ItemDisco{itemAE=                   Just ItemAspectEffect{jeffects=_:_}}}, _)) ->         return ()  -- discover by use-      Just (iid, _) -> do+      Just (iid, (ItemFull{itemBase=itemBase2}, _)) -> do         seed <- getsServer $ (EM.! iid) . sitemSeedD-        execUpdAtomic $ UpdDiscoverSeed container iid seed+        Level{ldepth} <- getLevel $ jlid itemBase2+        execUpdAtomic $ UpdDiscoverSeed container iid seed ldepth   return $ Just aid  -- Server has to pick a random weapon or it could leak item discovery
Game/LambdaHack/Server/HandleEffectServer.hs view
@@ -168,7 +168,9 @@   case EM.lookup (jkindIx item) discoKind of     Just itemKindId -> do       seed <- getsServer $ (EM.! iid) . sitemSeedD-      execUpdAtomic $ UpdDiscover c iid itemKindId seed+      Level{ldepth} <- getLevel $ jlid item+      -- TODO: we leak first depth the item was created at on the server+      execUpdAtomic $ UpdDiscover c iid itemKindId seed ldepth       itemEffect source target iid recharged periodic effs     _ -> assert `failure` (source, target, iid, item) @@ -212,7 +214,7 @@     IK.RefillCalm p -> effectRefillCalm False execSfx p source target     IK.OverfillCalm p -> effectRefillCalm True execSfx p source target     IK.Dominate -> effectDominate recursiveCall source target-    IK.Impress -> effectImpress execSfx source target+    IK.Impress -> effectImpress source target     IK.CallFriend p -> effectCallFriend p source target     IK.Summon freqs p -> effectSummon freqs p source target     IK.Ascend p -> effectAscend recursiveCall execSfx p source target@@ -288,8 +290,7 @@       block = braced tb   return $! itemBonus - if block then 50 else 0 -halveCalm :: (MonadAtomic m, MonadServer m)-          => ActorId -> m ()+halveCalm :: (MonadAtomic m, MonadServer m) => ActorId -> m () halveCalm target = do   tb <- getsState $ getActorBody target   activeItems <- activeItemsServer target@@ -301,7 +302,7 @@   -- 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+  udpateCalm target deltaCalm  -- ** Burn @@ -409,7 +410,7 @@     then return False     else do       execSfx-      execUpdAtomic $ UpdRefillCalm target deltaCalm+      udpateCalm target deltaCalm       return True  -- ** Dominate@@ -431,14 +432,13 @@ -- ** Impress  effectImpress :: (MonadAtomic m, MonadServer m)-              => m () -> ActorId -> ActorId -> m Bool-effectImpress execSfx source target = do+              => ActorId -> ActorId -> m Bool+effectImpress source target = do   sb <- getsState $ getActorBody source   tb <- getsState $ getActorBody target   if bfidImpressed tb == bfid sb || bproj tb then     return False   else do-    execSfx     execUpdAtomic $ UpdFidImpressedActor target (bfidImpressed tb) (bfid sb)     return True @@ -495,7 +495,7 @@     return False   else do     let deltaCalm = - xM 10-    unless (bproj tb) $ execUpdAtomic $ UpdRefillCalm target deltaCalm+    unless (bproj tb) $ udpateCalm target deltaCalm     let validTile t = not $ Tile.hasFeature cotile TK.NoActor t     ps <- getsState $ nearbyFreePoints validTile (bpos tb) (blid tb)     localTime <- getsState $ getLocalTime (blid tb)@@ -927,7 +927,9 @@ identifyIid execSfx iid c itemKindId = do   execSfx   seed <- getsServer $ (EM.! iid) . sitemSeedD-  execUpdAtomic $ UpdDiscover c iid itemKindId seed+  item <- getsState $ getItemBody iid+  Level{ldepth} <- getLevel $ jlid item+  execUpdAtomic $ UpdDiscover c iid itemKindId seed ldepth  -- ** SendFlying 
Game/LambdaHack/Server/HandleRequestServer.hs view
@@ -363,7 +363,9 @@   else do     when (fromCStore == CGround) $ do       seed <- getsServer $ (EM.! iid) . sitemSeedD-      execUpdAtomic $ UpdDiscoverSeed fromC iid seed+      item <- getsState $ getItemBody iid+      Level{ldepth} <- getLevel $ jlid item+      execUpdAtomic $ UpdDiscoverSeed fromC iid seed ldepth     upds <- generalMoveItem iid k fromC toC     mapM_ execUpdAtomic upds     -- Reset timeout for equipped periodic items.@@ -439,7 +441,7 @@       activeItems <- activeItemsServer aid       actorSk <- actorSkillsServer aid       localTime <- getsState $ getLocalTime (blid b)-      let skill = EM.findWithDefault 0 Ability.AbProject actorSk+      let skill = EM.findWithDefault 0 Ability.AbApply actorSk           itemFull = itemToF iid kit           legal = permittedApply " " localTime skill itemFull b activeItems       case legal of
Game/LambdaHack/Server/LoopServer.hs view
@@ -3,6 +3,7 @@ -- moves turn by turn. module Game.LambdaHack.Server.LoopServer (loopSer) where +import Control.Applicative import Control.Arrow ((&&&)) import Control.Exception.Assert.Sugar import Control.Monad@@ -100,14 +101,23 @@   -- Run the leader and other actors moves. Eventually advance the time   -- and repeat.   let loop = do+        -- Note that if a faction enters dungeon on a level with no spawners,+        -- the faction won't cause spawning on its active arena+        -- as long as it has no leader. This may cause regeneration items+        -- of its opponents become overpowered and lead to micromanagement+        -- (make sure to kill all actors of the faction, go to a no-spawn+        -- level and heal fully with no risk nor cost).         let factionArena fact =               case gleader fact of-               -- Even spawners and horrors need an active arena-               -- for their leader, or they start clogging stairs.+               -- Even spawners need an active arena for their leader,+               -- or they start clogging stairs.                Just (leader, _) -> do                   b <- getsState $ getActorBody leader                   return $ Just $ blid b-               Nothing -> return Nothing+               Nothing -> if fleaderMode (gplayer fact) == LeaderNull+                             || EM.null (gvictims fact)+                          then return Nothing+                          else Just <$> getEntryArena fact         factionD <- getsState sfactionD         marenas <- mapM factionArena $ EM.elems factionD         let arenas = ES.toList $ ES.fromList $ catMaybes marenas@@ -275,15 +285,29 @@             hasWait _ = False         maybe (return ()) (setBWait (hasWait cmdS)) aidNew         -- Advance time once, after the leader switched perhaps many times.-        -- 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.+        -- The following was true before, but now we badly want to avoid double+        -- moves against the UI player (especially deadly when using stairs),+        -- so this is no longer true:+          -- 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.+        -- So now we exchange times of the old and new leader.+        -- This permits an abuse, because a slow tank can be moved fast+        -- by alternating between it and many fast actors (until all of them+        -- get slowed down by this and none remain). But at least the sum+        -- of all times of a faction is conserved. And we avoid double moves+        -- against the UI player caused by his leader changes. There may still+        -- happen double moves caused by AI leader changes, but that's rare.+        -- The flip side is the possibility of multi-moves of the UI player+        -- as in the case of the tank, but at least the sum of times is OK.         -- Warning: when the action is performed on the server,         -- the time of the actor is different than when client prepared that         -- action, so any client checks involving time should discount this.+        when (aidIsLeader && Just aid /= aidNew) $+          maybe (return ()) (swapTime aid) aidNew         maybe (return ()) advanceTime aidNew         action         maybe (return ()) managePerTurn aidNew
Game/LambdaHack/Server/PeriodicServer.hs view
@@ -2,13 +2,14 @@ -- and related operations. module Game.LambdaHack.Server.PeriodicServer   ( spawnMonster, addAnyActor, dominateFidSfx-  , advanceTime, managePerTurn, leadLevelSwitch+  , advanceTime, swapTime, managePerTurn, leadLevelSwitch, udpateCalm   ) where  import Control.Exception.Assert.Sugar import Control.Monad import qualified Data.EnumMap.Strict as EM import qualified Data.EnumSet as ES+import Data.Int (Int64) import Data.List import Data.Maybe @@ -91,7 +92,7 @@       pers <- getsServer sper       let allPers = ES.unions $ map (totalVisible . (EM.! lid))                     $ EM.elems $ EM.delete fid pers  -- expensive :(-          mobile = any (`elem` freqNames) ["civilian", "horror"]+          mobile = any (`elem` freqNames) ["mobile", "horror"]       pos <- case mpos of         Just pos -> return pos         Nothing -> do@@ -110,16 +111,14 @@              mobile lid Level{ltile, lxsize, lysize} fact s = do   let inhabitants = actorRegularList (isAtWar fact) lid s       as = actorList (const True) lid s-      isLit = Tile.isLit cotile       distantSo df p _ =         all (\b -> df $ chessDist (bpos b) p) inhabitants       middlePos = Point (lxsize `div` 2) (lysize `div` 2)       distantMiddle d p _ = chessDist p middlePos < d       condList | mobile =-        [ \_ t -> not (isLit t) -- no such tiles on some maps-        , distantSo (<= 10)  -- try to harass enemies-        , distantSo (>= 5)   -- but leave room for yourself for ranged combat-        , distantSo (<= 20)  -- but applying pressure is more important+        [ distantSo (<= 10)  -- try hard to harass enemies+        , distantSo (<= 15)+        , distantSo (<= 20)         ]                | otherwise =         [ distantMiddle 5@@ -135,8 +134,9 @@               && not (Tile.hasFeature cotile TK.NoActor t)               && unoccupied as p)     (condList-     ++ [ \p _ -> not (p `ES.member` visible)  -- surprise and believability-        , distantSo (>= 3)  -- otherwise fast actor approach and hit in one turn+     ++ [ distantSo (> 5)  -- otherwise actors in dark rooms are swarmed+        , distantSo (> 2)  -- otherwise actors can be hit on entering level+        , \p _ -> not (p `ES.member` visible)  -- surprise and believability         ])  dominateFidSfx :: (MonadAtomic m, MonadServer m)@@ -183,8 +183,10 @@   execUpdAtomic $ UpdSpotActor target bNew ais   let discoverSeed (iid, cstore) = do         seed <- getsServer $ (EM.! iid) . sitemSeedD+        item <- getsState $ getItemBody iid+        Level{ldepth} <- getLevel $ jlid item         let c = CActor target cstore-        execUpdAtomic $ UpdDiscoverSeed c iid seed+        execUpdAtomic $ UpdDiscoverSeed c iid seed ldepth       aic = getCarriedIidCStore tb   mapM_ discoverSeed aic   mleaderOld <- getsState $ gleader . (EM.! fid) . sfactionD@@ -207,6 +209,31 @@   let t = ticksPerMeter $ bspeed b activeItems   execUpdAtomic $ UpdAgeActor aid t +-- | Swap the relative move times of two actors (e.g., when switching+-- a UI leader).+swapTime :: (MonadAtomic m, MonadServer m) => ActorId -> ActorId -> m ()+swapTime source target = do+  sb <- getsState $ getActorBody source+  tb <- getsState $ getActorBody target+  slvl <- getsState $ getLocalTime (blid sb)+  tlvl <- getsState $ getLocalTime (blid tb)+  let lvlDelta = slvl `timeDeltaToFrom` tlvl+      bDelta = btime sb `timeDeltaToFrom` btime tb+      sdelta = timeDeltaSubtract lvlDelta bDelta+      tdelta = timeDeltaReverse sdelta+  -- Equivalent, for the assert:+  let !_A = let sbodyDelta = btime sb `timeDeltaToFrom` slvl+                tbodyDelta = btime tb `timeDeltaToFrom` tlvl+                sgoal = slvl `timeShift` tbodyDelta+                tgoal = tlvl `timeShift` sbodyDelta+                sdelta' = sgoal `timeDeltaToFrom` btime sb+                tdelta' = tgoal `timeDeltaToFrom` btime tb+            in assert (sdelta == sdelta' && tdelta == tdelta'+                      `blame` ( slvl, tlvl, btime sb, btime tb+                              , sdelta, sdelta', tdelta, tdelta' )) ()+  when (sdelta /= Delta timeZero) $ execUpdAtomic $ UpdAgeActor source sdelta+  when (tdelta /= Delta timeZero) $ execUpdAtomic $ UpdAgeActor target tdelta+ -- | Check if the given actor is dominated and update his calm. -- We don't update calm once per game turn (even though -- it would make fast actors less overpowered),@@ -222,6 +249,10 @@     activeItems <- activeItemsServer aid     fact <- getsState $ (EM.! bfid b) . sfactionD     dominated <-+      -- We react one turn after bcalm reaches 0, to let it be+      -- displayed first, to let the player panic in advance+      -- and also to avoid the dramatic domination message+      -- be swamped in other enemy turn messages.       if bcalm b == 0          && bfidImpressed b /= bfid b          && fleaderMode (gplayer fact) /= LeaderNull@@ -233,13 +264,25 @@       let clearMark = 0       unless (newCalmDelta <= 0) $         -- Update delta for the current player turn.-        execUpdAtomic $ UpdRefillCalm aid newCalmDelta+        udpateCalm aid newCalmDelta       unless (bcalmDelta b == ResDelta 0 0) $         -- Clear delta for the next player turn.         execUpdAtomic $ UpdRefillCalm aid clearMark       unless (bhpDelta b == ResDelta 0 0) $         -- Clear delta for the next player turn.         execUpdAtomic $ UpdRefillHP aid clearMark++udpateCalm :: (MonadAtomic m, MonadServer m) => ActorId -> Int64 -> m ()+udpateCalm target deltaCalm = do+  tb <- getsState $ getActorBody target+  activeItems <- activeItemsServer target+  let calmMax64 = xM $ sumSlotNoFilter IK.EqpSlotAddMaxCalm activeItems+  execUpdAtomic $ UpdRefillCalm target deltaCalm+  when (bcalm tb < calmMax64+        && bcalm tb + deltaCalm >= calmMax64+        && bfidImpressed tb /= bfidOriginal tb) $+    execUpdAtomic $+      UpdFidImpressedActor target (bfidImpressed tb) (bfidOriginal tb)  leadLevelSwitch :: (MonadAtomic m, MonadServer m) => m () leadLevelSwitch = do
GameDefinition/Content/CaveKind.hs view
@@ -30,15 +30,15 @@   , cminPlaceSize = DiceXY (2 * d 2 + 2) 4   , cmaxPlaceSize = DiceXY 15 10   , cdarkChance   = d 54 + dl 20-  , cnightChance  = 51+  , cnightChance  = 51  -- always night   , cauxConnects  = 1%3   , cmaxVoid      = 1%6   , cminStairDist = 30   , cdoorChance   = 1%2   , copenChance   = 1%10   , chidden       = 8-  , cactorCoeff   = 150  -- the maze requires time to explore-  , cactorFreq    = [("monster", 40), ("animal", 60)]+  , cactorCoeff   = 130  -- the maze requires time to explore+  , cactorFreq    = [("monster", 60), ("animal", 40)]   , citemNum      = 10 * d 2   , citemFreq     = [("useful", 50), ("treasure", 50)]   , cplaceFreq    = [("rogue", 100)]@@ -57,12 +57,14 @@   , cfreq         = [("campaign random", 50), ("caveArena", 1)]   , cgrid         = DiceXY (2 * d 2) (2 * d 2)   , cminPlaceSize = DiceXY (2 * d 2 + 3) 4-  , cdarkChance   = d 60 + dl 60-  , cnightChance  = d 60 + dl 60 -- trails provide enough light for fun stealth+  , cdarkChance   = d 100 - dl 50+  -- Trails provide enough light for fun stealth. Light is not too deadly,+  -- because not many obstructions, so foes visible from far away.+  , cnightChance  = d 50 + dl 50   , cmaxVoid      = 1%4   , chidden       = 1000   , cactorCoeff   = 100-  , cactorFreq    = [("monster", 70), ("animal", 30)]+  , cactorFreq    = [("monster", 30), ("animal", 70)]   , citemNum      = 9 * d 2  -- few rooms   , citemFreq     = [("useful", 20), ("treasure", 30), ("any scroll", 50)]   , cpassable     = True@@ -78,12 +80,12 @@   , cminPlaceSize = DiceXY 10 10   , cmaxPlaceSize = DiceXY 24 12   , cdarkChance   = d 80 + dl 80-  , cnightChance  = 0+  , cnightChance  = 0  -- always day   , cauxConnects  = 1   , cmaxVoid      = 1%2   , cminStairDist = 50   , chidden       = 1000-  , cactorCoeff   = 20+  , cactorCoeff   = 3   , cactorFreq    = [("monster", 2), ("animal", 8), ("immobile vents", 90)]       -- The geysers on lvl 3 act like HP resets. They are needed to avoid       -- cascading failure, if the particular starting conditions were@@ -104,11 +106,14 @@   , cgrid         = DiceXY (2 + d 2) 3   , cminPlaceSize = DiceXY 12 5   , cmaxPlaceSize = DiceXY 24 12-  , cnightChance  = d 80 + dl 80+  , cdarkChance   = 0  -- few rooms, so all lit+  -- Light is deadly, because nowhere to hide and pillars enable spawning+  -- very close to heroes, so deep down light should be rare.+  , cnightChance  = dl 300   , cauxConnects  = 0   , cmaxVoid      = 0   , chidden       = 1000-  , cactorCoeff   = 200  -- the maze requires time to explore+  , cactorCoeff   = 160  -- the maze requires time to explore   , cactorFreq    = [("monster", 80), ("animal", 20)]   , citemNum      = 12 * d 2  -- an incentive to explore the labyrinth   , cpassable     = True@@ -134,7 +139,7 @@   , cminPlaceSize = DiceXY 4 4   , cmaxPlaceSize = DiceXY 9 7   , cdarkChance   = 0-  , cnightChance  = 100+  , cnightChance  = 51  -- always night   , cmaxVoid      = 0   , cdoorChance   = 2%10   , copenChance   = 9%10@@ -177,7 +182,7 @@   , cminPlaceSize = DiceXY 3 3   , cmaxPlaceSize = DiceXY 5 5   , cdarkChance   = 0-  , cnightChance  = 100+  , cnightChance  = 51  -- always night   , cauxConnects  = 1   , cdoorChance   = 1%10   , copenChance   = 9%10
GameDefinition/Content/ItemKind.hs view
@@ -150,7 +150,7 @@   , ifreq    = [("useful", 100)]   , iflavour = zipPlain [White]   , icount   = 1-  , irarity  = [(1, 2), (10, 1)]+  , irarity  = [(1, 2)]   , iverbHit = "prod"   , iweight  = 10000   , iaspects = [Timeout $ d 2 + 2 - dl 2 |*| 10]@@ -180,7 +180,7 @@   , ifreq    = [("useful", 100)]   , iflavour = zipPlain [Red]   , icount   = 1-  , irarity  = [(10, 1)]+  , irarity  = [(1, 1)]   , iverbHit = "gaze at"   , iweight  = 100   , iaspects = [ AddSight 10, AddMaxCalm 60, AddLight 2@@ -199,8 +199,8 @@   , iname    = "wooden torch"   , ifreq    = [("useful", 100), ("light source", 100)]   , iflavour = zipPlain [Brown]-  , icount   = 1 + d 3-  , irarity  = [(1, 8)]+  , icount   = d 2+  , irarity  = [(1, 10)]   , iverbHit = "scorch"   , iweight  = 1200   , iaspects = [ AddLight 3       -- not only flashes, but also sparks@@ -216,7 +216,7 @@   , ifreq    = [("useful", 100), ("light source", 100)]   , iflavour = zipPlain [BrYellow]   , icount   = 1-  , irarity  = [(7, 10)]+  , irarity  = [(6, 7)]   , iverbHit = "burn"   , iweight  = 1000   , iaspects = [AddLight 3, AddSight (-1)]@@ -232,7 +232,7 @@   , ifreq    = [("useful", 100), ("light source", 100)]   , iflavour = zipPlain [BrWhite]   , icount   = 1-  , irarity  = [(10, 3)]+  , irarity  = [(10, 5)]   , iverbHit = "burn"   , iweight  = 2400   , iaspects = [AddLight 4, AddSight (-1)]@@ -291,7 +291,7 @@   } necklace2 = necklace   { ifreq    = [("treasure", 100)]  -- just too nasty to call it useful-  , irarity  = [(10, 1)]+  , irarity  = [(1, 1)]   , iaspects = (Timeout $ d 3 + 3 - dl 3 |*| 10) : iaspects necklace   , ieffects = [ Recharging Impress                , Recharging (DropItem COrgan "temporary conditions" True)@@ -366,7 +366,7 @@   , iverbHit = "knock"   , iweight  = 15   , iaspects = []-  , ieffects = [Explode "blast 2"]+  , ieffects = [Explode "blast 20"]   , 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."   , ikit     = []@@ -408,15 +408,15 @@   } ring7 = ring   { ifreq    = [("useful", 100), ("ring of opportunity sniper", 1) ]-  , irarity  = [(10, 1)]-  , iaspects = [AddSkills $ EM.fromList [(AbProject, 9)]]+  , irarity  = [(1, 1)]+  , iaspects = [AddSkills $ EM.fromList [(AbProject, 8)]]   , ieffects = [ NoEffect "of opportunity sniper"                , Explode "distortion" ]  -- strong magic   , ifeature = ifeature ring ++ [EqpSlot (EqpSlotAddSkills AbProject) ""]   } ring8 = ring   { ifreq    = [("useful", 1), ("ring of opportunity grenadier", 1) ]-  , irarity  = [(10, 1)]+  , irarity  = [(1, 1)]   , iaspects = [AddSkills $ EM.fromList [(AbProject, 11)]]   , ieffects = [ NoEffect "of opportunity grenadier"                , Explode "distortion" ]  -- strong magic@@ -463,22 +463,24 @@                , OnSmash (Explode "healing mist 2") ]   } potion5 = potion-  { ieffects = [ OneOf [ Impress, OverfillHP 10, OverfillHP 5, Burn 5 ]+  { ieffects = [ OneOf [ OverfillHP 10, OverfillHP 5, Burn 5+                       , toOrganActorTurn "strengthened" (20 + d 5) ]                , OnSmash (OneOf [ Explode "healing mist"                                 , Explode "wounding mist"                                 , Explode "fragrance"+                                , Explode "smelly droplet"                                 , Explode "blast 10" ]) ]   } potion6 = potion   { irarity  = [(3, 3), (10, 6)]   , ieffects = [ Impress                , OneOf [ OverfillCalm (-60)-                       , toOrganActorTurn "fast 20" (20 + d 5)-                       , OverfillHP 20, OverfillHP 10, Burn 10 ]+                       , OverfillHP 20, OverfillHP 10, Burn 10+                       , toOrganActorTurn "fast 20" (20 + d 5) ]                , OnSmash (OneOf [ Explode "healing mist 2"                                 , Explode "calming mist"                                 , Explode "distressing odor"-                                , Explode "distortion"  -- outlier, OK+                                , Explode "eye drop"                                 , Explode "blast 20" ]) ]   } potion7 = potion@@ -561,18 +563,17 @@   { irarity  = [(10, 7)]   , ieffects = [ NoEffect "of eye drops"                , toOrganActorTurn "far-sighted" (20 + d 5)-               , OnSmash (Explode "eye drop") ]+               , OnSmash (Explode "blast 10") ]   } flask8 = flask   { irarity  = [(10, 3)]   , ieffects = [ NoEffect "of smelly concoction"                , toOrganActorTurn "keen-smelling" (20 + d 5)-               , OnSmash (Explode "smelly droplet") ]+               , OnSmash (Explode "blast 10") ]   } flask9 = flask   { ieffects = [ NoEffect "of bait cocktail"                , toOrganActorTurn "drunk" (5 + d 5)-               , Impress                , OnSmash (Summon [("mobile animal", 1)] $ 1 + dl 2)                , OnSmash (Explode "waste") ]   }@@ -721,7 +722,7 @@   , irarity  = [(5, 9), (10, 9)]   , iverbHit = "flap"   , iweight  = 100-  , iaspects = [ AddHurtMelee $ (d 2 + dl 10) * 3+  , iaspects = [ AddHurtMelee $ (d 2 + dl 10) |*| 3                , AddArmorRanged $ d 2 |*| 5 ]   , ieffects = []   , ifeature = [ toVelocity 30  -- flaps and flutters@@ -795,7 +796,7 @@   , ifreq    = [("useful", 100), ("starting weapon", 100)]   , iflavour = zipPlain [BrCyan]   , icount   = 1-  , irarity  = [(1, 20), (10, 1)]+  , irarity  = [(1, 20)]   , iverbHit = "stab"   , iweight  = 1000   , iaspects = [ AddHurtMelee $ d 3 + dl 3 |*| 3@@ -950,7 +951,9 @@   , irarity  = []   , iverbHit = "tap"   , iweight  = 50-  , iaspects = [AddLight 1, AddSpeed (-1)]  -- reflects strongly, distracts+  , iaspects = [AddLight 1, AddSpeed (-1)]+                 -- reflects strongly, distracts; so it glows in the dark,+                 -- is visible on dark floor, but not too tempting to wear   , ieffects = []   , ifeature = [Precious]   , 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!"@@ -971,7 +974,7 @@   , irarity  = [(1, 40), (10, 40)]   , iaspects = []   , ieffects = [NoEffect "of youth", OverfillCalm 5, OverfillHP 15]-  , ifeature = [Identified, Precious]  -- TODO: only for humans+  , ifeature = [Identified, Applicable, Precious]  -- TODO: only heal humans   , idesc    = "A crystal vial of amber liquid, supposedly granting eternal youth and fetching 100 gold per piece. The main effect seems to be mild euphoria, but it admittedly heals minor ailments rather well."   } currency = ItemKind
GameDefinition/Content/ItemKindActor.hs view
@@ -20,7 +20,7 @@ warrior = ItemKind   { isymbol  = '@'   , iname    = "warrior"  -- modified if in hero faction-  , ifreq    = [("hero", 100), ("civilian", 100)]+  , ifreq    = [("hero", 100), ("civilian", 100), ("mobile", 1)]   , iflavour = zipPlain [BrBlack]  -- modified if in hero faction   , icount   = 1   , irarity  = [(1, 5)]@@ -46,12 +46,12 @@  soldier = warrior   { iname    = "soldier"-  , ifreq    = [("soldier", 100)]+  , ifreq    = [("soldier", 100), ("mobile", 1)]   , ikit     = ikit warrior ++ [("starting weapon", CEqp)]   } sniper = warrior   { iname    = "sniper"-  , ifreq    = [("sniper", 100)]+  , ifreq    = [("sniper", 100), ("mobile", 1)]   , ikit     = ikit warrior                ++ [ ("ring of opportunity sniper", CEqp)                   , ("any arrow", CInv), ("any arrow", CInv)@@ -62,7 +62,7 @@  civilian = warrior   { iname    = "clerk"-  , ifreq    = [("civilian", 100)] }+  , ifreq    = [("civilian", 100), ("mobile", 1)] } civilian2 = civilian   { iname    = "hairdresser" } civilian3 = civilian@@ -118,7 +118,7 @@   , iverbHit = "thud"   , iweight  = 80000   , iaspects = [ AddMaxHP 30, AddMaxCalm 30, AddSpeed 18-               , AddSkills $ EM.fromList [(AbProject, -1), (AbApply, -1)] ]+               , AddSkills $ EM.fromList [(AbProject, -1)] ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = "No mouth, yet it devours everything around, constantly sniffing itself inward; pure movement structure, no constant point to focus one's maddened gaze on."@@ -149,13 +149,13 @@ torsor = ItemKind   { isymbol  = 'T'   , iname    = "The Forgetful Torsor"-  , ifreq    = [("monster", 100)]+  , ifreq    = [("monster", 100), ("mobile", 1)]   , iflavour = zipFancy [BrCyan]   , icount   = 1   , irarity  = [(9, 0), (10, 1000)]  -- unique   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ Unique, AddMaxHP 200, AddMaxCalm 100, AddSpeed 10+  , iaspects = [ Unique, AddMaxHP 300, AddMaxCalm 100, AddSpeed 10                , AddSkills $ EM.fromList                    [(AbProject, 2), (AbApply, 1), (AbTrigger, -1)] ]                    -- can't switch levels, a miniboss@@ -292,7 +292,7 @@   , irarity  = [(7, 0), (10, 10)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 40, AddMaxCalm 60, AddSpeed 16 ]+  , iaspects = [ AddMaxHP 41, AddMaxCalm 60, AddSpeed 16 ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = ""@@ -326,7 +326,7 @@   , irarity  = [(6, 1), (10, 9)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 40, AddMaxCalm 60, AddSpeed 17 ]+  , iaspects = [ AddMaxHP 41, AddMaxCalm 60, AddSpeed 15 ]   , ieffects = []   , ifeature = [Durable, Identified]   , idesc    = ""@@ -338,13 +338,13 @@ rhinoceros = ItemKind   { isymbol  = 'R'   , iname    = "The Maddened Rhinoceros"-  , ifreq    = [("animal", 100)]+  , ifreq    = [("animal", 100), ("mobile", 1)]   , iflavour = zipPlain [Brown]   , icount   = 1   , irarity  = [(2, 0), (3, 1000000), (4, 0)]  -- unique   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ Unique, AddMaxHP 70, AddMaxCalm 60, AddSpeed 25+  , iaspects = [ Unique, AddMaxHP 90, AddMaxCalm 60, AddSpeed 25                , AddSkills $ EM.singleton AbTrigger (-1) ]                    -- can't switch levels, a miniboss   , ieffects = []@@ -395,10 +395,10 @@ thornbush = ItemKind   { isymbol  = 't'   , iname    = "thornbush"-  , ifreq    = [("animal", 100)]+  , ifreq    = [("animal", 50), ("immobile vents", 100)]   , iflavour = zipPlain [Brown]   , icount   = 1-  , irarity  = [(1, 3), (10, 1)]+  , irarity  = [(1, 3)]   , iverbHit = "thud"   , iweight  = 80000   , iaspects = [ AddMaxHP 20, AddMaxCalm 999, AddSpeed 20@@ -411,13 +411,13 @@ geyserBoiling = ItemKind   { isymbol  = 'g'   , iname    = "geyser"-  , ifreq    = [("animal", 50), ("immobile vents", 100)]+  , ifreq    = [("animal", 50), ("immobile vents", 50)]   , iflavour = zipPlain [Blue]   , icount   = 1-  , irarity  = [(5, 2), (10, 1)]+  , irarity  = [(5, 2)]   , iverbHit = "thud"   , iweight  = 80000-  , iaspects = [ AddMaxHP 10, AddMaxCalm 999, AddSpeed 5+  , iaspects = [ AddMaxHP 10, AddMaxCalm 999, AddSpeed 10                , AddSkills $ EM.fromList (zip [AbWait, AbMelee] [1, 1..])                , AddArmorMelee 80, AddArmorRanged 80 ]   , ieffects = []@@ -431,7 +431,7 @@   , ifreq    = [("animal", 50), ("immobile vents", 100)]   , iflavour = zipPlain [Cyan]   , icount   = 1-  , irarity  = [(5, 2), (10, 1)]+  , irarity  = [(5, 2)]   , iverbHit = "thud"   , iweight  = 80000   , iaspects = [ AddMaxHP 30, AddMaxCalm 999, AddSpeed 20, AddLight 3@@ -447,13 +447,13 @@   , ifreq    = [("animal", 50), ("immobile vents", 300)]   , iflavour = zipPlain [BrYellow]  -- exception, animal with bright color   , icount   = 1-  , irarity  = [(5, 4), (10, 2)]+  , irarity  = [(5, 2)]   , iverbHit = "thud"   , iweight  = 80000   , iaspects = [ AddMaxHP 30, AddMaxCalm 999, AddSpeed 20, AddLight 3                , AddSkills $ EM.fromList (zip [AbWait, AbMelee] [1, 1..]) ]   , ieffects = []-  , ifeature = [Durable, Identified]+  , ifeature = [Durable, Identified]  -- TODO: only heal humans   , idesc    = ""   , ikit     = [("sulfur vent", COrgan), ("sulfur fissure", COrgan)]   }
GameDefinition/Content/ItemKindBlast.hs view
@@ -47,7 +47,7 @@   , iverbHit = "tear apart"   , iweight  = 1   , iaspects = [AddLight $ intToDice n]-  , ieffects = [Impress, RefillHP (- n `div` 2)]+  , ieffects = [RefillHP (- n `div` 2)]                ++ [PushActor (ThrowMod (100 * (n `div` 5)) 50)]                ++ [DropItem COrgan "temporary conditions" True | n >= 10]   , ifeature = [Fragile, toLinger 20, Identified]@@ -68,8 +68,7 @@   , iverbHit = "crack"   , iweight  = 1   , iaspects = [AddLight $ intToDice $ n `div` 2]-  , ieffects = [Impress]-               ++ [ RefillCalm (-1) | n >= 5 ]+  , ieffects = [ RefillCalm (-1) | n >= 5 ]                ++ [ DropBestWeapon | n >= 5]                ++ [ OnSmash (Explode $ toGroupName                              $ "firecracker" <+> tshow (n - 1))@@ -212,7 +211,7 @@   , iverbHit = "engulf"   , iweight  = 1   , iaspects = []-  , ieffects = [Impress, Teleport $ 15 + d 10]+  , ieffects = [Teleport $ 15 + d 10]   , ifeature = [ toVelocity 7  -- the slowest that gets anywhere (1 step only)                , Fragile, Identified ]   , idesc    = ""@@ -482,7 +481,7 @@   , iverbHit = "inebriate"   , iweight  = 1   , iaspects = []-  , ieffects = [Impress, toOrganActorTurn "drunk" (3 + d 3)]+  , ieffects = [toOrganActorTurn "drunk" (3 + d 3)]   , ifeature = [ toVelocity 13  -- the slowest that travels at least 2 steps                , Fragile, Identified ]   , idesc    = ""
GameDefinition/Content/ItemKindOrgan.hs view
@@ -409,16 +409,14 @@   , ifreq    = [("arsenic vent", 100)]   , iflavour = zipPlain [Cyan]   , iaspects = [Periodic, Timeout $ 2 + d 2 |*| 5]-  , ieffects = [ Recharging (Explode "weakness mist")-               , Recharging (RefillHP (-1)) ]+  , ieffects = [Recharging (Explode "weakness mist")]   } sulfurVent = boilingVent   { iname    = "vent"   , ifreq    = [("sulfur vent", 100)]   , iflavour = zipPlain [BrYellow]   , iaspects = [Periodic, Timeout $ 2 + d 2 |*| 5]-  , ieffects = [ Recharging (Explode "strength mist")-               , Recharging (RefillHP (-1)) ]+  , ieffects = [Recharging (Explode "strength mist")]   } bonusHP = armoredSkin   { iname    = "bonus HP"
GameDefinition/Content/ModeKind.hs view
@@ -263,8 +263,12 @@                      "Animal Magnificent Specimen Variety")                   , ("Monster Tourist Office",                      "Animal Exquisite Herds and Packs") ]-  , rosterAlly = [( "Animal Magnificent Specimen Variety"-                  , "Animal Exquisite Herds and Packs" )] }+  , rosterAlly = [ ( "Animal Magnificent Specimen Variety"+                   , "Animal Exquisite Herds and Packs" )+                 , ( "Animal Magnificent Specimen Variety"+                   , "Hunam Convict Pack" )+                 , ( "Hunam Convict Pack"+                   , "Animal Exquisite Herds and Packs" ) ] }  rosterSafariSurvival = rosterSafari   { rosterList = [ playerMonsterTourist@@ -309,13 +313,10 @@                  , ("Green", "Monster Hive")                  , ("Green", "Leaderless Monster Hive") ] } -rosterDefense = Roster+rosterDefense = rosterCampaign   { rosterList = [ playerAntiHero                  , playerAntiMonster-                 , playerAnimal ]-  , rosterEnemy = [ ("Adventurer Party", "Monster Hive")-                  , ("Adventurer Party", "Animal Kingdom") ]-  , rosterAlly = [("Monster Hive", "Animal Kingdom")] }+                 , playerAnimal ] }  rosterBoardgame = Roster   { rosterList = [ playerHero { fname = "Blue"
GameDefinition/PLAYING.md view
@@ -95,14 +95,14 @@                  /|\            /|\            /|\                 1 2 3          j k l          b j n -In aiming mode the same keys (or the middle and right mouse buttons)-move the crosshair (the white box). In normal mode, `SHIFT` (or `CTRL`)-and a movement key make the current party leader run in the indicated-direction, until anything of interest is spotted. The `5` keypad key-and the `i` and `.` keys consume a turn and make you brace for combat,-which reduces any damage taken for a turn and makes it impossible-for foes to displace you. You displace enemies or friends by bumping-into them with `SHIFT` (or `CTRL`).+In aiming mode (KEYPAD_* or \) the same keys (or the middle and right+mouse buttons) move the crosshair (the white box). In normal mode,+`SHIFT` (or `CTRL`) and a movement key make the current party leader+run in the indicated direction, until anything of interest is spotted.+The `5` keypad key and the `i` and `.` keys consume a turn and make you+brace for combat, which reduces any damage taken for a turn and makes it+impossible for foes to displace you. You displace enemies or friends+by bumping into them with `SHIFT` (or `CTRL`).  Melee, searching for secret doors, looting and opening closed doors can be done by bumping into a monster, a wall and a door, respectively.@@ -123,7 +123,7 @@         >               descend a level         c               close door         E               manage equipment of the leader-        g and ,         get items+        g or ,          get items         a               apply consumable         f               fling projectile         +               swerve the aiming line@@ -141,8 +141,8 @@ using the aiming and targeting commands listed below.          keys            command-        KEYPAD_* and \  aim at an enemy-        KEYPAD_/ and |  cycle aiming styles+        KEYPAD_* or \   aim at an enemy+        KEYPAD_/ or |   cycle aiming styles         +               swerve the aiming line         -               unswerve the aiming line         CTRL-?          set crosshair to the closest unknown spot@@ -150,7 +150,7 @@         CTRL-{          set crosshair to the closest stairs up         CTRL-}          set crosshair to the closest stairs down         BACKSPACE       reset target/crosshair-        RET and INSERT  accept target/choice+        RET or INSERT   accept target/choice  For ranged attacks, setting the crosshair or individual targets beforehand is not mandatory, because the crosshair is set automatically
LambdaHack.cabal view
@@ -5,7 +5,7 @@ -- PVP summary: +-+------- breaking API changes --              | | +----- non-breaking API additions --              | | |   +--- code changes with no API change-version:        0.4.101.0+version:        0.4.101.1 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,
Makefile view
@@ -243,27 +243,3 @@ 	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.--default : dist/setup-config-	runghc Setup build--dist/setup-config : LambdaHack.cabal-	runghc Setup configure -fvty --user--vty :-	runghc Setup configure -fvty --user--gtk :-	runghc Setup configure --user--curses :-	runghc Setup configure -fcurses --user--clean :-	runghc Setup clean--ghci :-	ghci -XCPP -idist/build/autogen:.
README.md view
@@ -72,11 +72,11 @@  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. This mode is probably+keys and run with SHIFT (or CONTROL) and the keys. This mode is probably the best if you use mouse for running. 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.+and requiring SHIFT (or CONTROL) for the error-prone step by step walking.  If you don't have the numeric keypad, you can use laptop keys (uk8o79jl) or you can enable the Vi keys (aka roguelike keys) in the config file.