1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
|
{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-}
-- | 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
$ zip [SkMove .. SkApply] (repeat (-10))
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
$ zip [SkMoveItem, SkProject, SkApply] (repeat (-10))
|