#include <math.h>
#include <malloc.h>
#include <stdlib.h>
#include <direct.h>
#include <conio.h>
#include <string.h>

#include <pr.h>
#include <prgui.h>

#if defined (MSGLIDE) || defined (WTGLIDE)
#include <pr3dfx.h>
#include <glide.h>
#include <terrain.h>
#include <winutil.h>

#elif defined (WIN32)

#include <terrain.h>
#include <winutil.h>
#else

#include <terrain.h>

 #ifdef __3DFX__
  #include <pr3dfx.h>
  #include <glide.h>
 #endif

 #ifdef __MYST__
  #include <msidos.h>
  #include <prmyst.h>
 #endif

#endif


PR_DWORD lowres = 0;
PR_DWORD draw_sky = 1;
PR_DWORD dynamic_lights = 1;

#define FOG_3DFX_START 48
#define FOG_3DFX_END 240

#define FOG_3DFX_SKYSTART 1000
#define FOG_3DFX_SKYEND 56000

#define FOG_VGA_START 128
#define FOG_VGA_END 256

#define MISSILE_LIGHT_SIZE 768
#define EXPLOSION_LIGHT_SIZE 1024

#define SMOKE_DELAY 3

#define WORLD_SCALE 256
#define HEIGHT_SCALE 8

PR_TERRAIN *terrain;
PR_CAMERA  *newcam;
PR_VIEWPORT viewport;                  /* Our viewport structure */
PR_DWORD vwidth, vheight;
PR_DWORD landtable;
PR_LIGHTLIST userlights;
PR_LIGHTLIST explosionlights;
PR_LIGHTLIST sunlight;

PR_DWORD sky_color;
PR_DWORD white_color;

#define ABOVE_GROUND 125
#define MAX_HEIGHT 2000

PR_DWORD terrain_size;

block tilesprites[1024];
color temppal[256];
#define NUM_TILES 256
#define OBJ_TILES 256           /* Number of materials allowed for objects */

PR_DWORD  device=0;                    /* Display device */

PR_DWORD translucent_table;

/* Smoke sprites */
#define MAXSMOKE 400
#define SMOKE_MATERIAL 2
#define SMOKELIFE 100
PR_OBJECT *smoke_shape;
PR_ENTITY *smoke_sprites[MAXSMOKE];



/* Shrapnel pieces */
#define MAXSHRAP 80
#define SHRAPHEIGHT 20
#define NUMSHRAP 5
PR_OBJECT *shrapnel_shape;
PR_ENTITY *shrapnel[MAXSHRAP];


PR_OBJECT *sprite_shape;
PR_ENTITY *sprite_entity;

PR_OBJECT *sky_shape;
PR_ENTITY *sky_entity;


#define MISSILELIFE 100
#define MAXMISSILE 30
#define MISSILE_SPEED 80
PR_OBJECT *missile_shape;
PR_ENTITY *missiles[MAXMISSILE];
int num_missiles;
int missile_reload = 0;

void EntityCoordinateFix (PR_ENTITY *ent);


/* -----------------------------------------------------------------------
   Wave code
   ----------------------------------------------------------------------- */


#define WAVEHEIGHT 6
PR_REAL wcos[360], wsin[360]; /* Wave Sin/cos tables */
PR_DWORD wave1 = 0, wave2 = 128;

void CreateWaveTables (void)
{
int i;
  
  for  (i = 0; i < 256; i++)
    {
     wsin[i] = sin (3.1415*((float)i / (float)128))*WAVEHEIGHT;
     wcos[i] = cos (3.1415*((float)i / (float)128))*WAVEHEIGHT;
    }
}


void MoveWaves (void)
{
 wave1 += 3;
 wave2 += 5;

 if (wave1 > 255)
   wave1 -= 256;
 if (wave2 > 255)
   wave2 -= 256;
}

void PR_TerrainWaveFunction (PR_WORD *col, PR_WORD *hgt, PR_WORD *shd,
                             PR_DWORD x, PR_DWORD y)
{
PR_DWORD waveadd1;
PR_DWORD waveadd2;
PR_REAL waveheight;

  if (*col != 1)
    return;

  waveadd1 = (wave1 + x * 40) % 256;
  waveadd2 = (wave2 + y * 20) % 256;
  waveheight = (wsin[waveadd1] + wsin[waveadd2]);

  *hgt = waveheight - 4;
  *shd = waveheight*2 + 22;
}



PR_DWORD PR_HitLand (PR_TERRAIN *terrain, PR_REAL x, PR_REAL y, PR_REAL z)
{
PR_REAL hgt1;

 hgt1 = PR_GetTerrainHeight (terrain, x, z, terrain->level1_heightpic, terrain->level1_colorpic);

 if (y < hgt1)
   return 1;
 else
   return 0;
}



/* -----------------------------------------------------------------------
   Initialization code
   ----------------------------------------------------------------------- */

void Initialize_Sky (PR_OBJECT *obj)
/* Sets the precalculated light to full, for each vertex */
{
PR_DWORD i;
PR_FACE_DATA *fdata;
PR_FACE_DATA_REAL *fdatar;

  if (PR_Settings.Hardware)
    {
     for (i = 0; i < obj->segment_list[0].num_faces; i++)
       {
        fdatar = (PR_FACE_DATA_REAL *)&obj->segment_list[0].face_list[i].face_data;
        fdatar->col[0] = 1;
        fdatar->col[1] = 1;
        fdatar->col[2] = 1;
       }

    }
  else
    {
     for (i = 0; i < obj->segment_list[0].num_faces; i++)
       {
        fdata = &obj->segment_list[0].face_list[i].face_data;
        fdata->col[0] = 32 << 16;
        fdata->col[1] = 32 << 16;
        fdata->col[2] = 32 << 16;
       }
    }
}


void InitializeMissiles (void)
{
int i;
PR_LIGHTLIST *missilelight;

  if (dynamic_lights)
    {
     /* Here we create 1 light that goes into the object definition.
        It lies in the center of the missile. */
     missilelight = &missile_shape->segment_list[0].seglights;

     /* Allocate 1 light */
     PR_AllocLights (missilelight, 1);
     missilelight->NumLights = 1;

     PR_SetLightPosition (missilelight, 0, 0, 0, 0);
     PR_SetLightOn (missilelight, 0);
     PR_SetLightType (missilelight, 0, POINT_LIGHT);
     PR_SetLightStrength (missilelight, 0, 2.0);
     PR_SetLightFalloff (missilelight, 0, MISSILE_LIGHT_SIZE);

     PR_SetLightColor (missilelight, 0, 1.0, 0.2, 0.2);
    }

  /* Clear the missile entities */
  for (i = 0; i < MAXMISSILE; i++)
    {
     missiles[i] = PR_CreateEntity (missile_shape, "missile");
     PR_ScaleEntityAbs (missiles[i], 1.0, 1.0, 1.0);
     PR_SetEntityFlags (missiles[i], FLAG_NORENDER | FLAG_NOTRANSFORM, FLAG_COPY);
    }
}


void InitializeSmoke (void)
{
int i;

  /* Clear the smoke entities */
  for (i = 0; i < MAXSMOKE; i++)
    {
     smoke_sprites[i] = PR_CreateEntity (smoke_shape, "Smoker");
     PR_SetEntityFlags (smoke_sprites[i], FLAG_NORENDER | FLAG_NOTRANSFORM, FLAG_COPY);
    }
}


void InitializeShrapnel (void)
{
int i;

  /* Clear the smoke entities */
  for (i = 0; i < MAXSHRAP; i++)
    {
     shrapnel[i] = PR_CreateEntity (shrapnel_shape, "Shrapnel");
     PR_ScaleEntityAbs (shrapnel[i], 0.5, 0.5, 0.5);
     PR_RotateEntityAbs (shrapnel[i], 0.0, 0.0, 0.0);
     PR_SetEntityFlags (shrapnel[i], FLAG_NORENDER | FLAG_NOTRANSFORM, FLAG_COPY);
    }
}




/* -----------------------------------------------------------------------
   Smoke code
   ----------------------------------------------------------------------- */
void AddSmoke (PR_REAL x, PR_REAL y, PR_REAL z)
/* Adds a smoke sprite entity */
{
int i;

  for (i = 0; i < MAXSMOKE; i++)
    {
     if (PR_GetEntityFlags (smoke_sprites[i]) & FLAG_NOTRANSFORM)
     /* We use this flag to see if the sprite is not active */
       {
        PR_SetEntityFlags (smoke_sprites[i], 0, FLAG_COPY);
        PR_PositionEntity (smoke_sprites[i], x,y,z);
        PR_ScaleEntityAbs (smoke_sprites[i], 0.8,0.8,0.8);
        PR_RotateSegmentAbs (smoke_sprites[i], 0, 0,0,0);

        /* This is the number of frames before the missile dies out */
        smoke_sprites[i]->userdata[3] = SMOKELIFE;

        i = MAXSMOKE + 1;
       }
    }
}


void MoveSmoke (void)
/* Update any smoke sprites that are active */
{
int i;

  for (i = MAXSMOKE-1; i >= 0; i--)
    {
     if (!(PR_GetEntityFlags (smoke_sprites[i]) & FLAG_NOTRANSFORM))
       /* Smoke is active */
       {
        /* Decrease the amount of 'life' in the smoke */
        smoke_sprites[i]->userdata[3]--;
        if (smoke_sprites[i]->userdata[3] < 1)
           PR_SetEntityFlags (smoke_sprites[i], FLAG_NORENDER | FLAG_NOTRANSFORM, FLAG_COPY);
           /* Turn the smoke off */
        else
          {
           EntityCoordinateFix (smoke_sprites[i]);

           PR_ScaleEntity (smoke_sprites[i], 0.93, 0.93, 0.93);
           PR_MoveEntity (smoke_sprites[i], 0, 1, 0);

           PR_RotateSegment (smoke_sprites[i], 0, 0,0,50);
           PR_TransformEntity (smoke_sprites[i]);
           PR_RenderEntity (smoke_sprites[i]);
          }
       }
    }
}



/* -----------------------------------------------------------------------
   Shrapnel code
   ----------------------------------------------------------------------- */
void AddShrapnel (PR_REAL x, PR_REAL y, PR_REAL z)
/* Adds a shrapnel entity */
{
int i;

  for (i = 0; i < MAXSHRAP; i++)
    {
     if (PR_GetEntityFlags (shrapnel[i]) & FLAG_NOTRANSFORM)
     /* We use this flag to see if the sprite is not active */
       {
        PR_SetEntityFlags (shrapnel[i], 0, FLAG_COPY);
        PR_RotateSegmentAbs (shrapnel[i], 0, 0, 0, 0);

        /* Random Movement Direction */
        shrapnel[i]->userdata[0] = (rand () % 20) - 10;   /* X */
        shrapnel[i]->userdata[1] = (rand () % 20) - 10;   /* Z */

        PR_PositionEntity (shrapnel[i], x + shrapnel[i]->userdata[0] * 3,
                           y + 50, z + shrapnel[i]->userdata[1] * 3);

        /* Random rotation Direction */
        shrapnel[i]->userdata[2] = (rand () % 30)  - 15;         /* X */
        shrapnel[i]->userdata[3] = (rand () % 30)  - 15;         /* Y */
        shrapnel[i]->userdata[4] = (rand () % 30)  - 15;         /* Z */

        /* This is the gravity factor */
        shrapnel[i]->userdata[5] = SHRAPHEIGHT;


        i = MAXSHRAP + 1;
       }
    }
}


void MoveShrapnel (void)
/* Update any shrapnel pieces that are active */
{
int i;

  for (i = 0; i < MAXSHRAP; i++)
    {
     if (!(PR_GetEntityFlags (shrapnel[i]) & FLAG_NOTRANSFORM))
       /* Shrapnel is active */
       {

        if (PR_HitLand (terrain,
                        shrapnel[i]->orientation.location.x,
                        shrapnel[i]->orientation.location.y,
                        shrapnel[i]->orientation.location.z))
           PR_SetEntityFlags (shrapnel[i], FLAG_NORENDER | FLAG_NOTRANSFORM, FLAG_COPY);
           /* Turn the shrapnel off */
        else
          {
           EntityCoordinateFix (shrapnel[i]);

           /* Increase the gravity factor */
           PR_MoveEntity (shrapnel[i],
                          shrapnel[i]->userdata[0],
                          shrapnel[i]->userdata[5]--,
                          shrapnel[i]->userdata[1]);

           PR_RotateEntity (shrapnel[i],
                           shrapnel[i]->userdata[2],
                           shrapnel[i]->userdata[3],
                           shrapnel[i]->userdata[4]);
           PR_TransformEntity (shrapnel[i]);
           PR_RenderEntity (shrapnel[i]);
          }
       }
    }
}


/* -----------------------------------------------------------------------
   Missile code
   ----------------------------------------------------------------------- */
void ModifyLand (PR_REAL x, PR_REAL y, PR_REAL z)
{
PR_DWORD offset;
PR_DWORD wx, wy;
unsigned char *bits;
PR_DWORD pitch;

  wx = x / WORLD_SCALE;
  wy = z / WORLD_SCALE;
  while (wx < 0)
    wx += terrain->width;
  while (wx >= terrain->width)
    wx -= terrain->width;
  while (wy < 0)
    wy += terrain->height;
  while (wy >= terrain->height)
    wy -= terrain->height;

#if defined (MSDD) || defined (WTDD)
  bits = (unsigned char *)wgetblocksurface (terrain->level1_heightpic, &pitch);
  offset = wy * pitch + wx;
#else
  bits = terrain->level1_heightpic;
  offset = wy * terrain->width + wx + 4;
#endif

  if (bits == NULL)
     return;
  
  bits += offset;
  if (*bits > 5)
    *bits -= 5;

#if defined (MSDD) || defined (WTDD)
  bits = wgetblocksurface (terrain->level1_shadepic, &pitch);
  offset = wy * pitch + wx;
#else
  bits = terrain->level1_shadepic;
#endif

 
  if (bits == NULL)
     return;

  bits += offset;
  if (*bits > 8)
    *bits -= 8;
  else
    *bits = 0;

  PR_TerrainModify (terrain);

#if defined (MSDD) || defined (WTDD)
  wunlocksurface (terrain->level1_shadepic);
  wunlocksurface (terrain->level1_heightpic);
#endif
}



void AddExplosion (PR_REAL x, PR_REAL y, PR_REAL z)
{
PR_DWORD i;

  ModifyLand (x,y,z);

  for (i = 0; i < 20; i++)
    if (!PR_GetLightState (&explosionlights, i))
      {
       PR_SetLightOn (&explosionlights, i);
       PR_SetLightColor (&explosionlights, i, 1.0, 0.7, 0);
       PR_SetLightStrength (&explosionlights, i, 1.0);
       PR_SetLightPosition (&explosionlights, i, x, y, z);
       break;
      }

  for (i = 0; i < NUMSHRAP; i++)
    AddShrapnel (x,y,z);
}


void UpdateExplosions (void)
{
PR_DWORD i;
PR_REAL strength;

  for (i = 0; i < 20; i++)
    if (PR_GetLightState (&explosionlights, i))
      {
       strength = PR_GetLightStrength (&explosionlights, i) - 0.1;

       if (strength < 0)
         PR_SetLightOff (&explosionlights, i);
       else
         PR_SetLightStrength (&explosionlights, i, strength);
      }
}




void FireMissile (void)
/* Let one of those bad boys fly */
{
int i;

  if (missile_reload < 1)
    {
     for (i = 0; i < MAXMISSILE; i++)
       {
        if (PR_GetEntityFlags (missiles[i]) & FLAG_NOTRANSFORM)
          /* We use this flag to see if the missile is not active */
          {
           missile_reload = 4;
           /* Can't fire again until 4 frames have passed */
 
           PR_SetEntityFlags (missiles[i], 0, FLAG_COPY);
           PR_PositionEntity (missiles[i], newcam->source.x,
                                           newcam->source.y - 50,
                                           newcam->source.z); 
 
           /* Reset the rotation matrix */
           PR_MatrixIdentity (missiles[i]->orientation.rot_mat);
           missiles[i]->orientation.rot.x = 0;
           missiles[i]->orientation.rot.y = 0;
           missiles[i]->orientation.rot.z = 0;
 
           /* Rotate the missile to match the camera's angle */
           PR_RotateEntity (missiles[i],
                newcam->rotation.x, newcam->rotation.y, newcam->tilt_angle);
 
           /* Set up a movement vector */
           missiles[i]->userdata[0] = -newcam->direction.x * MISSILE_SPEED;
           missiles[i]->userdata[1] = -newcam->direction.y * MISSILE_SPEED;
           missiles[i]->userdata[2] = -newcam->direction.z * MISSILE_SPEED;
 
           /* This is the number of frames before the missile dies out */
           missiles[i]->userdata[3] = MISSILELIFE;

           /* This is the number of frames before the missile gives a
              smoke puff */
           missiles[i]->userdata[4] = SMOKE_DELAY;

           i = MAXMISSILE + 1;
          }
       }
    }
}



void MoveMissiles (void)
/* Move any missile that are active, and check for collisions */
{
int i;

  for (i = 0; i < MAXMISSILE; i++)
    {
     if (!(PR_GetEntityFlags (missiles[i]) & FLAG_NOTRANSFORM))
       /* Missile is active */
       {
        PR_MoveEntity (missiles[i], missiles[i]->userdata[0],
                                    missiles[i]->userdata[1],
                                    missiles[i]->userdata[2]);

        /* Do a collision check with the ground */
        if (PR_HitLand (terrain,
                        missiles[i]->orientation.location.x,
                        missiles[i]->orientation.location.y,
                        missiles[i]->orientation.location.z))
          {
           PR_SetEntityFlags (missiles[i], FLAG_NORENDER | FLAG_NOTRANSFORM, FLAG_COPY);
           AddExplosion (missiles[i]->orientation.location.x,
                         missiles[i]->orientation.location.y,
                         missiles[i]->orientation.location.z);
          }

        EntityCoordinateFix (missiles[i]);

        PR_RotateEntity (missiles[i], 0, 0, 10);
        PR_TransformEntity (missiles[i]);
        PR_RenderEntity (missiles[i]);

        /* Decrease the amount of 'fuel' in the missile */
        missiles[i]->userdata[3]--;
        if (missiles[i]->userdata[3] < 1)
           PR_SetEntityFlags (missiles[i], FLAG_NORENDER | FLAG_NOTRANSFORM, FLAG_COPY);
           /* Turn the missile off */


        /* Decrease the smoke delay */
        missiles[i]->userdata[4]--;
        if (missiles[i]->userdata[4] < 1)
          {
           AddSmoke (missiles[i]->orientation.location.x,
                     missiles[i]->orientation.location.y,
                     missiles[i]->orientation.location.z);

           missiles[i]->userdata[4] = SMOKE_DELAY;
           /* Reset the delay for the next smoke puff */
          }

       }
    }

}






/* Frame Rate Control  */
PR_DWORD ticks = 0;             /* Total number of ticks passed */
PR_DWORD updates = 0;           /* Total number of updates */
PR_DWORD framerate;             /* Resulting frame rate (updates/ticks/tickrate) */
/* ---------------------------------------------------------------------- */
/* Timer Interrupt */
/* ---------------------------------------------------------------------- */
void timerproc (void)
{
  ticks++;
}                



/* ---------------------------------------------------------------------- */
/* Makes sure the camera is above the landscape for the first frame */
/* ---------------------------------------------------------------------- */
void SetInitialPosition (void)
{
PR_REAL hgt;


  hgt = PR_GetTerrainHeight (terrain, newcam->source.x,
                                      newcam->source.z,
                                      terrain->level1_heightpic,
                                      terrain->level1_colorpic)
                                      + ABOVE_GROUND;


  PR_PositionCameraSource (newcam,
                      newcam->source.x,
                      hgt + ABOVE_GROUND,
                      newcam->source.z);
}



/* ---------------------------------------------------------------------- */
/* Checks for ground collision in the flying model */
/* ---------------------------------------------------------------------- */
void SetFlyingHeight (void)
{
PR_REAL hgt1;

  hgt1 = PR_GetTerrainHeight (terrain, newcam->source.x,
                                       newcam->source.z,
                                       terrain->level1_heightpic,
                                       terrain->level1_colorpic)
                                       + ABOVE_GROUND;

  if (newcam->source.y < hgt1)
    {
     newcam->dest.y += hgt1 - newcam->source.y;
     newcam->source.y = hgt1;
    }


  if (newcam->source.y > hgt1 + MAX_HEIGHT)
    {
     newcam->dest.y += (hgt1 + MAX_HEIGHT) - newcam->source.y;
     newcam->source.y = hgt1 + MAX_HEIGHT;
    }
}





#define LOWRES_TEXTURE T_GOURAUD_TEXTURED8

PR_DWORD tilemap[256*6];
/* ---------------------------------------------------------------------- */
/* Creates a material list with mip mapping for the landscape */
/* ---------------------------------------------------------------------- */
void InitializeMaterials (void)
{
PR_MATERIAL *m;
PR_DWORD i;
char tilename[80];

  if ((PR_OutputDevice.devicetype == DEVICE_VGA) ||
      (PR_OutputDevice.devicetype == DEVICE_SVGA))
    {
     PR_SetMipMapState (TRUE);
     PR_SetMipMapShrink (TRUE);
    }

  /* You can adjust these distances depending on what detail you want,
     and how far you can see in the distance. */
  PR_SetMipMapDepth (MIP_LEVEL1, (float)terrain_size * 96);
  PR_SetMipMapDepth (MIP_LEVEL2, (float)terrain_size * 120);
  PR_SetMipMapDepth (MIP_LEVEL3, (float)terrain_size * 142);
  PR_SetMipMapDepth (MIP_LEVEL4, (float)terrain_size * 192);


  /* Create a NULL material for objects */
  m = &PR_ObjectMaterialList[0];
  PR_SetMaterialName (m, "NULL");
  PR_SetMaterialMethod (m, NULL_TYPE);


  m = &PR_ObjectMaterialList[1];
  PR_SetMaterialName (m, "sprite");
  PR_SetMaterialMethod (m, T_XTRANSLUCENT_TEXTURED);
  PR_SetMaterialBaseColor (m, 0);
  PR_SetMaterialColor (m, 255, 255, 255);
  PR_SetMaterialTexture (m, PR_FindTexture("sprite.pcx"));
  PR_SetMaterialTable (m, translucent_table);
  PR_SetMaterialShades (m, 31);
  PR_SetMaterialEnvironmentMap (m, 0);
  PR_SetMaterialMipMapState (m, FALSE);
  PR_SetMaterialAlpha (m, 64);

  m = &PR_ObjectMaterialList[SMOKE_MATERIAL];
  PR_SetMaterialName (m, "smoke sprite");
  PR_SetMaterialMethod (m, T_XTRANSLUCENT_TEXTURED);
  PR_SetMaterialBaseColor (m, 0);
  PR_SetMaterialColor (m, 255, 255, 255);
  PR_SetMaterialTexture (m, PR_FindTexture("smoke.pcx"));
  PR_SetMaterialTable (m, translucent_table);
  PR_SetMaterialShades (m, 31);
  PR_SetMaterialEnvironmentMap (m, 0);
  PR_SetMaterialMipMapState (m, FALSE);
  PR_SetMaterialAlpha (m, 64);


  /* Now load the tiles from a sprite file, and set up 4 levels of
     mip mapping for each.  */

  wloadsprites (global_palette, "landtile.spr", tilesprites, 0, 1023);
  /* Load the images into the tilesprites array */
#if !defined (MSGLIDE) && !defined (WTGLIDE)
  if ((device == DEVICE_VGA) || (device == DEVICE_SVGA))
    wsetpalette (0, 255, global_palette);
#endif

  /* Set the null type for land tiles
     This is to allow for holes in the landscape. */
  m = &PR_ObjectMaterialList[OBJ_TILES];
  PR_SetMaterialName (m, "NULL");
  PR_SetMaterialMethod (m, NULL_TYPE);


  /* For each tile image, make 4 materials for the mip mapping */

  for (i = 1; i < NUM_TILES; i++)
    {
     m = &PR_ObjectMaterialList[OBJ_TILES + i];

     /* We already have the images loaded in tilesprites, so just
        make a pointer to them */
     sprintf (tilename, "tile%i", i-1);
	 if (tilesprites[i] != NULL)
       tilemap[i] = PR_AddTexture (tilename, tilesprites[i]);

     sprintf (tilename, "tile%i", i-1 + 256);
	 if (tilesprites[i + 256] != NULL)
       tilemap[i + 256] = PR_AddTexture (tilename, tilesprites[i + 256]);

     sprintf (tilename, "tile%i", i-1 + 512);
	 if (tilesprites[i + 512] != NULL)
       tilemap[i + 512] = PR_AddTexture (tilename, tilesprites[i + 512]);

     sprintf (tilename, "tile%i", i-1 + 768);
  	 if (tilesprites[i + 768] != NULL)
	   tilemap[i + 768] = PR_AddTexture (tilename, tilesprites[i + 768]);

     /* This is the master material.  Mip mapping information is entered
        in here only.  */
     m = &PR_ObjectMaterialList[OBJ_TILES + i];
     PR_SetMaterialName (m, "tile1");

     if (lowres)
       PR_SetMaterialMethod (m, LOWRES_TEXTURE);
     else
       PR_SetMaterialMethod (m, T_PGOURAUD_TEXTURED8);

     PR_SetMaterialBaseColor (m, 0);
     PR_SetMaterialColor (m, 255, 255, 255);
     PR_SetMaterialTexture (m, tilemap[i]);
     PR_SetMaterialTable (m, 0);
     PR_SetMaterialShades (m, 31);
     PR_SetMaterialEnvironmentMap (m, 0);

     PR_SetMaterialMipMapState (m, TRUE);
     PR_SetMaterialMipMap (m, MIP_LEVEL1, OBJ_TILES + i + 256);
     PR_SetMaterialMipMap (m, MIP_LEVEL2, OBJ_TILES + i + 512);
     PR_SetMaterialMipMap (m, MIP_LEVEL3, OBJ_TILES + i + 768);
     PR_SetMaterialMipMap (m, MIP_LEVEL4, OBJ_TILES + i + 1024);
     PR_SetMaterialMipMapShift (m, MIP_LEVEL1, 0);
     PR_SetMaterialMipMapShift (m, MIP_LEVEL2, 1);
     PR_SetMaterialMipMapShift (m, MIP_LEVEL3, 2);
     PR_SetMaterialMipMapShift (m, MIP_LEVEL4, 3);


     /* Set the tile properties at distance level 1 */
     m = &PR_ObjectMaterialList[OBJ_TILES + i + 256];
     PR_SetMaterialName (m, "mip1");

     if (lowres)
       PR_SetMaterialMethod (m, LOWRES_TEXTURE);
     else
       PR_SetMaterialMethod (m, T_GOURAUD_TEXTURED8);
           
     PR_SetMaterialBaseColor (m, 0);
     PR_SetMaterialColor (m, 255, 255, 255);
     PR_SetMaterialTexture (m, tilemap[i]);
     PR_SetMaterialTable (m, 0);
     PR_SetMaterialShades (m, 31);
     PR_SetMaterialEnvironmentMap (m, 0);

     /* Set the tile properties at distance level 2 */
     m = &PR_ObjectMaterialList[OBJ_TILES + i + 512];
     PR_SetMaterialName (m, "mip2");

     if (lowres)
       PR_SetMaterialMethod (m, LOWRES_TEXTURE);
     else
       PR_SetMaterialMethod (m, T_GOURAUD_TEXTURED8);

     PR_SetMaterialBaseColor (m, 0);
     PR_SetMaterialColor (m, 255, 255, 255);
     PR_SetMaterialTexture (m, tilemap[i + 256]);
     PR_SetMaterialTable (m, 0);
     PR_SetMaterialShades (m, 31);
     PR_SetMaterialEnvironmentMap (m, 0);

     /* Set the tile properties at distance level 3 */
     m = &PR_ObjectMaterialList[OBJ_TILES + i + 768];
     PR_SetMaterialName (m, "mip3");

     if (lowres)
       PR_SetMaterialMethod (m, LOWRES_TEXTURE);
     else
       PR_SetMaterialMethod (m, T_GOURAUD_TEXTURED8);

     PR_SetMaterialBaseColor (m, 0);
     PR_SetMaterialColor (m, 255, 255, 255);
     PR_SetMaterialTexture (m, tilemap[i + 512]);
     PR_SetMaterialTable (m, 0);
     PR_SetMaterialShades (m, 31);
     PR_SetMaterialEnvironmentMap (m, 0);

     /* Set the tile properties at distance level 4 */
     m = &PR_ObjectMaterialList[OBJ_TILES + i + 1024];
     PR_SetMaterialName (m, "mip4");

     if (lowres)
       PR_SetMaterialMethod (m, LOWRES_TEXTURE);
     else
       PR_SetMaterialMethod (m, T_GOURAUD_TEXTURED8);

     PR_SetMaterialBaseColor (m, 0);
     PR_SetMaterialColor (m, 255, 255, 255);
     PR_SetMaterialTexture (m, tilemap[i + 768]);
     PR_SetMaterialTable (m, 0);
     PR_SetMaterialShades (m, 31);
     PR_SetMaterialEnvironmentMap (m, 0);
    }
}



void InitializeTexture (void)
{
  /* Texture hack */
  PR_Settings.LoadPalette = 1;
  InitializeMaterials ();
}



void EntityCoordinateFix (PR_ENTITY *ent)
{
PR_DWORD dx, dz;
PR_DWORD hx, hz;

  dx = ent->orientation.location.x - newcam->source.x;
  dz = ent->orientation.location.z - newcam->source.z;

  hx = (terrain->width >> 1) * terrain->world_scale;
  hz = (terrain->height >> 1) * terrain->world_scale;


  if (dx > hx)
    ent->orientation.location.x -= terrain->width * terrain->world_scale;
  if (dx < -hx)
    ent->orientation.location.x += terrain->width * terrain->world_scale;

  if (dz > hz)
    ent->orientation.location.z -= terrain->height * terrain->world_scale;
  if (dz < -hz)
    ent->orientation.location.z += terrain->height * terrain->world_scale;
}


PR_REAL fs = 1.0f, fe = 65535.0f;


/* ---------------------------------------------------------------------- */
/* Check the mouse and keyboard input */
/* ---------------------------------------------------------------------- */
void UserInput (void)
{
        
   PR_ReadInput ();
  /* Move around depending on what movement model we are using */

  PR_CameraDirection (newcam);

  PR_DollyCamera (newcam, currentspeed);
  SetFlyingHeight ();

  if (newcam->source.x < 0)
    newcam->source.x += terrain->width * terrain->world_scale;
  if (newcam->source.z < 0)
    newcam->source.z += terrain->height * terrain->world_scale;

  if (newcam->source.x >= terrain->width * terrain->world_scale)
    newcam->source.x -= terrain->width * terrain->world_scale;
  if (newcam->source.z >= terrain->height * terrain->world_scale)
    newcam->source.z -= terrain->height * terrain->world_scale;


  PR_SetActiveCamera (newcam);
  /* Updates the transformation matrix */

  if (kbdon[KEY_F1])
    {
     draw_sky = !draw_sky;
     while (kbdon[KEY_F1]) 
       { 
        #ifdef WIN32
          UpdateMessages ();
        #else
          wretrace ();
        #endif
       }           /* Wait for user to release key */
    }

  /* Missiles */
  if (kbdon[KEY_SPACE])
    FireMissile ();
  if (missile_reload > 0)
    missile_reload--;
}




void InitializeDevices (void)
{
  /* Attempt to find the device */

  #ifdef __MYST__
  if ((device == DEVICE_MYSTIQUE) || (device == DEVICE_ANY))
    device = PR_DetectMystique ();
  #endif

  #ifdef __3DFX__
  if ((device == DEVICE_3DFX) || (device == DEVICE_ANY))
    device = PR_Detect3Dfx ();
  #endif

#if defined (MSDD) || defined (WTDD)
  if ((device == DEVICE_D3D) || (device == DEVICE_ANY))
    device = PR_DetectD3D ();   
#endif

#if !defined (MSGLIDE) && !defined (WTGLIDE)
  if ((device == DEVICE_SVGA) || (device == DEVICE_ANY))
    device = PR_DetectSVGA ();   

  if ((device == DEVICE_VGA) || (device == DEVICE_ANY))
    device = PR_DetectVGA ();   
#endif


  #ifdef __MYST__
  if (device == DEVICE_MYSTIQUE)
    {
     PR_InitializeMystique ();
     atexit (PR_ShutdownMystique);
    }
  #endif

  /* Attempt to initialize the device */
  #ifdef __3DFX__
  if (device == DEVICE_3DFX)
    {
     PR_Initialize3Dfx ();
     atexit (PR_Shutdown3Dfx);
    }
  #endif

#if defined (MSDD) || defined (WTDD)
  if (device == DEVICE_D3D)
    {
     PR_InitializeD3D ();
     atexit (PR_ShutdownD3D);
    }
#endif

#if !defined (MSGLIDE) && !defined (WTGLIDE)
  if (device == DEVICE_SVGA)
    {
     PR_InitializeSVGA ();
     atexit (PR_ShutdownSVGA);
    }
  else if (device == DEVICE_VGA)
    {
     PR_InitializeVGA ();
     atexit (PR_ShutdownVGA);
    }
#endif
}


#if defined (MSDD) || defined (WTDD)
void PR_RestoreAll (void)
{
HRESULT ddrval;

 ddrval = IDirectDrawSurface2_Restore (wgtpdds);

 if (ddrval != DD_OK)
   return;

  PR_LostSurface = 0;
}
#endif


void main (int argc, char *argv[])
{
PR_DWORD i;
PR_REAL cenx, ceny, cenz;
PR_DWORD flipdelay;

  printf ("Power Render Terrain Demo\n");
  printf ("Keys: A/Z   - Move forward and backward\n");
  printf ("      Space - Fire Missiles\n");
  printf ("Use the mouse to steer\n");
 
//  PR_SetDebugFile ("debug.log", REPORT_ERRORS | REPORT_ACTIONS);

  flipdelay = 1;

#ifndef WIN32
  while (kbhit ())
    getch ();

  #if defined (__3DFX__) || defined (__MYST__)
    printf ("Use 3D hardware? (y/n)\n");
    device = getch ();

    if (device == 'y' || device == 'Y')
      device = DEVICE_ANY;
    else
  #endif
      {
       device = DEVICE_VGA;
       flipdelay = 0;
      }
#else /* We are in windows */

  /* First check for Direct3D/DDraw */
  #if defined(MSDD) || defined(WTDD)
    device = MessageBox(NULL, "Use Direct3D Hardware?", "Power Render Initialization", MB_YESNO);
    if (device == IDYES)
      {
       device = DEVICE_D3D;
       PR_Settings.Hardware = 1;
      }
    else
      { 
       device = DEVICE_SVGA;
       PR_Settings.Hardware = 0;
      }
 
  /* Next look for specific hardware */
  #elif defined (MSGLIDE) || defined (WTGLIDE)
    device = DEVICE_3DFX;
  #elif defined (MSMYST) || defined (WTMYST)
    device = DEVICE_MYST;
  #endif
#endif


  PR_Initialize (7500);                /* Maximum triangles per frame */

#ifndef WIN32
  vga256 ();
  minit ();
#endif
  msetbounds (0, 0, 319, 199);
  msetxy (160, 100);
  /* Necessary for the standard input routines */

  installkbd ();

  winittimer ();
  wstarttimer (timerproc, TICKS(60));

  InitializeDevices ();

  if ((device == DEVICE_VGA) || (device == DEVICE_SVGA) ||
      (device == DEVICE_MYSTIQUE) || (device == DEVICE_D3D))
     terrain_size = 14;
  else
     terrain_size = 18;

  PR_AllocMaterials (256 * 6);
  PR_AllocTextures (256 * 6);
  PR_AllocShadeTables (32);

  vwidth = PR_VideoModes[0].width;     /* Initialize the video mode */
  vheight = PR_VideoModes[0].height;
  PR_SetMode (vwidth, vheight, 60);

  PR_OpenViewport (&viewport, 0, 0, vwidth-1, vheight-1, VIEW_PLAIN);
  PR_SetViewport (&viewport);          /* Open a full screen viewport */

  PR_Settings.HardwareMipmaps = 1;

#if defined (MSGLIDE) || defined (WTGLIDE)
  PRGUI_GoStartPath ();
#endif

  PR_Settings.LoadPalette = 1;
  PR_Settings.FrontToBack = 0;

  /* Load the sprite textures */
  PR_SetTextureFormat (TEXTURE_XRAY);
  PR_LoadTexture ("sprite.pcx");
  PR_LoadTexture ("smoke.pcx");
  PR_LoadTexture ("shrapnel.pcx");
  PR_SetTextureFormat (TEXTURE_NORMAL);


  PR_FogShadeTable = PR_LoadTable ("fog.dat");
  landtable = PR_FogShadeTable;
  translucent_table = PR_LoadTable ("trans.tab");

  InitializeTexture ();

  PR_FogMaxShade = 63 << 16;
  PR_ShadeTables[landtable].normal_shade = 128 << 16;


  PR_Settings.LoadPalette = 0;

  /* -------------- Load objects ----------------- */
  terrain = PR_AllocateTerrain (terrain_size,
                                TERRAIN_PLAIN | TERRAIN_SHADING,
                                WORLD_SCALE, HEIGHT_SCALE, OBJ_TILES);
  /* Load in the height and tile maps */
  PR_LoadTerrain (terrain, 1, "hf1.pcx", "track1.wmp", "shading.pcx", 32);
//  PR_LoadTerrain (terrain, 2, "hf2.pcx", "track2.wmp", "shading.pcx", 32);
//  PR_LoadTerrain (terrain, 3, "hf3.pcx", "track3.wmp", "shading.pcx", 32);

#if !defined (MSGLIDE) && !defined (WTGLIDE)
  if ((device == DEVICE_VGA) || (device == DEVICE_SVGA))
    wsetpalette (0, 255, global_palette);
#endif

  shrapnel_shape = PR_LoadPRO ("shrapnel.pro", LOAD_NORMAL);
  PR_CenterObject (shrapnel_shape, &cenx, &ceny, &cenz);
  InitializeShrapnel ();


  missile_shape = PR_LoadPRO ("missile.pro", LOAD_NORMAL);
  PR_CenterObject (missile_shape, &cenx, &ceny, &cenz);
  InitializeMissiles ();


  sky_shape = PR_LoadPRO ("sky.pro", LOAD_NORMAL);
  Initialize_Sky (sky_shape);
  PR_CenterObject (sky_shape, &cenx, &ceny, &cenz);
  sky_entity = PR_CreateEntity (sky_shape, "Sky");
  PR_PositionEntity (sky_entity, 0, 400, 0);
  PR_ScaleEntityAbs (sky_entity, 1, 1, 1);

  smoke_shape = PR_AllocSprite (SMOKE_MATERIAL);
  InitializeSmoke ();


  sprite_shape = PR_AllocSprite (1);
  sprite_entity = PR_CreateEntity (sprite_shape, "Sprite 1");
  PR_PositionEntity (sprite_entity, 0, 600, -2500);
  PR_ScaleEntityAbs (sprite_entity, 1, 1, 1);


  /* -------------- Misc Setup ----------- */
  if ((device == DEVICE_MYSTIQUE) || (device == DEVICE_D3D))
    {
     sky_color = PRGFX_MakeColor (0, 0, 0);
     draw_sky = 0;
    }
  else
    sky_color = PRGFX_MakeColor (0, 31, 63);

  white_color = PRGFX_MakeColor (63, 63, 63);

  PR_SetFogState (TRUE);
  PR_SetFogColor (sky_color);
  if ((device == DEVICE_VGA) || (device == DEVICE_SVGA) ||
      (device == DEVICE_MYSTIQUE) || (device == DEVICE_D3D))
    PR_SetFogRange (terrain_size * FOG_VGA_START, terrain_size * FOG_VGA_END);
  else /* hardware */
    PR_SetFogRange (terrain_size * FOG_3DFX_START, terrain_size * FOG_3DFX_END);

  gui_font = NULL;
  PRGFX_SetTextForeground (PRGFX_MakeColor (63,63,63));
  PRGFX_SetTextBackground (PRGFX_MakeColor (32,32,32));
  PRGFX_SetTextTransparent (TEXTFGBG);


  PR_AmbientLight = 0;
  PR_AmbientRed = 0;
  PR_AmbientGreen = 0;
  PR_AmbientBlue = 0;

  newcam = PR_AllocCamera ();
  PR_InitializeCamera (newcam);
  PR_PositionCameraSource (newcam, 0, 0, 0);
  PR_PositionCameraTarget (newcam, 0, 0, 0);
  PR_SetCameraMode (newcam, CAMFLAG_ANGLE_BASED); 
  SetInitialPosition ();

  PR_AllocLights (&userlights, 60);      /* User lights */
  PR_AllocLights (&sunlight, 1);         /* Infinite sun light */
  PR_AllocLights (&scenelights, 60);     /* Master scene light list */
  PR_AllocLights (&explosionlights, 60);
  for (i = 0; i < 20; i++)
    {
     PR_SetLightOff (&explosionlights, i);
     PR_SetLightType (&explosionlights, i, POINT_LIGHT);
     PR_SetLightFalloff (&explosionlights, i, EXPLOSION_LIGHT_SIZE);
     PR_SetLightColor (&explosionlights, i, 1.0, 0.5, 0.0);
    }
  explosionlights.NumLights = 20;

  PR_SetLightPosition (&sunlight, 0, 0, 0, 10000);
  PR_SetLightType (&sunlight, 0, DIRECTIONAL_LIGHT);
  PR_SetLightOn (&sunlight, 0);
  PR_SetLightStrength (&sunlight, 0, 1.0);
  PR_SetLightColor (&sunlight, 0, 0.5, 0.5, 0.5);

  PR_SetLightType (&userlights, 0, POINT_LIGHT);
  PR_SetLightOn (&userlights, 0);
  PR_SetLightStrength (&userlights, 0, 1.0);
  PR_SetLightFalloff (&userlights, 0, 768);
  PR_SetLightColor (&userlights, 0, 1.0, 1.0, 1.0);

  /* Initialize the input devices */
  PR_SetInputDevice (INPUT_MOUSE);
  PR_SetInputModel (MODEL_FLYING);
  PR_SetInputEntity (NO_ENTITY, NO_SEGMENT);
  PR_SetInputCamera (newcam);

  TRANSLATE_SPEED = 4.68;
  MAX_TRANSLATE_SPEED= 43.0;
  TRANSLATE_DRAG     = 0.61;

  userlights.NumLights = 1;     /* This sets how many lights are actually used */
  sunlight.NumLights = 1;     

  CreateWaveTables ();

#if !defined (MSGLIDE) && !defined (WTGLIDE)
  if ((device == DEVICE_VGA) || (device == DEVICE_SVGA))
    PR_SetPerspectiveDivisions (TEXTURE_DIVISION_32);
#endif

  /* -------------- Loop ----------- */
  PR_OpenScreen (PR_BACKBUFFER);


  while (!kbdon[KEY_ESC])
    {
     /* -------------- Camera Position ----------- */
     UserInput ();

     #ifdef WIN32
       UpdateMessages ();
     #endif

     /* -------------- Render Normal Frame ----------- */

     #if defined (MSDD) || defined (WTDD)
       if (PR_LostSurface)
         PR_RestoreAll ();
     #endif

     PR_OpenScreen (PR_BACKBUFFER);

#if !defined (MSGLIDE) && !defined (WTGLIDE)
     if (PR_Settings.FrontToBack)
       PR_ResetHoles ();
     else
#endif
       {
        PRGFX_SetColor (sky_color);
        PRGFX_ClearScreen ();
       }

     PR_NewFrame ();

     newcam->farclip = terrain_size * WORLD_SCALE * 2;
     PR_SetActiveCamera (newcam);

     PR_SetLightPosition (&sunlight, 0,
                          newcam->source.x,
                          newcam->source.y,
                          newcam->source.z);
     PR_SetLightPosition (&userlights, 0,
                          newcam->source.x,
                          newcam->source.y,
                          newcam->source.z);

     PR_TransformLights (&userlights);

     UpdateExplosions ();
     PR_TransformLights (&explosionlights);

     MoveMissiles ();


//     MoveWaves ();
//     PR_TerrainModify (terrain);

     PR_UpdateTerrain (terrain);
     PR_TransformTerrain (terrain);
     PR_TransformLights (&sunlight);
  
     if (draw_sky)
       {
        newcam->farclip = 65535.0;
        PR_SetActiveCamera (newcam);

        PR_SetFogRange (FOG_3DFX_SKYSTART, FOG_3DFX_SKYEND);
        PR_PositionEntity (sky_entity, newcam->source.x, newcam->source.y + 3000, newcam->source.z);
        PR_TransformEntity (sky_entity);
        PR_RenderEntity (sky_entity);          

        if ((device == DEVICE_VGA) || (device == DEVICE_SVGA) ||
            (device == DEVICE_MYSTIQUE))
           PR_SetFogRange (terrain_size * FOG_VGA_START, terrain_size * FOG_VGA_END);
        else
          PR_SetFogRange (terrain_size * FOG_3DFX_START, terrain_size * FOG_3DFX_END);

        newcam->farclip = terrain_size * WORLD_SCALE * 2;
        PR_SetActiveCamera (newcam);
       }

     EntityCoordinateFix (sprite_entity);
     PR_TransformEntity (sprite_entity);
     PR_RenderEntity (sprite_entity);          

     MoveShrapnel ();
     MoveSmoke ();

     PR_RenderFrame ();

#if !defined (MSGLIDE) && !defined (WTGLIDE)
     if (PR_Settings.FrontToBack)
       PR_FillHoles (sky_color);
#endif

     PR_OpenScreen (PR_BACKBUFFER);
     PRGUI_printf (10, 10, "FPS: %i", framerate);
     /* Calculate the frame rate */
     if (ticks > 60)
       {
        framerate = updates;
        ticks = 0;
        updates = 0;
       }
     else
       updates++; 

     PR_Flip (flipdelay);
    }

  mdeinit ();
  uninstallkbd ();
  wstoptimer ();
  wdonetimer ();



  PR_FreeCamera (newcam);
  PR_FreeEntity (sprite_entity);
  PR_FreeObject (sprite_shape);

  for (i = 0; i < MAXMISSILE; i++)
    PR_FreeEntity (missiles[i]);
  PR_FreeObject (missile_shape);

  PR_CloseViewport (&viewport);

  PR_DeleteAllTextures ();
  PR_DeleteAllShadeTables ();
  PR_DeleteAllMaterials ();
  PR_ShutDown ();
}


