{- $Id: ObjectBehavior.as,v 1.2 2003/11/10 21:28:58 antony Exp $
******************************************************************************
*                              I N V A D E R S                               *
*                                                                            *
*       Module:		ObjectBehavior					     *
*       Purpose:	Behavior of objects.				     *
*       Author:		Henrik Nilsson					     *
*                                                                            *
*             Copyright (c) Yale University, 2003                            *
*                                                                            *
******************************************************************************
-}

module ObjectBehavior (
    gun,	-- :: Position2 -> Object
    fallingGun,	-- :: Position2 -> Object
    missile, 	-- :: Position2 -> Velocity2 -> Object
    alien 	-- :: RandomGen g => g -> Position2 -> Object
) where

import qualified Random

import AFRP
import AFRPUtilities
import AFRPGeometry

import PhysicalDimensions
import WorldGeometry
import Parser
import Object


------------------------------------------------------------------------------
-- Gun
------------------------------------------------------------------------------

fallingGun :: Position2 -> Object
fallingGun p0 = proc _ -> do
    rec v <- integral -< gravity
        p@(Point2 x y) <- (p0 .+^) ^<< integral -< v
    let atGround = Event () `gate` (y <= 0 + gunHeight/2)
    returnA -< ObjOutput { ooObsObjState = oosFallingGun p v,
                           ooKillReq = atGround,
                           ooSpawnReq = atGround `tag` [gun (Point2 worldXMax 50)]
                         }

gun :: Position2 -> Object
gun (Point2 x0 y0) = proc (ObjInput {oiGameInput = gi, oiHit = hitEvent}) -> do
    -- Position.
    (Point2 xd _) <- ptrPos -< gi		-- Desired position
    let isHit = isEvent hitEvent
    rec
        -- Controller.
        let ad = 10 * (xd - x) - 5 * v		-- Desired acceleration

        -- Physics with hard limits on acceleration and speed.
        v <- integral -< let a = symLimit gunAccMax ad
                         in  if isHit then 0 else
			     if (-gunSpeedMax) <= v && v <= gunSpeedMax
                                || v < (-gunSpeedMax) && a > 0
                                || v > gunSpeedMax && a < 0
                             then a
                             else 0
        x <- (x0+) ^<< integral -< v

    -- Fire mechanism and ammunition level.
    trigger       <- lbp             -< gi
    (level, fire) <- magazine 20 0.5 -< trigger

    returnA -< ObjOutput {
	           ooObsObjState = oosGun (Point2 x y0) (vector2 v 0) level,
		   ooKillReq     = noEvent,
                   ooSpawnReq    =
                       fire `tag` [missile (Point2 x (y0 + (gunHeight/2))) 
                                           (vector2 v missileInitialSpeed)]
               }


-- Ammunition magazine. Reloaded up to maximal
-- capacity at constant rate.
-- n ... Maximal and initial number of missiles.
-- f ..........	Reload rate.
-- input ......	Trigger.
-- output .....	Tuple:
--   #1: Current number of missiles in magazine.
--   #2: Missile fired event.
magazine :: 
  Int -> Frequency 
      -> SF (Event ()) (Int, Event ())
magazine n f = proc trigger -> do
  reload <- repeatedly (1/f) () -< ()
  (level,canFire) 
      <- accumHold (n,True) -< 
             (trigger `tag` dec)
             `lMerge` (reload `tag` inc)
  returnA -< (level, 
	      trigger `gate` canFire)
  where
    inc :: (Int,Bool) -> (Int, Bool)
    inc (l,_) | l < n = (l + 1, l > 0)
              | otherwise = (l, True)
    dec :: (Int,Bool) -> (Int, Bool)
    dec (l,_) | l > 0 = (l - 1, True)
              | otherwise = (l, False)

-- Ammunition magazine. Reloaded up to maximal capacity at constant rate.
-- n ..........	Maximal and initial number of missiles.
-- f ..........	Reload rate.
-- input ......	Trigger.
-- output .....	Tuple:
--		#1 ....	Current number of missiles in magazine.
--		#2 ....	Missile fired.

{-
 Henrik's original version, commented out for now:

magazine :: Int -> Frequency -> SF (Event ()) (Int, Event ())
magazine n f = proc trigger -> do
    reload       <- repeatedly (1/f) ()      -< ()
    -- We have a reverse application operator #, but for some reason arrowp
    -- chokes on (#).
    newLevelFire <- accumFilter (flip ($)) n -< (trigger `tag` dec)
                                                `lMerge` (reload `tag` inc)
    level        <- hold n              -< fmap fst newLevelFire
    returnA -< (level, filterE snd newLevelFire `tag` ())
    where
	-- inc, dec :: Int -> (Int, Maybe (Int, Bool))
	inc l | l < n     = (l + 1, Just (l + 1, False))
              | otherwise = (l, Nothing)
        dec l | l > 0     = (l - 1, Just (l - 1, True))
              | otherwise = (l, Nothing)

-}

------------------------------------------------------------------------------
-- Missile
------------------------------------------------------------------------------

-- Of course, this would be much better if we used the real impulse stuff:
-- No bogus iPre, for instance.
missile :: Position2 -> Velocity2 -> Object
missile p0 v0 = proc oi -> do
    rec
        -- Basic physics
        vp  <- iPre v0                     -< v
        ffi <- forceField                  -< (p, vp)
        v  <- (v0 ^+^) ^<< impulseIntegral -< (gravity, ffi)
	p  <- (p0 .+^) ^<< integral        -< v
    die <- after missileLifeSpan () -< ()
    returnA -< ObjOutput {
	           ooObsObjState = oosMissile p v,
		   ooKillReq     = (oiHit oi `tag` ()) `lMerge` die,
                   ooSpawnReq    = noEvent
               }


------------------------------------------------------------------------------
-- Alien
------------------------------------------------------------------------------

type ShieldLevel = Double


-- Alien behavior.
-- g ..........	Random generator.
-- p0 .........	Initial position.
-- vyd ........ Desired vertical speed.

alien :: RandomGen g => g -> Position2 -> Velocity -> Bool -> Object
alien g p0 vyd hasGun = proc oi -> do
    rec
	-- Pick a desired horizontal position.
        rx     <- noiseR (worldXMin, worldXMax) g -< () 
        sample <- occasionally g 5 ()             -< ()
        xd     <- hold (point2X p0)               -< sample `tag` rx    
	
        -- Controller. Control constants not optimized. Who says aliens know
	-- anything about control theory?
        let axd = 5 * (xd - point2X p) - 3 * (vector2X v)
	    ayd = 20 * (vyd - (vector2Y v))
	    ad  = vector2 axd ayd
	    h   = vector2Theta ad

	-- Physics
	let a = vector2Polar (min alienAccMax (vector2Rho ad)) h
        vp  <- iPre v0                      -< v
        ffi <- forceField                   -< (p, vp)
        v   <- (v0 ^+^) ^<< impulseIntegral -< (gravity ^+^ a, ffi)
	p   <- (p0 .+^) ^<< integral        -< v

	-- Shields
	sl  <- shield -< oiHit oi
	die <- edge   -< sl <= 0

    returnA -< ObjOutput {
	           ooObsObjState = oosAlien p h v hasGun,
		   ooKillReq     = die,
                   ooSpawnReq    = die `tag` [fallingGun p] `gate` hasGun
               }
    where
	v0 = zeroVector


shield :: SF (Event v) ShieldLevel
shield = proc hit -> do
    rec
	let rechargeRate = if sl < slMax then slMax / 10 else 0
        sl <- (slMax +) ^<< impulseIntegral -< (rechargeRate, hit `tag` damage)
    returnA -< sl
    where
        slMax  = 100
        damage = -50


------------------------------------------------------------------------------
-- Force fields acting on objects
------------------------------------------------------------------------------

-- Object are subject to gravity and a strange repellent forcefield that
-- drives objects away from the edges, effectively creating a corridor.
-- The strange field is inversely proportional to the cube of the distance
-- from either edge. It is thought that the field is a remnant of a defence
-- system put in place by the mythical and technologically advanced
-- "Predecessors" eons ago.

{-
field :: Position2 -> Acceleration2
field (Point2 x _) = vector2 (leftAcc - rightAcc) 0 ^+^ gravity
    where
	leftAcc  = min (if x > worldXMin
                        then k / (x - worldXMin)^3
                        else maxAcc)
                       maxAcc
	rightAcc = min (if x < worldXMax
                        then k / (worldXMax - x)^3
                        else maxAcc)
                       maxAcc
        k        = 10000000
        maxAcc   = 10000
-}

-- New attempt. Force fields act like invisible walls.
-- The fact that this is a stateful *signal* function (Fields having state?
-- Come on ...), can be attributed to the fact that we are cheating in the
-- first place by abstracting events of short duration to instantaneous
-- events. "field" being a stateful signal functio is part of the price
-- one have to pay for that to make this work in practice.

forceField :: SF (Position2, Velocity2) (Event Acceleration2)
forceField = proc (p, v) -> do
    lfi    <- edge    -< point2X p < worldXMin && vector2X v < 0
    rfi    <- edge    -< point2X p > worldXMax && vector2X v > 0
    returnA -< (mergeBy (^+^) (lfi `tag` (vector2 (-2 * vector2X v) 0))
                              (rfi `tag` (vector2 (-2 * vector2X v) 0)))


gravity = vector2 0 (-20)


------------------------------------------------------------------------------
-- Support
------------------------------------------------------------------------------

limit ll ul x = if x < ll then ll else if x > ul then ul else x

symLimit l = let absl = abs l in limit (-absl) absl
