packages feed

Ninjas-0.1.0.0: src/Character.hs

{-# LANGUAGE RecordWildCards #-}
module Character where

import Graphics.Gloss.Data.Point
import Graphics.Gloss.Data.Vector

import VectorUtils
import Parameters

data Character = Character
  { charPos    :: Point
  , charFacing :: Vector -- Unit vector
  , charState  :: State
  }
  deriving (Read, Show, Eq)

data WalkInfo = Walk
  { walkTarget    :: Point
  -- Cached, so that we don't recompute all the time.
  , walkDist      :: Float
  , walkVelocity  :: Vector
  }
  deriving (Show, Read, Eq)

data WaitInfo = Wait
  { waitWaiting :: Maybe Float
  , waitStunned :: Bool
  }
  deriving (Show, Read, Eq)

data State
  = Walking WalkInfo
  | Waiting WaitInfo
  | Dead
  | Attacking Float
  deriving (Show, Read, Eq)

data ThinkTask = ChooseWait | ChooseDestination

-- | Compute a new character given a number of elapsed seconds. An optional
-- update task will be returned if the server should compute a new
-- goal for the character. Clients will ignore this task and wait for the
-- server to send an update.
stepCharacter ::
  Float {- ^ elapsed seconds -} ->
  Character ->
  (Character, Bool, Maybe ThinkTask) -- (new character, did we change states, AI task)
stepCharacter elapsed c =
  case state of
    Walking w
      | walkDist w < step -> done (Just ChooseWait)
      | otherwise ->
        working (Walking w { walkDist = walkDist w - step })
                c { charPos = newPos }

      where step = elapsed * speed
            newPos = addPt (mulSV elapsed (walkVelocity w)) (charPos c)

    Waiting w ->
      case waitWaiting w of
        Nothing -> working state c
        Just todo
          | todo < elapsed -> done (Just ChooseDestination)
          | otherwise ->
              working (Waiting w { waitWaiting = Just (todo - elapsed) }) c


    Attacking delay
      | elapsed > delay -> done Nothing
      | otherwise       -> working (Attacking (delay - elapsed)) c

    Dead -> working state c

  where
  state       = charState c
  done next   = (c { charState = Waiting Wait { waitWaiting = Nothing
                                              , waitStunned = False } }
                , True
                , next)
  working s n = (n { charState = s}, False, Nothing)

-- | Update a Character to have the goal of walking to a given point.
walkingCharacter :: Point -> Character -> Character
walkingCharacter walkTarget c = c { charState = state
                                  , charFacing = facing }
  where
  state       = Walking Walk { .. }

  facing      | walkDist > 0.001 = mulSV (1 / walkDist) path
              | otherwise        = charFacing c

  walkVelocity = mulSV speed facing

  path        = subPt walkTarget (charPos c)
  walkDist    = magV path

-- | Update an character to have the goal of waiting for an optional
-- duration and optionally to be drawn as stunned. If a duration
-- is specified the character will be updated automatically by the server.
-- characters without a duration are typically players.
waitingCharacter :: Maybe Float -> Bool -> Character -> Character
waitingCharacter waitWaiting waitStunned c =
  c { charState = Waiting Wait { .. } }

-- | Update an character to be dead
deadCharacter :: Character -> Character
deadCharacter c = c { charState = Dead }

-- | Update an character to be stunned for the default stun duration.
stunnedCharacter :: Character -> Character
stunnedCharacter = waitingCharacter (Just stunTime) True

-- | Update an character to be in the attacking state for the
-- default attack duration.
attackingCharacter :: Character -> Character
attackingCharacter c = c { charState = Attacking attackDelay }

isStunned :: Character -> Bool
isStunned c =
  case charState c of
    Waiting Wait { waitStunned = True } -> True
    _                                   -> False