--------------------------------------------------------------------------------
module Xanthous.Game.Draw
( drawGame
) where
--------------------------------------------------------------------------------
import Xanthous.Prelude
--------------------------------------------------------------------------------
import Brick hiding (loc, on)
import Brick.Widgets.Border
import Brick.Widgets.Border.Style
import Brick.Widgets.Edit
import Control.Monad.State.Lazy (evalState)
import Control.Monad.State.Class ( get, MonadState, gets )
--------------------------------------------------------------------------------
import Xanthous.Data
import Xanthous.Data.App (ResourceName, Panel(..))
import qualified Xanthous.Data.App as Resource
import qualified Xanthous.Data.EntityMap as EntityMap
import Xanthous.Game.State
import Xanthous.Entities.Common (Wielded(..), wielded, backpack)
import Xanthous.Entities.Character
import Xanthous.Entities.Item (Item)
import Xanthous.Game
( characterPosition
, character
, revealedEntitiesAtPosition
)
import Xanthous.Game.Prompt
import Xanthous.Orphans ()
import Brick.Widgets.Center (hCenter)
import Xanthous.Command (Keybinding (..), keybindings, Command, commandIsHidden)
import Graphics.Vty.Input.Events (Modifier(..))
import Graphics.Vty.Input (Key(..))
import Brick.Widgets.Table
--------------------------------------------------------------------------------
cursorPosition :: GameState -> Widget ResourceName -> Widget ResourceName
cursorPosition game
| WaitingPrompt _ (Prompt _ _ (preview promptStatePosition -> Just pos) _ _)
<- game ^. promptState
= showCursor Resource.Prompt (pos ^. loc)
| otherwise
= showCursor Resource.Character (game ^. characterPosition . loc)
drawMessages :: MessageHistory -> Widget ResourceName
drawMessages = txtWrap . (<> " ") . unwords . reverse . oextract
drawPromptState :: GamePromptState m -> Widget ResourceName
drawPromptState NoPrompt = emptyWidget
drawPromptState (WaitingPrompt msg (Prompt _ pt ps pri _)) =
case (pt, ps, pri) of
(SStringPrompt, StringPromptState edit, mDef) ->
txt msg
<+> txt (maybe "" (\def -> "(default: " <> def <> ") ") mDef)
<+> renderEditor (txt . fold) True edit
(SDirectionPrompt, DirectionPromptState, _) -> txtWrap msg
(SMenu, _, menuItems) ->
txtWrap msg
<=> foldl' (<=>) emptyWidget (map drawMenuItem $ itoList menuItems)
_ -> txtWrap msg
where
drawMenuItem (chr, MenuOption m _) =
str ("[" <> pure chr <> "] ") <+> txtWrap m
drawEntities
:: forall m. MonadState GameState m
=> m (Widget ResourceName)
drawEntities = do
allEnts <- use entities
let entityPositions = EntityMap.positions allEnts
maxY = fromMaybe 0 $ maximumOf (folded . y) entityPositions
maxX = fromMaybe 0 $ maximumOf (folded . x) entityPositions
rows = traverse mkRow [0..maxY]
mkRow rowY = hBox <$> traverse (renderEntityAt . flip Position rowY) [0..maxX]
renderEntityAt pos
= renderTopEntity pos <$> revealedEntitiesAtPosition pos
renderTopEntity pos ents
= let neighbors = EntityMap.neighbors pos allEnts
in maybe (str " ") (drawWithNeighbors neighbors)
$ maximumBy (compare `on` drawPriority)
<$> fromNullable ents
vBox <$> rows
drawMap :: MonadState GameState m => m (Widget ResourceName)
drawMap = do
cursorPos <- gets cursorPosition
viewport Resource.MapViewport Both . cursorPos <$> drawEntities
bullet :: Char
bullet = '•'
drawInventoryPanel :: GameState -> Widget ResourceName
drawInventoryPanel game
= drawWielded (game ^. character . inventory . wielded)
<=> drawBackpack (game ^. character . inventory . backpack)
where
drawWielded (Hands Nothing Nothing) = emptyWidget
drawWielded (DoubleHanded i) =
txtWrap $ "You are holding " <> description i <> " in both hands"
drawWielded (Hands l r) = drawHand "left" l <=> drawHand "right" r
drawHand side = maybe emptyWidget $ \i ->
txtWrap ( "You are holding "
<> description i
<> " in your " <> side <> " hand"
)
<=> txt " "
drawBackpack :: Vector Item -> Widget ResourceName
drawBackpack Empty = txtWrap "Your backpack is empty right now."
drawBackpack backpackItems
= txtWrap ( "You are currently carrying the following items in your "
<> "backpack:")
<=> txt " "
<=> foldl' (<=>) emptyWidget
(map
(txtWrap . ((bullet <| " ") <>) . description)
backpackItems)
drawHelpPanel :: Widget ResourceName
drawHelpPanel
= txtWrap "To move in a direction or attack, use vi keys (hjklyubn):"
<=> txt " "
<=> hCenter keyStar
<=> txt " "
<=> cmds
where
keyStar
= txt "y k u"
<=> txt " \\|/"
<=> txt "h-.-l"
<=> txt " /|\\"
<=> txt "b j n"
cmds
= renderTable
. alignRight 0
. setDefaultRowAlignment AlignTop
. surroundingBorder False
. rowBorders False
. columnBorders False
. table $ help <&> \(key, cmd) -> [ txt $ key <> " : "
, hLimitPercent 100 $ txtWrap cmd]
help =
extraHelp <>
keybindings
^.. ifolded
. filtered (not . commandIsHidden)
. withIndex
. to (bimap displayKeybinding displayCommand)
extraHelp
= [("Shift-Dir", "Auto-move")]
displayCommand = tshow @Command
displayKeybinding (Keybinding k mods) = foldMap showMod mods <> showKey k
showMod MCtrl = "Ctrl-"
showMod MShift = "Shift-"
showMod MAlt = "Alt-"
showMod MMeta = "Meta-"
showKey (KChar c) = pack [c]
showKey KEsc = "<Esc>"
showKey KBS = "<Backspace>"
showKey KEnter = "<Enter>"
showKey KLeft = "<Left>"
showKey KRight = "<Right>"
showKey KUp = "<Up>"
showKey KDown = "<Down>"
showKey KUpLeft = "<UpLeft>"
showKey KUpRight = "<UpRight>"
showKey KDownLeft = "<DownLeft>"
showKey KDownRight = "<DownRight>"
showKey KCenter = "<Center>"
showKey (KFun n) = "<F" <> tshow n <> ">"
showKey KBackTab = "<BackTab>"
showKey KPrtScr = "<PrtScr>"
showKey KPause = "<Pause>"
showKey KIns = "<Ins>"
showKey KHome = "<Home>"
showKey KPageUp = "<PageUp>"
showKey KDel = "<Del>"
showKey KEnd = "<End>"
showKey KPageDown = "<PageDown>"
showKey KBegin = "<Begin>"
showKey KMenu = "<Menu>"
drawPanel :: GameState -> Panel -> Widget ResourceName
drawPanel game panel
= border
. hLimit 35
. viewport (Resource.Panel panel) Vertical
$ case panel of
HelpPanel -> drawHelpPanel
InventoryPanel -> drawInventoryPanel game
ItemDescriptionPanel desc -> txtWrap desc
drawCharacterInfo :: Character -> Widget ResourceName
drawCharacterInfo ch = txt " " <+> charName <+> charHitpoints
where
charName | Just n <- ch ^. characterName
= txt $ n <> " "
| otherwise
= emptyWidget
charHitpoints
= txt "Hitpoints: "
<+> txt (tshow $ let Hitpoints hp = characterHitpoints ch in hp)
drawGame :: GameState -> [Widget ResourceName]
drawGame = evalState $ do
game <- get
drawnMap <- drawMap
pure
. pure
. withBorderStyle unicode
$ case game ^. promptState of
NoPrompt -> drawMessages (game ^. messageHistory)
_ -> emptyWidget
<=> drawPromptState (game ^. promptState)
<=>
(maybe emptyWidget (drawPanel game) (game ^. activePanel)
<+> border drawnMap
)
<=> drawCharacterInfo (game ^. character)