LambdaHack-0.11.0.1: engine-src/Game/LambdaHack/Client/UI/UIOptionsParse.hs
-- | UI client options.
module Game.LambdaHack.Client.UI.UIOptionsParse
( mkUIOptions, applyUIOptions
#ifdef EXPOSE_INTERNAL
-- * Internal operations
, configError, readError, parseConfig
#endif
) where
import Prelude ()
import Game.LambdaHack.Core.Prelude
import Control.DeepSeq
import qualified Data.Ini as Ini
import qualified Data.Ini.Reader as Ini
import qualified Data.Ini.Types as Ini
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import Data.Version
import System.FilePath
import Text.ParserCombinators.ReadP (readP_to_S)
import Text.Read
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.ClientOptions
import Game.LambdaHack.Common.File
import Game.LambdaHack.Common.Kind
import Game.LambdaHack.Common.Misc
import Game.LambdaHack.Common.Save (compatibleVersion, delayPrint)
import qualified Game.LambdaHack.Common.Save as Save
import Game.LambdaHack.Content.RuleKind
configError :: String -> a
configError err = error $ "Error when parsing configuration file. Please fix config.ui.ini or remove it altogether. The details:\n" ++ err
readError :: Read a => String -> a
readError s = either (configError . ("when reading:\n" ++ s `showFailure`)) id
$ readEither s
parseConfig :: Ini.Config -> UIOptions
parseConfig cfg =
let uCommands =
let mkCommand (ident, keydef) =
case stripPrefix "Cmd_" ident of
Just _ ->
let (key, def) = readError keydef
in (K.mkKM key, def :: CmdTriple)
Nothing ->
configError
$ "macro id should start with Cmd_ and it does not:"
`showFailure` ident
section = Ini.allItems "additional_commands" cfg
in map mkCommand section
uHeroNames =
let toNumber (ident, nameAndPronoun) =
case stripPrefix "HeroName_" ident of
Just n -> (readError n, readError nameAndPronoun)
Nothing -> configError
$ "wrong hero name id" `showFailure` ident
section = Ini.allItems "hero_names" cfg
in map toNumber section
lookupFail :: forall b. String -> String -> b
lookupFail optionName err =
configError $ "config file access failed"
`showFailure` (err, optionName, cfg)
_getOptionMaybe :: forall a. Read a => String -> Maybe a
_getOptionMaybe optionName =
let ms = Ini.getOption "ui" optionName cfg
in either (lookupFail optionName) id . readEither <$> ms
getOption :: forall a. Read a => String -> a
getOption optionName =
let s = fromMaybe (lookupFail optionName "")
$ Ini.getOption "ui" optionName cfg
in either (lookupFail optionName) id $ readEither s
uVi = getOption "movementViKeys_hjklyubn"
uLeftHand = getOption "movementLeftHandKeys_axwdqezc"
uChosenFontset = getOption "chosenFontset"
uAllFontsScale = getOption "allFontsScale"
uFullscreenMode = getOption "fullscreenMode"
uhpWarningPercent = getOption "hpWarningPercent"
uMsgWrapColumn = getOption "msgWrapColumn"
uHistoryMax = getOption "historyMax"
uMaxFps = max 1 $ getOption "maxFps"
uNoAnim = getOption "noAnim"
uOverrideCmdline = glueSeed $ words $ getOption "overrideCmdline"
uFonts =
let toFont (ident, fontString) = (T.pack ident, readError fontString)
section = Ini.allItems "fonts" cfg
in map toFont section
uFontsets =
let toFontSet (ident, fontSetString) =
(T.pack ident, readError fontSetString)
section = Ini.allItems "fontsets" cfg
in map toFontSet section
uMessageColors =
map (second readError) $ Ini.allItems "message_colors" cfg
in UIOptions{..}
glueSeed :: [String] -> [String]
glueSeed [] = []
glueSeed ("SMGen" : s1 : s2 : rest) =
("SMGen" ++ " " ++ s1 ++ " " ++ s2) : glueSeed rest
glueSeed (s : rest) = s : glueSeed rest
-- | Read and parse UI config file.
mkUIOptions :: RuleContent -> ClientOptions -> IO UIOptions
mkUIOptions corule clientOptions = do
let benchmark = sbenchmark clientOptions
cfgUIName = rcfgUIName corule
(configText, cfgUIDefault) = rcfgUIDefault corule
dataDir <- appDataDir
let path bkp = dataDir </> bkp <> cfgUIName
cfgUser <- if benchmark then return Ini.emptyConfig else do
cpExists <- doesFileExist (path "")
if not cpExists
then return Ini.emptyConfig
else do
sUser <- readFile (path "")
return $! either (configError . ("Ini.parse sUser" `showFailure`)) id
$ Ini.parse sUser
let cfgUI = M.unionWith M.union cfgUser cfgUIDefault -- user cfg preferred
vExe1 = rexeVersion corule
vExe2 =
let optionName = "version"
-- Lenient to parse, and reject, old config files:
s = fromMaybe "" $ Ini.getOption "version" optionName cfgUser
dummyVersion = makeVersion []
in case find ((== "") . snd) $ readP_to_S parseVersion s of
Just (ver, "") -> ver
_ -> dummyVersion
if benchmark || compatibleVersion vExe1 vExe2 then do
let conf = parseConfig cfgUI
-- Catch syntax errors in complex expressions ASAP.
return $! deepseq conf conf
else do
cpExists <- doesFileExist (path "")
when cpExists $ do
renameFile (path "") (path "bkp.")
moveAside <- Save.bkpAllSaves corule clientOptions
let msg = "Config file" <+> T.pack (path "")
<+> "from an incompatible version '"
<> T.pack (showVersion vExe2)
<> "' detected while starting"
<+> T.pack (showVersion vExe1)
<+> "game."
<+> if moveAside
then "The config file and savefiles have been moved aside."
else "The config file has been moved aside."
delayPrint msg
dataDirExists <- doesFileExist dataDir
when dataDirExists $ -- may not exist, e.g., when testing
tryWriteFile (path "") configText
let confDefault = parseConfig cfgUIDefault
return confDefault
-- | Modify client options with UI options.
applyUIOptions :: COps -> UIOptions -> ClientOptions -> ClientOptions
applyUIOptions COps{corule} uioptions =
(\opts -> opts {schosenFontset =
schosenFontset opts `mplus` Just (uChosenFontset uioptions)}) .
(\opts -> opts {sallFontsScale =
sallFontsScale opts `mplus` Just (uAllFontsScale uioptions)}) .
(\opts -> opts {sfullscreenMode =
sfullscreenMode opts `mplus` Just (uFullscreenMode uioptions)}) .
(\opts -> opts {smaxFps =
smaxFps opts `mplus` Just (uMaxFps uioptions)}) .
(\opts -> opts {snoAnim =
snoAnim opts `mplus` Just (uNoAnim uioptions)}) .
(\opts -> opts {stitle =
stitle opts `mplus` Just (rtitle corule)}) .
(\opts -> opts {sfonts = uFonts uioptions}) .
(\opts -> opts {sfontsets = uFontsets uioptions})