{-# LANGUAGE TupleSections #-}
-- | Let AI pick the best target for an actor.
module Game.LambdaHack.Client.AI.PickTargetM
  ( refreshTarget
#ifdef EXPOSE_INTERNAL
    -- * Internal operations
  , computeTarget
#endif
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import qualified Data.EnumMap.Strict as EM
import qualified Data.EnumSet as ES

import           Game.LambdaHack.Client.AI.ConditionM
import           Game.LambdaHack.Client.Bfs
import           Game.LambdaHack.Client.BfsM
import           Game.LambdaHack.Client.CommonM
import           Game.LambdaHack.Client.MonadClient
import           Game.LambdaHack.Client.State
import           Game.LambdaHack.Common.Actor
import           Game.LambdaHack.Common.ActorState
import           Game.LambdaHack.Common.Faction
import           Game.LambdaHack.Common.Item
import           Game.LambdaHack.Common.Kind
import           Game.LambdaHack.Common.Level
import           Game.LambdaHack.Common.MonadStateRead
import           Game.LambdaHack.Common.Point
import qualified Game.LambdaHack.Common.PointArray as PointArray
import           Game.LambdaHack.Common.State
import qualified Game.LambdaHack.Common.Tile as Tile
import           Game.LambdaHack.Common.Time
import           Game.LambdaHack.Common.Types
import           Game.LambdaHack.Common.Vector
import qualified Game.LambdaHack.Content.CaveKind as CK
import           Game.LambdaHack.Content.FactionKind
import           Game.LambdaHack.Content.RuleKind
import           Game.LambdaHack.Content.TileKind (isUknownSpace)
import           Game.LambdaHack.Core.Frequency
import           Game.LambdaHack.Core.Random
import qualified Game.LambdaHack.Definition.Ability as Ability

-- | Verify and possibly change the target of an actor. This function both
-- updates the target in the client state and returns the new target explicitly.
refreshTarget :: MonadClient m
              => [(ActorId, Actor)] -> [(ActorId, Actor)] -> (ActorId, Actor)
              -> m (Maybe TgtAndPath)
refreshTarget :: forall (m :: * -> *).
MonadClient m =>
[(ActorId, Actor)]
-> [(ActorId, Actor)] -> (ActorId, Actor) -> m (Maybe TgtAndPath)
refreshTarget [(ActorId, Actor)]
foeAssocs [(ActorId, Actor)]
friendAssocs (ActorId
aid, Actor
body) = do
  side <- (StateClient -> FactionId) -> m FactionId
forall a. (StateClient -> a) -> m a
forall (m :: * -> *) a.
MonadClientRead m =>
(StateClient -> a) -> m a
getsClient StateClient -> FactionId
sside
  let !_A = Bool -> () -> ()
forall a. (?callStack::CallStack) => Bool -> a -> a
assert (Actor -> FactionId
bfid Actor
body FactionId -> FactionId -> Bool
forall a. Eq a => a -> a -> Bool
== FactionId
side
                    Bool -> (String, (ActorId, Actor, FactionId)) -> Bool
forall v. Show v => Bool -> v -> Bool
`blame` String
"AI tries to move an enemy actor"
                    String
-> (ActorId, Actor, FactionId)
-> (String, (ActorId, Actor, FactionId))
forall v. String -> v -> (String, v)
`swith` (ActorId
aid, Actor
body, FactionId
side)) ()
  let !_A = Bool -> () -> ()
forall a. (?callStack::CallStack) => Bool -> a -> a
assert (Bool -> Bool
not (Actor -> Bool
bproj Actor
body)
                    Bool -> (String, (ActorId, Actor, FactionId)) -> Bool
forall v. Show v => Bool -> v -> Bool
`blame` String
"AI gets to manually move its projectiles"
                    String
-> (ActorId, Actor, FactionId)
-> (String, (ActorId, Actor, FactionId))
forall v. String -> v -> (String, v)
`swith` (ActorId
aid, Actor
body, FactionId
side)) ()
  mtarget <- computeTarget foeAssocs friendAssocs aid
  case mtarget of
    Maybe TgtAndPath
Nothing -> do
      -- Melee in progress and the actor can't contribute
      -- and would slow down others if he acted.
      -- Or he's just asleep.
      (StateClient -> StateClient) -> m ()
forall (m :: * -> *).
MonadClient m =>
(StateClient -> StateClient) -> m ()
modifyClient ((StateClient -> StateClient) -> m ())
-> (StateClient -> StateClient) -> m ()
forall a b. (a -> b) -> a -> b
$ \StateClient
cli -> StateClient
cli {stargetD = EM.delete aid (stargetD cli)}
      Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe TgtAndPath
forall a. Maybe a
Nothing
    Just TgtAndPath
tgtMPath -> do
      -- _debugoldTgt <- getsClient $ EM.lookup aid . stargetD
      (StateClient -> StateClient) -> m ()
forall (m :: * -> *).
MonadClient m =>
(StateClient -> StateClient) -> m ()
modifyClient ((StateClient -> StateClient) -> m ())
-> (StateClient -> StateClient) -> m ()
forall a b. (a -> b) -> a -> b
$ \StateClient
cli ->
        StateClient
cli {stargetD = EM.insert aid tgtMPath (stargetD cli)}
      Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe TgtAndPath
mtarget
      -- let _debug = T.unpack
      --       $ "\nHandleAI symbol:"    <+> tshow (bsymbol body)
      --       <> ", aid:"               <+> tshow aid
      --       <> ", pos:"               <+> tshow (bpos body)
      --       <> "\nHandleAI oldTgt:"   <+> tshow _debugoldTgt
      --       <> "\nHandleAI strTgt:"   <+> tshow stratTarget
      --       <> "\nHandleAI target:"   <+> tshow tgtMPath
      -- trace _debug $ return $ Just tgtMPath

computeTarget :: forall m. MonadClient m
              => [(ActorId, Actor)] -> [(ActorId, Actor)] -> ActorId
              -> m (Maybe TgtAndPath)
computeTarget :: forall (m :: * -> *).
MonadClient m =>
[(ActorId, Actor)]
-> [(ActorId, Actor)] -> ActorId -> m (Maybe TgtAndPath)
computeTarget [(ActorId, Actor)]
foeAssocs [(ActorId, Actor)]
friendAssocs ActorId
aid = do
  cops@COps{cocave, corule=RuleContent{rWidthMax, rHeightMax, rnearby}, coTileSpeedup}
    <- (State -> COps) -> m COps
forall a. (State -> a) -> m a
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState State -> COps
scops
  b <- getsState $ getActorBody aid
  mleader <- getsClient sleader
  salter <- getsClient salter
  -- We assume the actor eventually becomes a leader (or has the same
  -- set of skills as the leader, anyway) and set his target accordingly.
  actorMaxSkills <- getsState sactorMaxSkills
  condInMelee <- condInMeleeM $ blid b
  let lalter = AlterLid
salter AlterLid -> LevelId -> Array Word8
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> LevelId
blid Actor
b
      actorMaxSk = ActorMaxSkills
actorMaxSkills ActorMaxSkills -> ActorId -> Skills
forall k a. Enum k => EnumMap k a -> k -> a
EM.! ActorId
aid
      alterSkill = Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkAlter Skills
actorMaxSk
  lvl <- getLevel $ blid b
  localTime <- getsState $ getLocalTime (blid b)
  let stepAccesible :: [Point] -> Bool
      stepAccesible (Point
q : [Point]
_) =
        -- Effectively, only @alterMinWalk@ is checked, because real altering
        -- is not done via target path, but action after end of path.
        Int
alterSkill Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8 -> Int
forall a. Enum a => a -> Int
fromEnum (Array Word8
lalter Array Word8 -> Point -> Word8
forall c. UnboxRepClass c => Array c -> Point -> c
PointArray.! Point
q)
      stepAccesible [] = Bool
False
  mtgtMPath <- getsClient $ EM.lookup aid . stargetD
  oldTgtUpdatedPath <- case mtgtMPath of
    Just TgtAndPath{Target
tapTgt :: Target
tapTgt :: TgtAndPath -> Target
tapTgt,tapPath :: TgtAndPath -> Maybe AndPath
tapPath=Maybe AndPath
Nothing} ->
      -- This case is especially for TEnemyPos that would be lost otherwise.
      -- This is also triggered by @UpdLeadFaction@.
      TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just (TgtAndPath -> Maybe TgtAndPath)
-> m TgtAndPath -> m (Maybe TgtAndPath)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ActorId -> Target -> m TgtAndPath
forall (m :: * -> *).
MonadClient m =>
ActorId -> Target -> m TgtAndPath
createPath ActorId
aid Target
tapTgt
    Just tap :: TgtAndPath
tap@TgtAndPath{Target
tapTgt :: TgtAndPath -> Target
tapTgt :: Target
tapTgt,tapPath :: TgtAndPath -> Maybe AndPath
tapPath=Just AndPath{Int
[Point]
Point
pathSource :: Point
pathList :: [Point]
pathGoal :: Point
pathLen :: Int
pathLen :: AndPath -> Int
pathGoal :: AndPath -> Point
pathList :: AndPath -> [Point]
pathSource :: AndPath -> Point
..}} -> do
      mvalidPos <- (State -> Maybe Point) -> m (Maybe Point)
forall a. (State -> a) -> m a
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Maybe Point) -> m (Maybe Point))
-> (State -> Maybe Point) -> m (Maybe Point)
forall a b. (a -> b) -> a -> b
$ Maybe ActorId -> LevelId -> Maybe Target -> State -> Maybe Point
aidTgtToPos (ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
aid) (Actor -> LevelId
blid Actor
b) (Target -> Maybe Target
forall a. a -> Maybe a
Just Target
tapTgt)
      return $!
        if | isNothing mvalidPos -> Nothing  -- wrong level
           | bpos b == pathGoal ->
               mtgtMPath  -- goal reached; stay there picking up items
                          -- or hiding in ambush or in panic
           | pathSource == bpos b ->  -- no move
               -- If next step not accessible, something serious happened,
               -- so reconsider the target, not only path.
               if stepAccesible pathList then mtgtMPath else Nothing
           | otherwise -> case break (== bpos b) pathList of
               ([Point]
crossed, Point
_ : [Point]
rest) ->  -- step or many steps along path
                 if [Point] -> Bool
forall a. [a] -> Bool
null [Point]
rest
                 then Maybe TgtAndPath
forall a. Maybe a
Nothing  -- path to the goal was partial, so tiles
                               -- discovered or altered, so reconsider target
                 else let newPath :: AndPath
newPath =
                            AndPath{ pathSource :: Point
pathSource = Actor -> Point
bpos Actor
b
                                   , pathList :: [Point]
pathList = [Point]
rest
                                   , Point
pathGoal :: Point
pathGoal :: Point
pathGoal
                                   , pathLen :: Int
pathLen = Int
pathLen Int -> Int -> Int
forall a. Num a => a -> a -> a
- [Point] -> Int
forall a. [a] -> Int
length [Point]
crossed Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1 }
                      in if [Point] -> Bool
stepAccesible [Point]
rest
                         then TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap{tapPath=Just newPath}
                         else Maybe TgtAndPath
forall a. Maybe a
Nothing
               ([Point]
_, []) -> Maybe TgtAndPath
forall a. Maybe a
Nothing  -- veered off the path, e.g., due to push
                                   -- by enemy or congestion, so serious,
                                   -- so reconsider target, not only path
    Maybe TgtAndPath
Nothing -> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe TgtAndPath
forall a. Maybe a
Nothing  -- no target assigned yet
  factionD <- getsState sfactionD
  seps <- getsClient seps
  let fact = FactionDict
factionD FactionDict -> FactionId -> Faction
forall k a. Enum k => EnumMap k a -> k -> a
EM.! Actor -> FactionId
bfid Actor
b
      slackDoctrine = Faction -> Doctrine
gdoctrine Faction
fact
                      Doctrine -> [Doctrine] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ Doctrine
Ability.TMeleeAndRanged, Doctrine
Ability.TMeleeAdjacent
                             , Doctrine
Ability.TBlock, Doctrine
Ability.TRoam, Doctrine
Ability.TPatrol ]
      canMove = Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkMove Skills
actorMaxSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
      canReach = Bool
canMove
                 Bool -> Bool -> Bool
|| Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkDisplace Skills
actorMaxSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
                 -- Needed for now, because AI targets and shoots enemies
                 -- based on the path to them, not LOS to them:
                 Bool -> Bool -> Bool
|| Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkProject Skills
actorMaxSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
      canAlter = Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkAlter Skills
actorMaxSk
                 Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= if Bool
slackDoctrine then Int
2 else Int
4
      canMoveItem = Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkMoveItem Skills
actorMaxSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
      calmE = Actor -> Skills -> Bool
calmEnough Actor
b Skills
actorMaxSk
      heavilyDistressed =  -- actor hit by a proj or similarly distressed
        ResDelta -> Bool
deltasSerious (Actor -> ResDelta
bcalmDelta Actor
b)
  -- Speedup compared to @currentSkillsClient@.
  actorMinSk <- getsState $ actorCurrentSkills Nothing aid
  condCanProject <-
    condCanProjectM (Ability.getSk Ability.SkProject actorMaxSk) aid
  fleeD <- getsClient sfleeD
  let condCanMelee = ActorMaxSkills -> ActorId -> Actor -> Bool
actorCanMelee ActorMaxSkills
actorMaxSkills ActorId
aid Actor
b
      condHpTooLow = Actor -> Skills -> Bool
hpTooLow Actor
b Skills
actorMaxSk
      mfled = ActorId
aid ActorId -> EnumMap ActorId (Point, Time) -> Maybe (Point, Time)
forall k a. Enum k => k -> EnumMap k a -> Maybe a
`EM.lookup` EnumMap ActorId (Point, Time)
fleeD
      recentlyFled =
        Bool -> ((Point, Time) -> Bool) -> Maybe (Point, Time) -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (\(Point
_, Time
time) -> Time -> Time -> Bool
timeRecent5 Time
localTime Time
time) Maybe (Point, Time)
mfled
      recentlyFled20 =
        Bool -> ((Point, Time) -> Bool) -> Maybe (Point, Time) -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (\(Point
_, Time
time) -> Time -> Time -> Bool
timeRecent5 Time
localTime Time
time) Maybe (Point, Time)
mfled
      actorTurn = Speed -> Delta Time
ticksPerMeter (Speed -> Delta Time) -> Speed -> Delta Time
forall a b. (a -> b) -> a -> b
$ Skills -> Speed
gearSpeed Skills
actorMaxSk
  let canEscape = FactionKind -> Bool
fcanEscape (Faction -> FactionKind
gkind Faction
fact)
      canSmell = Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkSmell Skills
actorMaxSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
      meleeNearby | Bool
canEscape = Int
rnearby Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
2
                  | Bool
otherwise = Int
rnearby
      rangedNearby = Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
meleeNearby
      -- We do target foes that already attack ours or have benign weapons.
      -- We assume benign weapons run out if they are the sole cause
      -- of targeting, to avoid stalemate.
      worthTargeting ActorId
aidE Actor
body =
        let attacksFriends :: Bool
attacksFriends =
              ((ActorId, Actor) -> Bool) -> [(ActorId, Actor)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Point -> Point -> Bool
adjacent (Actor -> Point
bpos Actor
body) (Point -> Bool)
-> ((ActorId, Actor) -> Point) -> (ActorId, Actor) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Actor -> Point
bpos (Actor -> Point)
-> ((ActorId, Actor) -> Actor) -> (ActorId, Actor) -> Point
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ActorId, Actor) -> Actor
forall a b. (a, b) -> b
snd) [(ActorId, Actor)]
friendAssocs
              Bool -> Bool -> Bool
&& ActorMaxSkills -> ActorId -> Actor -> Bool
actorCanMeleeToHarm ActorMaxSkills
actorMaxSkills ActorId
aidE Actor
body
        in Bool
attacksFriends
           Bool -> Bool -> Bool
|| Actor -> Int
bweapBenign Actor
body Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
           Bool -> Bool -> Bool
|| ActorMaxSkills -> ActorId -> Actor -> Bool
actorWorthChasing ActorMaxSkills
actorMaxSkills ActorId
aidE Actor
body
      targetableMelee Actor
body =
        let attacksFriends :: Bool
attacksFriends =
              ((ActorId, Actor) -> Bool) -> [(ActorId, Actor)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Point -> Point -> Bool
adjacent (Actor -> Point
bpos Actor
body) (Point -> Bool)
-> ((ActorId, Actor) -> Point) -> (ActorId, Actor) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Actor -> Point
bpos (Actor -> Point)
-> ((ActorId, Actor) -> Actor) -> (ActorId, Actor) -> Point
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ActorId, Actor) -> Actor
forall a b. (a, b) -> b
snd) [(ActorId, Actor)]
friendAssocs
            -- 3 is
            -- 1 from condSupport1
            -- + 2 from foe being 2 away from friend before he closed in
            -- + 1 for as a margin for ambush, given than actors exploring
            -- can't physically keep adjacent all the time
            n :: Int
n | Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkAggression Skills
actorMaxSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
2
              = Int
rangedNearby
                  -- boss never waits
              | Bool
condInMelee = if Bool
attacksFriends then Int
8 else Int
4
                  -- attack even if foe not in melee, to create another
                  -- skirmish and perhaps overwhelm them in this one;
                  -- also, this looks more natural; also sometimes the foe
                  -- would attack our friend in a couple of turns anyway,
                  -- but we may be too far from him at that time
              | Bool
otherwise = Int
meleeNearby
        in Bool
canMove
           Bool -> Bool -> Bool
&& Bool
condCanMelee
           Bool -> Bool -> Bool
&& Point -> Point -> Int
chessDist (Actor -> Point
bpos Actor
body) (Actor -> Point
bpos Actor
b) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
n
      -- Even when missiles run out, the non-moving foe will still be
      -- targeted, which is fine, since he is weakened by ranged, so should be
      -- meleed ASAP, even if without friends.
      targetableRanged Actor
body =
        (Bool -> Bool
not Bool
condInMelee Bool -> Bool -> Bool
|| Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkAggression Skills
actorMaxSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
2)
          -- boss fires at will
        Bool -> Bool -> Bool
&& Point -> Point -> Int
chessDist (Actor -> Point
bpos Actor
body) (Actor -> Point
bpos Actor
b) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
rangedNearby
        Bool -> Bool -> Bool
&& Bool
condCanProject
        Bool -> Bool -> Bool
&& (Bool
canMove Bool -> Bool -> Bool
|| Actor -> Bool
targetableLine Actor
body)
              -- prevent the exploit of using cover against non-moving shooters
              -- causing them to ignore any other distant foes
      targetableLine Actor
body = Maybe Int -> Bool
forall a. Maybe a -> Bool
isJust (Maybe Int -> Bool) -> Maybe Int -> Bool
forall a b. (a -> b) -> a -> b
$ Bool -> Actor -> Point -> Int -> COps -> Level -> Maybe Int
makeLine Bool
False Actor
b (Actor -> Point
bpos Actor
body) Int
seps COps
cops Level
lvl
      targetableEnemy (ActorId
aidE, Actor
body) = ActorId -> Actor -> Bool
worthTargeting ActorId
aidE Actor
body
                                     Bool -> Bool -> Bool
&& (Point -> Point -> Bool
adjacent (Actor -> Point
bpos Actor
body) (Actor -> Point
bpos Actor
b)
                                            -- target regardless of anything,
                                            -- e.g., to flee if helpless
                                         Bool -> Bool -> Bool
|| Actor -> Bool
targetableMelee Actor
body
                                         Bool -> Bool -> Bool
|| Actor -> Bool
targetableRanged Actor
body)
      targetableFoes = ((ActorId, Actor) -> Bool)
-> [(ActorId, Actor)] -> [(ActorId, Actor)]
forall a. (a -> Bool) -> [a] -> [a]
filter (ActorId, Actor) -> Bool
targetableEnemy [(ActorId, Actor)]
foeAssocs
      canMeleeEnemy (ActorId
aidE, Actor
body) = ActorMaxSkills -> ActorId -> Actor -> Bool
actorCanMeleeToHarm ActorMaxSkills
actorMaxSkills ActorId
aidE Actor
body
      nearbyFoes = if Bool
recentlyFled Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
condInMelee
                   then ((ActorId, Actor) -> Bool)
-> [(ActorId, Actor)] -> [(ActorId, Actor)]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool)
-> ((ActorId, Actor) -> Bool) -> (ActorId, Actor) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ActorId, Actor) -> Bool
canMeleeEnemy) [(ActorId, Actor)]
targetableFoes
                   else [(ActorId, Actor)]
targetableFoes
  discoBenefit <- getsClient sdiscoBenefit
  getKind <- getsState $ flip getIidKind
  getArItem <- getsState $ flip aspectRecordFromIid
  cstashes <- if canMove
                 && (calmE || null nearbyFoes) -- danger or risk of defecting
                 && not heavilyDistressed
                 && gunderAI fact  -- humans target any stashes explicitly
              then closestStashes aid
              else return []
  let desirableIid (ItemId
iid, (Int
k, ItemTimers
_)) =
        let Benefit{Double
benPickup :: Double
benPickup :: Benefit -> Double
benPickup} = DiscoveryBenefit
discoBenefit DiscoveryBenefit -> ItemId -> Benefit
forall k a. Enum k => EnumMap k a -> k -> a
EM.! ItemId
iid
        in COps -> Bool -> Double -> AspectRecord -> ItemKind -> Int -> Bool
desirableItem COps
cops Bool
canEscape Double
benPickup
                         (ItemId -> AspectRecord
getArItem ItemId
iid) (ItemId -> ItemKind
getKind ItemId
iid) Int
k
      desirableBagFloor ItemBag
bag = ((ItemId, (Int, ItemTimers)) -> Bool)
-> [(ItemId, (Int, ItemTimers))] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (ItemId, (Int, ItemTimers)) -> Bool
desirableIid ([(ItemId, (Int, ItemTimers))] -> Bool)
-> [(ItemId, (Int, ItemTimers))] -> Bool
forall a b. (a -> b) -> a -> b
$ ItemBag -> [(ItemId, (Int, ItemTimers))]
forall k a. Enum k => EnumMap k a -> [(k, a)]
EM.assocs ItemBag
bag
      desirableFloor (Int
_, (Point
_, ItemBag
bag)) = ItemBag -> Bool
desirableBagFloor ItemBag
bag
      focused = Skills -> Speed
gearSpeed Skills
actorMaxSk Speed -> Speed -> Bool
forall a. Ord a => a -> a -> Bool
< Speed
speedWalk Bool -> Bool -> Bool
|| Bool
condHpTooLow
      couldMoveLastTurn =  -- approximated; could have changed
        let actorSk :: Skills
actorSk = if Maybe ActorId
mleader Maybe ActorId -> Maybe ActorId -> Bool
forall a. Eq a => a -> a -> Bool
== ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
aid then Skills
actorMaxSk else Skills
actorMinSk
        in Skill -> Skills -> Int
Ability.getSk Skill
Ability.SkMove Skills
actorSk Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
      isStuck = Actor -> Bool
actorWaits Actor
b Bool -> Bool -> Bool
&& Bool
couldMoveLastTurn
      setPath :: Target -> m (Maybe TgtAndPath)
      setPath Target
tgt = do
        let take6 :: TgtAndPath -> TgtAndPath
take6 tap :: TgtAndPath
tap@TgtAndPath{tapTgt :: TgtAndPath -> Target
tapTgt=TEnemy{}} = TgtAndPath
tap
              -- @TEnemy@ needed for projecting, even by roaming actors;
              -- however, CStash not as binding, so excursions possible
            take6 TgtAndPath{tapPath :: TgtAndPath -> Maybe AndPath
tapPath=Just AndPath{Int
[Point]
Point
pathLen :: AndPath -> Int
pathGoal :: AndPath -> Point
pathList :: AndPath -> [Point]
pathSource :: AndPath -> Point
pathSource :: Point
pathList :: [Point]
pathGoal :: Point
pathLen :: Int
..}} =
              -- Path followed for up to 6 moves regardless if the target valid
              -- and then target forgot and a new one picked.
              let path6 :: [Point]
path6 = Int -> [Point] -> [Point]
forall a. Int -> [a] -> [a]
take Int
6 [Point]
pathList
                  vOld :: Vector
vOld = if Actor -> Point
bpos Actor
b Point -> Point -> Bool
forall a. Eq a => a -> a -> Bool
/= Point
pathGoal
                         then Point -> Point -> Vector
towards (Actor -> Point
bpos Actor
b) Point
pathGoal
                         else Int -> Int -> Vector
Vector Int
0 Int
0
                  tapTgt :: Target
tapTgt = Vector -> Target
TVector Vector
vOld
                  tapPath :: Maybe AndPath
tapPath = AndPath -> Maybe AndPath
forall a. a -> Maybe a
Just AndPath{pathList :: [Point]
pathList=[Point]
path6, Int
Point
pathLen :: Int
pathGoal :: Point
pathSource :: Point
pathSource :: Point
pathGoal :: Point
pathLen :: Int
..}
              in TgtAndPath{Maybe AndPath
Target
tapTgt :: Target
tapPath :: Maybe AndPath
tapTgt :: Target
tapPath :: Maybe AndPath
..}
            take6 TgtAndPath
tap = TgtAndPath
tap
        tgtpath <- ActorId -> Target -> m TgtAndPath
forall (m :: * -> *).
MonadClient m =>
ActorId -> Target -> m TgtAndPath
createPath ActorId
aid Target
tgt
        return $ Just $ if slackDoctrine then take6 tgtpath else tgtpath
      pickNewTarget = Maybe ActorId -> m (Maybe TgtAndPath)
pickNewTargetIgnore Maybe ActorId
forall a. Maybe a
Nothing
      pickNewTargetIgnore :: Maybe ActorId -> m (Maybe TgtAndPath)
      pickNewTargetIgnore Maybe ActorId
maidToIgnore =
        case [(Int, (FactionId, Point))]
cstashes of
          (Int
_, (FactionId
fid2, Point
pos2)) : [(Int, (FactionId, Point))]
_ -> Target -> m (Maybe TgtAndPath)
setPath (Target -> m (Maybe TgtAndPath)) -> Target -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TGoal -> LevelId -> Point -> Target
TPoint (FactionId -> TGoal
TStash FactionId
fid2) (Actor -> LevelId
blid Actor
b) Point
pos2
          [] -> do
            let f :: ActorId -> [(ActorId, Actor)]
f ActorId
aidToIgnore = ((ActorId, Actor) -> Bool)
-> [(ActorId, Actor)] -> [(ActorId, Actor)]
forall a. (a -> Bool) -> [a] -> [a]
filter ((ActorId -> ActorId -> Bool
forall a. Eq a => a -> a -> Bool
/= ActorId
aidToIgnore) (ActorId -> Bool)
-> ((ActorId, Actor) -> ActorId) -> (ActorId, Actor) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ActorId, Actor) -> ActorId
forall a b. (a, b) -> a
fst) [(ActorId, Actor)]
nearbyFoes
                notIgnoredFoes :: [(ActorId, Actor)]
notIgnoredFoes = [(ActorId, Actor)]
-> (ActorId -> [(ActorId, Actor)])
-> Maybe ActorId
-> [(ActorId, Actor)]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [(ActorId, Actor)]
nearbyFoes ActorId -> [(ActorId, Actor)]
f Maybe ActorId
maidToIgnore
            cfoes <- [(ActorId, Actor)] -> ActorId -> m [(Int, (ActorId, Actor))]
forall (m :: * -> *).
MonadClient m =>
[(ActorId, Actor)] -> ActorId -> m [(Int, (ActorId, Actor))]
closestFoes [(ActorId, Actor)]
notIgnoredFoes ActorId
aid
            case cfoes of
             (Int
_, (ActorId
aid2, Actor
_)) : [(Int, (ActorId, Actor))]
_ -> Target -> m (Maybe TgtAndPath)
setPath (Target -> m (Maybe TgtAndPath)) -> Target -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ ActorId -> Target
TEnemy ActorId
aid2
             [] | Bool
condInMelee -> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe TgtAndPath
forall a. Maybe a
Nothing  -- don't slow down fighters
               -- this looks a bit strange, because teammates stop
               -- in their tracks all around the map (unless very close
               -- to the combatant), but the intuition is, not being able
               -- to help immediately, and not being too friendly
               -- to each other, they just wait and see and also shout
               -- to the teammate to flee and lure foes into ambush
             [] -> do
              mhideout <- if Bool
recentlyFled20
                          then ActorId -> m (Maybe (Point, Int))
forall (m :: * -> *).
MonadClient m =>
ActorId -> m (Maybe (Point, Int))
closestHideout ActorId
aid
                          else Maybe (Point, Int) -> m (Maybe (Point, Int))
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe (Point, Int)
forall a. Maybe a
Nothing
              case (mhideout, mfled) of
               (Just (Point
p, Int
dist), Just (Point
_, Time
time))
                 | Time -> Time -> Delta Time
timeDeltaToFrom Time
localTime Time
time
                   Delta Time -> Delta Time -> Bool
forall a. Ord a => a -> a -> Bool
<= Delta Time -> Int -> Delta Time
timeDeltaScale Delta Time
actorTurn (Int
20 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
dist) ->
                -- Only target if can reach the hideout 20 turns from fleeing
                -- start, given the actor speed as a leader.
                Target -> m (Maybe TgtAndPath)
setPath (Target -> m (Maybe TgtAndPath)) -> Target -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TGoal -> LevelId -> Point -> Target
TPoint TGoal
THideout (Actor -> LevelId
blid Actor
b) Point
p
               (Maybe (Point, Int), Maybe (Point, Time))
_ -> do
                citemsRaw <- if Bool
canMoveItem Bool -> Bool -> Bool
&& Bool
canMove
                             then ActorId -> m [(Int, (Point, ItemBag))]
forall (m :: * -> *).
MonadClient m =>
ActorId -> m [(Int, (Point, ItemBag))]
closestItems ActorId
aid
                             else [(Int, (Point, ItemBag))] -> m [(Int, (Point, ItemBag))]
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return []
                let citems = Text -> [(Int, (Point, ItemBag))] -> Frequency (Point, ItemBag)
forall a. Text -> [(Int, a)] -> Frequency a
toFreq Text
"closestItems"
                             ([(Int, (Point, ItemBag))] -> Frequency (Point, ItemBag))
-> [(Int, (Point, ItemBag))] -> Frequency (Point, ItemBag)
forall a b. (a -> b) -> a -> b
$ ((Int, (Point, ItemBag)) -> Bool)
-> [(Int, (Point, ItemBag))] -> [(Int, (Point, ItemBag))]
forall a. (a -> Bool) -> [a] -> [a]
filter (Int, (Point, ItemBag)) -> Bool
desirableFloor [(Int, (Point, ItemBag))]
citemsRaw
                if nullFreq citems then do
                  -- Tracking enemies is more important than exploring,
                  -- but smell is unreliable and may lead to allies,
                  -- not foes, so avoid it. However, let's keep smell
                  -- more imporant than getting to stairs, to let smelling
                  -- monsters follow cues even on explored levels.
                  smpos <- if canSmell
                           then closestSmell aid
                           else return []
                  case smpos of
                    [] -> do
                      ctriggersRaw <- FleeViaStairsOrEscape
-> ActorId -> m [(Int, (Point, (Point, ItemBag)))]
forall (m :: * -> *).
MonadClient m =>
FleeViaStairsOrEscape
-> ActorId -> m [(Int, (Point, (Point, ItemBag)))]
closestTriggers FleeViaStairsOrEscape
ViaAnything ActorId
aid
                      let ctriggers = Text
-> [(Int, (Point, (Point, ItemBag)))]
-> Frequency (Point, (Point, ItemBag))
forall a. Text -> [(Int, a)] -> Frequency a
toFreq Text
"ctriggers" [(Int, (Point, (Point, ItemBag)))]
ctriggersRaw
                      if nullFreq ctriggers then do
                        let oldpos = Point -> Maybe Point -> Point
forall a. a -> Maybe a -> a
fromMaybe (Actor -> Point
bpos Actor
b) (Actor -> Maybe Point
boldpos Actor
b)
                            vOld = Actor -> Point
bpos Actor
b Point -> Point -> Vector
`vectorToFrom` Point
oldpos
                            pNew = Int -> Int -> Point -> Vector -> Point
shiftBounded Int
rWidthMax Int
rHeightMax (Actor -> Point
bpos Actor
b) Vector
vOld
                        if slackDoctrine && not isStuck && calmE && not focused
                           && isUnit vOld && bpos b /= pNew
                                -- both are needed, e.g., when just teleported
                                -- or when the shift bounded by level borders
                        then do
                          let vFreq = Text -> [(Int, Vector)] -> Frequency Vector
forall a. Text -> [(Int, a)] -> Frequency a
toFreq Text
"vFreq"
                                      ([(Int, Vector)] -> Frequency Vector)
-> [(Int, Vector)] -> Frequency Vector
forall a b. (a -> b) -> a -> b
$ (Int
20, Vector
vOld) (Int, Vector) -> [(Int, Vector)] -> [(Int, Vector)]
forall a. a -> [a] -> [a]
: (Vector -> (Int, Vector)) -> [Vector] -> [(Int, Vector)]
forall a b. (a -> b) -> [a] -> [b]
map (Int
1,) [Vector]
moves
                          v <- rndToAction $ frequency vFreq
                          -- Once the most pressing targets exhaused,
                          -- wander around for 7 steps and only then,
                          -- or if blocked or derailed, consider again
                          -- the old and new targets.
                          --
                          -- Together with depending on heroes or aliens
                          -- to keep arean, sleepiness, inability to displace
                          -- and chasing random smells, this makes it very hard
                          -- to fully explore and change levels for, e.g.,
                          -- animals. Heroes idling on the level help a lot.
                          let pathSource = Actor -> Point
bpos Actor
b
                              traSlack7 = Int -> Int -> Point -> [Vector] -> [Point]
trajectoryToPathBounded
                                            Int
rWidthMax Int
rHeightMax Point
pathSource
                                            (Int -> Vector -> [Vector]
forall a. Int -> a -> [a]
replicate Int
7 Vector
v)  -- > 6 from take6
                              pathList = ([Point] -> Point) -> [[Point]] -> [Point]
forall a b. (a -> b) -> [a] -> [b]
map [Point] -> Point
forall a. (?callStack::CallStack) => [a] -> a
head ([[Point]] -> [Point]) -> [[Point]] -> [Point]
forall a b. (a -> b) -> a -> b
$ [Point] -> [[Point]]
forall a. Eq a => [a] -> [[a]]
group [Point]
traSlack7
                              pathGoal = [Point] -> Point
forall a. (?callStack::CallStack) => [a] -> a
last [Point]
pathList
                              pathLen = [Point] -> Int
forall a. [a] -> Int
length [Point]
pathList
                              tapTgt = Vector -> Target
TVector Vector
v
                              tapPath = AndPath -> Maybe AndPath
forall a. a -> Maybe a
Just AndPath{Int
[Point]
Point
pathLen :: Int
pathGoal :: Point
pathList :: [Point]
pathSource :: Point
pathSource :: Point
pathList :: [Point]
pathGoal :: Point
pathLen :: Int
..}
                          return $ Just TgtAndPath {..}
                        else do
                          upos <- if canMove
                                  then closestUnknown aid
                                  else return Nothing
                          case upos of
                            Maybe Point
Nothing -> do
                              -- If can't move (and so no BFS data),
                              -- no info gained. Or if can't open doors.
                              -- If stuck among ice pillars, we can't help it.
                              Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
canMove Bool -> Bool -> Bool
&& Bool
canAlter) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                                (StateClient -> StateClient) -> m ()
forall (m :: * -> *).
MonadClient m =>
(StateClient -> StateClient) -> m ()
modifyClient ((StateClient -> StateClient) -> m ())
-> (StateClient -> StateClient) -> m ()
forall a b. (a -> b) -> a -> b
$ \StateClient
cli -> StateClient
cli {sexplored =
                                  ES.insert (blid b) (sexplored cli)}
                              ctriggersRaw2 <- FleeViaStairsOrEscape
-> ActorId -> m [(Int, (Point, (Point, ItemBag)))]
forall (m :: * -> *).
MonadClient m =>
FleeViaStairsOrEscape
-> ActorId -> m [(Int, (Point, (Point, ItemBag)))]
closestTriggers FleeViaStairsOrEscape
ViaExit ActorId
aid
                              let ctriggers2 = Text
-> [(Int, (Point, (Point, ItemBag)))]
-> Frequency (Point, (Point, ItemBag))
forall a. Text -> [(Int, a)] -> Frequency a
toFreq Text
"ctriggers2" [(Int, (Point, (Point, ItemBag)))]
ctriggersRaw2
                              if nullFreq ctriggers2 then do
                                let toKill = ActorMaxSkills -> ActorId -> Actor -> Bool
actorWorthKilling ActorMaxSkills
actorMaxSkills
                                    worthyFoes = ((ActorId, Actor) -> Bool)
-> [(ActorId, Actor)] -> [(ActorId, Actor)]
forall a. (a -> Bool) -> [a] -> [a]
filter ((ActorId -> Actor -> Bool) -> (ActorId, Actor) -> Bool
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry ActorId -> Actor -> Bool
toKill)
                                                        [(ActorId, Actor)]
foeAssocs
                                afoes <- closestFoes worthyFoes aid
                                case afoes of
                                  (Int
_, (ActorId
aid2, Actor
_)) : [(Int, (ActorId, Actor))]
_ ->
                                    -- All stones turned, time to win or die.
                                    Target -> m (Maybe TgtAndPath)
setPath (Target -> m (Maybe TgtAndPath)) -> Target -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ ActorId -> Target
TEnemy ActorId
aid2
                                  [] -> do
                                    furthest <- ActorId -> m Point
forall (m :: * -> *). MonadClient m => ActorId -> m Point
furthestKnown ActorId
aid
                                    setPath $ TPoint TKnown (blid b) furthest
                              else do
                                (p, (p0, bag)) <-
                                  rndToAction $ frequency ctriggers2
                                setPath $ TPoint (TEmbed bag p0) (blid b) p
                            Just Point
p -> Target -> m (Maybe TgtAndPath)
setPath (Target -> m (Maybe TgtAndPath)) -> Target -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TGoal -> LevelId -> Point -> Target
TPoint TGoal
TUnknown (Actor -> LevelId
blid Actor
b) Point
p
                      else do
                        (p, (p0, bag)) <- rndToAction $ frequency ctriggers
                        setPath $ TPoint (TEmbed bag p0) (blid b) p
                    (Int
_, (Point
p, Time
_)) : [(Int, (Point, Time))]
_ -> Target -> m (Maybe TgtAndPath)
setPath (Target -> m (Maybe TgtAndPath)) -> Target -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TGoal -> LevelId -> Point -> Target
TPoint TGoal
TSmell (Actor -> LevelId
blid Actor
b) Point
p
                else do
                  (p, bag) <- rndToAction $ frequency citems
                  setPath $ TPoint (TItem bag) (blid b) p
      tellOthersNothingHere = do
        let f :: TgtAndPath -> Bool
f TgtAndPath{Target
tapTgt :: TgtAndPath -> Target
tapTgt :: Target
tapTgt} = case Target
tapTgt of
              TPoint TGoal
_ LevelId
lid Point
p -> Point
p Point -> Point -> Bool
forall a. Eq a => a -> a -> Bool
/= Actor -> Point
bpos Actor
b Bool -> Bool -> Bool
|| LevelId
lid LevelId -> LevelId -> Bool
forall a. Eq a => a -> a -> Bool
/= Actor -> LevelId
blid Actor
b
              Target
_ -> Bool
True
        (StateClient -> StateClient) -> m ()
forall (m :: * -> *).
MonadClient m =>
(StateClient -> StateClient) -> m ()
modifyClient ((StateClient -> StateClient) -> m ())
-> (StateClient -> StateClient) -> m ()
forall a b. (a -> b) -> a -> b
$ \StateClient
cli -> StateClient
cli {stargetD = EM.filter f (stargetD cli)}
        m (Maybe TgtAndPath)
pickNewTarget
      updateTgt :: TgtAndPath -> m (Maybe TgtAndPath)
      updateTgt TgtAndPath{tapPath :: TgtAndPath -> Maybe AndPath
tapPath=Maybe AndPath
Nothing} = m (Maybe TgtAndPath)
pickNewTarget
      updateTgt tap :: TgtAndPath
tap@TgtAndPath{tapPath :: TgtAndPath -> Maybe AndPath
tapPath=Just AndPath{Int
[Point]
Point
pathLen :: AndPath -> Int
pathGoal :: AndPath -> Point
pathList :: AndPath -> [Point]
pathSource :: AndPath -> Point
pathSource :: Point
pathList :: [Point]
pathGoal :: Point
pathLen :: Int
..},Target
tapTgt :: TgtAndPath -> Target
tapTgt :: Target
tapTgt} = case Target
tapTgt of
        TEnemy ActorId
a -> do
          body <- (State -> Actor) -> m Actor
forall a. (State -> a) -> m a
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
a
          if   (condInMelee  -- fight close foes or nobody at all
                || bweapon body <= 0  -- not dangerous
                || not focused && not (null nearbyFoes))  -- prefers closer foes
               && a `notElem` map fst nearbyFoes  -- old one not close enough
               || blid body /= blid b  -- wrong level
               || actorDying body  -- foe already dying
               || not (worthTargeting a body)
               || recentlyFled
          then
                    -- forget enemy positions to prevent attacking them
                    -- again soon after flight
               pickNewTarget
          else do
               -- If there are no unwalkable tiles on the path to enemy,
               -- he gets target @TEnemy@ and then, even if such tiles emerge,
               -- the target updated by his moves remains @TEnemy@.
               -- Conversely, he is stuck with @TBlock@ if initial target had
               -- unwalkable tiles, for as long as they remain. Harmless quirk.
               mpath <- getCachePath aid $ bpos body
               case mpath of
                 Maybe AndPath
Nothing -> Maybe ActorId -> m (Maybe TgtAndPath)
pickNewTargetIgnore (ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
a)
                   -- enemy became unreachable
                 Just AndPath{pathList :: AndPath -> [Point]
pathList=[]} -> m (Maybe TgtAndPath)
pickNewTarget
                   -- he is his own enemy
                 Just AndPath{pathList :: AndPath -> [Point]
pathList= Point
q : [Point]
_} ->
                   -- If in melee and path blocked by actors (even proj.)
                   -- change target for this turn due to urgency.
                   -- Because of @condInMelee@ new target will be stash
                   -- or enemy if any other is left, or empty target.
                   -- If not in melee, keep target and consider your options
                   -- (wait until blocking actors move or displace or melee
                   -- or sidestep). We don't want to wander away
                   -- in search of loot, only to turn around next turn
                   -- when the enemy is again considered.
                   if Bool -> Bool
not Bool
condInMelee
                      Bool -> Bool -> Bool
|| Point
q Point -> Point -> Bool
forall a. Eq a => a -> a -> Bool
== Actor -> Point
bpos Actor
body  -- blocked by the enemy, great!
                      Bool -> Bool -> Bool
|| Bool -> Bool
not (Point -> Level -> Bool
occupiedBigLvl Point
q Level
lvl)
                         Bool -> Bool -> Bool
&& Bool -> Bool
not (Point -> Level -> Bool
occupiedProjLvl Point
q Level
lvl)
                   then Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe TgtAndPath -> m (Maybe TgtAndPath))
-> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap{tapPath=mpath}
                   else Maybe ActorId -> m (Maybe TgtAndPath)
pickNewTargetIgnore (ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
a)
        TPoint TGoal
_ LevelId
lid Point
_ | LevelId
lid LevelId -> LevelId -> Bool
forall a. Eq a => a -> a -> Bool
/= Actor -> LevelId
blid Actor
b -> m (Maybe TgtAndPath)
pickNewTarget  -- wrong level
        TPoint TGoal
tgoal LevelId
lid Point
pos -> case TGoal
tgoal of
          TStash FactionId
fid2 -> do
            oursExploring <- (State -> [(ActorId, Actor)]) -> m [(ActorId, Actor)]
forall a. (State -> a) -> m a
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> [(ActorId, Actor)]) -> m [(ActorId, Actor)])
-> (State -> [(ActorId, Actor)]) -> m [(ActorId, Actor)]
forall a b. (a -> b) -> a -> b
$ FactionId -> State -> [(ActorId, Actor)]
oursExploringAssocs (Actor -> FactionId
bfid Actor
b)
            let oursExploringLid =
                  ((ActorId, Actor) -> Bool)
-> [(ActorId, Actor)] -> [(ActorId, Actor)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(ActorId
_, Actor
body) -> Actor -> LevelId
blid Actor
body LevelId -> LevelId -> Bool
forall a. Eq a => a -> a -> Bool
== LevelId
lid) [(ActorId, Actor)]
oursExploring
                spawnFreqs = CaveKind -> Freqs ItemKind
CK.cactorFreq (CaveKind -> Freqs ItemKind) -> CaveKind -> Freqs ItemKind
forall a b. (a -> b) -> a -> b
$ ContentData CaveKind -> ContentId CaveKind -> CaveKind
forall a. ContentData a -> ContentId a -> a
okind ContentData CaveKind
cocave (ContentId CaveKind -> CaveKind) -> ContentId CaveKind -> CaveKind
forall a b. (a -> b) -> a -> b
$ Level -> ContentId CaveKind
lkind Level
lvl
                hasGroup GroupName ItemKind
grp = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe Int
0 (GroupName ItemKind -> Freqs ItemKind -> Maybe Int
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup GroupName ItemKind
grp Freqs ItemKind
spawnFreqs) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
                lvlSpawnsUs = ((GroupName ItemKind, Int) -> Bool) -> Freqs ItemKind -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (GroupName ItemKind -> Bool
hasGroup (GroupName ItemKind -> Bool)
-> ((GroupName ItemKind, Int) -> GroupName ItemKind)
-> (GroupName ItemKind, Int)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (GroupName ItemKind, Int) -> GroupName ItemKind
forall a b. (a, b) -> a
fst) (Freqs ItemKind -> Bool) -> Freqs ItemKind -> Bool
forall a b. (a -> b) -> a -> b
$ ((GroupName ItemKind, Int) -> Bool)
-> Freqs ItemKind -> Freqs ItemKind
forall a. (a -> Bool) -> [a] -> [a]
filter ((Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0) (Int -> Bool)
-> ((GroupName ItemKind, Int) -> Int)
-> (GroupName ItemKind, Int)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (GroupName ItemKind, Int) -> Int
forall a b. (a, b) -> b
snd)
                                                   (Freqs ItemKind -> Freqs ItemKind)
-> Freqs ItemKind -> Freqs ItemKind
forall a b. (a -> b) -> a -> b
$ FactionKind -> Freqs ItemKind
fgroups (Faction -> FactionKind
gkind Faction
fact)
           -- Even if made peace with the faction, loot stash one last time.
            if (calmE || null nearbyFoes)  -- no risk or can't defend anyway
               && not heavilyDistressed  -- not under heavy fire
               && gstash (factionD EM.! fid2) == Just (lid, pos)
               -- The condition below is more lenient than in @closestStashes@
               -- to avoid wasting time on guard's movement.
               && (fid2 == bfid b
                   && (pos == bpos b  -- guarded by me, so keep guarding
                       && (null nearbyFoes  -- if no foes nearby
                           || length oursExploringLid > 1) -- or buddies nearby
                       || isNothing (posToBigLvl pos lvl))  -- or unguarded
                   && (length oursExploring > 1  -- other actors able to explore
                       || lvlSpawnsUs)  -- or future spawned will be able
                   || isFoe (bfid b) fact fid2)
            then return $ Just tap
            else pickNewTarget
          -- In this case, need to retarget, to focus on foes that melee ours
          -- and not, e.g., on remembered foes or items.
          TGoal
_ | Bool
condInMelee Bool -> Bool -> Bool
|| Bool -> Bool
not ([(Int, (FactionId, Point))] -> Bool
forall a. [a] -> Bool
null [(Int, (FactionId, Point))]
cstashes) -> m (Maybe TgtAndPath)
pickNewTarget
          TEnemyPos ActorId
_  -- chase last position even if foe hides
            | Actor -> Point
bpos Actor
b Point -> Point -> Bool
forall a. Eq a => a -> a -> Bool
== Point
pos -> m (Maybe TgtAndPath)
tellOthersNothingHere
            | Bool
recentlyFled -> m (Maybe TgtAndPath)
pickNewTarget
                -- forget enemy positions to prevent attacking them again soon
            | Bool -> Bool
not (Bool
couldMoveLastTurn Bool -> Bool -> Bool
|| [(ActorId, Actor)] -> Bool
forall a. [a] -> Bool
null [(ActorId, Actor)]
nearbyFoes) -> m (Maybe TgtAndPath)
pickNewTarget
                -- if only, possibly, shooting, forget hotspots, target foes;
                -- this results in only pointman humans chasing old foes
                -- in preference of new visible ones, but it's fine
            | Bool
otherwise -> do
              -- Here pick the closer enemy, the remembered or seen, to avoid
              -- loops when approaching new enemy obscures him behind obstacle
              -- but reveals the previously remembered one, etc.
              let remainingDist :: Int
remainingDist = Point -> Point -> Int
chessDist (Actor -> Point
bpos Actor
b) Point
pos
              if ((ActorId, Actor) -> Bool) -> [(ActorId, Actor)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\(ActorId
_, Actor
b3) -> Point -> Point -> Int
chessDist (Actor -> Point
bpos Actor
b) (Actor -> Point
bpos Actor
b3) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
remainingDist)
                     [(ActorId, Actor)]
nearbyFoes
              then m (Maybe TgtAndPath)
pickNewTarget
              else Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe TgtAndPath -> m (Maybe TgtAndPath))
-> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap
          -- Don't stop fleeing into hideout after 5 turns even if foes appear.
          TGoal
THideout ->
            -- Approach or stay in the hideout until 20 turns pass.
            if Bool -> Bool
not Bool
recentlyFled20
            then m (Maybe TgtAndPath)
pickNewTarget
            else Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe TgtAndPath -> m (Maybe TgtAndPath))
-> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap
          -- Prefer close foes to anything else below.
          TGoal
_ | Bool -> Bool
not ([(ActorId, Actor)] -> Bool
forall a. [a] -> Bool
null [(ActorId, Actor)]
nearbyFoes) -> m (Maybe TgtAndPath)
pickNewTarget
          -- Below we check the target could not be picked again in
          -- pickNewTarget (e.g., an item got picked up by our teammate)
          -- and only in this case it is invalidated.
          -- This ensures targets are eventually reached (unless a foe
          -- shows up) and not changed all the time mid-route
          -- to equally interesting, but perhaps a bit closer targets,
          -- most probably already targeted by other actors.
          TEmbed ItemBag
bag Point
p -> Bool -> m (Maybe TgtAndPath) -> m (Maybe TgtAndPath)
forall a. (?callStack::CallStack) => Bool -> a -> a
assert (Point -> Point -> Bool
adjacent Point
pos Point
p) (m (Maybe TgtAndPath) -> m (Maybe TgtAndPath))
-> m (Maybe TgtAndPath) -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ do
            -- First, stairs and embedded items from @closestTriggers@.
            -- We don't check skills, because they normally don't change
            -- or we can put some equipment back and recover them.
            -- We don't determine if the stairs or embed are interesting
            -- (this changes with time), but allow the actor
            -- to reach them and then retarget. The two things we check
            -- is whether the embedded bag is still there, or used up
            -- and whether we happen to be already adjacent to @p@,
            -- even though not necessarily at @pos@.
            bag2 <- (State -> ItemBag) -> m ItemBag
forall a. (State -> a) -> m a
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> ItemBag) -> m ItemBag)
-> (State -> ItemBag) -> m ItemBag
forall a b. (a -> b) -> a -> b
$ LevelId -> Point -> State -> ItemBag
getEmbedBag LevelId
lid Point
p  -- not @pos@
            if | bag /= bag2 -> pickNewTarget  -- others will notice soon enough
               | adjacent (bpos b) p ->  -- regardless if at @pos@ or not
                   setPath $ TPoint TKnown lid (bpos b)
                     -- stay there one turn (high chance to become leader)
                     -- to enable triggering; if trigger fails
                     -- (e.g, changed skills), will retarget next turn (@TAny@)
               | otherwise -> return $ Just tap
          TItem ItemBag
bag -> do
            bag2 <- (State -> ItemBag) -> m ItemBag
forall a. (State -> a) -> m a
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> ItemBag) -> m ItemBag)
-> (State -> ItemBag) -> m ItemBag
forall a b. (a -> b) -> a -> b
$ LevelId -> Point -> State -> ItemBag
getFloorBag LevelId
lid Point
pos
            if | bag /= bag2 -> pickNewTarget  -- others will notice soon enough
               | bpos b == pos ->
                   setPath $ TPoint TKnown lid (bpos b)
                     -- stay there one turn (high chance to become leader)
                     -- to enable pickup; if pickup fails, will retarget
               | otherwise -> return $ Just tap
          TGoal
TSmell ->
            if Bool -> Bool
not Bool
canSmell
               Bool -> Bool -> Bool
|| let sml :: Time
sml = Time -> Point -> EnumMap Point Time -> Time
forall k a. Enum k => a -> k -> EnumMap k a -> a
EM.findWithDefault Time
timeZero Point
pos (Level -> EnumMap Point Time
lsmell Level
lvl)
                  in Time
sml Time -> Time -> Bool
forall a. Ord a => a -> a -> Bool
<= Level -> Time
ltime Level
lvl
            then m (Maybe TgtAndPath)
pickNewTarget  -- others will notice soon enough
            else Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe TgtAndPath -> m (Maybe TgtAndPath))
-> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap
          TGoal
TBlock -> do  -- e.g., door or first unknown tile of an area
            let t :: ContentId TileKind
t = Level
lvl Level -> Point -> ContentId TileKind
`at` Point
pos
            if Bool
isStuck  -- not a very important target, because blocked
               Bool -> Bool -> Bool
|| Int
alterSkill Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Word8 -> Int
forall a. Enum a => a -> Int
fromEnum (Array Word8
lalter Array Word8 -> Point -> Word8
forall c. UnboxRepClass c => Array c -> Point -> c
PointArray.! Point
pos)
                    -- tile was searched or altered or skill lowered
               Bool -> Bool -> Bool
|| TileSpeedup -> ContentId TileKind -> Bool
Tile.isWalkable TileSpeedup
coTileSpeedup ContentId TileKind
t
                    -- tile is no longer unwalkable, so was explored
                    -- so time to recalculate target
            then m (Maybe TgtAndPath)
pickNewTarget  -- others will notice soon enough
            else Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe TgtAndPath -> m (Maybe TgtAndPath))
-> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap
          TGoal
TUnknown ->
            let t :: ContentId TileKind
t = Level
lvl Level -> Point -> ContentId TileKind
`at` Point
pos
            in if Level -> Int
lexpl Level
lvl Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Level -> Int
lseen Level
lvl
                  Bool -> Bool -> Bool
|| Bool -> Bool
not (ContentId TileKind -> Bool
isUknownSpace ContentId TileKind
t)
               then m (Maybe TgtAndPath)
pickNewTarget  -- others will notice soon enough
               else Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe TgtAndPath -> m (Maybe TgtAndPath))
-> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap
          TGoal
TKnown ->
            if Actor -> Point
bpos Actor
b Point -> Point -> Bool
forall a. Eq a => a -> a -> Bool
== Point
pos
               Bool -> Bool -> Bool
|| Bool
isStuck
               Bool -> Bool -> Bool
|| Int
alterSkill Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Word8 -> Int
forall a. Enum a => a -> Int
fromEnum (Array Word8
lalter Array Word8 -> Point -> Word8
forall c. UnboxRepClass c => Array c -> Point -> c
PointArray.! Point
pos)
                    -- tile was searched or altered or skill lowered
            then m (Maybe TgtAndPath)
pickNewTarget  -- others unconcerned
            else Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe TgtAndPath -> m (Maybe TgtAndPath))
-> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap
        Target
_ | Bool
condInMelee Bool -> Bool -> Bool
|| Bool -> Bool
not ([(ActorId, Actor)] -> Bool
forall a. [a] -> Bool
null [(ActorId, Actor)]
nearbyFoes Bool -> Bool -> Bool
&& [(Int, (FactionId, Point))] -> Bool
forall a. [a] -> Bool
null [(Int, (FactionId, Point))]
cstashes) ->
            m (Maybe TgtAndPath)
pickNewTarget
        TNonEnemy ActorId
_ | Maybe ActorId
mleader Maybe ActorId -> Maybe ActorId -> Bool
forall a. Eq a => a -> a -> Bool
== ActorId -> Maybe ActorId
forall a. a -> Maybe a
Just ActorId
aid ->  -- a leader, never follow
          m (Maybe TgtAndPath)
pickNewTarget
        TNonEnemy ActorId
a -> do
          body <- (State -> Actor) -> m Actor
forall a. (State -> a) -> m a
forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
forall a b. (a -> b) -> a -> b
$ ActorId -> State -> Actor
getActorBody ActorId
a
          if blid body /= blid b  -- wrong level
          then pickNewTarget
          else do
            -- Update path. If impossible, pick another target.
            mpath <- getCachePath aid $ bpos body
            case mpath of
              Maybe AndPath
Nothing -> m (Maybe TgtAndPath)
pickNewTarget
              Just AndPath{pathList :: AndPath -> [Point]
pathList=[]} -> m (Maybe TgtAndPath)
pickNewTarget
              Maybe AndPath
_ -> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe TgtAndPath -> m (Maybe TgtAndPath))
-> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap{tapPath=mpath}
        TVector{} -> if Actor -> Point
bpos Actor
b Point -> Point -> Bool
forall a. Eq a => a -> a -> Bool
/= Point
pathGoal
                     then Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe TgtAndPath -> m (Maybe TgtAndPath))
-> Maybe TgtAndPath -> m (Maybe TgtAndPath)
forall a b. (a -> b) -> a -> b
$ TgtAndPath -> Maybe TgtAndPath
forall a. a -> Maybe a
Just TgtAndPath
tap
                     else m (Maybe TgtAndPath)
pickNewTarget
  if canReach
  then maybe pickNewTarget updateTgt oldTgtUpdatedPath
  else return Nothing