1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
|
-- | The type of definitions of key-command mappings to be used for the UI
-- and shorthands for specifying command triples in the content files.
module Game.LambdaHack.Client.UI.Content.Input
( InputContentRaw(..), InputContent(..), makeData
, evalKeyDef
, addCmdCategory, replaceDesc, moveItemTriple, repeatTriple, repeatLastTriple
, mouseLMB, mouseMMB, mouseMMBMute, mouseRMB
, goToCmd, runToAllCmd, autoexploreCmd, autoexplore25Cmd
, aimFlingCmd, projectI, projectA, flingTs, applyIK, applyI
, grabItems, dropItems, descIs, defaultHeroSelect, macroRun25
, memberCycle, memberCycleLevel
#ifdef EXPOSE_INTERNAL
-- * Internal operations
, replaceCmd, projectICmd, grabCmd, dropCmd
#endif
) where
import Prelude ()
import Game.LambdaHack.Core.Prelude
import qualified Data.Char as Char
import qualified Data.Map.Strict as M
import qualified NLP.Miniutter.English as MU
import Game.LambdaHack.Client.UI.HumanCmd
import qualified Game.LambdaHack.Client.UI.Key as K
import Game.LambdaHack.Client.UI.UIOptions
import Game.LambdaHack.Common.Misc
import Game.LambdaHack.Definition.Defs
-- | Key-command mappings to be specified in content and used for the UI.
newtype InputContentRaw = InputContentRaw [(K.KM, CmdTriple)]
-- | Bindings and other information about human player commands.
data InputContent = InputContent
{ bcmdMap :: M.Map K.KM CmdTriple -- ^ binding of keys to commands
, bcmdList :: [(K.KM, CmdTriple)] -- ^ the properly ordered list
-- of commands for the help menu
, brevMap :: M.Map HumanCmd [K.KM] -- ^ and from commands to their keys
}
-- | Create binding of keys to movement and other standard commands,
-- as well as commands defined in the config file.
makeData :: Maybe UIOptions -- ^ UI client options
-> InputContentRaw -- ^ default key bindings from the content
-> InputContent -- ^ concrete binding
makeData muiOptions (InputContentRaw copsClient) =
let (uCommands0, uVi0, uLeftHand0) = case muiOptions of
Just UIOptions{uCommands, uVi, uLeftHand} -> (uCommands, uVi, uLeftHand)
Nothing -> ([], True, True)
waitTriple = ([CmdMove], "", Wait)
wait10Triple = ([CmdMove], "", Wait10)
moveXhairOr n cmd v = ByAimMode $ AimModeCmd { exploration = cmd v
, aiming = MoveXhair v n }
rawContent = copsClient ++ uCommands0
movementDefinitions =
K.moveBinding uVi0 uLeftHand0
(\v -> ([CmdMove], "", moveXhairOr 1 MoveDir v))
(\v -> ([CmdMove], "", moveXhairOr 10 RunDir v))
++ [ (K.mkKM "KP_Begin", waitTriple)
, (K.mkKM "C-KP_Begin", wait10Triple)
, (K.mkKM "KP_5", wait10Triple)
, (K.mkKM "S-KP_5", wait10Triple) -- rxvt
, (K.mkKM "C-KP_5", wait10Triple) ]
++ [(K.mkKM "period", waitTriple) | uVi0]
++ [(K.mkKM "C-period", wait10Triple) | uVi0]
++ [(K.mkKM "s", waitTriple) | uLeftHand0]
++ [(K.mkKM "S", wait10Triple) | uLeftHand0]
-- This is the most common case of duplicate keys and it usually
-- has an easy solution, so it's tested for first.
!_A = flip assert () $
let movementKeys = map fst movementDefinitions
filteredNoMovement = filter (\(k, _) -> k `notElem` movementKeys)
rawContent
in rawContent == filteredNoMovement
`blame` "commands overwrite the enabled movement keys (you can disable some in config file and try again)"
`swith` rawContent \\ filteredNoMovement
bcmdList = rawContent ++ movementDefinitions
-- This catches repetitions (usually) not involving movement keys.
rejectRepetitions _ t1 (_, "", _) = t1
rejectRepetitions _ (_, "", _) t2 = t2
rejectRepetitions k t1 t2 =
error $ "duplicate key among command definitions (you can instead disable some movement key sets in config file and overwrite the freed keys)" `showFailure` (k, t1, t2)
in InputContent
{ bcmdMap = M.fromListWithKey rejectRepetitions bcmdList
, bcmdList
, brevMap = M.fromListWith (flip (++)) $ concat
[ [(cmd, [k])]
| (k, (cats, _desc, cmd)) <- bcmdList
, not (null cats)
&& CmdDebug `notElem` cats
]
}
evalKeyDef :: (String, CmdTriple) -> (K.KM, CmdTriple)
evalKeyDef (t, triple@(cats, _, _)) =
let km = if CmdInternal `elem` cats
then K.KM K.NoModifier $ K.Unknown t
else K.mkKM t
in (km, triple)
addCmdCategory :: CmdCategory -> CmdTriple -> CmdTriple
addCmdCategory cat (cats, desc, cmd) = (cat : cats, desc, cmd)
replaceDesc :: Text -> CmdTriple -> CmdTriple
replaceDesc desc (cats, _, cmd) = (cats, desc, cmd)
replaceCmd :: HumanCmd -> CmdTriple -> CmdTriple
replaceCmd cmd (cats, desc, _) = (cats, desc, cmd)
moveItemTriple :: [CStore] -> CStore -> MU.Part -> Bool -> CmdTriple
moveItemTriple stores1 store2 object auto =
let verb = MU.Text $ verbCStore store2
desc = makePhrase [verb, object]
in ([CmdItemMenu, CmdItem], desc, MoveItem stores1 store2 Nothing auto)
repeatTriple :: Int -> [CmdCategory] -> CmdTriple
repeatTriple n cats =
( cats
, if n == 1
then "voice recorded macro again"
else "voice recorded macro" <+> tshow n <+> "times"
, Repeat n )
repeatLastTriple :: Int -> [CmdCategory] -> CmdTriple
repeatLastTriple n cats =
( cats
, if n == 1
then "voice last action again"
else "voice last action" <+> tshow n <+> "times in a row"
, RepeatLast n )
-- @AimFloor@ is not there, but @AimEnemy@ and @AimItem@ almost make up for it.
mouseLMB :: HumanCmd -> Text -> CmdTriple
mouseLMB goToOrRunTo desc =
([CmdMouse], desc, ByAimMode aimMode)
where
aimMode = AimModeCmd
{ exploration = ByArea $ common ++ -- exploration mode
[ (CaMapLeader, grabCmd)
, (CaMapParty, PickLeaderWithPointer)
, (CaMap, goToOrRunTo)
, (CaArenaName, Dashboard)
, (CaPercentSeen, autoexploreCmd) ]
, aiming = ByArea $ common ++ -- aiming mode
[ (CaMap, aimFlingCmd)
, (CaArenaName, Accept)
, (CaPercentSeen, XhairStair True) ] }
common =
[ (CaMessage, AllHistory)
, (CaLevelNumber, AimAscend 1)
, (CaXhairDesc, AimEnemy) -- inits aiming and then cycles enemies
, (CaSelected, PickLeaderWithPointer)
-- , (CaCalmGauge, Macro ["KP_Begin", "C-v"])
, (CaCalmValue, Yell)
, (CaHPGauge, Macro ["KP_Begin", "C-v"])
, (CaHPValue, Wait)
, (CaLeaderDesc, projectICmd flingTs) ]
mouseMMB :: CmdTriple
mouseMMB = ( [CmdMouse]
, "snap crosshair to floor under pointer/cycle detail level"
, XhairPointerFloor )
mouseMMBMute :: CmdTriple
mouseMMBMute = ([CmdMouse], "", XhairPointerMute)
mouseRMB :: CmdTriple
mouseRMB = ( [CmdMouse]
, "start aiming at enemy under pointer/cycle detail level"
, ByAimMode aimMode )
where
aimMode = AimModeCmd
{ exploration = ByArea $ common ++
[ (CaMapLeader, dropCmd)
, (CaMapParty, SelectWithPointer)
, (CaMap, AimPointerEnemy)
, (CaArenaName, ExecuteIfClear MainMenuAutoOff)
, (CaPercentSeen, autoexplore25Cmd) ]
, aiming = ByArea $ common ++
[ (CaMap, XhairPointerEnemy) -- hack; same effect, but matches LMB
, (CaArenaName, Cancel)
, (CaPercentSeen, XhairStair False) ] }
common =
[ (CaMessage, Hint)
, (CaLevelNumber, AimAscend (-1))
, (CaXhairDesc, AimItem)
, (CaSelected, SelectWithPointer)
-- , (CaCalmGauge, Macro ["C-KP_Begin", "A-v"])
, (CaCalmValue, Yell)
, (CaHPGauge, Macro ["C-KP_Begin", "A-v"])
, (CaHPValue, Wait10)
, (CaLeaderDesc, ComposeUnlessError ClearTargetIfItemClear ItemClear) ]
-- This is duplicated wrt content, instead of included via @semicolon@,
-- because the C- commands are less likely to be modified by the player
-- and so more dependable than @semicolon@, @colon@, etc.
goToCmd :: HumanCmd
goToCmd = Macro ["A-MiddleButtonRelease", "C-semicolon", "C-quotedbl", "C-v"]
-- This is duplicated wrt content, instead of included via @colon@,
-- because the C- commands are less likely to be modified by the player
-- and so more dependable than @semicolon@, @colon@, etc.
runToAllCmd :: HumanCmd
runToAllCmd = Macro ["A-MiddleButtonRelease", "C-colon", "C-quotedbl", "C-v"]
autoexploreCmd :: HumanCmd
autoexploreCmd = Macro ["C-?", "C-quotedbl", "C-v"]
autoexplore25Cmd :: HumanCmd
autoexplore25Cmd = Macro ["'", "C-?", "C-quotedbl", "'", "C-V"]
aimFlingCmd :: HumanCmd
aimFlingCmd = ComposeIfLocal AimPointerEnemy (projectICmd flingTs)
projectICmd :: [TriggerItem] -> HumanCmd
projectICmd ts = ComposeUnlessError (ChooseItemProject ts) Project
projectI :: [TriggerItem] -> CmdTriple
projectI ts = ([CmdItem], descIs ts, projectICmd ts)
projectA :: [TriggerItem] -> CmdTriple
projectA ts =
let fling = Compose2ndLocal Project ItemClear
flingICmd = ComposeUnlessError (ChooseItemProject ts) fling
in replaceCmd (ByAimMode AimModeCmd { exploration = AimTgt
, aiming = flingICmd })
(projectI ts)
-- | flingTs - list containing one flingable projectile
-- >>> flingTs
-- [TriggerItem {tiverb = Text "fling", tiobject = Text "in-range projectile", tisymbols = ""}]
--
-- I question the value of that test. But would Bob Martin like it
-- on the grounds it's like double-bookkeeping?
flingTs :: [TriggerItem]
flingTs = [TriggerItem { tiverb = "fling"
, tiobject = "in-range projectile"
, tisymbols = [] }]
applyIK :: [TriggerItem] -> CmdTriple
applyIK ts =
([CmdItem], descIs ts, ComposeUnlessError (ChooseItemApply ts) Apply)
applyI :: [TriggerItem] -> CmdTriple
applyI ts =
let apply = Compose2ndLocal Apply ItemClear
in ([CmdItem], descIs ts, ComposeUnlessError (ChooseItemApply ts) apply)
grabCmd :: HumanCmd
grabCmd = MoveItem [CGround] CStash (Just "grab") True
-- @CStash@ is the implicit default; refined in HandleHumanGlobalM
grabItems :: Text -> CmdTriple
grabItems t = ([CmdItemMenu, CmdItem], t, grabCmd)
dropCmd :: HumanCmd
dropCmd = MoveItem [CStash, CEqp] CGround Nothing False
dropItems :: Text -> CmdTriple
dropItems t = ([CmdItemMenu, CmdItem], t, dropCmd)
descIs :: [TriggerItem] -> Text
descIs [] = "trigger an item"
descIs (t : _) = makePhrase [tiverb t, tiobject t]
defaultHeroSelect :: Int -> (String, CmdTriple)
defaultHeroSelect k = ([Char.intToDigit k], ([CmdMeta], "", PickLeader k))
macroRun25 :: [String]
macroRun25 = ["C-comma", "C-v"]
memberCycle :: Direction -> [CmdCategory] -> CmdTriple
memberCycle d cats = ( cats
, "cycle"
<+> (if d == Backward then "backwards" else "")
<+> "among all party members"
, PointmanCycle d )
memberCycleLevel :: Direction -> [CmdCategory] -> CmdTriple
memberCycleLevel d cats = ( cats
, "cycle"
<+> (if d == Backward then "backwards" else "")
<+> " among party members on the level"
, PointmanCycleLevel d )
|