/**********************************************************************
 *
 * File :     ratrophy.c
 *
 * Abstract : The rat and trophy bit handler. This module handles
 *            the movement and display of the rats and the trophy that
 *            appears once all the rats are destroyed.
 *
 **********************************************************************
 *
 * This file is a product of Criterion Software Ltd.
 *
 * This file is provided as is with no warranties of any kind and is
 * provided without any obligation on Criterion Software Ltd. or
 * Canon Inc. to assist in its use or modification.
 *
 * Criterion Software Ltd. will not, under any
 * circumstances, be liable for any lost revenue or other damages arising
 * from the use of this file.
 *
 * Copyright (c) 1995 Criterion Software Ltd.
 * All Rights Reserved.
 *
 * RenderWare is a trademark of Canon Inc.
 *
 ************************************************************************/

/*--- Include Files ---*/

#include "global.h"     /* Application general includes */

#define TARGET_HITFORCE     0.3  /* Objects that are hit move at this velocity */

#define RAT_CREATE_TIME     200  /* No of frames before a new rat is created */

/* Rat State definitions: These are the different ways that a rat can move */

#define RAT_WAIT            1
#define RAT_TURN_LEFT       2
#define RAT_TURN_RIGHT      3
#define RAT_LOOK            4
#define RAT_FORWARD         5

#define RAT_MOVE_SIZE       0.15  /* Once a rat gets this close to a wall or
                                     the ground it bounces */
#define RAT_TRAP_SIZE       0.05  /* If the rat is on the ground and it gets
                                     this close to a wall or the kerb then 
                                     change its behaviour */

#define TROPHY_MOVE_SIZE    0.5   /* Once the trophy gets this close to a wall
                                     or the ground it bounces */

/* The start position for the trophy when it enters the street */

#define TROPHY_X            0.0
#define TROPHY_Y            4.0
#define TROPHY_Z	    2.0


#define RAT_START_HEIGHT    4.0     /* The height that the rats fall from */
#define RAT_SPEED           0.02    /* The speed that rats move forward at */
#define RAT_HITS            4       /* The number of hits that a rat can take 
                                       before it explodes */
#define RAT_BOUNCE          (-1.0/4.0) /* Change in velocity for a rat when it
                                          bounces off of a wall or the ground */
#define RAT_FRICTION        (1.0/4.0)  /* When a rat bounces of the ground, its
                                          forward momentum is damped by this amount */

#define RAT_MAX_FRAMES      7       /* the number of different clumps that
                                       make up the rat animation */

#define GROUND_TRAP         0.01    /* When a rat gets this close to the
                                       ground it behaves as though it is
                                       on the ground */

/* All Rats: A single global variable of this type is defined. It holds all 
 * of the rat related items that are shared across mulitple instances 
 */
typedef struct 
{
    RwClump *cpaFrames[RAT_MAX_FRAMES]; /* Rat animation clumps */
    RwClump *cpShadow;                  /* Rat shadow clumps */
    RwMatrix4d *mpTurnLeft;             /* Matrix for a rat turning left */
    RwMatrix4d *mpTurnRight;            /* Matrix for a rat turning right */
    RwClump *cpCollision;               /* minimal rat used for picking */
    int nRats;                          /* number of rats */
    int nDeadRats;                      /* number of dead rats */
    int nCreateCount;                   /* number of frames since last rat 
                                           was created */
} AllRats;

/* Rat: This is the basic structure for a rat. One of these is created for
 * each rat in the scene */

typedef struct 
{
    Object oObj;            /* see object.c for this definition */
    RwClump *cpCollision;   /* collision clump for this rat */
    RwClump *cpShadow;      /* Rat shadow clump */
    int nState;             /* current rat motion type */
    int nCount;             /* number of frames to have this motion */
    int nFrame;             /* current rat animation frame */
    RwMatrix4d *mpDir;      /* rat direction matrix */
    RwV3d vVel;             /* Current rat velocity */
    int nHit;               /* Number of times that this rat has been hit */
} Rat;

/* Trophy: The trophy is loaded at startup time but only dropped into the
 * scene once all of the rats have been destroyed. This structure stores
 * the transient trophy data.
 */

typedef struct 
{
    Object oObj;
    RwClump *cpCollision;   /* collision clump for the trophy */
    RwClump *cpShadow;      /* shadow clump for the trophy */
    RwMatrix4d *mpDir;      /* trophy direction */
    RwV3d vVel;             /* Current trophy velocity */
} Trophy;

static AllRats arGRats;   
static Trophy  tpGTrophy;

/* Rat Functions */

/************************************************************************
 *
 *      Function:       NumRats()
 *                      
 *      Description:    Returns the number of rats in the street
 *
 *      Return Value:   The number of rats
 *
 ************************************************************************/
int NumRats(void)
{
    return(arGRats.nRats);
}

/************************************************************************
 *
 *      Function:       NumDeadRats()
 *                      
 *      Description:    Returns the number of rats that have been destroyed
 *
 *      Return Value:   The number of dead rats
 *
 ************************************************************************/
int NumDeadRats(void)
{
    return(arGRats.nDeadRats);
}

/************************************************************************
 *
 *      Function:       RatHit()
 *                      
 *      Description:    Decrement the hit count for the rat
 *
 *      Parameters:     opObj - the object structure that represents the rat
 *
 *      Return Value:   TRUE if the rat has been destroyed, FALSE otherwise
 *
 ************************************************************************/
int RatHit(Object *opObj)
{
    RwV3d vPos;
    Rat *rpRat;

    /* Extract the Rat data from the Object */

    rpRat = (Rat *)opObj->pExtra;

    /* Make the target move as a result of the hit by determining the vector
     * from the camera to the object and moving the rat in that direction 
     */

    /* Determine the unit vector from the camera to the rat */
    RwGetCameraPosition(cpGCamera, &vPos);
    RwSubtractVector(&(opObj->vPos), &vPos, &vPos);
    RwNormalize(&vPos);

    /* Make the rat move in the strike direction */
    RwScaleVector(&vPos, CREAL(TARGET_HITFORCE), &vPos);
    RwAddVector(&rpRat->vVel, &vPos, &rpRat->vVel);

    /* Can the rat take any more hits ?? */
    if (--(rpRat->nHit) < 0)
        return(TRUE);
    else
        return(FALSE); 
}

/************************************************************************
 *
 *      Function:       RatGivePurpose()
 *                     
 *      Description:    Assign a random rat movement type to the rat
 *
 *      Parameters:    Rat - the rat to assign the behaviour to
 *
 *      Return Value:  None
 *
 ************************************************************************/
static void RatGivePurpose(Rat *rpRat)
{
    RwInt32 nRand1,nRand2;

    /* Generate 2 Random numbers */

    nRand1 = RwRandom();
    nRand2 = (nRand1>>8);
    nRand1 &= 0xff;

    if (nRand1 < 10) 
    {
        rpRat->nState = RAT_TURN_LEFT;
        rpRat->nCount = (nRand2 & 0xf) + 15;  /* stay in this state for 
                                                 15 to 30 frames */
    } 
    else if (nRand1 < 20) 
    {
        rpRat->nState = RAT_TURN_RIGHT;
        rpRat->nCount = (nRand2 & 0xf) + 15;  /* stay in this state for 
                                                 15 to 30 frames */
    } 
    else if (nRand1 < 40) 
    {
        rpRat->nState = RAT_WAIT;
        rpRat->nCount = (nRand2 & 0x1f) + 4; /* stay in this state for 
                                                 4 to 35 frames */

    } 
    else 
    {
        rpRat->nState = RAT_FORWARD;
        rpRat->nCount = (nRand2 & 0xf) + 10; /* stay in this state for 
                                                10 to 25 frames */
    }
}

/************************************************************************
 *
 *      Function:       RatUpdate()
 *                     
 *      Description:    Update the position of a single rat. This function
 *                      is called by the generic object handler on every
 *                      frame for each rat in the street
 *
 *      Parameters:     opObj - the object that defines the rat
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void RatUpdate(Object *opObj)
{
    Rat *rpRat;
    RwReal nX,nY,nZ;

    /* Extract the rat data from the object */

    rpRat = (Rat *)opObj->pExtra;

    /* Apply the force of gravity to the rat */

    rpRat->vVel.y -= CREAL(GRAVITY);  

    /* Move the rat to its new position */

    RwTranslateMatrix(rpRat->mpDir,
                      rpRat->vVel.x, rpRat->vVel.y, rpRat->vVel.z,
                      rwPOSTCONCAT);
    nX = RwGetMatrixElement(rpRat->mpDir,3,0);
    nY = RwGetMatrixElement(rpRat->mpDir,3,1);
    nZ = RwGetMatrixElement(rpRat->mpDir,3,2);

    /* Test for collisions with the side walls */

    if (rpRat->vVel.x < CREAL(0.0))
    {
        /* Moving to the left - check against left wall */
        if (rpRat->oObj.vPos.x < CREAL(LEFT_STREET + RAT_MOVE_SIZE))
        {
            /* collision - bounce */
            rpRat->vVel.x = RMul(rpRat->vVel.x, CREAL(RICOCHET));
            nX = CREAL(LEFT_STREET + RAT_MOVE_SIZE);
            RwSetMatrixElement(rpRat->mpDir, 3, 0, nX);
        }
    }
    else
    {
        /* Moving to the right - check against right wall */
        if (rpRat->oObj.vPos.x > CREAL(RIGHT_STREET - RAT_MOVE_SIZE)) 
        {
            rpRat->vVel.x = RMul(rpRat->vVel.x, CREAL(RICOCHET));
            nX = CREAL(RIGHT_STREET - RAT_MOVE_SIZE);
            RwSetMatrixElement(rpRat->mpDir, 3, 0, nX);
        }
    }

    /* Test for collisions with the end walls */

    if (rpRat->vVel.z < CREAL(0))
    {
        /* moving towards far wall */
        if (rpRat->oObj.vPos.z < CREAL(FAR_STREET + RAT_MOVE_SIZE)) 
        {
            rpRat->vVel.z = RMul(rpRat->vVel.z, CREAL(RICOCHET));
            nZ = CREAL(FAR_STREET + RAT_MOVE_SIZE);
            RwSetMatrixElement(rpRat->mpDir, 3, 2, nZ);
        }
    }
    else
    {
        /* moving towards near wall */
        if (rpRat->oObj.vPos.z > CREAL(NEAR_STREET - RAT_MOVE_SIZE)) 
        {
            rpRat->vVel.z = RMul(rpRat->vVel.z, CREAL(RICOCHET));
            nZ = CREAL(NEAR_STREET - RAT_MOVE_SIZE);
            RwSetMatrixElement(rpRat->mpDir,3,2,nZ);
        }
    }

    /* Test for collisions with the ground */

    if ((nX < CREAL(LEFT_KERB)) || (nX > CREAL(RIGHT_KERB))) 
    {
        /* On the pavement */

        if (nY <= CREAL(KERB_HEIGHT)) 
        {
            RwTranslateMatrix(rpRat->mpDir, 
                              CREAL(0.0), RSub(CREAL(KERB_HEIGHT), nY), CREAL(0.0), 
                              rwPOSTCONCAT);

#ifdef WITH_SOUND
            /* Make the sound */

            if (rpRat->vVel.y < CREAL(-0.05)) 
            {
                int nVolume = REAL2INT(RMul(-rpRat->vVel.y, CREAL(50.0)));
                if (nVolume > 0x2f) 
                {
                    nVolume = 0x2f;
                }
                AllSoundsPlaySoundVolume(nVolume, "boink");
            }

#endif /* WITH_SOUND */

            /* Bounce the rat !*/

            rpRat->vVel.y = RMul(rpRat->vVel.y, CREAL(RAT_BOUNCE));

            /* Take into account friction */

            rpRat->vVel.x = RMul(rpRat->vVel.x, CREAL(RAT_FRICTION));
            rpRat->vVel.z = RMul(rpRat->vVel.z, CREAL(RAT_FRICTION));
            nY = CREAL(KERB_HEIGHT);
        }
    }
    else 
    {
        /* On the road */

        if (nY <= CREAL(STREET_HEIGHT)) 
        {
            RwTranslateMatrix(rpRat->mpDir, 
                              CREAL(0.0), RSub(CREAL(STREET_HEIGHT), nY), CREAL(0.0), rwPOSTCONCAT);

#ifdef WITH_SOUND
            /* Make the sound */

            if (rpRat->vVel.y < CREAL(-0.05)) 
            {
                int nVolume = REAL2INT(RMul(-rpRat->vVel.y, CREAL(50)));
                if (nVolume > 0x2f) 
                {
                    nVolume = 0x2f;
                }
                AllSoundsPlaySoundVolume(nVolume, "boink");
            }

#endif /* SOUND */

            /* Bounce the rat !*/

            rpRat->vVel.y = RMul(rpRat->vVel.y, CREAL(RAT_BOUNCE));

            /* Take into account friction */

            rpRat->vVel.x = RMul(rpRat->vVel.x, CREAL(RAT_FRICTION));
            rpRat->vVel.z = RMul(rpRat->vVel.z, CREAL(RAT_FRICTION));
            nY = CREAL(STREET_HEIGHT);
        }
    }
    /* If on the ground make it do things */

    if ( (nY < CREAL(KERB_HEIGHT + GROUND_TRAP)) && 
        (rpRat->vVel.y < CREAL(GRAVITY)))
    {
        if (rpRat->nCount-- < 0) 
        {
            /* previous movement completed */
            RatGivePurpose(rpRat);
        }

        switch (rpRat->nState) 
        {
            case RAT_WAIT: 
                break;

            case RAT_TURN_LEFT: 
                RwTransformMatrix(rpRat->mpDir, arGRats.mpTurnLeft, rwPRECONCAT);
                break;

            case RAT_TURN_RIGHT:
                RwTransformMatrix(rpRat->mpDir, arGRats.mpTurnRight, rwPRECONCAT);
                break;

            case RAT_FORWARD:
                RwTranslateMatrix(rpRat->mpDir, 
                                  CREAL(0.0), CREAL(0.0), CREAL(RAT_SPEED),
                                  rwPRECONCAT);
                if (!(rpRat->nCount)) 
                {
                    /* If its the last frame then ortho normalize it */
                    RwOrthoNormalizeMatrix(rpRat->mpDir,rpRat->mpDir);
                }
                rpRat->nFrame++;

                if (rpRat->nFrame >= RAT_MAX_FRAMES) 
                {
                    rpRat->nFrame = 0;
                }
                break;

            default:
                break;
        }

        /* Bound the movement of ratty */

        if (nY == CREAL(STREET_HEIGHT)) 
        {
            if (nX < CREAL(LEFT_KERB + RAT_TRAP_SIZE)) 
            {
                RwSetMatrixElement(rpRat->mpDir, 3, 0, 
                                   CREAL(LEFT_KERB + RAT_TRAP_SIZE));
                RatGivePurpose(rpRat);
            }
            if (nX > CREAL(RIGHT_KERB - RAT_TRAP_SIZE)) 
            {
                RwSetMatrixElement(rpRat->mpDir, 3, 0, 
                                   CREAL(RIGHT_KERB - RAT_TRAP_SIZE));
                RatGivePurpose(rpRat);
            }
        } 
        else 
        {
            if (nX < CREAL(LEFT_STREET + RAT_MOVE_SIZE)) 
            {
                RwSetMatrixElement(rpRat->mpDir, 3, 0, CREAL(LEFT_STREET + RAT_MOVE_SIZE));
                RatGivePurpose(rpRat);
            }
            if (nX > CREAL(RIGHT_STREET - RAT_MOVE_SIZE)) 
            {
                RwSetMatrixElement(rpRat->mpDir, 3, 0, CREAL(RIGHT_STREET - RAT_MOVE_SIZE));
                RatGivePurpose(rpRat);
            }
        }

        if (nZ < CREAL(FAR_STREET + RAT_MOVE_SIZE)) 
        {
             RwSetMatrixElement(rpRat->mpDir, 3, 2, CREAL(FAR_STREET + RAT_MOVE_SIZE));
             RatGivePurpose(rpRat);
        }
        else if (nZ > CREAL(NEAR_STREET - RAT_MOVE_SIZE)) 
        {
            RwSetMatrixElement(rpRat->mpDir, 3, 2, CREAL(NEAR_STREET - RAT_MOVE_SIZE));
            RatGivePurpose(rpRat);
        }
    } 
    opObj->cpClump = arGRats.cpaFrames[rpRat->nFrame];
    opObj->vPos.x = RwGetMatrixElement(rpRat->mpDir,3,0);
    opObj->vPos.y = RwGetMatrixElement(rpRat->mpDir,3,1);
    opObj->vPos.z = RwGetMatrixElement(rpRat->mpDir,3,2);

    RwTransformClump(rpRat->cpCollision, rpRat->mpDir, rwREPLACE);
}

/************************************************************************
 *
 *      Function:       RatRender()
 *                     
 *      Description:    Render a single rat. This function
 *                      is called by the generic object handler on every
 *                      frame for each rat in the street
 *
 *      Parameters:     opObj - the object that defines the rat
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void RatRender(Object *opObj)
{
  Rat *rpRat;
  RwReal nX;

    /* extract the Rat data from the generic object */

    rpRat = (Rat *)opObj->pExtra;

#ifdef WITH_SHADOWS
    
    /* Render the shadow below the rat. The location of the rat on the
     * street needs to be taken into consideration to allow for the
     * different heights of the street and the kerb 
     */

    /* Extract the location in X from the matrix */

     nX = RwGetMatrixElement(rpRat->mpDir, 3, 0);

    /* Extract the position and orientation info from the rat */

    RwCopyMatrix(rpRat->mpDir, RwScratchMatrix());

    /* Adjust the matrix to place the shadow on the street of the kerb */

    if ( (nX < CREAL(LEFT_KERB)) || (nX > CREAL(RIGHT_KERB)) ) 
    {
        RwSetMatrixElement(RwScratchMatrix(), 3, 1, CREAL(KERB_HEIGHT + SHADOW_HEIGHT));
    } 
    else 
    {
        RwSetMatrixElement(RwScratchMatrix(), 3, 1, CREAL(STREET_HEIGHT + SHADOW_HEIGHT));
    }

    /* Transform and render the shadow */
    RwTransformClump(rpRat->cpShadow, RwScratchMatrix(), rwREPLACE);
    RwRenderClump(rpRat->cpShadow);
#endif

  /* Transform and Render the rat */

  RwTransformClump(opObj->cpClump, rpRat->mpDir, rwREPLACE);
  RwRenderClump(opObj->cpClump);
}

/************************************************************************
 *
 *      Function:       RatDestroy()
 *                     
 *      Description:    Destroy a single rat. This function
 *                      is called by the generic object handler once the
 *                      object has been deleted.
 *
 *      Parameters:     opObj - the object that defines the rat
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void RatDestroy(Object *opObj)
{
    Rat *rpRat;

    /* extract the rat data from the generic object */

    rpRat = (Rat *)opObj->pExtra;

    /* Remove the collision object for this rat from the collision scene */

    RwRemoveClumpFromScene(rpRat->cpCollision);
    RwDestroyClump(rpRat->cpCollision);

    /* Destroy the position matrix for this rat */

    RwDestroyMatrix(rpRat->mpDir);

    /* There is one less rat in the world */

    arGRats.nRats--;

    /* Add 1 to the number of dead rats */

    arGRats.nDeadRats++;

    /* free up the rat data */
    free(rpRat);
}

/************************************************************************
 *
 *      Function:       RatSetup()
 *                     
 *      Description:    Create a new rat and drop it into the street
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void RatSetup(void)
{  
    RwReal nX, nY, nZ;
    Rat *rpRat;

    /* Generate a random start position for the rat */

    nX = RANDOM_REAL(CREAL(LEFT_STREET + 0.2), CREAL(RIGHT_STREET - 0.2));
    nY = CREAL(RAT_START_HEIGHT);
    nZ = RANDOM_REAL(CREAL(FAR_STREET + 0.2), CREAL(NEAR_STREET - 0.2));

    /* Create a new rat */
    rpRat = (Rat *)malloc(sizeof(Rat));

    /* initialise the generic bit */
    ObjectSetup(&rpRat->oObj, nX, nY, nZ, NULL);

    /* define the functions that the generic object handler needs to
       control the rat */

    rpRat->oObj.fpUpdate = RatUpdate;
    rpRat->oObj.fpRender = RatRender;
    rpRat->oObj.fpDestroy = RatDestroy;
    rpRat->oObj.pExtra = (void *)rpRat;

    /* We want to assign some behaviour as soon as the rat hits the ground */
    rpRat->nFrame = 0;

    /* Start with zero velocity. Gravity will start things moving */
    rpRat->vVel.x = CREAL(0.0);
    rpRat->vVel.y = CREAL(0.0);
    rpRat->vVel.z = CREAL(0.0);

    /* Give the rat some behaviour */
    RatGivePurpose(rpRat);

    /* Generate and initialise the transformation matrix for this rat */
    rpRat->mpDir = RwCreateMatrix();
    RwTranslateMatrix(rpRat->mpDir, nX, nY, nZ, rwREPLACE);

    /* Copy the generic rat collision object, transform it to the position
       and orientation of the rat and add it to the collision scene */

    rpRat->cpCollision = RwDuplicateClump(arGRats.cpCollision);
    RwSetClumpData(rpRat->cpCollision, &rpRat->oObj);
    RwTransformClump(rpRat->cpCollision, rpRat->mpDir, rwREPLACE);
    RwAddClumpToScene(spGTargetScene, rpRat->cpCollision);

    /* Set the Shadow image */

    rpRat->cpShadow = arGRats.cpShadow;

    /* Set the amount of hits it can take before being destroyed */

    rpRat->nHit = RAT_HITS;

    /* Just added a new rat so add one more to the number in the street */

    arGRats.nRats++;

    /* Add to the generic object scene management stuff */

    AllObjectsAddObject(&rpRat->oObj);

    /* Give the rat a random direction */

    RwRotateMatrix(RwScratchMatrix(), 
                   CREAL(0.0), CREAL(1.0), CREAL(0.0),
                   RANDOM_REAL(CREAL(0.0), CREAL(360.0)),
                   rwREPLACE );

    RwTransformMatrix(rpRat->mpDir, RwScratchMatrix(), rwPRECONCAT);
}
/************************************************************************
 *
 *      Function:       AllRatsDestroy()
 *                     
 *      Description:    Destroy the shared rat data.
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void AllRatsDestroy(void)
{
    RwDestroyClump(arGRats.cpShadow);
    RwDestroyClump(arGRats.cpCollision);
    RwDestroyClump(arGRats.cpaFrames[0]);
    RwDestroyClump(arGRats.cpaFrames[1]);
    RwDestroyClump(arGRats.cpaFrames[2]);
    RwDestroyClump(arGRats.cpaFrames[3]);
    RwDestroyClump(arGRats.cpaFrames[4]);
    RwDestroyClump(arGRats.cpaFrames[5]);
    RwDestroyClump(arGRats.cpaFrames[6]);

    RwDestroyMatrix(arGRats.mpTurnLeft);
    RwDestroyMatrix(arGRats.mpTurnRight);
}

/************************************************************************
 *
 *      Function:       AllRatsSetup()
 *                     
 *      Description:    Create the shared rat data.
 *
 *      Parameters:     None
 *
 *      Return Value:   TRUE if successful, FALSE otherwise
 *
 ************************************************************************/
int AllRatsSetup(void)
{
    /* start with zero rats */

    arGRats.nRats = 0;

    /* Read all of the clumps needed for the rats*/
    if (!(arGRats.cpCollision = DoRwReadShape("collisio.rwx")) ||
#ifdef WITH_SHADOWS
        !(arGRats.cpShadow = DoRwReadShape("ratshad.rwx")) ||
#endif
    /* Get the rat animation frames */

        !(arGRats.cpaFrames[0] = DoRwReadShape("rat1.rwx")) ||
        !(arGRats.cpaFrames[2] = DoRwReadShape("rat3.rwx")) ||
        !(arGRats.cpaFrames[4] = DoRwReadShape("rat5.rwx")) ||
        !(arGRats.cpaFrames[6] = DoRwReadShape("rat7.rwx")) ||
        !(arGRats.cpaFrames[1] = DoRwReadShape("rat2.rwx")) || 
        !(arGRats.cpaFrames[3] = DoRwReadShape("rat4.rwx")) ||
        !(arGRats.cpaFrames[5] = DoRwReadShape("rat6.rwx")))
    {
        /* Failed to read something - error */
        return FALSE;
    }

    /* Create The matrices used to turn the rats to the left and right */

    if (!(arGRats.mpTurnLeft = RwCreateMatrix()) ||
        !(arGRats.mpTurnRight = RwCreateMatrix())) 
    {
        /* failed to create the matrices - error */
        return FALSE;
    }

    /* And initialise these matrices */
    RwRotateMatrix(arGRats.mpTurnLeft,CREAL(0.0),CREAL(1.0),CREAL(0.0),
                   CREAL(3.0),rwREPLACE);

    RwRotateMatrix(arGRats.mpTurnRight,CREAL(0.0),CREAL(1.0),CREAL(0.0),
                   CREAL(-3.0),rwREPLACE);


    /* Create the initial amount of rats. Use half of the maximum */

    arGRats.nCreateCount=0;  

    while (arGRats.nRats < RAT_MAXIMUM/2)
    {
        RatSetup();
    }
    return TRUE;
}

/************************************************************************
 *
 *      Function:       AllRatsRelease()
 *                     
 *      Description:    Control the steady flow of rats into the street
 *
 *      Parameters:     None. Uses global rat data
 *
 *      Return Value:   None
 *
 ************************************************************************/
void AllRatsRelease(void)
{
    /* Increment the creation timer */
    arGRats.nCreateCount++;

    if ( (arGRats.nCreateCount >= RAT_CREATE_TIME) &&
         (arGRats.nRats) ) 
    {
        /* It's time to create a rat */
        arGRats.nCreateCount = 0;

        if (arGRats.nRats < RAT_MAXIMUM) 
        {
            /* We haven't reached the limit yet. Create anothe one */
            RatSetup();
        }
    }
}

/* Trophy Functions */

/************************************************************************
 *
 *      Function:       TrophyHit()
 *                     
 *      Description:    Move the trophy in response to a hit
 *
 *      Parameters:     None. Uses the global trophy data object
 *
 *      Return Value:   None
 *
 ************************************************************************/
void TrophyHit(void)
{
    RwV3d vPos;

    /* Use the vector from the camera to the trophy to determine the strike
       direction */

    RwGetCameraPosition(cpGCamera, &vPos);
    RwSubtractVector(&(tpGTrophy.oObj.vPos), &vPos, &vPos);
    RwNormalize(&vPos);

    /* Move the trophy in the direction of the strike */
    RwScaleVector(&vPos, CREAL(TARGET_HITFORCE), &vPos);
    RwAddVector(&tpGTrophy.vVel, &vPos, &tpGTrophy.vVel);
}

/************************************************************************
 *
 *      Function:       TrophyUpdate()
 *                     
 *      Description:    This function is called every frame to 
 *                      update the position of the trophy,
 *
 *      Parameters:     opObj - the generic object
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void TrophyUpdate(Object *opObj)
{
    RwReal nX,nY,nZ;

    /* Apply the force of gravity */

    tpGTrophy.vVel.y -= CREAL(GRAVITY);  

    /* Preserve the momentum of the trophy */
    
    RwTranslateMatrix(tpGTrophy.mpDir,
                      tpGTrophy.vVel.x, tpGTrophy.vVel.y, tpGTrophy.vVel.z,
                      rwPOSTCONCAT);
    nX = RwGetMatrixElement(tpGTrophy.mpDir,3,0);
    nY = RwGetMatrixElement(tpGTrophy.mpDir,3,1);
    nZ = RwGetMatrixElement(tpGTrophy.mpDir,3,2);

    /* Test for collisions with the side walls */

    if (tpGTrophy.vVel.x < CREAL(0.0))
    {
        /* Moving to the left - check against left wall */
        if (nX < CREAL(LEFT_STREET + RAT_MOVE_SIZE))
        {
            /* collision - bounce */
            tpGTrophy.vVel.x = RMul(tpGTrophy.vVel.x, CREAL(RICOCHET));
            nX = CREAL(LEFT_STREET + RAT_MOVE_SIZE);
        }
    }
    else
    {
        /* Moving to the right - check against right wall */
        if (nX > CREAL(RIGHT_STREET - RAT_MOVE_SIZE)) 
        {
            tpGTrophy.vVel.x = RMul(tpGTrophy.vVel.x, CREAL(RICOCHET));
            nX = CREAL(RIGHT_STREET - RAT_MOVE_SIZE);
        }
    }

    /* Test for collisions with the end walls */

    if (tpGTrophy.vVel.z < CREAL(0))
    {
        /* moving towards far wall */
        if (nZ < CREAL(FAR_STREET + RAT_MOVE_SIZE)) 
        {
            tpGTrophy.vVel.z = RMul(tpGTrophy.vVel.z, CREAL(RICOCHET));
            nZ = CREAL(FAR_STREET + RAT_MOVE_SIZE);
        }
    }
    else
    {
        /* moving towards near wall */
        if (nZ > CREAL(NEAR_STREET - RAT_MOVE_SIZE)) 
        {
            tpGTrophy.vVel.z = RMul(tpGTrophy.vVel.z, CREAL(RICOCHET));
            nZ = CREAL(NEAR_STREET - RAT_MOVE_SIZE);
        }
    }

    /* Test for collisions with the ground */

    if ((nX < CREAL(LEFT_KERB)) || (nX > CREAL(RIGHT_KERB))) 
    {
        /* On the pavement */

        if (nY <= CREAL(KERB_HEIGHT)) 
        {
            RwTranslateMatrix(tpGTrophy.mpDir, 
                              CREAL(0.0), CREAL(KERB_HEIGHT), CREAL(0.0), 
                              rwPOSTCONCAT);
#ifdef WITH_SOUND
            /* Make the sound */

            if (tpGTrophy.vVel.y < CREAL(-0.05)) 
            {
                int nVolume = REAL2INT(RMul(-tpGTrophy.vVel.y, CREAL(50)));
                if (nVolume > 0x2f) 
                {
                    nVolume = 0x2f;
                }
                AllSoundsPlaySoundVolume(nVolume, "boink");
            }
#endif /* SOUND */

            /* Bounce the trophy. Use the rat friction and bounce parameters */

            tpGTrophy.vVel.y = RMul(tpGTrophy.vVel.y, CREAL(RAT_BOUNCE));

            /* Take into account friction */

            tpGTrophy.vVel.x = RMul(tpGTrophy.vVel.x, CREAL(RAT_FRICTION));
            tpGTrophy.vVel.z = RMul(tpGTrophy.vVel.z, CREAL(RAT_FRICTION));
            nY = CREAL(KERB_HEIGHT);
        }
    }
    else 
    {
        /* On the road */

        if (nY <= CREAL(STREET_HEIGHT)) 
        {
            RwTranslateMatrix(tpGTrophy.mpDir, 
                              CREAL(0.0), CREAL(STREET_HEIGHT) - nY, CREAL(0.0), rwPOSTCONCAT);

#ifdef WITH_SOUND
            /* Make the sound */

            if (tpGTrophy.vVel.y < CREAL(-0.05)) 
            {
                int nVolume = REAL2INT(RMul(-tpGTrophy.vVel.y, CREAL(50)));
                if (nVolume > 0x2f) 
                {
                    nVolume = 0x2f;
                }
                AllSoundsPlaySoundVolume(nVolume, "boink");
            }
#endif /* SOUND */

            /* Bounce the trophy !*/

            tpGTrophy.vVel.y = RMul(tpGTrophy.vVel.y, CREAL(RAT_BOUNCE));

            /* Take into account friction */

            tpGTrophy.vVel.x = RMul(tpGTrophy.vVel.x, CREAL(RAT_FRICTION));
            tpGTrophy.vVel.z = RMul(tpGTrophy.vVel.z, CREAL(RAT_FRICTION));
            nY = CREAL(STREET_HEIGHT);
        }
    }
    /* Move the trophy to its new position */
    RwSetMatrixElement(tpGTrophy.mpDir,3,0, nX);
    RwSetMatrixElement(tpGTrophy.mpDir,3,1, nY);
    RwSetMatrixElement(tpGTrophy.mpDir,3,2, nZ);

    opObj->vPos.x = nX;
    opObj->vPos.y = nY;
    opObj->vPos.z = nZ;

    RwTransformClump(tpGTrophy.cpCollision, tpGTrophy.mpDir, rwREPLACE);
}

/************************************************************************
 *
 *      Function:       TrophyRender()
 *                     
 *      Description:    This function is called every frame to 
 *                      render the position of the trophy,
 *
 *      Parameters:     opObj - generic object data
 *
 *      Return Value:   None
 *
 ************************************************************************/
void TrophyRender(Object *opObj)
{  
    RwReal nX;

#ifdef WITH_SHADOWS
    /* Render the shadow below the trophy. The location of the trophy on the
     * street needs to be taken into consideration to allow for the
     * different heights of the street and the kerb 
     */

    /* Extract the location in X from the matrix */

    nX = RwGetMatrixElement(tpGTrophy.mpDir, 3, 0);

    /* Extract the position and orientation info from the trophy */

    RwCopyMatrix(tpGTrophy.mpDir, RwScratchMatrix());

    /* Adjust the matrix to place the shadow on the street of the kerb */

    if ( (nX < CREAL(LEFT_KERB)) || (nX > CREAL(RIGHT_KERB)) ) 
    {
        RwSetMatrixElement(RwScratchMatrix(), 3, 1, CREAL(KERB_HEIGHT + SHADOW_HEIGHT));
    } 
    else 
    {
        RwSetMatrixElement(RwScratchMatrix(), 3, 1, CREAL(STREET_HEIGHT + SHADOW_HEIGHT));
    }

    /* Position and Render the shadow */

    RwTransformClump(tpGTrophy.cpShadow, RwScratchMatrix(), rwREPLACE);
    RwRenderClump(tpGTrophy.cpShadow);
#endif

  /* Render the trophy */
  RwTransformClump(opObj->cpClump, tpGTrophy.mpDir, rwREPLACE);
  RwRenderClump(opObj->cpClump);
}

/************************************************************************
 *
 *      Function:       TrophyEnable()
 *                     
 *      Description:    Enable the trophy. Note that all of the trophy
 *                      data was initialised at load time so all we need
 *                      top do is drop it into the street .
 *
 *      Parameters:     None. Uses global trophy data object
 *
 *      Return Value:   None
 *
 ************************************************************************/
void TrophyEnable(void)
{
    AllObjectsAddObject(&tpGTrophy.oObj);
}

/************************************************************************
 *
 *      Function:       TrophySetup()
 *                     
 *      Description:    Create and initialise the trophy
 *
 *      Parameters:     None. Uses global trophy data object
 *
 *      Return Value:   None
 *
 ************************************************************************/
int TrophySetup(void)
{
    /* Initialise the generic object bit */

    ObjectSetup(&tpGTrophy.oObj, CREAL(TROPHY_X), CREAL(TROPHY_Y), CREAL(TROPHY_Z), NULL);

    /* Define the functions that the generic handler will call to update
       and render the trophy */

    tpGTrophy.oObj.fpUpdate = TrophyUpdate;
    tpGTrophy.oObj.fpRender = TrophyRender;

    /* Don't define a destroy here since the trophy is only destroyed when 
       we exit from cyberstreet */

    tpGTrophy.oObj.pExtra = (void *)&tpGTrophy;

    /* Trophy starts with zero velocity but gravity soon changes that */
    tpGTrophy.vVel.x = CREAL(0.0);
    tpGTrophy.vVel.y = CREAL(0.0);
    tpGTrophy.vVel.z = CREAL(0.0);

    /* Create the position matrix for the trophy */
    if (tpGTrophy.mpDir = RwCreateMatrix())
    {
        /* Initialise the trophy's position */
        RwTranslateMatrix(tpGTrophy.mpDir, CREAL(TROPHY_X), CREAL(TROPHY_Y), CREAL(TROPHY_Z), rwREPLACE);

        /* Read the trophy script file */ 
        if (tpGTrophy.oObj.cpClump = DoRwReadShape("trophy.rwx"))
        {
            /* Create the collision data for teh trophy. Use the entire 
               trophy for the collision */
           if (tpGTrophy.cpCollision = RwDuplicateClump(tpGTrophy.oObj.cpClump))
	   {
                /* Initialise the collision data and add it to the 
                   collision scene */
	       	RwSetClumpData(tpGTrophy.cpCollision, &tpGTrophy.oObj);
		RwTransformClump(tpGTrophy.cpCollision, tpGTrophy.mpDir, rwREPLACE);
    		RwAddClumpToScene(spGTargetScene, tpGTrophy.cpCollision);
                /* Read the shadow script file */
    		if (tpGTrophy.cpShadow = DoRwReadShape("trophys.rwx"))
		{
		  return(TRUE);
		}
		RwDestroyClump(tpGTrophy.cpCollision);
	   }
	   RwDestroyClump(tpGTrophy.oObj.cpClump);
        }
        RwDestroyMatrix(tpGTrophy.mpDir);
    }
    return(FALSE);
}

