content: {-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving, TupleSections #-}
  -- | Abilities of items, actors and factions.
  module Game.LambdaHack.Definition.Ability
    ( Skill(..), Skills, Flag(..), ActivationFlag(..), Flags(..)
    , Doctrine(..), EqpSlot(..)
    , getSk, addSk, checkFl, skillsToList
    , zeroSkills, addSkills, sumScaledSkills
    , nameDoctrine, describeDoctrine, doctrineSkills
    , blockOnly, meleeAdjacent, meleeAndRanged, ignoreItems
  #ifdef EXPOSE_INTERNAL
      -- * Internal operations
    , scaleSkills
  #endif
    ) where
  
  import Prelude ()
  
  import Game.LambdaHack.Core.Prelude
  
  import           Data.Binary
  import qualified Data.EnumMap.Strict as EM
  import qualified Data.EnumSet as ES
  import           Data.Hashable (Hashable)
  import           GHC.Generics (Generic)
  
  -- | Actor and faction skills. They are a subset of actor aspects.
  -- See 'Game.LambdaHack.Client.UI.EffectDescription.skillDesc'
  -- for documentation.
  data Skill =
    -- Stats, that is skills affecting permitted actions.
      SkMove
    | SkMelee
    | SkDisplace
    | SkAlter
    | SkWait
    | SkMoveItem
    | SkProject
    | SkApply
    -- Assorted skills.
    | SkSwimming
    | SkFlying
    | SkHurtMelee
    | SkArmorMelee
    | SkArmorRanged
    | SkMaxHP
    | SkMaxCalm
    | SkSpeed
    | SkSight  -- ^ FOV radius, where 1 means a single tile FOV area
    | SkSmell
    | SkShine
    | SkNocto
    | SkHearing
    | SkAggression
    | SkOdor
    | SkDeflectRanged  -- ^ intended to reflect how many items granting complete
                       --   invulnerability are among organs and equipment;
                       --   this is not strength of deflection nor duration, etc.
    | SkDeflectMelee   -- ^ see above
    deriving (Show, Eq, Enum, Bounded, Generic)
  
  -- | Strength of particular skills. This is cumulative from actor
  -- organs and equipment and so pertain to an actor as well as to items.
  --
  -- This representation is sparse, so better than a record when there are more
  -- item kinds (with few skills) than actors (with many skills),
  -- especially if the number of skills grows as the engine is developed.
  -- It's also easier to code and maintain.
  --
  -- The tree is by construction sparse, so the derived equality is semantical.
  newtype Skills = Skills {skills :: EM.EnumMap Skill Int}
    deriving (Show, Eq, Ord, Hashable, Binary)
  
  -- | Item flag aspects.
  data Flag =
      Fragile       -- ^ as a projectile, break at target tile, even if no hit;
                    --   also, at each periodic activation a copy is destroyed
                    --   and all other copies require full cooldown (timeout)
    | Lobable       -- ^ drop at target tile, even if no hit
    | Durable       -- ^ don't break even when hitting or applying
    | Equipable     -- ^ AI and UI flag: consider equipping (may or may not
                    --   have 'EqpSlot', e.g., if the benefit is periodic)
    | Benign        -- ^ AI and UI flag: the item is not meant to harm
    | Precious      -- ^ AI and UI flag: don't risk identifying by use;
                    --   also, can't throw or apply if not calm enough;
                    --   also may be used for UI flavour or AI hints
    | Blast         -- ^ the item is an explosion blast particle
    | Condition     -- ^ item is a condition (buff or de-buff) of an actor
                    --   and is displayed as such, not activated at death;
                    --   this differs from belonging to the @CONDITION@ group,
                    --   which doesn't guarantee any behaviour or display,
                    --   but governs removal by items that drop @CONDITION@
    | Unique        -- ^ at most one copy can ever be generated
    | MetaGame      -- ^ once identified, the item is known until savefile deleted
    | MinorEffects  -- ^ override: the effects on this item are considered
                    --   minor and so possibly not causing identification on use,
                    --   and so this item will identify on pick-up
    | MinorAspects  -- ^ override: don't show question marks by weapons in HUD
                    --   even when unidentified item with this flag equipped
    | -- The flags below specify all conditions under which the item activates,
      -- charges permitting, in addition to universal conditions, which are
      -- hitting an actor as projectiles and being explicitly triggered
      -- by an actor (item destruction and combining only pertain
      -- to explicitly listed effects).
      Meleeable     -- ^ meleeing with the item is permitted and so the item
                    --   activates when meleed with
    | Periodic      -- ^ at most one of any copies without cooldown (timeout)
                    --   activates each turn; the cooldown required after
                    --   activation is specified in @Timeout@ (or is zero);
                    --   the initial cooldown can also be specified
                    --   as @TimerDice@ in @CreateItem@ effect; uniquely, this
                    --   activation never destroys a copy, unless item is fragile;
                    --   all this happens only for items in equipment or organs;
                    --   kinetic damage is not applied
    | UnderRanged   -- ^ activates when non-projectile actor with this item
                    --   as equipment or organ is under ranged attack;
                    --   kinetic damage is not applied
    | UnderMelee    -- ^ activates when non-projectile actor with this item
                    --   as equipment or organ is under melee attack;
                    --   kinetic damage is not applied
    deriving (Show, Eq, Enum, Bounded, Generic)
  
  -- | These flags correspond to the last cases of @Flag@ and addtionally
  -- to all the universal circumstances of item activation,
  -- under which every item activates (even if vacuusly).
  data ActivationFlag =
      ActivationMeleeable
    | ActivationPeriodic
    | ActivationUnderRanged
    | ActivationUnderMelee
    | -- | From here on, all items affected regardless of their `Flag` content.
      ActivationProjectile
    | ActivationTrigger
    | ActivationOnSmash
    | ActivationOnCombine
    | ActivationEmbed
    | ActivationConsume
    deriving (Show, Eq)
  
  newtype Flags = Flags {flags :: ES.EnumSet Flag}
    deriving (Show, Eq, Ord, Hashable, Binary)
  
  -- | Doctrine of non-leader actors. Apart of determining AI operation,
  -- each doctrine implies a skill modifier, that is added to the non-leader
  -- skills defined in @fskillsOther@ field of @FactionKind@.
  data Doctrine =
      TExplore  -- ^ if enemy nearby, attack, if no items, etc., explore unknown
    | TFollow   -- ^ always follow leader's target or his position if no target
    | TFollowNoItems   -- ^ follow but don't do any item management nor use
    | TMeleeAndRanged  -- ^ only melee and do ranged combat
    | TMeleeAdjacent   -- ^ only melee (or wait)
    | TBlock    -- ^ always only wait, even if enemy in melee range
    | TRoam     -- ^ if enemy nearby, attack, if no items, etc., roam randomly
    | TPatrol   -- ^ find an open and uncrowded area, patrol it according
                --   to sight radius and fallback temporarily to @TRoam@
                --   when enemy is seen by the faction and is within
                --   the actor's sight radius
    deriving (Show, Eq, Enum, Bounded, Generic)
  
  instance Binary Doctrine
  
  instance Hashable Doctrine
  
  -- | AI and UI hints about the role of the item.
  data EqpSlot =
      EqpSlotMove
    | EqpSlotMelee
    | EqpSlotDisplace
    | EqpSlotAlter
    | EqpSlotWait
    | EqpSlotMoveItem
    | EqpSlotProject
    | EqpSlotApply
    | EqpSlotSwimming
    | EqpSlotFlying
    | EqpSlotHurtMelee
    | EqpSlotArmorMelee
    | EqpSlotArmorRanged
    | EqpSlotMaxHP
    | EqpSlotSpeed
    | EqpSlotSight
    | EqpSlotShine
    | EqpSlotMiscBonus
    | EqpSlotWeaponFast
    | EqpSlotWeaponBig
    deriving (Show, Eq, Ord, Enum, Bounded, Generic)
  
  instance Binary Skill where
    put = putWord8 . toEnum . fromEnum
    get = fmap (toEnum . fromEnum) getWord8
  
  instance Binary Flag where
    put = putWord8 . toEnum . fromEnum
    get = fmap (toEnum . fromEnum) getWord8
  
  instance Binary EqpSlot where
    put = putWord8 . toEnum . fromEnum
    get = fmap (toEnum . fromEnum) getWord8
  
  instance Hashable Skill
  
  instance Hashable Flag
  
  instance Hashable EqpSlot
  
  getSk :: Skill -> Skills -> Int
  {-# INLINE getSk #-}
  getSk sk (Skills skills) = EM.findWithDefault 0 sk skills
  
  addSk :: Skill -> Int -> Skills -> Skills
  addSk sk n = addSkills (Skills $ EM.singleton sk n)
  
  checkFl :: Flag -> Flags -> Bool
  {-# INLINE checkFl #-}
  checkFl flag (Flags flags) = flag `ES.member` flags
  
  skillsToList :: Skills -> [(Skill, Int)]
  skillsToList (Skills sk) = EM.assocs sk
  
  zeroSkills :: Skills
  zeroSkills = Skills EM.empty
  
  -- This avoids costly compaction (required for Eq) even in case of adding
  -- empty skills, etc. This function is used a lot.
  addSkills :: Skills -> Skills -> Skills
  addSkills (Skills sk1) (Skills sk2) =
    let combine _ s1 s2 = case s1 + s2 of
          0 -> Nothing
          s -> Just s
    in Skills $ EM.mergeWithKey combine id id sk1 sk2
  
  scaleSkills :: (Skills, Int) -> Skills
  scaleSkills (_, 0) = zeroSkills
  scaleSkills (Skills sk, n) = Skills $ EM.map (n *) sk
  
  sumScaledSkills :: [(Skills, Int)] -> Skills
  sumScaledSkills = foldr (addSkills . scaleSkills) zeroSkills
  
  nameDoctrine :: Doctrine -> Text
  nameDoctrine TExplore        = "explore"
  nameDoctrine TFollow         = "follow freely"
  nameDoctrine TFollowNoItems  = "follow only"
  nameDoctrine TMeleeAndRanged = "fight only"
  nameDoctrine TMeleeAdjacent  = "melee only"
  nameDoctrine TBlock          = "block only"
  nameDoctrine TRoam           = "roam freely"
  nameDoctrine TPatrol         = "patrol area"
  
  describeDoctrine :: Doctrine -> Text
  describeDoctrine TExplore = "investigate unknown positions, chase targets"
  describeDoctrine TFollow = "follow pointman's target or position, grab items"
  describeDoctrine TFollowNoItems =
    "follow pointman's target or position, ignore items"
  describeDoctrine TMeleeAndRanged =
    "engage in both melee and ranged combat, don't move"
  describeDoctrine TMeleeAdjacent = "engage exclusively in melee, don't move"
  describeDoctrine TBlock = "block and wait, don't move"
  describeDoctrine TRoam = "move freely, chase targets"
  describeDoctrine TPatrol = "find and patrol an area"
  
  doctrineSkills :: Doctrine -> Skills
  doctrineSkills TExplore = zeroSkills
  doctrineSkills TFollow = zeroSkills
  doctrineSkills TFollowNoItems = ignoreItems
  doctrineSkills TMeleeAndRanged = meleeAndRanged
  doctrineSkills TMeleeAdjacent = meleeAdjacent
  doctrineSkills TBlock = blockOnly
  doctrineSkills TRoam = zeroSkills
  doctrineSkills TPatrol = zeroSkills
  
  minusTen, blockOnly, meleeAdjacent, meleeAndRanged, ignoreItems :: Skills
  
  -- To make sure only a lot of weak items can override move-only-leader, etc.
  minusTen = Skills $ EM.fromDistinctAscList
                    $ map (, - 10) [SkMove .. SkApply]
  
  blockOnly = Skills $ EM.delete SkWait $ skills minusTen
  
  meleeAdjacent = Skills $ EM.delete SkMelee $ skills blockOnly
  
  -- Melee and reaction fire.
  meleeAndRanged = Skills $ EM.delete SkProject $ skills meleeAdjacent
  
  ignoreItems = Skills $ EM.fromList
                       $ map (, - 10) [SkMoveItem, SkProject, SkApply]
  