packages feed

roguestar-gl-0.4.0.0: src/VisibleObject.hs

{-# LANGUAGE
    Arrows,
    OverloadedStrings,
    Rank2Types,
    TypeFamilies,
    FlexibleContexts #-}

module VisibleObject
    (ToolThreadInput(..),
     CreatureThreadOutput(..),
     AbstractEmpty(..),
     AvatarSwitch,
     visibleObjectThreadLauncher,
     objectTypeGuard,
     questionMarkAvatar,
     visibleObjects,
     allObjects,
     VisibleObjectReference(..),
     getVisibleObject,
     isBeingWielded,
     isWielding,
     visibleObject,
     objectDetailsLookup,
     objectDestination,
     objectIdealPosition,
     objectFacing,
     objectIdealFacing,
     objectIdealOrientation,
     visibleObjectHeader,
     wieldableObjectIdealOrientation)
    where

import RSAGL.FRP
import Data.Maybe
import ProtocolTypes
import Animation
import Tables
import Control.Arrow
import Data.List
import RSAGL.Animation
import AnimationExtras
import RSAGL.Math
import qualified Data.Set as Set
import qualified Data.Map as Map
import Control.Monad
import Scene
import Models.LibraryData
import qualified Data.ByteString.Char8 as B

data ToolThreadInput = ToolThreadInput {
    -- | Wield points of all creatures that are on screen.
    tti_wield_points :: Map.Map Integer CoordinateSystem }

data CreatureThreadOutput = CreatureThreadOutput {
    -- | Wield point for this creature.  This is the coordinate system in which
    -- a tool should be rendered, where the origin is the point at which the
    -- creature is grasping the weapon.
    cto_wield_point :: CoordinateSystem }

class AbstractEmpty a where
    abstractEmpty :: a

instance AbstractEmpty () where
    abstractEmpty = ()

instance AbstractEmpty (Maybe a) where
    abstractEmpty = Nothing

type AvatarSwitch i o m = RSwitch Enabled (Maybe Integer) i o m

-- | Avatar for unrecognized objects.  Basically this is a big conspicuous
-- warning that something is implemented in the engine but not in the client.
questionMarkAvatar :: (FRPModel m) => FRP e (AvatarSwitch i o m) i (Maybe CreatureThreadOutput)
questionMarkAvatar = proc _ ->
    do visibleObjectHeader -< ()
       t <- threadTime -< ()
       m_object_type <- objectDetailsLookup ThisObject "object-type" -< ()
       m_species <- objectDetailsLookup ThisObject "species" -< ()
       m_tool <- objectDetailsLookup ThisObject "tool" -< ()
       m_building <- objectDetailsLookup ThisObject "building" -< ()
       debugOnce -< if any (isJust) [m_object_type,m_species,m_tool]
                    then (Just $ B.concat $ ["questionMarkAvatar: apparently didn't recognize object: ",
		                 B.pack $ show m_object_type, ", ", B.pack $ show m_species, ", ", B.pack $ show m_tool, ", ", B.pack $ show m_building]) 
		    else Nothing
       m_position <- objectIdealPosition ThisObject -< ()
       let float_y = sine $ fromRotations $ t `cyclical'` (fromSeconds 5)
       let m_transform = fmap (translate (Vector3D 0 (0.7 + float_y/10) 0)) m_position 
       transformA libraryA -< maybe (Affine id,(scene_layer_local,NullModel))
           (\p -> (Affine $ translateToFrom p origin_point_3d,
                  (scene_layer_local,SimpleModel QuestionMark))) m_transform
       m_wield_point <- whenJust exportCoordinateSystem -< fmap (\p -> translate (vectorToFrom p origin_point_3d `add` Vector3D 0.4 0 0)) m_transform 
       returnA -< 
           do wield_point <- m_wield_point
	      return $ CreatureThreadOutput {
                           cto_wield_point = wield_point }


-- | Launch threads to represent every visible object.
visibleObjectThreadLauncher :: (FRPModel m,AbstractEmpty o) =>
                               FRP e (AvatarSwitch i o m) i o ->
                               FRP e (AvatarSwitch i o m) i o
visibleObjectThreadLauncher avatarA =
    arr (const abstractEmpty) <<<
    spawnThreads <<<
    arr (map (\x -> (Just x,avatarA))) <<<
    newListElements <<< allObjects <<<
    arr (const ())

-- | Kill a thread if an object has the wrong \"object-type\" field, e.g. anything that isn't a \"creature\".
objectTypeGuard :: (FRPModel m, StateOf m ~ AnimationState, ThreadingOf m ~ Enabled, ThreadIDOf m ~ Maybe Integer) => (B.ByteString -> Bool) -> FRP e m () ()
objectTypeGuard f = proc () ->
    do m_obj_type <- objectDetailsLookup ThisObject "object-type" -< ()
       killThreadIf -< maybe False (not . f) m_obj_type

-- | Header function that just kills the current thread if it isn't a visible object.
visibleObjectHeader :: (FRPModel m, ThreadingOf m ~ Enabled, ThreadIDOf m ~ Maybe Integer, StateOf m ~ AnimationState) => FRP e m () ()
visibleObjectHeader = proc () ->
    do unique_id <- arr (fromMaybe (error "visibleObjectHeader: threadIdentity was Nothing")) <<< threadIdentity -< ()
       uids <- allObjects -< ()
       killThreadIf -< not $ unique_id `elem` uids

-- | List all 'VisibleObject' records.
visibleObjects :: (FRPModel m, StateOf m ~ AnimationState) => FRP e m () [VisibleObject]
visibleObjects = proc () -> arr (maybe [] tableSelectTyped) <<< sticky isJust Nothing <<< driverGetTableA -< ("visible-objects","0")

-- | List all object UIDs.
allObjects :: (FRPModel m,StateOf m ~ AnimationState) => FRP e m () [Integer]
allObjects = proc () ->
    do visible_object_ids <- arr (map vo_unique_id . maybe [] tableSelectTyped)
           <<< sticky isJust Nothing
           <<< driverGetTableA -< ("visible-objects","0")
       wielded_object_ids <- arr (map wo_unique_id . maybe [] tableSelectTyped)
           <<< sticky isJust Nothing
           <<< driverGetTableA -< ("wielded-objects","0")
       returnA -< Set.toList $ Set.fromList visible_object_ids `Set.union`
                               Set.fromList wielded_object_ids

{-------------------------------------------------------------------------------
  Retrieving information about specific visible objects.
-------------------------------------------------------------------------------}

-- | Indirect or direct reference to a visible object.
data VisibleObjectReference =
    ThisObject
  | UniqueID Integer
  | WieldedParent VisibleObjectReference
  | WieldedTool VisibleObjectReference
  | Attacker

-- | As a Tool, who is wielding you?
wieldedParent :: (FRPModel m,StateOf m ~ AnimationState) => FRP e m (Maybe Integer) (Maybe Integer)
wieldedParent = proc m_unique_id -> 
    do wielded_pairs <- arr (maybe [] tableSelectTyped) <<< sticky isJust Nothing <<< driverGetTableA -< ("wielded-objects","0")
       returnA -< fmap wo_creature_id $ find ((== m_unique_id) . Just . wo_unique_id) wielded_pairs

-- | As a creature, what tool are you wielding?
wieldedTool :: (FRPModel m,StateOf m ~ AnimationState) => FRP e m (Maybe Integer) (Maybe Integer)
wieldedTool = proc m_unique_id ->
    do wielded_pairs <- arr (maybe [] tableSelectTyped) <<< sticky isJust Nothing <<< driverGetTableA -< ("wielded-objects","0")
       returnA -< fmap wo_unique_id $ find ((== m_unique_id) . Just . wo_creature_id) wielded_pairs

-- | Get the unique ID of the object specified.
getVisibleObject :: (FRPModel m, StateOf m ~ AnimationState, ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m () (Maybe Integer)
getVisibleObject (ThisObject) = threadIdentity
getVisibleObject (UniqueID unique_id) = proc () -> returnA -< Just unique_id
getVisibleObject (WieldedParent (WieldedTool ref)) = getVisibleObject ref
getVisibleObject (WieldedTool (WieldedParent ref)) = getVisibleObject ref
getVisibleObject (WieldedParent ref) = getVisibleObject ref >>> wieldedParent
getVisibleObject (WieldedTool ref) = getVisibleObject ref >>> wieldedTool
getVisibleObject Attacker = arr (const "who-attacks") >>> driverGetAnswerA >>> arr (maybe Nothing readInteger)

-- | As a Tool, are you currently being wielded by anyone?
isBeingWielded :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m () Bool
isBeingWielded obj = getVisibleObject (WieldedParent obj) >>> arr isJust

-- | As a Creature, are you currently wielding a tool?
isWielding :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m () Bool
isWielding obj = getVisibleObject (WieldedTool obj) >>> arr isJust

-- | Get the 'VisibleObject' record for any object.
visibleObject :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m () (Maybe VisibleObject)
visibleObject obj = proc () ->
    do m_unique_id <- getVisibleObject obj -< ()
       visible_objects <- visibleObjects -< ()
       returnA -< do find ((== m_unique_id) . Just . vo_unique_id) visible_objects

-- | Get an "object-details" field for the specified visible object.
objectDetailsLookup :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> B.ByteString -> FRP e m () (Maybe B.ByteString)
objectDetailsLookup obj field = proc _ ->
    do m_unique_id <- getVisibleObject obj -< ()
       m_details_table <- arr (fromMaybe Nothing) <<< whenJust (driverGetTableA <<< arr (\x -> ("object-details",B.pack $ show x))) -< m_unique_id
       sticky isJust Nothing -< 
           do details_table <- m_details_table
              tableLookup details_table ("property","value") field

-- | Grid position to which the specified object should move.
objectDestination :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m () (Maybe (Integer,Integer))
objectDestination obj = arr (fmap vo_xy) <<< visibleObject obj

-- | Correct position of the specified object, with smooth translation.
objectIdealPosition :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m () (Maybe Point3D)
objectIdealPosition obj = 
    whenJust (approachA 0.25 (perSecond 3)) <<< 
    arr (fmap (\(x,y) -> Point3D (realToFrac x) 0 (negate $ realToFrac y))) <<< 
    objectDestination obj

-- | Goal direction in which the specified object should be pointed.
objectFacing :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m () (Maybe BoundAngle)
objectFacing obj = arr (fmap vo_facing) <<< visibleObject obj

-- | Direction in which the specified object should be pointed, with smooth rotation.
objectIdealFacing :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m () (Maybe Angle)
objectIdealFacing obj = arr (fmap unboundAngle) <<< whenJust (approachA 0.1 (perSecond 1)) <<< objectFacing obj

-- | Combine 'objectIdealPosition' and 'objectIdealFacing' to place an object.
objectIdealOrientation :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m () (Maybe CoordinateSystem)
objectIdealOrientation obj = proc () ->
    do m_p <- objectIdealPosition obj -< ()
       m_a <- objectIdealFacing obj -< ()
       returnA -< do p <- m_p
                     a <- m_a
		     return $ translate (vectorToFrom p origin_point_3d) $ rotateY a $ root_coordinate_system

-- | 'objectIdealOrientation' implementation that is aware of wield points for wieldable objects.  If an object is being
-- wielded, it will snap to it's wield point.
wieldableObjectIdealOrientation :: (FRPModel m,StateOf m ~ AnimationState,ThreadIDOf m ~ Maybe Integer) => VisibleObjectReference -> FRP e m ToolThreadInput (Maybe CoordinateSystem)
wieldableObjectIdealOrientation obj = proc tti ->
    do ideal_resting <- objectIdealOrientation obj -< ()
       m_wielded_parent <- getVisibleObject (WieldedParent obj) -< ()
       let wield_point = do wielded_parent <- m_wielded_parent
                            Map.lookup wielded_parent $ tti_wield_points tti
       returnA -< wield_point `mplus` ideal_resting