Use whatever items the character has wielded, if any, to calculate the damage they deal when attacking. Currently this shortcuts handedness to just use the *first* item they have equipped, which is fine since it's currently only possible to equip something in the right hand.
271 lines
8.6 KiB
Haskell
271 lines
8.6 KiB
Haskell
{-# LANGUAGE ViewPatterns #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
module Xanthous.Entities.Character
|
|
( Character(..)
|
|
, characterName
|
|
, inventory
|
|
, characterDamage
|
|
, characterHitpoints'
|
|
, characterHitpoints
|
|
, hitpointRecoveryRate
|
|
, speed
|
|
|
|
-- * Inventory
|
|
, Inventory(..)
|
|
, backpack
|
|
, wielded
|
|
, items
|
|
-- ** Wielded items
|
|
, Wielded(..)
|
|
, hands
|
|
, leftHand
|
|
, rightHand
|
|
, inLeftHand
|
|
, inRightHand
|
|
, doubleHanded
|
|
, wieldedItems
|
|
, WieldedItem(..)
|
|
, wieldedItem
|
|
, wieldableItem
|
|
|
|
-- *
|
|
, mkCharacter
|
|
, pickUpItem
|
|
, isDead
|
|
, damage
|
|
) where
|
|
--------------------------------------------------------------------------------
|
|
import Xanthous.Prelude
|
|
--------------------------------------------------------------------------------
|
|
import Brick
|
|
import Data.Aeson.Generic.DerivingVia
|
|
import Data.Aeson (ToJSON, FromJSON)
|
|
import Data.Coerce (coerce)
|
|
import Test.QuickCheck
|
|
import Test.QuickCheck.Instances.Vector ()
|
|
import Test.QuickCheck.Arbitrary.Generic
|
|
--------------------------------------------------------------------------------
|
|
import Xanthous.Util.QuickCheck
|
|
import Xanthous.Game.State
|
|
import Xanthous.Entities.Item
|
|
import Xanthous.Data
|
|
( TicksPerTile, Hitpoints, Per, Ticks, (|*|), positioned
|
|
, Positioned(..)
|
|
)
|
|
import Xanthous.Entities.RawTypes (WieldableItem, wieldable)
|
|
import qualified Xanthous.Entities.RawTypes as Raw
|
|
--------------------------------------------------------------------------------
|
|
|
|
data WieldedItem = WieldedItem
|
|
{ _wieldedItem :: Item
|
|
, _wieldableItem :: WieldableItem
|
|
-- ^ Invariant: item ^. itemType . wieldable ≡ Just wieldableItem
|
|
}
|
|
deriving stock (Eq, Show, Ord, Generic)
|
|
deriving anyclass (NFData, CoArbitrary, Function)
|
|
deriving (ToJSON, FromJSON)
|
|
via WithOptions '[ FieldLabelModifier '[Drop 1] ]
|
|
WieldedItem
|
|
makeFieldsNoPrefix ''WieldedItem
|
|
|
|
instance Brain WieldedItem where
|
|
step ticks (Positioned p wi) =
|
|
over positioned (\i -> WieldedItem i $ wi ^. wieldableItem)
|
|
<$> step ticks (Positioned p $ wi ^. wieldedItem)
|
|
|
|
instance Draw WieldedItem where
|
|
draw = draw . view wieldedItem
|
|
|
|
instance Entity WieldedItem where
|
|
blocksVision = blocksVision . view wieldedItem
|
|
description = description . view wieldedItem
|
|
entityChar = entityChar . view wieldedItem
|
|
|
|
instance Arbitrary WieldedItem where
|
|
arbitrary = genericArbitrary <&> \wi ->
|
|
wi & wieldedItem . itemType . wieldable ?~ wi ^. wieldableItem
|
|
|
|
data Wielded
|
|
= DoubleHanded WieldedItem
|
|
| Hands { _leftHand :: !(Maybe WieldedItem)
|
|
, _rightHand :: !(Maybe WieldedItem)
|
|
}
|
|
deriving stock (Eq, Show, Ord, Generic)
|
|
deriving anyclass (NFData, CoArbitrary, Function)
|
|
deriving Arbitrary via GenericArbitrary Wielded
|
|
deriving (ToJSON, FromJSON)
|
|
via WithOptions '[ 'SumEnc 'ObjWithSingleField ]
|
|
Wielded
|
|
|
|
hands :: Prism' Wielded (Maybe WieldedItem, Maybe WieldedItem)
|
|
hands = prism' (uncurry Hands) $ \case
|
|
Hands l r -> Just (l, r)
|
|
_ -> Nothing
|
|
|
|
leftHand :: Traversal' Wielded WieldedItem
|
|
leftHand = hands . _1 . _Just
|
|
|
|
inLeftHand :: WieldedItem -> Wielded
|
|
inLeftHand wi = Hands (Just wi) Nothing
|
|
|
|
rightHand :: Traversal' Wielded WieldedItem
|
|
rightHand = hands . _2 . _Just
|
|
|
|
inRightHand :: WieldedItem -> Wielded
|
|
inRightHand wi = Hands Nothing (Just wi)
|
|
|
|
doubleHanded :: Prism' Wielded WieldedItem
|
|
doubleHanded = prism' DoubleHanded $ \case
|
|
DoubleHanded i -> Just i
|
|
_ -> Nothing
|
|
|
|
wieldedItems :: Traversal' Wielded WieldedItem
|
|
wieldedItems k (DoubleHanded wielded) = DoubleHanded <$> k wielded
|
|
wieldedItems k (Hands l r) = Hands <$> _Just k l <*> _Just k r
|
|
|
|
data Inventory = Inventory
|
|
{ _backpack :: Vector Item
|
|
, _wielded :: Wielded
|
|
}
|
|
deriving stock (Eq, Show, Ord, Generic)
|
|
deriving anyclass (NFData, CoArbitrary, Function)
|
|
deriving Arbitrary via GenericArbitrary Inventory
|
|
deriving (ToJSON, FromJSON)
|
|
via WithOptions '[ FieldLabelModifier '[Drop 1] ]
|
|
Inventory
|
|
makeFieldsNoPrefix ''Inventory
|
|
|
|
items :: Traversal' Inventory Item
|
|
items k (Inventory bp w) = Inventory
|
|
<$> traversed k bp
|
|
<*> (wieldedItems . wieldedItem) k w
|
|
|
|
type instance Element Inventory = Item
|
|
|
|
instance MonoFunctor Inventory where
|
|
omap = over items
|
|
|
|
instance MonoFoldable Inventory where
|
|
ofoldMap = foldMapOf items
|
|
ofoldr = foldrOf items
|
|
ofoldl' = foldlOf' items
|
|
otoList = toListOf items
|
|
oall = allOf items
|
|
oany = anyOf items
|
|
onull = nullOf items
|
|
ofoldr1Ex = foldr1Of items
|
|
ofoldl1Ex' = foldl1Of' items
|
|
headEx = headEx . toListOf items
|
|
lastEx = lastEx . toListOf items
|
|
|
|
instance MonoTraversable Inventory where
|
|
otraverse = traverseOf items
|
|
|
|
instance Semigroup Inventory where
|
|
inv₁ <> inv₂ =
|
|
let backpack' = inv₁ ^. backpack <> inv₂ ^. backpack
|
|
(wielded', backpack'') = case (inv₁ ^. wielded, inv₂ ^. wielded) of
|
|
(wielded₁, wielded₂@(DoubleHanded _)) ->
|
|
(wielded₂, backpack' <> fromList (wielded₁ ^.. wieldedItems . wieldedItem))
|
|
(wielded₁, wielded₂@(Hands (Just _) (Just _))) ->
|
|
(wielded₂, backpack' <> fromList (wielded₁ ^.. wieldedItems . wieldedItem))
|
|
(wielded₁, Hands Nothing Nothing) -> (wielded₁, backpack')
|
|
(Hands Nothing Nothing, wielded₂) -> (wielded₂, backpack')
|
|
(Hands (Just l₁) Nothing, Hands Nothing (Just r₂)) ->
|
|
(Hands (Just l₁) (Just r₂), backpack')
|
|
(wielded₁@(DoubleHanded _), wielded₂) ->
|
|
(wielded₁, backpack' <> fromList (wielded₂ ^.. wieldedItems . wieldedItem))
|
|
(Hands Nothing (Just r₁), Hands Nothing (Just r₂)) ->
|
|
(Hands Nothing (Just r₂), r₁ ^. wieldedItem <| backpack')
|
|
(Hands Nothing r₁, Hands (Just l₂) Nothing) ->
|
|
(Hands (Just l₂) r₁, backpack')
|
|
(Hands (Just l₁) Nothing, Hands (Just l₂) Nothing) ->
|
|
(Hands (Just l₂) Nothing, l₁ ^. wieldedItem <| backpack')
|
|
(Hands (Just l₁) (Just r₁), Hands Nothing (Just r₂)) ->
|
|
(Hands (Just l₁) (Just r₂), r₁ ^. wieldedItem <| backpack')
|
|
(Hands (Just l₁) (Just r₁), Hands (Just l₂) Nothing) ->
|
|
(Hands (Just l₂) (Just r₁), l₁ ^. wieldedItem <| backpack')
|
|
in Inventory backpack'' wielded'
|
|
|
|
instance Monoid Inventory where
|
|
mempty = Inventory mempty $ Hands Nothing Nothing
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
data Character = Character
|
|
{ _inventory :: !Inventory
|
|
, _characterName :: !(Maybe Text)
|
|
, _characterHitpoints' :: !Double
|
|
, _speed :: TicksPerTile
|
|
}
|
|
deriving stock (Show, Eq, Ord, Generic)
|
|
deriving anyclass (NFData, CoArbitrary, Function)
|
|
deriving (ToJSON, FromJSON)
|
|
via WithOptions '[ FieldLabelModifier '[Drop 1] ]
|
|
Character
|
|
makeLenses ''Character
|
|
|
|
characterHitpoints :: Character -> Hitpoints
|
|
characterHitpoints = views characterHitpoints' floor
|
|
|
|
scrollOffset :: Int
|
|
scrollOffset = 5
|
|
|
|
instance Draw Character where
|
|
draw _ = visibleRegion rloc rreg $ str "@"
|
|
where
|
|
rloc = Location (negate scrollOffset, negate scrollOffset)
|
|
rreg = (2 * scrollOffset, 2 * scrollOffset)
|
|
drawPriority = const maxBound -- Character should always be on top, for now
|
|
|
|
instance Brain Character where
|
|
step ticks = (pure .) $ positioned . characterHitpoints' %~ \hp ->
|
|
if hp > fromIntegral initialHitpoints
|
|
then hp
|
|
else hp + hitpointRecoveryRate |*| ticks
|
|
|
|
instance Entity Character where
|
|
blocksVision _ = False
|
|
description _ = "yourself"
|
|
entityChar _ = "@"
|
|
|
|
instance Arbitrary Character where
|
|
arbitrary = genericArbitrary
|
|
|
|
initialHitpoints :: Hitpoints
|
|
initialHitpoints = 10
|
|
|
|
hitpointRecoveryRate :: Double `Per` Ticks
|
|
hitpointRecoveryRate = 1.0 / (15 * coerce defaultSpeed)
|
|
|
|
defaultSpeed :: TicksPerTile
|
|
defaultSpeed = 100
|
|
|
|
mkCharacter :: Character
|
|
mkCharacter = Character
|
|
{ _inventory = mempty
|
|
, _characterName = Nothing
|
|
, _characterHitpoints' = fromIntegral initialHitpoints
|
|
, _speed = defaultSpeed
|
|
}
|
|
|
|
defaultCharacterDamage :: Hitpoints
|
|
defaultCharacterDamage = 1
|
|
|
|
-- | Returns the damage that the character currently does with an attack
|
|
-- TODO use double-handed/left-hand/right-hand here
|
|
characterDamage :: Character -> Hitpoints
|
|
characterDamage
|
|
= fromMaybe defaultCharacterDamage
|
|
. preview (inventory . wielded . wieldedItems . wieldableItem . Raw.damage)
|
|
|
|
isDead :: Character -> Bool
|
|
isDead = (== 0) . characterHitpoints
|
|
|
|
pickUpItem :: Item -> Character -> Character
|
|
pickUpItem it = inventory . backpack %~ (it <|)
|
|
|
|
damage :: Hitpoints -> Character -> Character
|
|
damage (fromIntegral -> amount) = characterHitpoints' %~ \case
|
|
n | n <= amount -> 0
|
|
| otherwise -> n - amount
|