/**********************************************************************
 *
 * File :     osdos.c
 *
 * Abstract : DOS specific implementation of OS specific files. Note:
 *            the program entry point and top level control flow is
 *            also performed here.
 *
 **********************************************************************
 *
 * 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 global includes */

/* Dos specific include files */
#include <i86.h>
#include "rwdos.h"
#include "doswrap.h"

/* Macro and Magic Number definitions */

#define KEY_RSHIFT      0x01
#define KEY_LSHIFT      0x02
#define KEY_CONTROL     0x04
#define KEY_ALT         0x08
#define KEY_SCRCLOCK    0x10
#define KEY_NUMLOCK     0x20
#define KEY_CAPLOCK     0x40
#define KEY_INSERT      0x80

#define LORES_SCREEN_WIDTH  320
#define LORES_SCREEN_HEIGHT 200
#define LORES_CAMERA_WIDTH  LORES_SCREEN_WIDTH
#define LORES_CAMERA_HEIGHT LORES_SCREEN_HEIGHT

#define HIRES_SCREEN_WIDTH  640
#define HIRES_SCREEN_HEIGHT 480
#define HIRES_CAMERA_WIDTH  PANEL_HOLE_WIDTH
#define HIRES_CAMERA_HEIGHT PANEL_HOLE_HEIGHT


#ifdef WITH_SCORE

#define SCORE_UP      CREAL(0.2)
#define SCORE_RIGHT   CREAL(0.5)
#define SCORE_FORWARD CREAL(2.5)
#define SCORE_STARTX  CREAL(2.0)
#define SCORE_STARTY  CREAL(1.4)


/*--- Structure Definitions ---*/

/* AllSprites: A single global variable of this type is defined and used to hold
 * global sprite data
 */

typedef struct
{
    RwRect rClip;   /* The clip rectangle for the sprites */
    int nDepth;     /* The current display depth. Used when masking sprites */
} AllSprites;

AllSprites asGSprites;

/* Score definitions */

#define NUM_LEFT_IMAGES 4      /* The number of images used for the big rat */


/* Score: A single global variable of this type is declared and used to hold
 * all of the global score data
 */

typedef struct
{
    RwClump *cpRat;         /* The clump for the small spinning rat */
    RwMatrix4d *mpPos;      /* The absolute position matrix for the
                               spinning rats */
    RwMatrix4d *mpRot;      /* The incremental rotation that is applied to the
                               rats each frame */
    RwMatrix4d *mpOffset;   /* The matrix used to determine the 3D location
                               that the score rats are drawn */
    RwRaster   *spaLeftImages[NUM_LEFT_IMAGES]; /* The rasters for the big rat
                                                   which changes dependant on
                                                   the number of rats in the street */

    RwRaster *spTimesOne;           /* The raster for the x1 overlay */
    RwRaster *spTimesFive;          /* The raster for the x5 overlay */
    RwRaster *spTimesTwentyFive;    /* The raster for the x25 overlay */
} Score;

Score sGScore;

/*--- Function Prototypes ---*/

static int AllSoundsAddSound(char *cpFilename, int nVolume, int nPeriod, int nChannel);

static int AllSpritesSetClip(RwInt32 nX, RwInt32 nY, RwInt32 nW, RwInt32 nH);

#endif /* WITH_SCORE */

/*--- Global Variables ---*/

static int nGLoRes;     /* TRUE if lores display else false */
static int nGHiCol;     /* TRUE if hi color display else false */

#ifdef WITH_SCORE
/************************************************************************
 *
 *      Function:       AllSpritesSetup()
 *
 *      Description:    Initialise the global sprite data
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
static int
AllSpritesSetup(void)
{
  AllSpritesSetClip(0,0,0,0);
  RwGetDeviceInfo(rwRENDERDEPTH,&asGSprites.nDepth,sizeof(asGSprites.nDepth));

  return TRUE;
}

/************************************************************************
 *
 *      Function:       AllSpritesSetClip()
 *
 *      Description:    Set the clip rectangle for sprites
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
static int
AllSpritesSetClip(RwInt32 nX, RwInt32 nY, RwInt32 nW, RwInt32 nH)
{
    asGSprites.rClip.x = nX;
    asGSprites.rClip.y = nY;
    asGSprites.rClip.w = nW;
    asGSprites.rClip.h = nH;

    return TRUE;
}

/************************************************************************
 *
 *      Function:       SpriteMask()
 *
 *      Description:    The supplied RwRaster is converted into a
 *                      transparent one by overwriting all pixels of
 *                      the specified color with the transparency value
 *
 *      Parameters:     rpSprite - the RwRaster to mask
 *                      nX, nY - identifies the pixel in the RwRaster that
 *                      contains the background color. All pixels in the
 *                      raster that are this color will be made transparent
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void
SpriteMask(RwRaster *rpSprite,int nX,int nY)
{
    short int *npData;
    int nMask;
    unsigned char *cpData;
    unsigned char cMask;
    int nW;
    int nH;
    int nStride;

    if (asGSprites.nDepth==8)
    {
        /* its an 8 bit raster */

        /* Get the background color */
        cpData = (unsigned char *)RwGetRasterPixels(rpSprite);
        cMask = cpData[(RwGetRasterStride(rpSprite)*nY)+nX];

        nStride = (RwGetRasterStride(rpSprite))-
                   RwGetRasterWidth(rpSprite);

        /* And replace it with the transparent color */
        for (nH = RwGetRasterHeight(rpSprite); nH>0;nH--)
        {
            for (nW = RwGetRasterWidth(rpSprite);nW>0;nW--)
            {
                if ((*cpData)==cMask)
                {
                    /* Its transparant really ! */
                    (*cpData) = 0x0;
                }
                cpData++;
            }
            cpData+=nStride;
        }
    }
    else
    {
        /* It's a 16 bit raster */

        /* Get the background color */
        npData = (short int *)RwGetRasterPixels(rpSprite);
        nMask = npData[((RwGetRasterStride(rpSprite)>>1)*nY)+nX];

        nStride = (RwGetRasterStride(rpSprite)>>1)-
                  RwGetRasterWidth(rpSprite);

        /* And replace it with the transparent color */
        for (nH = RwGetRasterHeight(rpSprite); nH>0;nH--)
        {
            for (nW = RwGetRasterWidth(rpSprite);nW>0;nW--)
            {
                if ((*npData)==nMask)
                {
                    /* Its transparant really ! */
                    (*npData) = 0x0;
                }
                npData++;
            }
            npData+=nStride;
        }
    }
}

/************************************************************************
 *
 *      Function:       SpriteWriteImage()
 *
 *      Description:
 *
 *      Parameters:     cpCamera - the RenderWare camera to write into
 *                      rpSpr - the RwRaster to write
 *                      nIX, nIY - relative position in outpu camera for
 *                                 the centre of the sprite
 *                      nX, nY - first sprite pixel to be drawn
 *                      nW, nH - width and height of sprite area to be drawn
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void
SpriteWriteImage(RwCamera *cpCamera, RwRaster *rpSpr,
                 RwInt32 nIX, RwInt32 nIY,
                 RwInt32 nX, RwInt32 nY, RwInt32 nW, RwInt32 nH)
{
    RwRaster *rpRas;
    unsigned char *cpData;
    unsigned char *cpSource;
    unsigned short int *npData;
    unsigned short *npSource;
    int nCount;
    int nSourceStride;
    int nDataStride;

    /* Get the camera's image buffer */

    rpRas = (RwRaster *)RwGetCameraImage(cpCamera);

    if (asGSprites.nDepth==8)
    {
        /* 8 bit transfer */
        cpSource = ((unsigned char *)RwGetRasterPixels(rpSpr)) +
                    (RwGetRasterStride(rpSpr) * nY) + nX;

        cpData = ((unsigned char *)RwGetRasterPixels(rpRas)) +
                  (RwGetRasterStride(rpRas) * nIY) + nIX;

        nSourceStride = RwGetRasterStride(rpSpr) - nW;
        nDataStride = RwGetRasterStride(rpRas) - nW;

        for (;nH>0;nH--)
        {
            for (nCount=0; nCount < nW; nCount++)
            {
                if (*cpSource++)
                {
                    (*cpData)=(*cpSource);
                }
                cpData++;
            }
            cpSource+=nSourceStride;
            cpData+=nDataStride;
        }
    }
    else
    {
        /* 16 bit transfer */
        npSource = (unsigned short int *)
                   (((unsigned char *)RwGetRasterPixels(rpSpr)) +
                   (RwGetRasterStride(rpSpr) * nY) + (nX<<1));

        npData =  (unsigned short int *)
                  ((unsigned char *)RwGetRasterPixels(rpRas))+
                  (nIY * RwGetRasterStride(rpRas))+ (nIX<<1);

        nSourceStride = (RwGetRasterStride(rpSpr)>>1) - nW;
        nDataStride = (RwGetRasterStride(rpRas)>>1) - nW;

        for (;nH>0;nH--)
        {
            for (nCount=0; nCount < nW; nCount++)
            {
                if (*npSource++)
                {
                    (*npData)=(*npSource);
                }
                npData++;
            }
            npSource += nSourceStride;
            npData += nDataStride;
        }
    }
}
#endif /* WITH_SCORE */

/************************************************************************
 *
 *      Function:       SpriteRender()
 *
 *      Description:    Render the sprite at the specified position in
 *                      the image buffer of the supplied camera. The sprite
 *                      will be clipped to the current global clip region.
 *
 *      Parameters:     cpCamera - the camera to write the sprite into
 *                      rwSprite - the RwRaster to write.
 *                      nIX, nIY - relative normalized offsets into the camera
 *                                 image buffer
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void
SpriteRender(RwCamera *cpCamera, RwRaster *rpSprite, RwInt32 nIX, RwInt32 nIY)
{
    RwInt32 nW, nH, nX, nY, nSW, nSH;

    if (!rpSprite)
    {
        return;
    }

    nX = 0;
    nY = 0;
    nSW= nW = RwGetRasterWidth(rpSprite);
    nSH= nH = RwGetRasterHeight(rpSprite);

	/* Clip the display region to the clip region */

    if ( (nIX > (asGSprites.rClip.x + asGSprites.rClip.w)) ||
         ((nIX+nSW) < asGSprites.rClip.x) ||
         (nIY > (asGSprites.rClip.y + asGSprites.rClip.h)) ||
         ((nIY+nSH) < asGSprites.rClip.y) )
    {
		/* Its not on the screen */
		return;
    }

	/* Clip to the region size */

    if (nIX < asGSprites.rClip.x)
    {
        nX = asGSprites.rClip.x - nIX;
        nW -= nX;
	}
    if ((nIX+nSW) > (asGSprites.rClip.x + asGSprites.rClip.w))
    {
        nW -= ((nIX+nSW) - (asGSprites.rClip.x + asGSprites.rClip.w));
	}
    if (nIY < asGSprites.rClip.y)
    {
        nY = asGSprites.rClip.y - nIY;
        nH -= nY;
	}
    if ((nIY+nSH) > (asGSprites.rClip.y + asGSprites.rClip.h))
    {
        nH -= ((nIY+nSH) - (asGSprites.rClip.y+asGSprites.rClip.h));
	}

	/* Check if after clipping all is ok */

    if ((nW <= 0) || (nH <= 0))
    {
	    /* Nothing to display ! */
        return;
	}

    SpriteWriteImage(cpCamera, rpSprite, nIX+nX, nIY+nY, nX, nY, nW, nH);
}

/************************************************************************
 *
 *      Function:       SpriteRenderRelative()
 *
 *      Description:    Draws a sprite at a relative normalized position.
 *                      The position specified is where the CENTRE of the
 *                      sprite will be located.
 *
 *      Parameters:     cpCam - the camera that the sprite will be rendered into
 *                      rwSprite - the sprite to render
 *                      nX, nY -  relative X and Y offsets into camera viewport
 *                                these are normalised so spepcifying (0.5, 0.5)
 *                                for example will draw the sprite in the centre
 *                                of the cameras viewport
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void
SpriteRenderRelative(RwCamera *cpCam, RwRaster *rpSprite, RwReal nX, RwReal nY)
{
    RwInt32 nW,nH,nCX,nCY;

    if (rpSprite)
    {
        RwGetCameraViewport(cpGCamera,&nCX,&nCY,&nW,&nH);

        nCX = REAL2INT(RMul(INT2REAL(nW),nX));
        nCY = REAL2INT(RMul(INT2REAL(nH),nY));

        nCX -= RwGetRasterWidth(rpSprite) >> 1;
        nCY -= RwGetRasterHeight(rpSprite) >> 1;

        SpriteRender(cpCam, rpSprite, nCX, nCY);
    }
}

/************************************************************************
 *
 *      Function:       OsBeginCameraUpdate()
 *
 *      Description:    Os specific version of RwBeginCameraUpdate().
 *                      For MS Windows RwBeginCameraUpdate() is passed
 *                      the handle to the window. We use the global
 *                      handle that is setup in the main window handler.
 *
 *      Parameters:     Camera - the Camera to pass to RwBeginCameraUpdate()
 *
 *      Return Value:   None
 *
 ************************************************************************/
void OsBeginCameraUpdate(RwCamera *Camera)
{
    RwBeginCameraUpdate(Camera, NULL);
}

/************************************************************************
 *
 *      Function:       OsMaxScreenWidth()
 *
 *      Description:    The maximum screen width is an OS specific
 *                      feature. This function returns this information to
 *                      the main body of the application
 *
 *      Parameters:     None
 *
 *      Return Value:   TRUE if successful else FALSE
 *
 ************************************************************************/
RwInt32 OsMaxScreenWidth(void)
{
    if (nGLoRes)
    {
        return ((RwInt32)LORES_SCREEN_WIDTH);
    }
    else
    {
        return ((RwInt32)HIRES_SCREEN_WIDTH);
    }
}

/************************************************************************
 *
 *      Function:       OsMaxScreenHeight()
 *
 *      Description:    The maximum screen height is an OS specific
 *                      feature. This function returns this information to
 *                      the main body of the application
 *
 *      Parameters:     None
 *
 *      Return Value:   TRUE if successful else FALSE
 *
 ************************************************************************/
RwInt32 OsMaxScreenHeight(void)
{
    if (nGLoRes)
    {
        return ((RwInt32)LORES_SCREEN_HEIGHT);
    }
    else
    {
        return ((RwInt32)HIRES_SCREEN_HEIGHT);
    }
}

/************************************************************************
 *
 *      Function:       OsMaxCameraWidth()
 *
 *      Description:    The maximum camera width is an OS specific
 *                      feature. This function returns this information to
 *                      the main body of the application
 *
 *      Parameters:     None
 *
 *      Return Value:   TRUE if successful else FALSE
 *
 ************************************************************************/
RwInt32 OsMaxCameraWidth(void)
{
    if (nGLoRes)
    {
        return ((RwInt32)LORES_CAMERA_WIDTH);
    }
    else
    {
        return ((RwInt32)HIRES_CAMERA_WIDTH);
    }
}

/************************************************************************
 *
 *      Function:       OsMaxCameraHeight()
 *
 *      Description:    The maximum camera height is an OS specific
 *                      feature. This function returns this information to
 *                      the main body of the application
 *
 *      Parameters:     None
 *
 *      Return Value:   TRUE if successful else FALSE
 *
 ************************************************************************/
RwInt32 OsMaxCameraHeight(void)
{
    if (nGLoRes)
    {
        return ((RwInt32)LORES_CAMERA_HEIGHT);
    }
    else
    {
        return ((RwInt32)HIRES_CAMERA_HEIGHT);
    }
}

/************************************************************************
 *
 *      Function:       OsShowCameraImage()
 *
 *      Description:    Os specific version of RwShowCameraImage().
 *                      For DOS the device specific parameter is NULL
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void OsShowCameraImage()
{
    RwShowCameraImage(cpGCamera, NULL);
}

/************************************************************************
 *
 *      Function:       OsError()
 *
 *      Description:    Os specific error handler. For DOS we just
 *                      call printf
 *
 *      Parameters:     fmt - stdarg format string
 *
 *      Return Value:   None
 *
 ************************************************************************/
void OsError(char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vprintf (fmt, args);
    va_end(args);
    printf("\n");
}

/************************************************************************
 *
 *      Function:       OsOpen()
 *
 *      Description:    Os specific RwOpen(). This function opens
 *                      RenderWare and sets the Shape Path
 *
 *      Parameters:     None
 *
 *      Return Value:   TRUE if successful else FALSE
 *
 ************************************************************************/
int OsOpen(void)
{
    long nError;
    RwOpenArgument arg[4];

    if (nGHiCol)
    {
        arg[0].option = rwSCRDEPTH;
        arg[0].value = (void *)16;
    }
    else
    {
        arg[0].option = rwSCRDEPTH;
        arg[0].value = (void *)8;
    }

     arg[1].option = rwSCRWIDTH;
     arg[1].value = (void *)OsMaxScreenWidth();
     arg[2].option = rwSCRHEIGHT;
     arg[2].value = (void *)OsMaxScreenHeight();

    if (!RwOpenExt("DOSMOUSE", &nError, 3, &arg))
    {
        printf("Unable to access renderware!!\n");
        switch (nError)
        {
        case E_RW_DOS_MODE_UNAVAILABLE:
            printf("The installed VESA card is unable to switch to the resolution");
            printf(" requested.\n");
            printf("Either install a different video adapter or use a ");
            printf("supported video mode.");
            break;

        case E_RW_DOS_NO_VESA_BIOS:
            printf("A VESA bios is unavailable on this machine.\n");
            printf("Either use a VESA compatible Video Adapter or install a ");
            printf("VESA bios emulation TSR.\n");
            break;

        case E_RW_DOS_INCOMPATIBLE_BIOS:
            printf("The VESA bios on this machine is not of high enough version ");
            printf("to function\ncorrectly with RenderWare. Use a version 1.0 or");
            printf(" higher VESA bios or TSR.\n");
            break;

        case E_RW_DOS_NO_MOUSE:
            printf("No Microsoft compatible mouse driver present.\n");
            printf("Install a microsoft compatible mouse driver and try again.\n");
            break;

        default:
            printf("Unknown Error !!!!!!!!!!!!!!!\n");
            break;

        }
        return FALSE;
    }
    RwSetShapePath("SCRIPTS", rwPRECONCAT);
    RwSetShapePath("TEXTURES", rwPRECONCAT);
    RwSetShapePath(".", rwPRECONCAT);

    return TRUE;
}

/************************************************************************
 *
 *      Function:       OsSetCameraViewport()
 *
 *      Description:    Os specific RwSetCameraViewport(). For DOS, the
 *                      viewport size is dependant on the screen resolution
 *                      so we have to take account of this before calling
 *                      RwSetCameraViewport
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void OsSetCameraViewport()
{
    if (nGLoRes)
    {
        RwSetCameraViewport(cpGCamera, 0,0,
                            OsMaxCameraWidth(), OsMaxCameraHeight());
    }
    else
    {
        RwSetCameraViewport(cpGCamera, PANEL_HOLE_XOFFSET, PANEL_HOLE_YOFFSET,
                            PANEL_HOLE_WIDTH, PANEL_HOLE_HEIGHT);
    }
}

/************************************************************************
 *
 *      Function:       OsInit()
 *
 *      Description:    Os specific application initialisation. This function
 *                      is called once the palette has been set up and
 *                      performs all of the device specific initialisation
 *                      and object loading
 *
 *      Parameters:     None
 *
 *      Return Value:   TRUE if successful else FALSE
 *
 ************************************************************************/
int OsInit()
{
#ifdef  WITH_SCORE
    RwRect rViewport;
#endif
    RwRaster *rpTitle;
    RwPointerImage piImage;
    RwReal naWhite[3]={CREAL(1),CREAL(1),CREAL(1)};
    RwReal naBlack[3]={CREAL(0),CREAL(0),CREAL(0)};

    /* Rematch the pointer image */

    piImage.image = NULL;
    piImage.hotx=0;
    piImage.hoty=0;
    piImage.w = RwDeviceControl(rwSCRGETCOLOR,0,naBlack,sizeof(naBlack));
    piImage.h = RwDeviceControl(rwSCRGETCOLOR,0,naWhite,sizeof(naWhite));

    RwDeviceControl(rwPOINTERSETIMAGE,0,&piImage,sizeof(piImage));

    /* Do the loading screen */

    if (!(rpTitle = RwReadRaster("title.bmp", 0)))
    {
        return FALSE;
    }
    RwSetCameraBackdrop(cpGCamera, rpTitle);
    RwSetCameraBackdropOffset(cpGCamera, 0,0);

    RwSetCameraBackdropViewportRect(cpGCamera, 0,0,
                                    RwGetRasterWidth(rpTitle),
                                    RwGetRasterHeight(rpTitle));

    /* Put the title in the centre of the screen */

    RwSetCameraViewport(cpGCamera,
                        (OsMaxScreenWidth() - RwGetRasterWidth(rpTitle))/2,
                        (OsMaxScreenHeight() - RwGetRasterHeight(rpTitle))/2,
                        RwGetRasterWidth(rpTitle), RwGetRasterHeight(rpTitle));

    OsBeginCameraUpdate(cpGCamera);
    RwClearCameraViewport(cpGCamera);
    RwEndCameraUpdate(cpGCamera);
    OsShowCameraImage();

    RwDestroyRaster(rpTitle);

    OsSetCameraViewport();

#ifdef WITH_SCORE
    /* Set up the sprites */

    if (!(AllSpritesSetup()))
    {
        return FALSE;
    }

    /* Set up sprite structure */

    RwGetCameraViewport(cpGCamera,
                &rViewport.x,&rViewport.y,&rViewport.w,&rViewport.h);
    AllSpritesSetClip(0,0,rViewport.w,rViewport.h);
#endif

    rpGPanel = NULL;
    if (!nGLoRes)
    {
        if (!(rpGPanel = RwReadRaster("panel", 0L)))
        {
            return FALSE;
        }
    }

#ifdef WITH_SCORE
    if (!ScoreSetup())
    {
        return(FALSE);
    }
#endif

    return TRUE;
}

/************************************************************************
 *
 *      Function:       OsTidy()
 *
 *      Description:    Clean up all of the stuff initialised in OsInit()
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
void OsTidy(void)
{
#ifdef WITH_SCORE
    ScoreDestroy();
#endif
    if (!nGLoRes)
    {
        RwDestroyRaster(rpGPanel);
    }
}

#ifdef WITH_SCORE
/************************************************************************
 *
 *      Function:       SpriteSetup()
 *
 *      Description:    Setup a single sprite
 *
 *      Parameters:     name - the name of the raster file to load
 *                      x,y the offset into the raster to obtain the mask color
 *
 *      Return Value:   None
 *
 ************************************************************************/
static RwRaster *SpriteSetup(char *name, int x, int y)
{
    RwRaster *raster = RwReadRaster(name, 0L);
    if (raster)
    {
        SpriteMask(raster, x, y);
    }
    return(raster);
}

/************************************************************************
 *
 *      Function:       ScoreSetup()
 *
 *      Description:    Initialise the score data structures
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
static int ScoreSetup(void)
{
    if (!(sGScore.cpRat = DoRwReadShape("ratsimpl.rwx")))
    {
        return FALSE;
    }

    /* Load in the sprite images */

    if (!(sGScore.spaLeftImages[0]  = SpriteSetup("ratface4.bmp", 0, 0)) ||
        !(sGScore.spaLeftImages[1]  = SpriteSetup("ratface3.bmp", 0, 0)) ||
        !(sGScore.spaLeftImages[2]  = SpriteSetup("ratface2.bmp", 0, 0)) ||
        !(sGScore.spaLeftImages[3]  = SpriteSetup("ratface1.bmp", 0, 0)))
    {
        return FALSE;
    }

    if (!(sGScore.spTimesOne        = SpriteSetup("t1.bmp", 2, 2)) ||
        !(sGScore.spTimesFive       = SpriteSetup("t5.bmp", 2, 2)) ||
        !(sGScore.spTimesTwentyFive = SpriteSetup("t25.bmp", 2, 2)) )
    {
        return FALSE;
    }

    /* Screen position */

    if (!(sGScore.mpPos = RwCreateMatrix()))
    {
        return FALSE;
    }
    RwIdentityMatrix(sGScore.mpPos);

    if (!(sGScore.mpRot = RwCreateMatrix()))
    {
        return FALSE;
    }
    RwIdentityMatrix(sGScore.mpRot);

    if (!(sGScore.mpOffset = RwCreateMatrix()))
    {
        return FALSE;
    }
    RwIdentityMatrix(sGScore.mpOffset);

    RwRotateMatrix(sGScore.mpRot,
                   CREAL(0.01), CREAL(1), CREAL(0.1), CREAL(6), rwREPLACE);

    RwRotateMatrix(sGScore.mpOffset,
                   CREAL(0.1), CREAL(-0.5), CREAL(2), CREAL(100), rwREPLACE);

    return TRUE;
}

/************************************************************************
 *
 *      Function:       OsDisplayScore()
 *
 *      Description:    Overlay the score on top of the rendered image
 *
 *      Parameters:     dead - number of dead rats
 *                      left - number of rats remaining
 *
 *      Return Value:   None
 *
 ************************************************************************/
void OsDisplayScore(int dead, int left)
{
    int nCount;
    int nRats;
    int nAmo;
    int nTmp;
    RwV3d vPos;
    RwV3d vAt;
    RwV3d vUp;
    RwV3d vRight;
    RwV3d vTmp;
    RwV3d vLeft;
    RwReal naPos[4][4];
    int nImage;

    RwMatrix4d *mpTmp;

    RwTransformMatrix(sGScore.mpPos, sGScore.mpRot, rwPOSTCONCAT);
    RwOrthoNormalizeMatrix(sGScore.mpPos, sGScore.mpPos);

    mpTmp = RwScratchMatrix();
    RwPushScratchMatrix();
    RwIdentityMatrix(mpTmp);

    RwGetCameraPosition(cpGCamera,&vPos);

    RwGetCameraLookAt(cpGCamera, &vAt);
    (*((RwV3d *)&naPos[2][0])) = vAt;

    RwScaleVector(&vAt, SCORE_FORWARD, &vAt);
    RwAddVector(&vPos,&vAt,&vPos);

    RwGetCameraLookUp(cpGCamera, &vUp);
    (*((RwV3d *)&naPos[1][0])) = vUp;

    RwScaleVector(&vUp, SCORE_STARTY, &vTmp);
    RwAddVector(&vPos,&vTmp,&vPos);
    RwScaleVector(&vUp, SCORE_UP, &vUp);

    vLeft = vPos;

    RwGetCameraLookRight(cpGCamera, &vRight);
    (*((RwV3d *)&naPos[0][0])) = vRight;

    RwScaleVector(&vRight, SCORE_STARTX, &vTmp);
    RwAddVector(&vPos,&vTmp,&vPos);
    RwScaleVector(&vRight, SCORE_RIGHT, &vRight);

    /* vPos points to the start position */

    nRats = dead;
    nCount = 0;

    (*((RwV3d *)&naPos[3][0])) = vPos;
    RwSetMatrixElements(RwScratchMatrix(),naPos);

    while (nCount<3)
    {
        RwCopyMatrix(RwScratchMatrix(), mpTmp);

        RwTransformMatrix(mpTmp, sGScore.mpPos, rwPRECONCAT);

        nAmo = nRats%5;
        nRats = nRats/5;

        for (nTmp=0; nTmp < nAmo; nTmp++)
        {
            /* Draw a rat */

            RwTransformClump(sGScore.cpRat, mpTmp, rwREPLACE);
            RwRenderClump(sGScore.cpRat);
            RwTranslateMatrix(mpTmp, -vUp.x, -vUp.y, -vUp.z,rwPOSTCONCAT);
            RwTransformMatrix(mpTmp, sGScore.mpOffset, rwPRECONCAT);
        }

        nCount++;

        /* Move one to the right */

        RwTranslateMatrix(RwScratchMatrix(),
                        -vRight.x,-vRight.y,-vRight.z,rwPOSTCONCAT);
    }
    RwPopScratchMatrix();

    /* Display the amount left */

    nImage = (left * (NUM_LEFT_IMAGES-1))/RAT_MAXIMUM;

    SpriteRenderRelative(cpGCamera,sGScore.spaLeftImages[nImage],CREAL(0.08),CREAL(0.08));

    SpriteRenderRelative(cpGCamera,sGScore.spTimesOne,CREAL(0.9),CREAL(0.052));
    SpriteRenderRelative(cpGCamera,sGScore.spTimesFive,CREAL(0.8),CREAL(0.052));
    SpriteRenderRelative(cpGCamera,sGScore.spTimesTwentyFive,CREAL(0.7),CREAL(0.052));
}

/************************************************************************
 *
 *      Function:       ScoreDestroy()
 *
 *      Description:    Destroy the score data structures
 *
 *      Parameters:     None
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void ScoreDestroy(void)
{
    RwDestroyClump(sGScore.cpRat);

    RwDestroyMatrix(sGScore.mpPos);
    RwDestroyMatrix(sGScore.mpRot);
    RwDestroyMatrix(sGScore.mpOffset);

    RwDestroyRaster(sGScore.spaLeftImages[0]);
    RwDestroyRaster(sGScore.spaLeftImages[1]);
    RwDestroyRaster(sGScore.spaLeftImages[2]);
    RwDestroyRaster(sGScore.spaLeftImages[3]);

    RwDestroyRaster(sGScore.spTimesOne);
    RwDestroyRaster(sGScore.spTimesFive);
    RwDestroyRaster(sGScore.spTimesTwentyFive);
}
#endif
/************************************************************************
 *
 *      Function:       DosGetKey()
 *
 *      Description:    Get the ascii key code of any depressed key.
 *
 *      Parameters:     None
 *
 *      Return Value:   0 if no key pressed else ASCII value for key pressed
 *
 ************************************************************************/
static int DosGetKey(void)
{
	union REGPACK rp;

	memset(&rp,0,sizeof(rp));
	rp.h.ah = 0x06;
	rp.h.dl = 0xff;

	intr(0x21,&rp);

	if (!(rp.w.flags & 0x40 )) {   	/* Check Z flag */
		/* Got key */

		if (rp.h.al) {
			return ((int)rp.h.al);
		};

		memset(&rp,0,sizeof(rp));
		rp.h.ah = 0x06;
		rp.h.dl = 0xff;
		intr(0x21,&rp);

		if (!(rp.w.flags & 0x40)) {
			return ((int)rp.h.al);
		};

		return (rp.h.al|0x80);
	};

	return 0;
}

/************************************************************************
 *
 *      Function:       DosShiftCtrl()
 *
 *      Description:    Get the status of the Shift Control keys.
 *
 *      Parameters:     None
 *
 *      Return Value:   Bit field indicating the key status
 *                      Bit         Meaning
 *                       0          Right Shift
 *                       1          Left Shift
 *                       2          Ctrl
 *                       3          Alt
 *                       4          Scroll Lock
 *                       5          Num Lock
 *                       6          Caps Lock
 *                       7          Insert on
 *
 ************************************************************************/
static int
DosShiftCtrl(void)
{
	union REGPACK rp;

	memset(&rp,0,sizeof(rp));

	rp.h.ah=0x02;
	intr(0x16,&rp);

	return ((int)rp.h.al);
}

/************************************************************************
 *
 *      Function:       ReadFromCommandLine()
 *
 *      Description:    Parse the arguments passed to the program. For this
 *                      program the valid arguments are -hires for a high
 *                      resolution display and -hicol for 16 bit color
 *
 *      Parameters:     argc, argv - standard C program arguments
 *
 *      Return Value:   None
 *
 ************************************************************************/
static void ReadFromCommandLine(int argc, char *argv[])
{
    int i;

    nGLoRes = 1;
    nGHiCol = 0;
    for (i=1; i<argc; i++)
    {
        if (!strcmp(argv[i],"-hires"))
            nGLoRes = 0;
        if (!strcmp(argv[i],"-hicol"))
            nGHiCol = 1;
    }
}


/************************************************************************
 *
 *      Function:       main()
 *
 *      Description:    Main entry point, All of the top level control
 *                      flow is handle directly here.
 *
 *      Parameters:     argc, argv - standard C parameters
 *
 *      Return Value:   None
 *
 ************************************************************************/
void
main(int nArgc,char *saArgv[])
{
    int nMouseX,nMouseY;
    int nKey;
    int nMouseBut, nOldMouseBut, nOldMouseX, nOldMouseY;
    int nDX,nDY;
    int nChange;
    int nCtrlShift;
    RwErrorCode eCode;

    /* Initialise the Random Number Seed */

    RwSRandom(time(0));

    ReadFromCommandLine(nArgc, saArgv);

    if (!Init3D())
    {
        eCode = RwGetError();

        switch(eCode)
        {
        case E_RW_BADOPEN:
            OsError("E_RW_BADOPEN");
            break;

        case E_RW_INTERNAL:
            OsError("E_RW_INTERNAL: Contact RenderWare Support");
            break;
        }
        exit(-1);
    }

  /* Load all of the sound effects */

#ifdef WITH_SOUND
    AllSoundsAddSound("gun.sb",0x1f,150,0);
    AllSoundsAddSound("ricochet.sb",0x2f,200,1);
    AllSoundsAddSound("squish.sb",0x3f,400,2);
    AllSoundsAddSound("squeak.sb",0x3f,200,2);
    AllSoundsAddSound("ratty.sb",0x3f,50,2);
    AllSoundsAddSound("boink.sb",0x3f,300,3);
    AllSoundsAddSound("ratty2.sb",0x3f,50,2);
    AllSoundsAddSound("clang.sb",0x3f,400,2);
#endif

    /* paint display for the first time */
    if (!nGLoRes)
    {
        HandlePaint();
    }

    RwDPointerDisplay(&nOldMouseX,&nOldMouseY,&nOldMouseBut);

    nKey = DosGetKey();

    while (nKey!=27)
    {
        /* ESC quits */

	    RwDPointerDisplay(&nMouseX,&nMouseY,&nMouseBut);

        nKey = DosGetKey();

        if ((nKey == 'g')||(nKey=='G'))
        {
            GunToggle();
        }

        if ((nKey == 'f')||(nKey=='F'))
        {
            ToggleFlyCamera();
        }

	    nCtrlShift = DosShiftCtrl();

	    nDX =(nMouseX-nOldMouseX);
	    nDY =(nMouseY-nOldMouseY);

        nChange = (nMouseBut&(2+8)) | ( (nOldMouseBut&(2+8)) >>1 );

	    switch (nChange)
        {
            case 0+0:
            case 2+1:
            case 8+4:
            case 8+2+4+1:
        break;

            case 2:
            case 8+2+4:
	        	/* Left Button Down */
		        HandleLeftButtonDown(nMouseX, nMouseY,
                                    nCtrlShift & KEY_CONTROL,
                                    nCtrlShift & (KEY_RSHIFT|KEY_LSHIFT));
        break;

            case 8:
            case 8+2+1:
		        /* Right Button Down */
		        HandleRightButtonDown(nMouseX, nMouseY,
                                    nCtrlShift & KEY_CONTROL,
                                    nCtrlShift & (KEY_RSHIFT|KEY_LSHIFT));
		    break;

            case 8+1:
		        /* Right down left Up */
		        HandleLeftButtonUp();
		        HandleRightButtonDown(nMouseX, nMouseY,
                                      nCtrlShift & KEY_CONTROL,
                                      nCtrlShift & (KEY_RSHIFT|KEY_LSHIFT));
		        break;

            case 2+4:
		        /* Right up left Down */
		        HandleRightButtonUp();
		        HandleLeftButtonDown(nMouseX, nMouseY,
                                      nCtrlShift & KEY_CONTROL,
                                      nCtrlShift & (KEY_RSHIFT|KEY_LSHIFT));
		        break;

            case 8+2:
		        /* Left down RIght Down */
		        HandleRightButtonDown(nMouseX, nMouseY,
                                      nCtrlShift & KEY_CONTROL,
                                      nCtrlShift & (KEY_RSHIFT|KEY_LSHIFT));
		        HandleLeftButtonDown(nMouseX, nMouseY,
                                      nCtrlShift & KEY_CONTROL,
                                      nCtrlShift & (KEY_RSHIFT|KEY_LSHIFT));
		        break;

            case 1+4:
		        /* Left up Right Up */
		        HandleRightButtonUp();
		        HandleLeftButtonUp();
		        break;

            case 1:
            case 8+4+1:
		        /* Left up */
		        HandleLeftButtonUp();
		        break;

            case 4:
            case 2+4+1:
		        /* Right up */
		        HandleRightButtonUp();
		        break;
        }
        if (nDX||nDY)
        {
            /* Mouse Move  */
            HandleMouseMove(nMouseX,nMouseY);
	    }

        HandleTimer();

        nOldMouseX = nMouseX;
        nOldMouseY = nMouseY;
        nOldMouseBut = nMouseBut;
    }

    /*
     * Tidy up the 3D (RenderWare) components of the application.
     */

    TidyUp3D();

    exit(0);
}
