-- | Actor preferences for targets and actions based on actor attributes. module Game.LambdaHack.Client.AI.Preferences ( totalUsefulness, effectToBenefit ) where import qualified Control.Monad.State as St import qualified Data.EnumMap.Strict as EM import Data.Maybe import Game.LambdaHack.Common.Actor import Game.LambdaHack.Common.ActorState import qualified Game.LambdaHack.Common.Dice as Dice import qualified Game.LambdaHack.Common.Effect as Effect import Game.LambdaHack.Common.Faction import Game.LambdaHack.Common.Item import Game.LambdaHack.Common.ItemStrongest import qualified Game.LambdaHack.Common.Kind as Kind import Game.LambdaHack.Common.Misc import Game.LambdaHack.Content.ItemKind import Game.LambdaHack.Content.ModeKind -- | How much AI benefits from applying the effect. Multipllied by item p. -- Negative means harm to the enemy when thrown at him. Effects with zero -- benefit won't ever be used, neither actively nor passively. effectToBenefit :: Kind.COps -> Actor -> [ItemFull] -> Faction -> Effect.Effect Int -> Int effectToBenefit cops b activeItems fact eff = let dungeonDweller = not $ fcanEscape $ gplayer fact in case eff of Effect.NoEffect _ -> 0 Effect.RefillHP p -> let hpMax = sumSlotNoFilter Effect.EqpSlotAddMaxHP activeItems in if p > 0 then 1 + 10 * min p (fromIntegral $ (xM hpMax - bhp b) `divUp` oneM) else max (-99) (10 * p) Effect.Hurt d -> -(min 99 $ round (10 * Dice.meanDice d)) Effect.RefillCalm p -> let calmMax = sumSlotNoFilter Effect.EqpSlotAddMaxCalm activeItems in if p > 0 then 1 + min p (fromIntegral $ (xM calmMax - bcalm b) `divUp` oneM) else max (-20) p Effect.Dominate -> -200 Effect.Impress -> -10 Effect.CallFriend p -> 20 * p Effect.Summon{} | dungeonDweller -> 1 -- probably summons friends or crazies Effect.Summon{} -> 0 -- probably generates enemies Effect.CreateItem p -> 20 * p Effect.ApplyPerfume -> -10 Effect.Burn p -> -15 * p -- usually splash damage, etc. Effect.Ascend{} -> 1 -- change levels sensibly, in teams Effect.Escape{} -> 10000 -- AI wants to win; spawners to guard Effect.Paralyze p -> -20 * p Effect.InsertMove p -> 50 * p Effect.DropBestWeapon -> -50 Effect.DropEqp ' ' False -> -80 Effect.DropEqp ' ' True -> -100 Effect.DropEqp _ False -> -40 Effect.DropEqp _ True -> -50 Effect.SendFlying _ -> -10 -- but useful on self sometimes, too Effect.PushActor _ -> -10 -- but useful on self sometimes, too Effect.PullActor _ -> -10 Effect.Teleport p | p < 5 -> 5 * p -- blink to shoot at foe Effect.Teleport p | p < 10 -> 1 -- neither escape nor repositioning Effect.Teleport p -> -5 * p -- get rid of the foe Effect.PolyItem _ -> 0 -- AI would loop Effect.Identify _ -> 0 -- AI would loop Effect.ActivateInv ' ' -> -100 Effect.ActivateInv _ -> -50 Effect.Explode _ -> -10 Effect.OneOf _ -> 1 -- usually a mixed blessing, but slightly beneficial Effect.OnSmash _ -> -10 Effect.TimedAspect k asp -> k * (aspectToBenefit cops b asp) `div` 50 -- | Return the value to add to effect value and another to multiply it. aspectToBenefit :: Kind.COps -> Actor -> Effect.Aspect Int -> Int aspectToBenefit _cops _b asp = case asp of Effect.Periodic{} -> 0 Effect.AddMaxHP p -> p * 10 Effect.AddMaxCalm p -> p `divUp` 2 Effect.AddSpeed p -> p * 10000 Effect.AddSkills m -> 5 * sum (EM.elems m) Effect.AddHurtMelee p -> p `divUp` 3 Effect.AddHurtRanged p -> p `divUp` 5 Effect.AddArmorMelee p -> p `divUp` 5 Effect.AddArmorRanged p -> p `divUp` 10 Effect.AddSight p -> p * 10 Effect.AddSmell p -> p * 2 Effect.AddLight p -> p * 10 -- | Determine the total benefit from having an item in eqp or inv, -- according to item type, and also the benefit confered by equipping the item -- and from meleeing with it or applying it or throwing it. totalUsefulness :: Kind.COps -> Actor -> [ItemFull] -> Faction -> ItemFull -> Maybe (Int, (Int, Int)) totalUsefulness cops b activeItems fact itemFull = let ben effects aspects = let effBens = map (effectToBenefit cops b activeItems fact) effects aspBens = map (aspectToBenefit cops b) aspects periodicEffBens = case strengthFromEqpSlot Effect.EqpSlotPeriodic itemFull of Nothing -> [] Just in100 -> map (\eff -> eff * in100 `div` 5) effBens selfBens = aspBens ++ periodicEffBens eqpSum = if not (null selfBens) && minimum selfBens < -10 && maximum selfBens > 10 then 0 -- significant mixed blessings out of AI control else sum selfBens effSum = sum effBens isWeapon = isJust (strengthFromEqpSlot Effect.EqpSlotWeapon itemFull) totalSum = if goesIntoInv $ itemBase itemFull then effSum else if isWeapon then effSum + eqpSum else eqpSum in (totalSum, (eqpSum, effSum)) in case itemDisco itemFull of Just ItemDisco{itemAE=Just ItemAspectEffect{jaspects, jeffects}} -> Just $ ben jeffects jaspects Just ItemDisco{itemKind=ItemKind{iaspects, ieffects}} -> let travA x = St.evalState (Effect.aspectTrav x (return . round . Dice.meanDice)) () jaspects = map travA iaspects travE x = St.evalState (Effect.effectTrav x (return . round . Dice.meanDice)) () jeffects = map travE ieffects in Just $ ben jeffects jaspects _ -> Nothing