module Game.LambdaHack.Client.HumanLocal
(
gameDifficultyCycle
, pickLeaderHuman, memberCycleHuman, memberBackHuman, inventoryHuman
, selectActorHuman, selectNoneHuman, clearHuman, repeatHuman, recordHuman
, historyHuman, markVisionHuman, markSmellHuman, markSuspectHuman
, helpHuman, mainMenuHuman, macroHuman
, moveCursorHuman, tgtFloorHuman, tgtEnemyHuman
, tgtUnknownHuman, tgtItemHuman, tgtStairHuman, tgtAscendHuman
, epsIncrHuman, tgtClearHuman, cancelHuman, acceptHuman
, floorItemOverlay, itemOverlay
, pickLeader, lookAt
) where
import qualified Paths_LambdaHack as Self (version)
import Control.Exception.Assert.Sugar
import Control.Monad
import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES
import Data.Function
import Data.List
import qualified Data.Map.Strict as M
import Data.Maybe
import Data.Monoid
import Data.Ord
import Data.Text (Text)
import qualified Data.Text as T
import Data.Version
import Game.LambdaHack.Frontend (frontendName)
import qualified NLP.Miniutter.English as MU
import Game.LambdaHack.Client.Action
import Game.LambdaHack.Client.Binding
import Game.LambdaHack.Client.State
import Game.LambdaHack.Common.Action
import Game.LambdaHack.Common.Actor
import Game.LambdaHack.Common.ActorState
import Game.LambdaHack.Common.Animation
import qualified Game.LambdaHack.Common.Effect as Effect
import Game.LambdaHack.Common.Faction
import qualified Game.LambdaHack.Common.Feature as F
import qualified Game.LambdaHack.Common.HumanCmd as HumanCmd
import Game.LambdaHack.Common.Item
import qualified Game.LambdaHack.Common.Key as K
import qualified Game.LambdaHack.Common.Kind as Kind
import Game.LambdaHack.Common.Level
import Game.LambdaHack.Common.Msg
import Game.LambdaHack.Common.Perception
import Game.LambdaHack.Common.Point
import Game.LambdaHack.Common.State
import qualified Game.LambdaHack.Common.Tile as Tile
import Game.LambdaHack.Common.Time
import Game.LambdaHack.Common.Vector
import Game.LambdaHack.Content.RuleKind
import Game.LambdaHack.Content.TileKind
failWith :: MonadClientUI m => Msg -> m Slideshow
failWith msg = do
modifyClient $ \cli -> cli {slastKey = Nothing}
stopPlayBack
assert (not $ T.null msg) $ promptToSlideshow msg
gameDifficultyCycle :: MonadClientUI m => m ()
gameDifficultyCycle = do
DebugModeCli{sdifficultyCli} <- getsClient sdebugCli
let d = if sdifficultyCli <= 4 then 4 else sdifficultyCli 1
modifyClient $ \cli -> cli {sdebugCli = (sdebugCli cli) {sdifficultyCli = d}}
msgAdd $ "Next game difficulty set to" <+> tshow (5 d) <> "."
pickLeaderHuman :: MonadClientUI m => Int -> m Slideshow
pickLeaderHuman k = do
side <- getsClient sside
fact <- getsState $ (EM.! side) . sfactionD
s <- getState
case tryFindHeroK s side k of
_ | isSpawnFact fact -> failWith "spawners cannot manually change leaders"
Nothing -> failWith "No such member of the party."
Just (aid, _) -> do
void $ pickLeader aid
return mempty
memberCycleHuman :: MonadClientUI m => m Slideshow
memberCycleHuman = do
side <- getsClient sside
fact <- getsState $ (EM.! side) . sfactionD
leader <- getLeaderUI
body <- getsState $ getActorBody leader
hs <- partyAfterLeader leader
case filter (\(_, b) -> blid b == blid body) hs of
_ | isSpawnFact fact -> failWith "spawners cannot manually change leaders"
[] -> failWith "Cannot pick any other member on this level."
(np, b) : _ -> do
success <- pickLeader np
assert (success `blame` "same leader" `twith` (leader, np, b)) skip
return mempty
partyAfterLeader :: MonadActionRO m => ActorId -> m [(ActorId, Actor)]
partyAfterLeader leader = do
faction <- getsState $ bfid . getActorBody leader
allA <- getsState $ EM.assocs . sactorD
s <- getState
let hs9 = mapMaybe (tryFindHeroK s faction) [0..9]
factionA = filter (\(_, body) ->
not (bproj body) && bfid body == faction) allA
hs = hs9 ++ deleteFirstsBy ((==) `on` fst) factionA hs9
i = fromMaybe (1) $ findIndex ((== leader) . fst) hs
(lt, gt) = (take i hs, drop (i + 1) hs)
return $! gt ++ lt
pickLeader :: MonadClientUI m => ActorId -> m Bool
pickLeader actor = do
leader <- getLeaderUI
stgtMode <- getsClient stgtMode
if leader == actor
then return False
else do
pbody <- getsState $ getActorBody actor
assert (not (bproj pbody) `blame` "projectile chosen as the leader"
`twith` (actor, pbody)) skip
let subject = partActor pbody
msgAdd $ makeSentence [subject, "picked as a leader"]
s <- getState
modifyClient $ updateLeader actor s
case stgtMode of
Nothing -> return ()
Just _ ->
modifyClient $ \cli -> cli {stgtMode = Just $ TgtMode $ blid pbody}
lookMsg <- lookAt False "" True (bpos pbody) actor ""
msgAdd lookMsg
return True
memberBackHuman :: MonadClientUI m => m Slideshow
memberBackHuman = do
side <- getsClient sside
fact <- getsState $ (EM.! side) . sfactionD
leader <- getLeaderUI
hs <- partyAfterLeader leader
case reverse hs of
_ | isSpawnFact fact -> failWith "spawners cannot manually change leaders"
[] -> failWith "No other member in the party."
(np, b) : _ -> do
success <- pickLeader np
assert (success `blame` "same leader" `twith` (leader, np, b)) skip
return mempty
inventoryHuman :: MonadClientUI m => m Slideshow
inventoryHuman = do
leader <- getLeaderUI
subject <- partAidLeader leader
bag <- getsState $ getActorBag leader
invRaw <- getsState $ getActorInv leader
if EM.null bag
then promptToSlideshow $ makeSentence
[ MU.SubjectVerbSg subject "be"
, "not carrying anything" ]
else do
let blurb = makePhrase
[MU.Capitalize $ MU.SubjectVerbSg subject "be carrying:"]
inv = EM.filter (`EM.member` bag) invRaw
io <- itemOverlay bag inv
overlayToSlideshow blurb io
selectActorHuman ::MonadClientUI m => m Slideshow
selectActorHuman = do
mleader <- getsClient _sleader
case mleader of
Nothing -> failWith "no leader picked, cannot select"
Just leader -> do
body <- getsState $ getActorBody leader
wasMemeber <- getsClient $ ES.member leader . sselected
let upd = if wasMemeber
then ES.delete leader
else ES.insert leader
modifyClient $ \cli -> cli {sselected = upd $ sselected cli}
let subject = partActor body
msgAdd $ makeSentence [subject, if wasMemeber
then "deselected"
else "selected"]
return mempty
selectNoneHuman :: (MonadClientUI m, MonadClient m) => m ()
selectNoneHuman = do
side <- getsClient sside
lidV <- viewedLevel
oursAssocs <- getsState $ actorNotProjAssocs (== side) lidV
let ours = ES.fromList $ map fst oursAssocs
oldSel <- getsClient sselected
let wasNone = ES.null $ ES.intersection ours oldSel
upd = if wasNone
then ES.union
else ES.difference
modifyClient $ \cli -> cli {sselected = upd (sselected cli) ours}
let subject = "all party members on the level"
msgAdd $ makeSentence [subject, if wasNone
then "selected"
else "deselected"]
clearHuman :: Monad m => m ()
clearHuman = return ()
repeatHuman :: MonadClient m => Int -> m ()
repeatHuman n = do
(_, seqPrevious, k) <- getsClient slastRecord
let macro = concat $ replicate n $ reverse seqPrevious
modifyClient $ \cli -> cli {slastPlay = macro ++ slastPlay cli}
let slastRecord = ([], [], if k == 0 then 0 else maxK)
modifyClient $ \cli -> cli {slastRecord}
maxK :: Int
maxK = 100
recordHuman :: MonadClientUI m => m Slideshow
recordHuman = do
modifyClient $ \cli -> cli {slastKey = Nothing}
(_seqCurrent, seqPrevious, k) <- getsClient slastRecord
case k of
0 -> do
let slastRecord = ([], [], maxK)
modifyClient $ \cli -> cli {slastRecord}
promptToSlideshow $ "Macro will be recorded for up to"
<+> tshow maxK <+> "steps."
_ -> do
let slastRecord = (seqPrevious, [], 0)
modifyClient $ \cli -> cli {slastRecord}
promptToSlideshow $ "Macro recording interrupted after"
<+> tshow (maxK k 1) <+> "steps."
historyHuman :: MonadClientUI m => m Slideshow
historyHuman = do
history <- getsClient shistory
arena <- getArenaUI
local <- getsState $ getLocalTime arena
global <- getsState stime
let msg = makeSentence
[ "You survived for"
, MU.CarWs (global `timeFit` timeTurn) "half-second turn"
, "(this level:"
, MU.Text (tshow (local `timeFit` timeTurn)) MU.:> ")" ]
<+> "Past messages:"
overlayToBlankSlideshow msg $ renderHistory history
markVisionHuman :: MonadClientUI m => m ()
markVisionHuman = do
modifyClient toggleMarkVision
cur <- getsClient smarkVision
msgAdd $ "Visible area display toggled" <+> if cur then "on." else "off."
markSmellHuman :: MonadClientUI m => m ()
markSmellHuman = do
modifyClient toggleMarkSmell
cur <- getsClient smarkSmell
msgAdd $ "Smell display toggled" <+> if cur then "on." else "off."
markSuspectHuman :: MonadClientUI m => m ()
markSuspectHuman = do
modifyClient $ \cli -> cli {sbfsD = EM.empty}
modifyClient toggleMarkSuspect
cur <- getsClient smarkSuspect
msgAdd $ "Suspect terrain display toggled" <+> if cur then "on." else "off."
helpHuman :: MonadClientUI m => m Slideshow
helpHuman = do
keyb <- askBinding
return $! keyHelp keyb
mainMenuHuman :: MonadClientUI m => m Slideshow
mainMenuHuman = do
Kind.COps{corule} <- getsState scops
Binding{brevMap, bcmdList} <- askBinding
scurDifficulty <- getsClient scurDifficulty
DebugModeCli{sdifficultyCli} <- getsClient sdebugCli
let stripFrame t = map (T.tail . T.init) $ tail . init $ T.lines t
pasteVersion art =
let pathsVersion = rpathsVersion $ Kind.stdRuleset corule
version = " Version " ++ showVersion pathsVersion
++ " (frontend: " ++ frontendName
++ ", engine: LambdaHack " ++ showVersion Self.version
++ ") "
versionLen = length version
in init art ++ [take (80 versionLen) (last art) ++ version]
kds =
let showKD cmd km = (K.showKM km, HumanCmd.cmdDescription cmd)
revLookup cmd = maybe ("", "") (showKD cmd) $ M.lookup cmd brevMap
cmds = [ (K.showKM km, desc)
| (km, (desc, HumanCmd.CmdMenu, cmd)) <- bcmdList,
cmd /= HumanCmd.GameDifficultyCycle ]
in [ (fst (revLookup HumanCmd.Cancel), "back to playing")
, (fst (revLookup HumanCmd.Accept), "see more help") ]
++ cmds
++ [ (fst ( revLookup HumanCmd.GameDifficultyCycle)
, "next game difficulty"
<+> tshow (5 sdifficultyCli)
<+> "(current"
<+> tshow (5 scurDifficulty) <> ")" ) ]
bindingLen = 25
bindings =
let fmt (k, d) = T.justifyLeft bindingLen ' '
$ T.justifyLeft 7 ' ' k <> " " <> d
in map fmt kds
overwrite =
let over [] line = ([], T.pack line)
over bs@(binding : bsRest) line =
let (prefix, lineRest) = break (=='{') line
(braces, suffix) = span (=='{') lineRest
in if length braces == 25
then (bsRest, T.pack prefix <> binding
<> T.drop (T.length binding bindingLen)
(T.pack suffix))
else (bs, T.pack line)
in snd . mapAccumL over bindings
mainMenuArt = rmainMenuArt $ Kind.stdRuleset corule
menuOverlay =
overwrite $ pasteVersion $ map T.unpack $ stripFrame mainMenuArt
case menuOverlay of
[] -> assert `failure` "empty Main Menu overlay" `twith` mainMenuArt
hd : tl -> overlayToBlankSlideshow hd (toOverlay tl)
macroHuman :: MonadClient m => [String] -> m ()
macroHuman kms =
modifyClient $ \cli -> cli {slastPlay = map K.mkKM kms ++ slastPlay cli}
moveCursorHuman :: MonadClientUI m => Vector -> Int -> m Slideshow
moveCursorHuman dir n = do
leader <- getLeaderUI
stgtMode <- getsClient stgtMode
let lidV = maybe (assert `failure` leader) tgtLevelId stgtMode
Level{lxsize, lysize} <- getLevel lidV
lpos <- getsState $ bpos . getActorBody leader
scursor <- getsClient scursor
cursorPos <- cursorToPos
let cpos = fromMaybe lpos cursorPos
shiftB pos = shiftBounded lxsize lysize pos dir
newPos = iterate shiftB cpos !! n
if newPos == cpos then failWith "never mind"
else do
let tgt = case scursor of
TVector{} -> TVector $ displacement lpos newPos
_ -> TPoint lidV newPos
modifyClient $ \cli -> cli {scursor = tgt}
doLook
lookAt :: MonadClientUI m
=> Bool
-> Text
-> Bool
-> Point
-> ActorId
-> Text
-> m Text
lookAt detailed tilePrefix canSee pos aid msg = do
Kind.COps{coitem, cotile=cotile@Kind.Ops{okind}} <- getsState scops
lidV <- viewedLevel
lvl <- getLevel lidV
subject <- partAidLeader aid
s <- getState
let is = lvl `atI` pos
verb = MU.Text $ if canSee then "notice" else "remember"
disco <- getsClient sdisco
let nWs (iid, k) = partItemWs coitem disco k (getItemBody iid s)
isd = case detailed of
_ | EM.size is == 0 -> ""
_ | EM.size is <= 2 ->
makeSentence [ MU.SubjectVerbSg subject verb
, MU.WWandW $ map nWs $ EM.assocs is]
True -> "Objects:"
_ -> "Objects here."
tile = lvl `at` pos
obscured | tile /= hideTile cotile lvl pos = "partially obscured"
| otherwise = ""
tileText = obscured <+> tname (okind tile)
tilePart | T.null tilePrefix = MU.Text tileText
| otherwise = MU.AW $ MU.Text tileText
tileDesc = [MU.Text tilePrefix, tilePart]
if not (null (Tile.causeEffects cotile tile)) then
return $! makeSentence ("activable:" : tileDesc)
<+> msg <+> isd
else if detailed then
return $! makeSentence tileDesc
<+> msg <+> isd
else return $! msg <+> isd
doLook :: MonadClientUI m => m Slideshow
doLook = do
leader <- getLeaderUI
stgtMode <- getsClient stgtMode
let lidV = maybe (assert `failure` leader) tgtLevelId stgtMode
lvl <- getLevel lidV
cursorPos <- cursorToPos
per <- getPerFid lidV
b <- getsState $ getActorBody leader
let p = fromMaybe (bpos b) cursorPos
canSee = ES.member p (totalVisible per)
inhabitants <- if canSee
then getsState $ posToActors p lidV
else return []
aims <- actorAimsPos leader p
let enemyMsg = case inhabitants of
[] -> ""
_ ->
let subjects = map (partActor . snd . fst) inhabitants
subject = MU.WWandW subjects
verb = "be here"
in makeSentence [MU.SubjectVerbSg subject verb]
vis | not canSee = "you cannot see"
| not aims = "you cannot penetrate"
| otherwise = "you see"
lookMsg <- lookAt True vis canSee p leader enemyMsg
modifyClient $ \cli -> cli {slastKey = Nothing}
let is = lvl `atI` p
if EM.size is <= 2 then
promptToSlideshow lookMsg
else do
io <- floorItemOverlay is
overlayToSlideshow lookMsg io
floorItemOverlay :: MonadClient m => ItemBag -> m Overlay
floorItemOverlay bag = do
Kind.COps{coitem} <- getsState scops
s <- getState
disco <- getsClient sdisco
let is = zip (EM.assocs bag) (allLetters ++ repeat (InvChar ' '))
pr ((iid, k), l) =
makePhrase [ letterLabel l
, partItemWs coitem disco k (getItemBody iid s) ]
<> " "
return $! toOverlay $ map pr is
itemOverlay :: MonadClient m => ItemBag -> ItemInv -> m Overlay
itemOverlay bag inv = do
Kind.COps{coitem} <- getsState scops
s <- getState
disco <- getsClient sdisco
let pr (l, iid) =
makePhrase [ letterLabel l
, partItemWs coitem disco (bag EM.! iid)
(getItemBody iid s) ]
<> " "
return $! toOverlay $ map pr $ EM.assocs inv
tgtFloorHuman :: MonadClientUI m => m Slideshow
tgtFloorHuman = do
lidV <- viewedLevel
leader <- getLeaderUI
lpos <- getsState $ bpos . getActorBody leader
cursorPos <- cursorToPos
scursor <- getsClient scursor
stgtMode <- getsClient stgtMode
side <- getsClient sside
fact <- getsState $ (EM.! side) . sfactionD
bfs <- getCacheBfs leader
bsAll <- getsState $ actorAssocs (const True) lidV
let cursor = fromMaybe lpos cursorPos
tgt = case scursor of
_ | isNothing stgtMode ->
scursor
TEnemy a False -> TEnemy a True
TEnemy{} -> TPoint lidV cursor
TEnemyPos{} -> TPoint lidV cursor
TPoint{} -> TVector $ displacement lpos cursor
TVector{} ->
let isEnemy b = isAtWar fact (bfid b)
&& not (bproj b)
&& posAimsPos bfs lpos (bpos b)
in case find (\(_, m) -> Just (bpos m) == cursorPos) bsAll of
Just (im, m) | isEnemy m -> TEnemy im False
Just (im, _) -> TEnemy im True
Nothing -> TPoint lidV cursor
modifyClient $ \cli -> cli {scursor = tgt, stgtMode = Just $ TgtMode lidV}
doLook
tgtEnemyHuman :: MonadClientUI m => m Slideshow
tgtEnemyHuman = do
lidV <- viewedLevel
leader <- getLeaderUI
lpos <- getsState $ bpos . getActorBody leader
cursorPos <- cursorToPos
scursor <- getsClient scursor
stgtMode <- getsClient stgtMode
side <- getsClient sside
fact <- getsState $ (EM.! side) . sfactionD
bfs <- getCacheBfs leader
bsAll <- getsState $ actorAssocs (const True) lidV
let ordPos (_, b) = (chessDist lpos $ bpos b, bpos b)
dbs = sortBy (comparing ordPos) bsAll
pickUnderCursor =
let i = fromMaybe (1)
$ findIndex ((== cursorPos) . Just . bpos . snd) dbs
in splitAt i dbs
(permitAnyActor, (lt, gt)) = case scursor of
TEnemy a permit | isJust stgtMode ->
let i = fromMaybe (1) $ findIndex ((== a) . fst) dbs
in (permit, splitAt (i + 1) dbs)
TEnemy a permit ->
let i = fromMaybe (1) $ findIndex ((== a) . fst) dbs
in (permit, splitAt i dbs)
TEnemyPos _ _ _ permit -> (permit, pickUnderCursor)
_ -> (False, pickUnderCursor)
gtlt = gt ++ lt
isEnemy b = isAtWar fact (bfid b)
&& not (bproj b)
&& posAimsPos bfs lpos (bpos b)
lf = filter (isEnemy . snd) gtlt
tgt | permitAnyActor = case gtlt of
(a, _) : _ -> TEnemy a True
[] -> scursor
| otherwise = case lf of
(a, _) : _ -> TEnemy a False
[] -> scursor
modifyClient $ \cli -> cli {scursor = tgt, stgtMode = Just $ TgtMode lidV}
doLook
tgtUnknownHuman :: MonadClientUI m => m Slideshow
tgtUnknownHuman = do
leader <- getLeaderUI
b <- getsState $ getActorBody leader
mpos <- closestUnknown leader
case mpos of
Nothing -> failWith "no more unknown spots left"
Just p -> do
let tgt = TPoint (blid b) p
modifyClient $ \cli -> cli {scursor = tgt}
return mempty
tgtItemHuman :: MonadClientUI m => m Slideshow
tgtItemHuman = do
leader <- getLeaderUI
b <- getsState $ getActorBody leader
items <- closestItems leader
case items of
[] -> failWith "no more items remembered or visible"
(_, (p, _)) : _ -> do
let tgt = TPoint (blid b) p
modifyClient $ \cli -> cli {scursor = tgt}
return mempty
tgtStairHuman :: MonadClientUI m => Bool -> m Slideshow
tgtStairHuman up = do
leader <- getLeaderUI
b <- getsState $ getActorBody leader
stairs <- closestTriggers (Just up) False leader
case stairs of
[] -> failWith $ "no stairs"
<+> if up then "up" else "down"
p : _ -> do
let tgt = TPoint (blid b) p
modifyClient $ \cli -> cli {scursor = tgt}
return mempty
tgtAscendHuman :: MonadClientUI m => Int -> m Slideshow
tgtAscendHuman k = do
Kind.COps{cotile=cotile@Kind.Ops{okind}} <- getsState scops
dungeon <- getsState sdungeon
scursorOld <- getsClient scursor
cursorPos <- cursorToPos
lidV <- viewedLevel
lvl <- getLevel lidV
let rightStairs = case cursorPos of
Nothing -> Nothing
Just cpos ->
let tile = lvl `at` cpos
in if Tile.hasFeature cotile (F.Cause $ Effect.Ascend k) tile
then Just cpos
else Nothing
case rightStairs of
Just cpos -> do
(nln, npos) <- getsState $ whereTo lidV cpos k . sdungeon
assert (nln /= lidV `blame` "stairs looped" `twith` nln) skip
let ascDesc (F.Cause (Effect.Ascend _)) = True
ascDesc _ = False
scursor =
if any ascDesc $ tfeature $ okind (lvl `at` npos)
then TPoint nln npos
else scursorOld
modifyClient $ \cli -> cli {scursor, stgtMode = Just (TgtMode nln)}
doLook
Nothing ->
case ascendInBranch dungeon k lidV of
[] -> failWith "no more levels in this direction"
nln : _ -> do
modifyClient $ \cli -> cli {stgtMode = Just (TgtMode nln)}
doLook
epsIncrHuman :: MonadClientUI m => Bool -> m Slideshow
epsIncrHuman b = do
stgtMode <- getsClient stgtMode
if isJust stgtMode
then do
modifyClient $ \cli -> cli {seps = seps cli + if b then 1 else 1}
return mempty
else failWith "never mind"
tgtClearHuman :: MonadClient m => m ()
tgtClearHuman = do
mleader <- getsClient _sleader
case mleader of
Nothing -> return ()
Just leader -> do
tgt <- getsClient $ getTarget leader
case tgt of
Just _ -> modifyClient $ updateTarget leader (const Nothing)
Nothing -> do
scursorOld <- getsClient scursor
b <- getsState $ getActorBody leader
let scursor = case scursorOld of
TEnemy _ permit -> TEnemy leader permit
TEnemyPos _ _ _ permit -> TEnemy leader permit
TPoint{} -> TPoint (blid b) (bpos b)
TVector{} -> TVector (Vector 0 0)
modifyClient $ \cli -> cli {scursor}
cancelHuman :: MonadClientUI m => m Slideshow -> m Slideshow
cancelHuman h = do
stgtMode <- getsClient stgtMode
if isJust stgtMode
then targetReject
else h
targetReject :: MonadClientUI m => m Slideshow
targetReject = do
modifyClient $ \cli -> cli {stgtMode = Nothing}
failWith "targeting canceled"
acceptHuman :: MonadClientUI m => m Slideshow -> m Slideshow
acceptHuman h = do
stgtMode <- getsClient stgtMode
if isJust stgtMode
then do
targetAccept
return mempty
else h
targetAccept :: MonadClientUI m => m ()
targetAccept = do
endTargeting
endTargetingMsg
modifyClient $ \cli -> cli {stgtMode = Nothing}
endTargeting :: MonadClientUI m => m ()
endTargeting = do
leader <- getLeaderUI
scursor <- getsClient scursor
modifyClient $ updateTarget leader $ const $ Just scursor
endTargetingMsg :: MonadClientUI m => m ()
endTargetingMsg = do
leader <- getLeaderUI
targetMsg <- targetDescLeader leader
subject <- partAidLeader leader
msgAdd $ makeSentence [MU.SubjectVerbSg subject "target", MU.Text targetMsg]