/*************************************************************************/
/***                                                                   ***/
/***      R A Y . C P P                    (C) 1995 NPS Software       ***/
/***                                                                   ***/
/***                Writen By:        Brad Broerman                    ***/
/***                Started:          June 1994                        ***/
/***                Last Modified:    March 13, 1999                   ***/
/***                Version:          1.5                              ***/
/***                                                                   ***/
/***                                                                   ***/
/***    A 3-D Raycasting graphics engine with up to a 64x64 cell map,  ***/
/***  35 textures (256 color 128x128 PCX files), textured floor and    ***/
/***  ceiling, sprites, 3 types of animated doors, 20 levels of        ***/
/***  transparency, translucency, animated textures,lighting and       ***/
/***  depth cueing (with fog effects), zone maps, and triggers.        ***/
/***                                                                   ***/
/***    To be added: 2 more door types, door side texturing, door api, ***/
/***  sprite logic api, lighting api, var. height floors, look up/down,***/
/***  animated palette section, main menu, stats updating, trigger api,***/
/***  better kbd map.                                                  ***/
/***                                                                   ***/
/***   Clean up: Split functionality in to multiple source and header  ***/
/***  files, Optimize rendering routines (convert to assembly),        ***/
/***  move collision detection functionality from main() to separate   ***/
/***  functions.                                                       ***/
/***                                                                   ***/
/*************************************************************************/
/***                            Change Log:                            ***/
/***                                                                   ***/
/***     06/15/94  Started RAY project                                 ***/
/***     06/30/94  Basic raycaster engine finished.                    ***/
/***     07/30/94  Base engine optimized.                              ***/
/***     09/10/94  Floor / ceiling texturing added.                    ***/
/***     09/15/94  RAY first distributed.                              ***/
/***                                                                   ***/
/***     09/25/98  Started working on Ray again...                     ***/
/***     10/03/98  Added depth cueing/fog effects                      ***/
/***     10/06/98  Added garage and elevator door styles               ***/
/***     10/10/98  Fixed doors, now recessed 1/2 way back              ***/
/***     10/14/98  Fixed array overruns ... Thx to Jorge A. Martin     ***/
/***     10/29/98  Added animated textures, translucency, and          ***/
/***                 recesed tile flags. Combined T & X tiles.         ***/
/***     11/01/98  Finally got it to run in Protected Mode!            ***/
/***     11/10/98  Added tiled light maps.                             ***/
/***     12/05/98  Added animated lighting                             ***/
/***     01/11/99  Increased PCX file resolution to 128x128            ***/
/***     02/24/99  Added zone maps and zone options.                   ***/
/***     03/11/99  Added Triggers (except 2,3,&10)                     ***/
/***                                                                   ***/
/*************************************************************************/

#include <time.h>
#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <bios.h>
#include <fcntl.h>
#include <memory.h>
#include <malloc.h>
#include <math.h>
#include <string.h>

// If defined, then floor and ceiling are rendered.
#define CAST_FLOORCIEL
// How close you can get to a wall.
#define OVERBOARD 60

// Our definitions for the angles.  ANGLE_60 = width of viewport. (60 deg. FOV)
// All the other angles are based on this.
#define ANGLE_0     0
#define ANGLE_1     4
#define ANGLE_2     8
#define ANGLE_4     16
#define ANGLE_5     20
#define ANGLE_6     24
#define ANGLE_10    40
#define ANGLE_15    60
#define ANGLE_20    80
#define ANGLE_30    120
#define ANGLE_45    180
#define ANGLE_60    240
#define ANGLE_90    360
#define ANGLE_120   480
#define ANGLE_135   540
#define ANGLE_150   600
#define ANGLE_180   720
#define ANGLE_210   840
#define ANGLE_225   900
#define ANGLE_240   960
#define ANGLE_270   1080
#define ANGLE_300   1200
#define ANGLE_315   1260
#define ANGLE_330   1320
#define ANGLE_360   1440

// The size of each cell. 128 makes the math easier. (also textures are 128 pixels wide)
#define CELL_X_SIZE   128
#define CELL_Y_SIZE   128

// The maximum size of our world 64x64 cells.
#define MAX_WORLD_ROWS 64
#define MAX_WORLD_COLS 64

// These defines are used for lighting.
#define MAX_LIGHT_LEVELS  128
#define PALETTE_SIZE      256ul
#define MAX_DISTANCE      (MAX_WORLD_ROWS * MAX_WORLD_COLS)

// We need a large stack.
extern unsigned _stklen = 20480U;

// The generic PCX header structure. I've listed some important fields, but
// I won't check for them until this code is "officially" released.
typedef struct PCX_HEADER_TYPE
{
  char  manufacturer;    // We don't care about these...
  char  version;
  char  encoding;
  char  bits_per_pixel;  // We want an 8 here...
  int   x,y;             // We ignore these.
  int   width,height;    // Will be either 64x64 or 320x200
  int   horiz_rez;       // Forget about these...
  int   vert_rez;
  char  EGA_palette[48]; // We won't even touch this.
  char  reserved;        // or this.
  char  Num_Planes;      // We want 1
  int   bytes_per_line;  // Either 64 or 320
  int   palette_type;    // We can forget about these too..
  char  padding[58];
} PCX_HEADER;

// Stores slice information for rendering. This is used so that we can
// add sprites, transparency, etc. and have the rendering engine draw them too.
struct col_obj
{
  int top,         // Wihch row to begin drawing this slice.
      col,         // Which screen column this slice is in.
      scale,       // How much do I scale this slice
      row,         // Which texture column to render for this slice.
      texnum,      // The number of the wall/sprite texture to use.
      lightnum,    // The number of the lighting tile to use. If -1, then use sptlight as the light for the whole sliver.
      sptlight,    // Flat light value (if used).
      zoneambient, // Ambient light for the zone the sliver is in.
      usefog;      // Use the fog table or the darkness table for the sliver.
  long dist;       // How far away is it (Used for sorting in sprites and light sourcing.)

};

// This implements the multiple levels of transparency we can use. The
// objecs are ordered from nearest to farthest.
struct scan_line_t
{
  col_obj line[10];  // We can go to 10 levels of transparency, sprites, etc.
  int     num_objs;  // The number of objects in 'line'.
};

// This structure is kept for every texture map loaded into the game. It is
// used (mainly) for doors and transparent items.
struct TextureFlagsTYPE
{
  char IsTransparent, // 1=transparent, 0=not.
       IsTranslucent, // 1=Translucent, 0=not.
       IsDoor,        // 1= door, 0= regular wall.
       DoorType,      // 1=normal, 2=elevator, 3=garage
       IsSecretDoor,  // 1=Secret door, 0=Normal door.
       IsRecessed,    // 1=Recessed texture(walls), 0=Not recessed
       DoorSideTxt,   // This holds the texture number for the door sides. (Not used yet.)
       CanWalkThru,   // 1 = you can walk trough it, 0 = Can not walk through it.
       speed,         // How fast the door opens and closes. (cols per frame)
       delay,         // How many frames to wait before closing the door.
       bc,            // Upper left corner of the door
       br,
       ec,            // Lower right corner of the door
       er,
       FloorReflect;
  int  AnimNbr;
};

// This structure keeps the parameters for the lights, including light type,
// and blink parameters.
struct LightFlagsTYPE
{
  char LightType;           // Either a 1, or a 2. 1: Regular blink, 2: Custom blink.
  int  PulseWidth,          // For a regular blink, defines the pulse.
       Period,
       CurrPatternIndx;     // counter... see ProcessBlinkLights() for usage.
  char *PulsePattern;       // For a custom blinking light, this is the pattern: 1 - on, 0= off.
  char far *OriginalBitmap; // Used to track the original bitmap. see ProcessBlinkLights() for usage.
  int  AnimNbr;
};

// This structure keeps track of all the doors that are opening and closing
// at any particular time.
struct Open_Doors_Info
{
  char USED;          // Used in finding an empty spot to use. See the code.
  int  x,             // Map location of the door.
       y,
       delay,         // The door's delay from above.
       Orig_Txt_Num,  // The texture number of the original (un-opened) door.
       New_Txt_Num,   // Points to the working copy of the door tex.
       Pcnt_opened,   // Percent opened.
       Num_Col_Opnd,  // The number of columns the door is opening.
       Opening;       // 1=opening, 0=closing.
};

// This structure keeps track of information associated with all doors on the map.
// Work in progress.
struct Door_Info
{
  int x,            // Map location of the door.
      y,
      locked,       // Can I open it or not? 1 = Locked , 0 = Un-Locked.
      opening;      // lets up know if the texture is not the original one. (!= 0) --> Don't touch!
};

// This is the basic structure for the sprites.
struct Sprite_Type
{
  char *Frames[8];  // Stores the views (angles object can be viewed from)
  int  translucent; // Specifies wether or not this sprites texture is translucent. (Sprites are always transparent)
};

// Sprite instance variables.
// Work in progrss,
struct object
{
  int sprite_num,  // The sprite array object we're using.
      x,           // The sprites position in the map (fine coordiates)
      y,
      angle,       // The angle the sprite is facing.
      state;       // The sprites state (To be used later)
};

//    This structure holds information about anumated textures. This includes the
//  frames, and counter / timing information.
struct Animations_Typ
{
  char far **Frames;  // Array of pointers to the frames.
  int  Flagged,       // 1=Animate this texture, 0=Show only 1st frame.
       Txt_Nbr,       // Which texture in the map this applies to.
       Delay,         // Delay time between frames.
       Timer,         // Counts the delay for this texture.
       Curr_Frame,    // The frame we are currently showing.
       Nbr_Frames;    // The number of frames.
};

//   This structure holds information about the various defined zones. These zones
// could be rooms, the outside, etc.
struct zone_info
{
  char ambient,   // Ambient light level 0-64 for this zone.
       inside,    // 1=inside, 0=outside  (turns on/off ceiling lighting)
       fog;       // 1=Use fog, 0=use normal depth cueing.
};

//   This structure is used to record trigger information.
// Work in progrss...
struct trigger_info
{
  int  Trigger_Nbr,
       Trigger_Type,
       Trigger_Area_Type,
       X1, Y1,
       X2, Y2,
       Radius,
       MapX,
       Mapy,
       ZoneNbr,
       NewLightLvl,
       NewAngle,
       TxtNbr;

  char NewFileName[13];
};

// These globals are used for speeding up the tracing and rendering parts.
// These are copied from the col_obj structure above.
long sliver_dist;
int  sliver_start,  // Where (y) to begin drawing the slice.
     sliver_column, // Screen column to draw in.
     sliver_scale,  // Height of the sliver (pixels).
     sliver_row,    // Column of the texture to draw.
     sliver_map,    // Which texture to use.
     sliver_light,  // Which lighting tile to use.
     sliver_sptlt,  // Lighting level for sprites.
     sliver_zoneamb,// Ambient light delta for the current zone.
     sliver_usefog, // If 1, use fog table, else use darkness table.

     // These are used in the tracing process. They are global for the same reason as above.
     cell_xx,       // These store the coordinates for the horiz. trace.
     cell_yx,       //
     cell_xy,       // These store the coordinates for the vert. trace.
     cell_yy;       //


// These are for the new keyboard ISR.
// They are globals BECAUSE they are used in the ISR.
void interrupt (*OldInt9)(...);
int  volatile  KeyScan = 0;
int  volatil   KeyState = 0;
char volatile  inProgress = 0;

// These are global because they're used all over, and I wanted to generate them ONLY
// at the beginning of the program. It also saves us on stack usage.
char far       *AutoMapBkgd;
char far       *world = NULL;               // pointer to matrix of cells that make up world
char far       *FloorMap = NULL;            // pointer to the matrix of cells that define the floor textures.
char far       *CeilMap = NULL;             // pointer to the matrix of cells that define the ceiling textures.
char far       *ZoneMap = NULL;             // pointer to the matrix of calls that define the 'zones' for various effects.
char far       TriggerMap[64][64];          // Assists in quick detection of simple triggers. Maps location to trigger number.
char far       *CeilLightMap = NULL;        // pointer to the ceiling light map.
char far       *FloorLightMap = NULL;       // pointer to the floor light map.
int            Num_Textures = 0;            // The number of textures we've loaded.
char far       *WallTextures[80];           // The 64x64 wall textures, door textures, and visible sprites.
int            Num_Light_Tiles = 0;         // The number of lighting tiles for the floor/ceiling light maps.
char far       *LightMapTiles[30];          // The 64x64 lighting map tiles.
char far       *LightLevel = NULL;          // Gives the lighting level as a function of distance.
char far       *LightMap = NULL;            // Maps a color/light pair into a pallete value for depth cueing. 32*256 in size.
char far       *FogMap = NULL;              // Maps a color/light pair into a pallete value for fog effects. 32*256 in size.
char far       *Translucency = NULL;        // Trans. lookup. Blends a color/color pair with 10% color 1, 90% color 2. Gives palette value.
char far       *BkgBuffer = NULL;           // The background picture.
long far       *tan_table = NULL;           // tangent tables used to compute initial
long far       *inv_tan_table = NULL;       // intersections with ray
long far       *y_step = NULL;              // x and y steps, used to find intersections
long far       *x_step = NULL;              // after initial one is found
long far       *cos_table = NULL;           // used to cacell out fishbowl effect
long far       *sin_table = NULL;           // Used for movement in different directions.
long far       *inv_cos_table = NULL;       // used to compute distances by calculating
long far       *inv_sin_table = NULL;       // the hypontenuse
long far       *sliver_factor = NULL;       // Scale factor for texture slivers.
long           Floor_Row_Table[151];        // Pre-calculated row start values.
long far       *Floor_inv_cos_table = NULL; // The following tables are used in the math for the floor/ceiling.
long far       *Floor_cos_table = NULL;
long far       *Floor_sin_table = NULL;
long far       *Floor_Dx_table = NULL;
long           row[200];                    // Pre-calculates 320*row.
int            oldmode;                     // Original grahics/text mode when the game starts.
char far       *scrnbuf,                    // Pointer to video memory
               *dblbuf;                     // Pointer to the double buffer ram.
int            WORLD_ROWS = 64;             // Current size of the map.
int            WORLD_COLUMNS = 64;
long           WORLD_X_SIZE = 8192;         // Total size of the world (64cells x 128 per cell)
long           WORLD_Y_SIZE = 8192;
short          ambient_level = 3;           // The global ambient light level.
char           palette[256][3];             // The current palette.
int            Num_Sprites = 0;             // The number of sprites defined.
int            Num_Objects = 0;             // The number of objects defined.
int            Sprite_Frame[361];           // Determines frame to display for sprites.
int            Num_Animations = 0;          // The number of animated textures we are using.
int            Num_LightAnimations = 0;     // The number of animated lighting tiles we are using.
int            Num_Triggers = 0;            // The number of defined triggers.
struct scan_line_t      scan_lines[240];    // Stores the scan-line info.
struct TextureFlagsTYPE TextureFlags[81];   // Stores the texture flags for each loaded texture.
struct LightFlagsTYPE   LightFlags[30];     // Stores the flags for the lights.
struct Open_Doors_Info  Opening_Doors[10];  // Moving door info.
struct zone_info        ZoneAttr[20];       // Attributes for the zones. Currently: ambient light, fog, and inside/outside
struct Sprite_Type      Sprites[10];           // The sprites. These are the bitmaps.
struct object           Objects[30];           // The actual objects that use the sprites.
struct Animations_Typ   Animations[10];        // Up to 10 animated textures.
struct Animations_Typ   Light_Animations[10];  // Up to 10 animated lighting tiles.
struct trigger_info Triggers[10];              // The loaded triggers.

// Graphics functions...
void initscreen(int buf)
{
  // Initialize the grapphics display, and the double buffer.
  // IF buf = 0, then NO double buffering.
  int om; // The old mode - from the BIOS call.

  dblbuf = NULL;
  scrnbuf=(char far *)MK_FP(__SegA000,0x0000); // Make a pointer to screen memory.
  if (buf) // If we want one, allocate a double buffer.
    {
      dblbuf =(char far *)farmalloc(64000l);
      if (!dblbuf)
        {
          printf("Error allocating screen buffer. Exiting. \n");
          exit(1);
        }
    }
  else    // Otherwise, just skip it.
    dblbuf = scrnbuf;

  // Now we get the old graphics mode, and set the new one.
  asm { mov ah,0xf
        int 0x10
        xor ah,ah
        mov om,ax
        mov ax,0x0013
        int 0x10
      }
  oldmode = om;
  return;
}

void txtscreen(void)
{
  // This function resets the VGA to the old mode.
  char om = oldmode&0xff;

  asm { mov ah,0x00
        mov al,om
        int 0x10
        int 0x10
      }
  farfree(dblbuf);
}

void clearscrn(int c)
{
  // This function clears te screen to the color "C", by blasting the
  // byte to the double buffer as fast as possible!
  unsigned int Sg, // These are declared here for the asm call.
               Of,
               color;

  Sg = FP_SEG(dblbuf); // Get the address of the double buffer.
  Of = FP_OFF(dblbuf);
  color = (c<<8)|c; // Replicate the color value in the upper byte.
  asm { mov ax,Sg   // Now blast it to the buffer.
        mov es,ax
        mov di,Of
        mov ax,color
        mov cx,32000
        rep stosw
      }
}

void copyscrn (char far *Buffer)
{
  // This function copies a bitmap from a buffer to the double buffer.
  unsigned int Sg,
               Of,
               Bsg,
               Bof;

  Sg = FP_SEG(dblbuf); // Get the adderess of the double buffer.
  Of = FP_OFF(dblbuf);
  Bsg = FP_SEG(Buffer); // Get the address of the input buffer.
  Bof = FP_OFF(Buffer);

  // Slam it to the doubl buffer.
  asm { push ds
        mov ax,Bof
        mov si,ax
        mov ax,Bsg
        mov ds,ax
        mov ax,Sg
        mov es,ax
        mov di,Of
        mov cx,32000
        rep movsw
        pop ds
      }
}

void dissolveto(char far *Buffer)
{
  // This routine does a "dissolve" from the curent screen to that stored
  // in Buffer. It writes directly to the screen.
  unsigned long index,
           x,y,
           offset;

  // Do LOTS of copies at random locations..
  // (We only need the top 1/2 of the screen, IE Dirty animation...)
  for (index=0; index<=500000; index++)
    {
      x = rand()%320;
      y = rand()%100;
      offset = x+(y<<6)+(y<<8);
      *(scrnbuf+offset) = *(Buffer+offset);
    }
}

void swapdblbuf()
{
  // This routine copies the double buffer to the screen.

  unsigned int Sg,
               Of,
               vidseg;

  Sg = FP_SEG(dblbuf);
  Of = FP_OFF(dblbuf);
  vidseg = __SegA000;
  // This snippet waits for the beginning of a verticle blank interval.
  //  while(inportb(0x3da) & 0x08) // Wait for any current VBI to end.
  //   {};
  //  while(!(inportb(0x3da) & 0x08)) // Wait for the beginning of the next one.
  //   {};
  // Now we can blast the buffer to the screen.
  asm { push ds
        mov ax,Of
        mov si,ax
        mov ax,Sg
        mov ds,ax
        mov ax,vidseg
        mov es,ax
        xor di,di
        mov cx,32000
        rep movsw
        pop ds
      }
}

void fadeout(int del)
{
  // This function fades the picture on the screen to black, using the
  // palette.
  //
  int r,g,b,    // The color values
      pl,       // The pallete entry number
      index;    // Loop counter... used as a divisor.

  // Loop through 16 stages of darkening...
  for (index = 1; index <= 16; ++index)
    {
      // And do it for all 256 colors...
      for (pl = 0; pl < 256; ++pl)
        {
          // Divide the current palette entry by the divisor,
          r = palette[pl][0]/index;
          g = palette[pl][1]/index;
          b = palette[pl][2]/index;
          // And stick it in the VGA
          outportb(0x3c6,0xff);
          outportb(0x3c8,pl);
          outportb(0x3c9,r);
          outportb(0x3c9,g);
          outportb(0x3c9,b);
        }
      // Without this, the whole thing's too fast...
      delay(del);
    }
  // Finally, we black it all out.
  for (pl = 0; pl < 256; ++pl)
    {
      outportb(0x3c6,0xff);
      outportb(0x3c8,pl);
      outportb(0x3c9,0);
      outportb(0x3c9,0);
      outportb(0x3c9,0);
    }
}

void fadein(int del)
{
  //
  // This function fades in from Black to the current palette values.
  //
  int r,g,b, // The color values to plug into the VGA
      pl,    // The index into the palette structure.
      index; // loop counter, used as the divisor...

  // Make sure we're starting out from black.
  for (pl = 0; pl < 256; ++pl)
    {
      outportb(0x3c6,0xff);
      outportb(0x3c8,pl);
      outportb(0x3c9,0);
      outportb(0x3c9,0);
      outportb(0x3c9,0);
    }
  // Now, go through all 16 levels of darkness.
  for (index = 16; index >= 1; --index)
    {
      for (pl = 0; pl < 256; ++pl)
        {
          r = palette[pl][0]/index;
          g = palette[pl][1]/index;
          b = palette[pl][2]/index;
          outportb(0x3c6,0xff);
          outportb(0x3c8,pl);
          outportb(0x3c9,r);
          outportb(0x3c9,g);
          outportb(0x3c9,b);
        }
      delay(del);
    }
}

// New interrupt handler...

void interrupt NewInt9(...)
{
  register keybyte;
  if (!inProgress)  // Let's not do is twice at the same time...
    {
      inProgress = 1;
      KeyScan =inp(0x60);   // Get the scan code.
      keybyte =inp(0x61);   // Get the byte code.
      outp(0x61,(keybyte|0x80)); // Clear the flag.
      outp(0x61,keybyte);
      switch(KeyScan) // Now process it...
        {
        case 59:  KeyState |= 0x80; break;  // Pressed the F1 key.     ---> Screen Shot.
        case 187: KeyState &= 0x7f; break;
        case 60:  KeyState |= 0x40; break;  // Pressed the F2 key.     ---> Torch level.
        case 188: KeyState &= 0xbf; break;
        case 15:  KeyState |= 0x20; break;  // Pressed the Tab key.    ---> Automap on/off.
        case 143: KeyState &= 0xdf; break;
        case 57:  KeyState |= 0x10; break;  // Pressed the space bar.  ---> Activate door / trigger
        case 185: KeyState &= 0xef; break;
        case 77:  KeyState |= 0x08; break;  // Pressed left            ---> Turn left.
        case 205: KeyState &= 0xf7; break;
        case 75:  KeyState |= 0x04; break;  // Pressed right           ---> Turn Right.
        case 203: KeyState &= 0xfb; break;
        case 72:  KeyState |= 0x02; break;  // Pressed up              ---> Move Forward.
        case 200: KeyState &= 0xfd; break;
        case 80:  KeyState |= 0x01; break;  // Pressed down            ---> Move Backwards.
        case 208: KeyState &= 0xfe; break;
        }
      inProgress = 0;
    }
  outp(0x20,0x20); // Signal the end of the interrupt.
}

// Misc. Functions.

void delay2(long time) // time is in seconds.
{
  // This is a delay that can be cancelled by pressing SPACE or ESCape.
  // It is used during the start-up screens.
  time_t start;

  start= clock();
  while ((KeyScan == 129) || (KeyScan == 57)) // Wait for the keys to clear.
    ;
  while ((clock() - start) < time*CLK_TCK) // Start the delay
    {
      if ((KeyScan == 129) || (KeyScan == 57))// if space or ESC, break out.
        break;
    }
}

// The following functions set up the world...

void free_tables(void)
{
  // Freee all the tables, if they were allocated.
  int i,j;

  if (world != NULL)
    farfree(world);
  if (FloorMap != NULL)
    farfree(FloorMap);
  if (CeilMap != NULL)
    farfree(CeilMap);
  if (ZoneMap != NULL)
    farfree(ZoneMap);
  if (CeilLightMap != NULL)
    farfree(CeilLightMap);
  if (FloorLightMap != NULL)
    farfree(FloorLightMap);
  for (i=0; i<Num_Textures; ++i)
    if (WallTextures[i] != NULL)
      farfree (WallTextures[i]);
  for (i=0; i<Num_Light_Tiles; ++i)
    if (LightMapTiles != NULL)
      farfree(LightMapTiles);
  if (tan_table != NULL)
    farfree(tan_table);
  if (inv_tan_table != NULL)
    farfree(inv_tan_table);
  if (y_step != NULL)
    farfree(y_step);
  if (x_step != NULL)
    farfree(x_step);
  if (cos_table != NULL)
    farfree(cos_table);
  if (sin_table != NULL)
    farfree(sin_table);
  if (inv_cos_table != NULL)
    farfree(inv_cos_table);
  if (inv_sin_table != NULL)
    farfree(inv_sin_table);
  if (sliver_factor != NULL)
    farfree(sliver_factor);
  if (Floor_inv_cos_table != NULL)
    farfree(Floor_inv_cos_table);
  if (Floor_cos_table != NULL)
    farfree(Floor_cos_table);
  if (Floor_sin_table != NULL)
    farfree(Floor_sin_table);
  if (Floor_Dx_table != NULL)
    farfree (Floor_Dx_table);
  if (LightLevel != NULL)
    farfree(LightLevel);
  if (LightMap != NULL)
    farfree(LightMap);
  if (FogMap != NULL)
    farfree(FogMap);
  if (Translucency != NULL)
    farfree(Translucency);
  for (j = 0; j <= Num_Sprites; ++j)
    for (i=0; i < 8; ++i)
      if (Sprites[j].Frames[i] != NULL)
        farfree (Sprites[j].Frames[i]);
  for (j = 0; j < Num_Animations; ++j)
    for (i=0; i < Animations[j].Nbr_Frames; ++ i)
      if (Animations[j].Frames[i] != NULL)
        farfree(Animations[j].Frames[i]);
  for (i=0; i < Num_Light_Tiles ; ++i)
    if (LightFlags[i].PulsePattern != NULL)
      farfree(LightFlags[i].PulsePattern);
}

void HndleAllocError(char *string)
{
  // If an allocation error was found, this function is called. It informs us of the
  // error, frees the tables, and quits.
  txtscreen();
  fprintf(stderr,"Memory allocation error while allocating '%s'\n",string);
  free_tables();
  getch();
  exit(1);
}

void HndleAllocError()
{
  // If an allocation error was found, this function is called. It informs us of the
  // error, frees the tables, and quits.
  txtscreen();
  fprintf(stderr,"General memory allocation error.\n");
  free_tables();
  getch();
  exit(1);
}

void Build_Tables(void)
{
  //   Here is were we build all the math tables. This is MUCH faster than
  // using the trig functions. We will also pre-calculate a couple of other
  // things here as well.
  //
  int   ang;       // The angle (from ANGLE_0 to ANGLE_360)
  int   tmp;
  int   i;
  long  dist;      // Distance value. Used in lighting table calculation.
  float ratio;     // Light Ratio. Used in lighting table calculation.
  float rad_angle; // The angle in radians.
  FILE *handle;

  // Step 1. Initialize static arrays pointers and counters.
  for (i = 0; i < 256; ++i)
    {
      palette[i][0] = 0;
      palette[i][1] = 0;
      palette[i][2] = 0;
    }
  // Then set up the WallTextures table.
  for (i=0; i < 80; ++i)
    {
      WallTextures[i] = NULL;
      TextureFlags[i].AnimNbr = -1;
    }
  // Then set up the LightMapTextures table.
  for (i=0; i < 30; ++i)
    {
      LightMapTiles[i] = NULL;
      LightFlags[i].AnimNbr = -1;
    }
  // And clean up the OpeningDoors table.
  for (i=0; i<10; ++i)
    Opening_Doors[i].USED = 0;
  // Initialize the light flags pattern param.
  for (i=0; i < 30 ; ++i)
    {
      LightFlags[i].PulsePattern = NULL;
      LightFlags[i].CurrPatternIndx = 0;
    }
  ZoneAttr[0].inside = 0;
  ZoneAttr[0].ambient = 0;
  ZoneAttr[0].fog = 0;
  for (tmp  = 0; tmp <10 ; ++tmp)
    for (i=0; i < 8; ++i)
      Sprites[tmp].Frames[i] = NULL;


  // Step 2. Allocate the maps. 4K each.
  if (!(world = (char far*)farmalloc(4096)))
    HndleAllocError("world map");
  if (!(FloorMap = (char far*)farmalloc(4096)))
    HndleAllocError("floor map");
  if (!(CeilMap = (char far *)farmalloc(4096)))
    HndleAllocError("ceiling map");
  if (!(ZoneMap = (char far *)farmalloc(4096)))
    HndleAllocError("zone map");
  if (!(CeilLightMap = (char far *)farmalloc(4096)))
    HndleAllocError("ceiling light map");
  if (!(FloorLightMap = (char far *)farmalloc(4096)))
    HndleAllocError("floor light map");
  if (!(tan_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1))))
    HndleAllocError("tangent table");
  if (!(inv_tan_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1))))
    HndleAllocError("inverse tangent table");
  if (!(y_step = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1))))
    HndleAllocError("y step table");
  if (!(x_step = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1))))
    HndleAllocError("x step table");
  if (!(cos_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1))))
    HndleAllocError("cosine table");
  if (!(sin_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1))))
    HndleAllocError("sine table");
  if (!(inv_cos_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1))))
    HndleAllocError("inverse cosine table");
  if (!(inv_sin_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1))))
    HndleAllocError("inverse sine table");
  if (!(sliver_factor = (long far *)farmalloc(sizeof(long)*600)))
    HndleAllocError("sliver scaling table");
  if (!(Floor_inv_cos_table = (long far *)farmalloc(sizeof(long)*(ANGLE_360+1))))
    HndleAllocError("Floor inverse cosine table");
  if (!(Floor_cos_table = (long far *)farmalloc(sizeof(long)*(ANGLE_360+1))))
    HndleAllocError("Floor cosine table");
  if (!(Floor_sin_table = (long far *)farmalloc(sizeof(long)*(ANGLE_360+1))))
    HndleAllocError("Floor sine table");
  if (!(Floor_Dx_table = (long far *)farmalloc(sizeof(long)*10000L)))
    HndleAllocError("Floor DX table");
  if (!(LightLevel = (char far *)farmalloc(sizeof(char)* MAX_DISTANCE ))) // Maximum distance.
    HndleAllocError("Light levels");
  if (!(LightMap = (char far *)farmalloc(sizeof(char)*(PALETTE_SIZE*MAX_LIGHT_LEVELS))))  // 128 levels of 256 colors.
    HndleAllocError("Light map");
  if (!(FogMap = (char far *)farmalloc(sizeof(char)*(PALETTE_SIZE*MAX_LIGHT_LEVELS))))  // 128 levels of 256 colors.
    HndleAllocError();
  if (!(Translucency = (char far *)farmalloc(65536ul))) // Mixing 256 colors with 256 colors.
    HndleAllocError("Translucency");

  // Step 3: Calculate scaling tables, and row tables. 16:16 and 22:10 fixed point
  for (ang=1; ang < 600; ++ang)
    sliver_factor[ang] = (long)(((float)128/ang)*65536l); // Used in scaling the slivers of texture
  for (ang = 150; ang > 76 ; --ang)
    Floor_Row_Table[ang]=(long)((11476.28125*1024.0)/(ang-75.0)); // Distance of floor row, dead ahead center.
  for (ang = 74; ang >=0 ; --ang)
    Floor_Row_Table[ang]=(long)((11476.28125*1024.0)/(75.0-ang)); // Distance of ceiling row, dead ahead center.
  for (ang = 0; ang < 10000; ++ang)
    Floor_Dx_table[ang] = (long)((1024.0 * ang)/239); // Not used yet. Will be for linear interp. of floor/ceil row rendering
  for (ang = 0; ang < 200; ++ang)
    row[ang] = ang*320;              // Pre-calculated beginning of the row in screen memory.
  // Determine the sprite frame to display based on the angle.
  for (ang=0; ang < 361; ++ang)
    {
      if ((ang > 157) && (ang <= 202)) Sprite_Frame[ang] = 0;
      if ((ang > 202) && (ang <= 247)) Sprite_Frame[ang] = 1;
      if ((ang > 247) && (ang <= 292)) Sprite_Frame[ang] = 2;
      if ((ang > 292) && (ang <= 337)) Sprite_Frame[ang] = 3;
      if ((ang > 337) && (ang <= 22))  Sprite_Frame[ang] = 4;
      if ((ang > 22)  && (ang <= 67))  Sprite_Frame[ang] = 5;
      if ((ang > 67)  && (ang <= 112)) Sprite_Frame[ang] = 6;
      if ((ang > 112) && (ang <= 157)) Sprite_Frame[ang] = 7;
    }
  // Step 4: Now calculate the trig tables.
  for (ang=ANGLE_0; ang<=ANGLE_360; ang++)
    {
      // Calculate the angle n radians (for the trig funcions...
      rad_angle = (3.272e-4)+ang*2*3.141592654/ANGLE_360;
      // Make the basic trig tables.
      tan_table[ang] = (long)(tan(rad_angle)*65536L);       // Used to get the y step for a specific angle.
      inv_tan_table[ang] = (long)(65536L/tan(rad_angle));   // Used to get the x step for a specific angle.
      Floor_cos_table[ang] = (long) (1024L*cos(rad_angle)); // Used to get the Y position for the angle and distance.
      Floor_sin_table[ang] = (long) (1024L*sin(rad_angle)); // Used to get the X position for the angle and distance.
      // Make the X and Y step tables, used while casting the rays.
      if (ang>=ANGLE_0 && ang<ANGLE_180)
        {
          y_step[ang] = (tan_table[ang])*CELL_Y_SIZE;
          if (y_step[ang] < 0)
            y_step[ang] = -y_step[ang];
        }
      else
        {
          y_step[ang] = (tan_table[ang])*CELL_Y_SIZE;
          if (y_step[ang] > 0)
            y_step[ang] = -y_step[ang];
        }
      if (ang>=ANGLE_90 && ang<ANGLE_270)
        {
          x_step[ang] = -(inv_tan_table[ang])*CELL_X_SIZE;
          if (x_step[ang] > 0)
            x_step[ang] = -x_step[ang];
        }
      else
        {
          x_step[ang] = inv_tan_table[ang]*CELL_X_SIZE;
          if (x_step[ang] < 0)
            x_step[ang] = -x_step[ang];
        }
      // Back to the basic tables...
      inv_cos_table[ang] = (long)(8192L/cos(rad_angle)); // Get Y for wall position from angle and distance.
      inv_sin_table[ang] = (long)(8192L/sin(rad_angle)); // Get X for wall position from andle and distance.
      sin_table[ang] = (long)(65536L*sin(rad_angle));    // Used for movement. % to move in X and Y for certain angle.
    }
  // These tables arre for scaling and the anti-fisheye stuff.
  for (ang=-ANGLE_30; ang<=ANGLE_30; ang++)
    {
      rad_angle = (3.272e-4)+ang*2*3.141592654/ANGLE_360;
      Floor_inv_cos_table[ang+ANGLE_30] = (long)(1024.0/cos(rad_angle)); // Anti-fisheye for floor.
      cos_table[ang+ANGLE_30] = (long)((11476.28125*256.0)/cos(rad_angle)); // Anti-fisheye and scaling for walls.
    }

  // Now we calculate the lighting tables...
  // Calculate light intensity table.
  for (dist = 1; dist < MAX_DISTANCE; dist++)
    {
      ratio = (float)(MAX_LIGHT_LEVELS/2.0) / dist * 2.0; // Normal intensity is mid-range.
      if (ratio > 1.0) ratio = 1.0;
      *(LightLevel + dist) = (char)( ratio * MAX_LIGHT_LEVELS );
    }
  // Now we load the palette for the lighting effects.
  if((handle=fopen("darkness.pal","rb")) == NULL)
    {
      txtscreen();
      fprintf(stdout,"Error loading depth palette\n");
      free_tables();
      getch();
      exit(1);
    }
  fread(LightMap, MAX_LIGHT_LEVELS, PALETTE_SIZE, handle);
  fclose(handle);

  // Now we load the palette for the fog mapping
  if((handle=fopen("fog.pal","rb")) == NULL)
    {
      txtscreen();
      fprintf(stdout,"Error loading fog palette\n");
      free_tables();
      getch();
      exit(1);
    }
  fread(FogMap, MAX_LIGHT_LEVELS, PALETTE_SIZE, handle);
  fclose(handle);

  //  // Now we load the palette for translucency
  if((handle=fopen("trans.pal","rb")) == NULL)
    {
      txtscreen();
      fprintf(stdout,"Error loading fog palette\n");
      free_tables();
      getch();
      exit(1);
    }
  fread(Translucency,1,65535l, handle);
  fclose(handle);

  _fmemset(ZoneMap,0,4096);
}

void CalcTorchLevel(int level)
{
  long  dist;
  float ratio;
  // There are 8 levels of torch. (1 = blackness, 8 = full)
  if ((level < 1) || (level > 8)) return;

  for (dist = 1; dist < MAX_DISTANCE; dist++)
    {
      ratio = (float)(MAX_LIGHT_LEVELS/(2.0 * (9 - level))) / dist * 1.5; // Normal intensity is mid-range.
      if (ratio > 1.0) ratio = 1.0;
      *(LightLevel + dist) = (char)( ratio * MAX_LIGHT_LEVELS );
    }
}

char far *Load_PCX(char *filename, unsigned long size, int load_pal)
{
  // This function loads in a 256 color PCX file, of a specified size, and
  // returns a pointer to it. If load_pal > 0 then it loads the palette as well.
  unsigned long count,   // the count for the RLE run.
                index;   // Overall byte count. main FOR loop.
  unsigned char data,    // The data byte read in.
                r,g,b;   // Color data.
  FILE          *infile; // The file pointer.
  FILE          *outfile;
  PCX_HEADER    header;  // The PCX header info.
  char far      *Pointer;     // Points to the bitmap data.

  // Step 1. Allocate a new bit of memory for the bitmap.
  if (!(Pointer = (char far *)farmalloc(size)))
    {
      // If there was a problem, quit and complain!
      txtscreen();
      printf("Memory error loading PCX File '%s'. \n", filename);
      free_tables();
      getch();
      exit(1);
    }
  // Step 2: Open the file
  infile = fopen(filename,"rb");
  if (!infile)
    {
      txtscreen();
      printf("Error opening PCX File '%s'. \n", filename);
      free_tables();
      farfree(Pointer);
      getch();
      exit(1);
    }

  // Step 3: Read in the header, and get it out of the way...
  fread((char *)&header,128,1,infile);

  // Step 4. Check the header to make sure we're reading the correct file type...
  if (( header.bits_per_pixel != 8) || (header.Num_Planes != 1))
    {
      fclose(infile);
      txtscreen();
      printf("Error opening PCX File. Wrong file type '%s'. \n", filename);
      farfree(Pointer);
      free_tables();
      getch();
      exit(1);
    }

  // Step 5: Read in the bitmap data.
  index = 0;
  while(index < size) // for each byte in the bitmap (WxL),
    {
      data = getc(infile); // Get the byte.
      if (data >= 192) // If it's an RLE run,
        {
          count = data-192; // Get the count,
          data = getc(infile); // and the data,
          while (count-- > 0) // and blast it out.
            {
              *(Pointer+index) = (char)data;
              ++index;
            }
        }
      else // Else if it isn't an RLE,
        {
          *(Pointer+index) = (char)data; // just write it.
          ++index;
        }
    }
  // If specified, load in the palette.
  if (load_pal > 0)
    {
      // Now seek to the palette data, and read that in.
      fseek(infile,-768l,SEEK_END);
      for (index = 0; index < 256; ++index)
        {
          // Get the data,
          palette[index][0] = getc(infile)>>2;
          palette[index][1] = getc(infile)>>2;
          palette[index][2] = getc(infile)>>2;
        }


    }
  // Step 5: Close the file.
  fclose(infile);

  return Pointer;
}


void load_bkgd(char *filename, int i)
{
  // This function loads in a 320x200, 256 color PCX file, and
  // places it into the BkgBuffer.
  unsigned long index;   // loop counter.

  // Step 1. Clear the background buffer.
  if (BkgBuffer)
    farfree(BkgBuffer);
  // Step 2. Load the PCX file.
  BkgBuffer = Load_PCX(filename,64000l,1);
  // Step 3. And set the VGA palette if requested.
  if (i)
    {
      for (index = 0; index < 256; ++ index)
        {
          outportb(0x3c6,0xff);
          outportb(0x3c8,index);
          outportb(0x3c9,palette[index][0]);
          outportb(0x3c9,palette[index][1]);
          outportb(0x3c9,palette[index][2]);
        }
    }
}

void transpose(char far *Bitmap)
{
  //
  // This function takes a bitmap and transposes the columns with the rows. This is
  // because the sliver renderer reads across a row of the bitmap, and draws a column.
  //
  int rows, // These two are loop counters.
      cols;
  char far *temp; // A temporary holding buffer.

  temp = (char far *)farmalloc(16385);
  if (temp == NULL)
    {
      printf("Memory error transposing PCX File \n");
      free_tables();
      getch();
      exit(1);
    }
  // Copy the bitmap into the temp array, transposed.
  for (rows = 0; rows < 128; ++rows)
    for (cols = 0; cols < 128; ++cols)
      temp[(128*rows)+cols] = Bitmap[(128*cols)+rows];

  // Now, copy it back...
  //  memcpy(Bitmap,temp,16384);
  farfree(temp);
}

void Load_Wall(char *filename,int offset)
{
  //
  // This function loads in the wall texture map. This will be a 256 color .PCX
  // file. The palette must be the same as that which is loaded in the background
  // The routine also calls Transpose, to order the rows and cols correctly.
  //
  //  offset is the texture number (as specified in the map file.
  //

  // Step 1. Decrement the offset since the array starts at 0.
  --offset;

  // Step 2. Check to see if a texture is already there. If so, then delete it.
  if (WallTextures[offset] != NULL)
    farfree(WallTextures[offset]);

  // Step 3. Load the PCX file.
  WallTextures[offset] = Load_PCX(filename,16384l,0);

  // Step 4. We need to transpose the rows and coumns now, because the renderer
  // traverses a ROW of the texture to draw a COLUMN of the screen (faster).
  transpose(WallTextures[offset]);
}

void Load_Light_Tile(char *filename,int offset)
{
  //
  // This function loads in the light tile bitmap. This will be a 256 color .PCX
  // file. The palette is unimportant. The color numbers will add to the lighting of the area.
  //  offset is the bitmap number (as specified in the map file.)
  //

  // Step 1. Check to see if a texture is already there. If so, then delete it.
  if (LightMapTiles[offset] != NULL)
    farfree(LightMapTiles[offset]);
  // Step 2. Load the PCX file.
  LightMapTiles[offset] = Load_PCX(filename,16384l,0);
  // Step 3. We need to transpose the rows and coumns now, because the renderer
  // traverses a ROW of the texture to draw a COLUMN of the screen (faster).
  transpose(LightMapTiles[offset]);
}

char far *Load_Sprite (char *filename)
{
  // Loads the bitmaps for a sprite object. The files are called [Filename][1-8].pcx
  // where [Filename] is the base filename (up to 7 characters), and [1-8] is a
  // single digit from 1 to 8 that is appended to the filename which represents the
  // frame number (each frame represents a 45 degree view of the object. This name formatting
  // is not done here, but in Allocate_World ( ). This routine just loads in 1 bitmap.
  // The function malloc's space for the bitmap, loads it in, and returns a pointer to it.
  char far      *Pointer;     // Points to the sprite data.

  // Step 1. Load the PCX file.
  Pointer = Load_PCX(filename,16384l,0);

  // Step 2. We need to transpose the rows and coumns now, because the renderer
  // traverses a ROW of the texture to draw a COLUMN of the screen (faster).
  transpose(Pointer);

  // Step 3. Return the pointer to the bitmap.
  return Pointer;
}

int Allocate_World(long &initx, long &inity, long &initang, char *MapFileName)
{
  //
  //   This routine reads and translates the map file, creates and fills the map arrays,
  //  and loads the textures into memory.
  //
  FILE *fp;          // Filepointer for the map file.
  int  row,          // Row number that we are readng in.
       column,       // Column number that we are reading in.
       txtnum,       // The texture to be assigned to the cell.
       cs,           // Te following is Door info, and has already been described in detail.
       rs,
       ce,
       re,
       de,
       tp,
       tl,
       sec,
       type,
       sidetxt,
       spd,
       fr,
       i,            // Generic loop index.
       sp_num,       // The number of the sprite we're loading.
       sp_txt;
  long sp_initx,     // The sprite's initial X coordinate.
       sp_inity,     // The sprite's initial Y coordinate.
       sp_initang;   // The sprite;s initial angle.
  char line[257],    // Temporary buffer to hold the current line of the map file.
       typ[100],     // This holds string data we're reading from the line.
       ch,           // Temporary holding var. for cell data during translation.
       trg_parms[100],
       sp_basename[12]; // Base filename for the sprite's PCX files.

  // Step 0. Clear out the old map settings.
  Num_Triggers = 0;
  Num_Objects = 0;
  for (fr  = 0; fr < Num_Sprites; ++fr)
    for (i=1; i<= 8; ++i)
      if (Sprites[fr].Frames[i-1] != NULL)
        {
          farfree(Sprites[fr].Frames[i-1]);
          Sprites[fr].Frames[i-1] = NULL;
        }
  Num_Sprites = 0;
  for (fr = 0; fr < Num_Textures; ++fr)
    {
      if (WallTextures[fr] != NULL)
        {
          farfree(WallTextures[fr]);
          WallTextures[fr] = NULL;
        }
      if (LightFlags[fr].PulsePattern != NULL)
        {
          farfree(LightFlags[fr].PulsePattern);
          LightFlags[fr].PulsePattern = NULL;
        }
      if (LightMapTiles[fr] != NULL)
        {
          farfree(LightMapTiles[fr]);
          LightMapTiles[fr] = NULL;
        }
      if (TextureFlags[fr].AnimNbr > -1)
        {
          if (Animations[TextureFlags[fr].AnimNbr].Frames != NULL )
            {
              for (i = 0; i < Animations[TextureFlags[fr].AnimNbr].Nbr_Frames; ++i)
                if (Animations[TextureFlags[fr].AnimNbr].Frames[i] != NULL)
                  {
                    farfree(Animations[TextureFlags[fr].AnimNbr].Frames[i]);
                    Animations[TextureFlags[fr].AnimNbr].Frames[i] = NULL;
                  }
              farfree(Animations[TextureFlags[fr].AnimNbr].Frames);
              Animations[TextureFlags[fr].AnimNbr].Frames = NULL;
            }
        }
    }
  Num_Textures = 0;
  for (fr = 0; fr <= Num_Light_Tiles ; ++fr)
    {
      if (LightFlags[fr].PulsePattern != NULL)
        {
          farfree(LightFlags[fr].PulsePattern);
          LightFlags[fr].PulsePattern = NULL;
        }
      if (LightMapTiles[fr] != NULL)
        {
          farfree(LightMapTiles[fr]);
          LightMapTiles[fr] = NULL;
        }
      if (LightFlags[fr].AnimNbr > -1)
        {
          if (Light_Animations[LightFlags[fr].AnimNbr].Frames != NULL )
            {
              for (i = 0; i < Light_Animations[LightFlags[fr].AnimNbr].Nbr_Frames; ++i)
                if (Light_Animations[LightFlags[fr].AnimNbr].Frames[i] != NULL)
                  {
                    farfree(Light_Animations[LightFlags[fr].AnimNbr].Frames[i]);
                    Light_Animations[LightFlags[fr].AnimNbr].Frames[i] = NULL;
                  }
              farfree(Light_Animations[LightFlags[fr].AnimNbr].Frames);
              Light_Animations[LightFlags[fr].AnimNbr].Frames = NULL;
            }
        }
    }
  Num_Light_Tiles = 0;

  // Step 1. Set up the 1st light map (map 0).
  if (LightMapTiles[0] != NULL)
    farfree(LightMapTiles[0]);
  LightMapTiles[0] = (char far *)farcalloc(1,16384l);
  // Step 2. Open the map file, and
  if ((fp = fopen(MapFileName,"r")) == NULL)
    {
      txtscreen();
      fprintf(stderr,"Error loading map file '%s'.",MapFileName);
      free_tables();
      getch();
      exit(1);
    }
  // Read in world data.
  fgets(line,256,fp);
  while (!feof(fp))
    {
      memset(typ,0,80);
      sscanf(line,"%s",typ); // This step cleans up much garbage that can be present.
      // Step 2a. Load the player's initial position and orientation
      if (!strcmpi(typ,"//")) // Comments.
        {
          fgets(line,256,fp); // Get the next line here.. (We test for EOF at the beginning..)
          continue;
        }
      if (!strcmpi(typ,"P"))
        {
          sscanf(line,"%*s %ld %ld %ld",&initx, &inity, &initang);
          // Translate into the fine coordinate system.
          initx = initx * 128+64;
          inity = inity * 128+64;
          initang *= 4;
        }
      // 2b. Load in a sprite definition..
      else if (!strcmpi(typ,"S"))
        {
          // [Number] [Base Filename] [Translucent]
          sscanf(line,"%*s %d %7s %d",&sp_num, sp_basename,&tp);
          for (i=1; i<= 8; ++i)
            {
              sprintf(typ,"%s%d.pcx",sp_basename,i);
              if (Sprites[sp_num].Frames[i-1] != NULL)
                farfree(Sprites[sp_num].Frames[i-1]);
              Sprites[sp_num].Frames[i-1] = Load_Sprite(typ);
            }
          Sprites[sp_num].translucent = tp;
          if (sp_num > Num_Sprites)
            Num_Sprites = sp_num;
        }
      // 2c.  Load in an object definition.
      else if (!strcmpi(typ,"O"))
        {
          // [Number] [Initial X] [Initial Y] [Initial Angle]
          sscanf(line,"%*s %d %d %d %d %d ",&sp_num, &sp_txt, &sp_initx, &sp_inity, &sp_initang );
          Objects[sp_num].sprite_num = sp_txt;
          Objects[sp_num].x = sp_initx*128+64;
          Objects[sp_num].y = sp_inity*128+64;
          Objects[sp_num].angle = sp_initang * 4;
          Objects[sp_num].state = 0;
          if (sp_num > Num_Objects)
            Num_Objects = sp_num;
        }
      // Step 2d. Load a texture definition.
      else if (!strcmpi(typ,"T"))
        {
          // [NUMBER] [Filename] [transparent] [translucent] [recessed] [Walk Through] [Floor Reflect]
          sscanf(line,"%*s %d %s %d %d %d %d %d",&txtnum,typ,&tp,&tl,&re,&de,&fr);
          // Track how many we've loaded.
          if (txtnum > Num_Textures)
            Num_Textures = txtnum;
          // We need to clear these, since this is normal texture.
          TextureFlags[txtnum].IsTransparent = tp;
          TextureFlags[txtnum].IsTranslucent = tl;
          TextureFlags[txtnum].IsRecessed = re;
          TextureFlags[txtnum].CanWalkThru = de;
          TextureFlags[txtnum].IsDoor = 0;
          TextureFlags[txtnum].FloorReflect = fr;
          TextureFlags[txtnum].AnimNbr = -1;
          // Load the bitmap.
          Load_Wall(typ,txtnum);
        }
      // Step 2f. Load a door definition.
      else if (!strcmpi(typ,"D"))
        {
          // Door format is: D [Number] [Door Filename] [Side Number] [Col Start] [Row Start] [Col End] [Row End] [Speed] [Delay] [transparent] [Translucent] [Secret]
          sscanf(line,"%*s %d %s %d %d %d %d %d %d %d %d %d %d %d",&txtnum,typ,&sidetxt,&type,&cs,&rs,&ce,&re,&spd,&de,&tp,&tl,&sec);
          // Still, keep trck of the number,
          if (txtnum > Num_Textures)
            Num_Textures = txtnum;
          // Since this is a door, we have lots more to set...
          TextureFlags[txtnum].IsTransparent = tp;
          TextureFlags[txtnum].CanWalkThru = 0;
          TextureFlags[txtnum].IsDoor = 1;
          TextureFlags[txtnum].DoorType = type;
          TextureFlags[txtnum].DoorSideTxt = sidetxt;
          TextureFlags[txtnum].bc = cs;
          TextureFlags[txtnum].br = rs;
          TextureFlags[txtnum].ec = ce;
          TextureFlags[txtnum].er = re;
          TextureFlags[txtnum].delay = de;
          TextureFlags[txtnum].speed = spd;
          TextureFlags[txtnum].IsTranslucent = tl;
          TextureFlags[txtnum].IsSecretDoor = sec;
          TextureFlags[txtnum].IsRecessed = (sec == 1?0:1);
          TextureFlags[sp_num].AnimNbr = -1;
          Load_Wall(typ,txtnum);
        }
      // Load in the actual wall map data.
      else if (!strcmpi(typ,"M"))
        {
          sscanf(line,"%*s %d %d",&WORLD_ROWS,&WORLD_COLUMNS);
          // Make sure the map size is OK. We set a maximum value above...
          if ((WORLD_ROWS < 10) || (WORLD_ROWS > MAX_WORLD_ROWS) || (WORLD_COLUMNS < 0) || (WORLD_COLUMNS > MAX_WORLD_COLS))
            {
              txtscreen();
              fprintf(stderr,"Invalid map size.\n");
              free_tables();
              getch();
              exit(1);
            }
          // Convert to fine coordinates.
          WORLD_X_SIZE = 128l*WORLD_COLUMNS;
          WORLD_Y_SIZE = 128l*WORLD_ROWS;
          // Now read in the cell data.
          for (row=0; row<WORLD_ROWS; row++) // For all rows...
            {
              for (column=0; column<WORLD_COLUMNS; column++) // For all columns...
                {
                  while((ch = getc(fp))==10) // filter out CR
                    {}
                  if (ch == ' ')  // If blank, default to zero (No wall)
                    ch=0;
                  else if (ch == '0') // zero is ten
                    ch = 10;
                  else if ((ch > '0') && (ch <= '9')) // 1 - 9 is 1 - 9
                    ch = ch - '0';
                  else if ((ch >= 'A') && (ch <= 'Z')) // A - Z is 11 - 35
                    ch = ch - 'A' + 11;
                  *(world+(row<<6)+column) = ch; // Place the converted value in the map.
                }
            }
        }
      else if (!strcmpi(typ,"F")) // Load in the Floor map.
        {
          for (row=0; row<WORLD_ROWS; row++) // The floor must have the same dimensions as the walls.
            {
              for (column=0; column<WORLD_COLUMNS; column++)
                {
                  while((ch = getc(fp))==10) // filter out CR
                    {}
                  if (ch == ' ') // Spaces map out to 'no texture' (default Bkg color)
                    ch=0;
                  else if (ch == '0')   // 0 is 10
                    ch = 10;
                  else if ((ch > '0') && (ch <= '9'))  // 1-9 is 1-9
                    ch = ch - '0';
                  else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 11-35
                    ch = ch - 'A' + 11;
                  *(FloorMap+(row<<6)+column) = ch;  // Add it to the floor map.
                }
            }
        }
      else if (!strcmpi(typ,"C")) // Load in the ceiling map. It must be the same size as the wall map.
        {
          for (row=0; row<WORLD_ROWS; row++) // For every row
            {
              for (column=0; column<WORLD_COLUMNS; column++) // For every column
                {
                  while((ch = getc(fp))==10) // read in a char, and filter out CR
                    {}
                  if (ch == ' ') // Space maps to 'no texture', default Bkg color.
                    ch=0;
                  else if (ch == '0') // 0 is 10
                    ch = 10;
                  else if ((ch > '0') && (ch <= '9')) // 1-9 is 1-9
                    ch = ch - '0';
                  else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 11-35
                    ch = ch - 'A' + 11;
                  *(CeilMap+(row<<6)+column) = ch; // Add it to the ceiling map.
                }
            }
        }
      else if (!strcmpi(typ,"Z")) // Load in the zone map. It must be the same size as the wall map.
        {
          for (row=0; row<WORLD_ROWS; row++) // For every row
            {
              for (column=0; column<WORLD_COLUMNS; column++) // For every column
                {
                  while((ch = getc(fp))==10) // read in a char, and filter out CR
                    {}
                  if (ch == ' ') // Space maps to zone 0.
                    ch=0;
                  else if (ch == '0') // 0 is 10
                    ch = 10;
                  else if ((ch > '0') && (ch <= '9')) // 1-9 is 1-9
                    ch = ch - '0';
                  else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 11-35
                    ch = ch - 'A' + 11;
                  *(ZoneMap+(row<<6)+column) = ch; // Add it to the ceiling map.
                }
            }
        }
      else if (!strcmpi(typ,"L")) // Load in the floor light map. It must be the same size as the wall map.
        {
          for (row=0; row<WORLD_ROWS; row++) // For every row
            {
              for (column=0; column<WORLD_COLUMNS; column++) // For every column
                {
                  while((ch = getc(fp))==10) // read in a char, and filter out CR
                    {}
                  if (ch == ' ') // Space maps to zone 0.
                    ch=0;
                  else if ((ch >= '0') && (ch <= '9')) // 0-9 is 0-9
                    ch = ch - '0';
                  else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 10-34
                    ch = ch - 'A' + 10;
                  *(FloorLightMap+(row<<6)+column) = ch; // Add it to the ceiling map.
                }
            }
        }
      else if (!strcmpi(typ,"I")) // Load in the ceiling light map. It must be the same size as the wall map.
        {
          for (row=0; row<WORLD_ROWS; row++) // For every row
            {
              for (column=0; column<WORLD_COLUMNS; column++) // For every column
                {
                  while((ch = getc(fp))==10) // read in a char, and filter out CR
                    {}
                  if (ch == ' ') // Space maps to zone 0.
                    ch=0;
                  else if ((ch >= '0') && (ch <= '9')) // 0-9 is 0-9
                    ch = ch - '0';
                  else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 10-34
                    ch = ch - 'A' + 10;
                  *(CeilLightMap+(row<<6)+column) = ch; // Add it to the ceiling map.
                }
            }
        }
      else if (!strcmpi(typ,"G")) // Load a lighting tile.
        {
          // G [Tile Number] [Filename] [Pulsed] [Pulse type] [Args] (1 or 2 args)
          //  Pulse type: regular -> args is [pulse width] and [Period]
          //              custom  -> arg is : [pattern] (all 1s and 0s)
          sscanf(line,"%*s %d %s %d",&txtnum,typ,&tp);
          // Track how many we've loaded.
          if (txtnum > Num_Light_Tiles)
            Num_Light_Tiles = txtnum;
          // Load the bitmap.
          Load_Light_Tile(typ,txtnum);
          LightFlags[txtnum].OriginalBitmap = LightMapTiles[txtnum];
          LightFlags[txtnum].AnimNbr = -1;
          // Is the light a pulsed light?
          if (tp == 1)
            {
              // Get the pulse type.
              sscanf(line,"%*s %*d %*s %*d %d",&tp); // Pulse type will be either 1 or 2
              if ( tp == 1) // Regular pulse. Get pulse width and period...
                {
                  sscanf(line,"%*s %*d %*s %*d %*d %d %d",&tp,&re);
                  LightFlags[txtnum].LightType = 1;
                  LightFlags[txtnum].PulseWidth = tp;
                  LightFlags[txtnum].Period = re;
                }
              else if ( tp == 2 ) // Custom pulse type. Get the mask.
                {
                  sscanf(line,"%*s %*d %*s %*d %*d %s",typ);
                  LightFlags[txtnum].LightType = 2;
                  if (LightFlags[txtnum].PulsePattern != NULL)
                    farfree(LightFlags[txtnum].PulsePattern);
                  LightFlags[txtnum].PulsePattern = (char far *)farmalloc(strlen(typ)+1);
                  strcpy(LightFlags[txtnum].PulsePattern,typ);
                }
            }
        }
      else if (!strcmpi(typ,"N")) // Load in the zone options.
        {
          // N [NUMBER] [Ambient] [Inside] [Fog]
          sscanf(line,"%*s %d %d %d %d",&txtnum, &de, &tp, &re);
          ZoneAttr[txtnum].ambient = de;
          ZoneAttr[txtnum].inside = tp;
          ZoneAttr[txtnum].fog = re;
        }
      else if (!strcmpi(typ,"A")) // Load in the animated textures
        {
          // A [Number] [Base File name] [Number of Textures] [Delay] [Triggered] [Transparent] [Translucent] [Recessed] [Walk Through]
          sscanf(line,"%*s %d %7s %d %d %d %d %d %d %d",&sp_num, sp_basename,&tp,&de,&re,&cs,&ce,&rs,&tl);
          // Now, clear out any previously loaded animation for this texture...
          if ((TextureFlags[sp_num].AnimNbr > -1) && (Animations[TextureFlags[sp_num].AnimNbr].Frames != NULL ))
            {
              for (i = 0; i < Animations[TextureFlags[sp_num].AnimNbr].Nbr_Frames; ++i)
                if (Animations[TextureFlags[sp_num].AnimNbr].Frames[i] != NULL)
                  {
                    farfree(Animations[TextureFlags[sp_num].AnimNbr].Frames[i]);
                    Animations[TextureFlags[sp_num].AnimNbr].Frames[i] = NULL;
                  }
              farfree(Animations[TextureFlags[sp_num].AnimNbr].Frames);
              Animations[TextureFlags[sp_num].AnimNbr].Frames = NULL;
            }
          Animations[Num_Animations].Flagged = re;
          Animations[Num_Animations].Txt_Nbr = sp_num-1;
          Animations[Num_Animations].Delay = de;
          Animations[Num_Animations].Timer = de;
          Animations[Num_Animations].Curr_Frame = 0;
          Animations[Num_Animations].Nbr_Frames = tp;
          TextureFlags[sp_num].IsTransparent = cs;
          TextureFlags[sp_num].CanWalkThru = tl;
          TextureFlags[sp_num].IsDoor = 0;
          TextureFlags[sp_num].delay = de;
          TextureFlags[sp_num].IsTranslucent = ce;
          TextureFlags[sp_num].IsSecretDoor = 0;
          TextureFlags[sp_num].IsRecessed = rs;
          TextureFlags[sp_num].AnimNbr = Num_Animations;
          // Allocate space for the pointers. **Frames
          Animations[Num_Animations].Frames = (char far **)farmalloc((sizeof(char *))*tp);
          // Load the bitmaps.
          for (i=1; i<= tp; ++i)
            {
              sprintf(typ,"%s%d.pcx",sp_basename,i);
              Animations[Num_Animations].Frames[i-1] = Load_Sprite(typ);
            }
          WallTextures[sp_num-1] = Animations[Num_Animations].Frames[0];
          ++Num_Animations;
        }
      else if (!strcmpi(typ,"H")) // Load in the animated light tile.
        {
          // [Txt Number] [Base File name] [Number of Textures] [Delay] [Triggered]
          sscanf(line,"%*s %d %7s %d %d %d",&sp_num, sp_basename,&tp,&de,&re);
          // Now, clear out any previously loaded animation for this texture...
          if ((LightFlags[sp_num].AnimNbr > -1) && (Light_Animations[LightFlags[sp_num].AnimNbr].Frames != NULL ))
            {
              for (i = 0; i < Light_Animations[LightFlags[sp_num].AnimNbr].Nbr_Frames; ++i)
                if (Light_Animations[LightFlags[sp_num].AnimNbr].Frames[i] != NULL)
                  {
                    farfree(Light_Animations[LightFlags[sp_num].AnimNbr].Frames[i]);
                    Light_Animations[LightFlags[sp_num].AnimNbr].Frames[i] = NULL;
                  }
              farfree(Light_Animations[LightFlags[sp_num].AnimNbr].Frames);
              Light_Animations[LightFlags[sp_num].AnimNbr].Frames = NULL;
            }
          Light_Animations[Num_LightAnimations].Flagged = re;
          Light_Animations[Num_LightAnimations].Txt_Nbr = sp_num;
          Light_Animations[Num_LightAnimations].Delay = de;
          Light_Animations[Num_LightAnimations].Timer = de;
          Light_Animations[Num_LightAnimations].Curr_Frame = 0;
          Light_Animations[Num_LightAnimations].Nbr_Frames = tp;
          // Allocate space for the pointers. **Frames
          Light_Animations[Num_LightAnimations].Frames = (char far **)farmalloc((sizeof(char *))*tp);
          LightFlags[txtnum].AnimNbr = Num_LightAnimations;
          // Load the bitmaps.
          for (i=1; i<= tp; ++i)
            {
              sprintf(typ,"%s%d.pcx",sp_basename,i);
              Light_Animations[Num_LightAnimations].Frames[i-1] = Load_Sprite(typ);
            }
          LightMapTiles[sp_num] = Light_Animations[Num_LightAnimations].Frames[0];
          ++Num_LightAnimations;
        }
      else if (!strcmpi(typ,"R")) // Load in a trigger.
        {
          // [Trgr Nbr] [Trgr Area Type] [Area Params]... [Space Flag] [Effect Type] [Effect Params]...
          sscanf(line,"%*s %d %d ",&sp_num,&tp);
          Num_Triggers++;
          Triggers[Num_Triggers].Trigger_Nbr = sp_num;
          Triggers[Num_Triggers].Trigger_Area_Type = tp;
          // Now depending on the trigger area type, we have 3 types of area parameters.
          switch (tp)
            {
            case 1: // Rectangle
              sscanf(line,"%*s %*d %*d %d %d %d %d %d %99c",&cs,&rs,&ce,&re,&tl,trg_parms);
              Triggers[Num_Triggers].X1 = cs;
              Triggers[Num_Triggers].Y1 = rs;
              Triggers[Num_Triggers].X2 = ce;
              Triggers[Num_Triggers].Y2 = re;
              break;
            case 2: // Circle
              sscanf(line,"%*s %*d %*d %d %d %d %d %99c",&cs,&rs,&ce,&tl,trg_parms);
              Triggers[Num_Triggers].X1 = cs;
              Triggers[Num_Triggers].Y1 = rs;
              Triggers[Num_Triggers].Radius = ce;
              printf("this: %s \n",trg_parms);
              break;
            case 3: // Time --- Not implemented yet...
              break;
            }
          // Now we need to mark the Trigger Map with the location of this trigger.
          if (Triggers[Num_Triggers].Trigger_Area_Type == 1)
            for (column = Triggers[Num_Triggers].X1>>7; column <= Triggers[Num_Triggers].X2>>7; ++ column)
              for (row = Triggers[Num_Triggers].Y1>>7; row <= Triggers[Num_Triggers].Y2>>7; ++row)
                if (TriggerMap[column][row] != 0)
                  TriggerMap[column][row] = 100;
                else
                  TriggerMap[column][row] = Triggers[Num_Triggers].Trigger_Nbr;
          else if (Triggers[Num_Triggers].Trigger_Area_Type == 2)
            for (column = (Triggers[Num_Triggers].X1 - Triggers[Num_Triggers].Radius )>>7; column <= (Triggers[Num_Triggers].X1 + Triggers[Num_Triggers].Radius )>>7; ++ column)
              for (row = (Triggers[Num_Triggers].Y1-Triggers[Num_Triggers].Radius)>>7; row <= (Triggers[Num_Triggers].Y1+Triggers[Num_Triggers].Radius)>>7; ++row)
                if (TriggerMap[column][row] != 0)
                  TriggerMap[column][row] = 100;
                else
                  TriggerMap[column][row] = Triggers[Num_Triggers].Trigger_Nbr;

          for(column = 0; column < 64; ++column)
            {
              for(row = 0; row < 64; ++row)
                printf("%d",(int)TriggerMap[row][column]);
              printf("\n");
            }

          // Now, depending on the trigger type, read in the rest of the parameters...
          Triggers[Num_Triggers].Trigger_Type = tl;
          switch(tl)
            {
            case 1:
            case 2:
            case 3: // X/Y Coords. ( open / close / lock door )
              sscanf(trg_parms,"%d %d",&cs,&rs);
              Triggers[Num_Triggers].MapX = cs;
              Triggers[Num_Triggers].Mapy = rs;
              break;
            case 4:
            case 5:
            case 16:
            case 17: // Texture Number ( Start / stop animated texture / Light )
              sscanf(trg_parms,"%d",&cs);
              Triggers[Num_Triggers].TxtNbr = cs;
              break;
            case 6: // Change ambient light level.
              sscanf(trg_parms,"%d",&cs);
              Triggers[Num_Triggers].NewLightLvl = cs;
              break;
            case 7: // Change Zone light level.
              sscanf(trg_parms,"%d %d",&cs, &rs);
              Triggers[Num_Triggers].ZoneNbr = cs;
              Triggers[Num_Triggers].NewLightLvl = rs;
              break;
            case 8: // Load new map.
              sscanf(trg_parms,"%s",sp_basename);
              strncpy(Triggers[Num_Triggers].NewFileName,sp_basename,13);
              *(Triggers[Num_Triggers].NewFileName + 13) = 0x00;
              break;
            case 9: // Teleport player.
              sscanf(trg_parms,"%d %d %d",&cs, &rs, &ce);
              Triggers[Num_Triggers].MapX = cs;
              Triggers[Num_Triggers].Mapy = rs;
              Triggers[Num_Triggers].NewAngle = ce;
              break;
            case 10: // Load new texture. --- Not implemented yet.
              break;
            case 11:
            case 12:
            case 13:
            case 14:
            case 15: // Change Map.
              sscanf(trg_parms,"%d %d %d",&cs, &rs, &ce);
              Triggers[Num_Triggers].MapX = cs;
              Triggers[Num_Triggers].Mapy = rs;
              Triggers[Num_Triggers].TxtNbr = ce;
              break;
            }
        }
      else if (!strcmpi(typ,"B")) // Load in the ambient light level.
        {
          sscanf(line,"%*s %d",&sp_num);
          ambient_level = sp_num;
        }
      else if (!strcmpi(typ,"#END")) // This quits... along with EOF.
        break;
      fgets(line,256,fp); // Get the next line here.. (We test for EOF at the beginning..)
    }
  fclose(fp); // Close the file,
  return 1;     // and return.
}

// The following functions render the world...

void draw_sliver_transparent(void)
{
  //
  // This function draws a sliver of wall texture. It reads from a texture (sliver_map) row
  // (sliver_row) and draws from the top down in the column (sliver_column). It is scaled
  // to (sliver_scale) and is centered on the screen vertically by the calling routing,
  // starting at (sliver_start). All bytes that are 0 (zero) are transparent.
  // Scaling is done using a pre-calculated array of fixed point fractions. These are added
  // to Tcount each time through the loop, and the whole part is used as the index into the
  // texture. (i.e. if Tcount = 0.5, then the texture is now 2x as large.)
  //
  int           level;
  extern int    sliver_column; // Screen column to draw in.
  extern int    sliver_start;  // Screen row to start drawing in.
  extern int    sliver_scale;  // how many pixels (rows) long the sliver is to be.
  extern int    sliver_row;    // Which row from the texture to read.
  extern int    sliver_map;    // Which texture to use.
  extern int    sliver_light;
  extern long   sliver_dist;
  extern int    sliver_sptlt;
  extern int    sliver_zoneamb;
  extern int    sliver_usefog;
  register int  y;             // Current screen row we're rendering
  register long Scale,         // Amount (fixed Pt) to increment Tcount each row.
                Tcount,        // The offset into the texture / lightmap row (Fixed point)
                sliver_end;    // The last screen row to render
  char far      *base,         // Beginning of the current texture row in memory.
                *lightbase,    // Beginning of the current light tile row in memory.
                *bufptr;       // Pointer to the beginning of screen memory for this sliver.
  register char pixl,          // The pixel value we're going to plot.
                lgt;           // The lighting value for the current pixel.

  // Reset Tcount
  Tcount = 0;
  // Preload the scaleing factor, and the pointer to the texture. (Faster this way).
  Scale = sliver_factor[sliver_scale];
  base = WallTextures[sliver_map]+(sliver_row<<7);
  if (sliver_light > -1) {
    if (sliver_light <= Num_Light_Tiles)
      lightbase = LightMapTiles[sliver_light]+(sliver_row<<7);
    else
      fprintf(stdout,"Error... Wrong light map number: %d \n",(int) sliver_light);
  }

  // Calculate the end screen row, and clip it if necessary.
  sliver_end = sliver_start+sliver_scale;
  if (sliver_end > 149)
    sliver_end = 149;
  // Clip the top of the sliver, and adjust Tcount appropriately.
  if (sliver_start < 0)
    {
      Tcount = (((long)(-sliver_start)) * Scale);
      sliver_start = 0;
    }
  // Set the double buffer pointer  to the starting row and column.
  bufptr = dblbuf+*(row+sliver_start)+sliver_column;
  // Are we using constant shading, or are we using the tile ?
  if (sliver_light > -1)
    {
      // Here, were using a lighting tile...
      // Now, for the entire sliver run,
      for (y = sliver_start; y <= sliver_end ; ++y)
        {
          // Get the pixel value from the texture, using the whole part of Tcount as the index.
          pixl = *(base+(long)(Tcount>>16));
          // If the value isn't zero, then draw it.
          if ((pixl != 0))
            {
              lgt  = *(lightbase+(Tcount>>16));
              level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + lgt + sliver_zoneamb;
              if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1;
              level <<= 8;
              if (sliver_usefog == 1)
                *bufptr = *(FogMap + level + pixl);
              else
                *bufptr = *(LightMap + level + pixl);
            }
          // And increment Tcount and the double buffer pointer.
          Tcount += Scale;
          bufptr += 320;
        }
    }
  else // sliver_light = -1, constant shading...
    {
      // Here we're using constant lighting for the whole run...
      level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + sliver_sptlt + sliver_zoneamb;
      if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1;
      level <<= 8;
      // Now, for the entire sliver run,
      for (y = sliver_start; y <= sliver_end ; ++y)
        {
          // Get the pixel value from the texture, using the whole part of Tcount as the index.
          pixl = *(base+(long)(Tcount>>16));
          // If the value isn't zero, then draw it.
          if ((pixl != 0))
            if (sliver_usefog == 1)
              *bufptr = *(FogMap + level + pixl);
            else
              *bufptr = *(LightMap + level + pixl);
          // And increment Tcount and the double buffer pointer.
          Tcount += Scale;
          bufptr += 320;
        }
    }
}

void draw_sliver_trans(void)
{
  //
  // This function draws a sliver of wall texture. It reads from a texture (sliver_map) row
  // (sliver_row) and draws from the top down in the column (sliver_column). It is scaled
  // to (sliver_scale) and is centered on the screen vertically by the calling routing,
  // starting at (sliver_start). All bytes that are 0 (zero) are transparent.
  // Scaling is done using a pre-calculated array of fixed point fractions. These are added
  // to Tcount each time through the loop, and the whole part is used as the index into the
  // texture. (i.e. if Tcount = 0.5, then the texture is now 2x as large.)
  //
  int           level;
  unsigned int  lvl;
  extern int    sliver_column;  // Screen column to draw in.
  extern int    sliver_start;   // Screen row to start drawing in.
  extern int    sliver_scale;   // how many pixels (rows) long the sliver is to be.
  extern int    sliver_row;     // Which row from the texture to read.
  extern int    sliver_map;     // Which texture to use.
  extern int    sliver_light;
  extern long   sliver_dist;
  extern int    sliver_sptlt;
  extern int    sliver_zoneamb;
  extern int    sliver_usefog;
  register int  y;              // Current screen row we're rendering
  register long Scale,          // Amount (fixed Pt) to increment Tcount each row.
                Tcount,         // The offset into the texture row (Fixed point)
                sliver_end;     // The last screen row to render
  char far      *base,          // Beginning of the current texture row in memory.
                *lightbase,
                *bufptr;        // Pointer to the beginning of screen memory for this sliver.
  register char pixl,           // The pixel value we're going to plot.
                lgt;

  // Reset Tcount
  Tcount = 0;
  // Preload the scaleing factor, and the pointer to the texture. (Faster this way).
  Scale = sliver_factor[sliver_scale];
  base = WallTextures[sliver_map]+(sliver_row<<7);
  if (sliver_light > -1) {
    if (sliver_light <= Num_Light_Tiles)
      lightbase = LightMapTiles[sliver_light]+(sliver_row<<7);
    else
      fprintf(stdout,"Error... Wrong light map number: %d \n",(int) sliver_light);
  }
  // Calculate the end screen row, and clip it if necessary.
  sliver_end = sliver_start+sliver_scale;
  if (sliver_end > 149)
    sliver_end = 149;
  // Clip the top of the sliver, and adjust Tcount appropriately.
  if (sliver_start < 0)
    {
      Tcount = (((long)(-sliver_start)) * Scale);
      sliver_start = 0;
    }
  // Set the double buffer pointer  to the starting row and column.
  bufptr = dblbuf+*(row+sliver_start)+sliver_column;

  if (sliver_light > -1)
    {
      // Now, for the entire sliver run,
      for (y = sliver_start; y <= sliver_end ; ++y)
        {
          // Get the pixel value from the texture, using the whole part of Tcount as the index.
          pixl = *(base+(long)(Tcount>>16));
          // If the value isn't zero, then draw it.
          if ((pixl != 0))
            {
              lgt  = *(lightbase+(Tcount>>16));
              level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + lgt + sliver_zoneamb;
              if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1;
              level <<= 8;
              if (sliver_usefog == 1)
                *bufptr =  *(Translucency + ((((unsigned int)*(FogMap + level + pixl)) & 0x00ff) << 8) + (((unsigned int)*bufptr) & 0x00ff));
              else
                *bufptr =  *(Translucency + ((((unsigned int)*(LightMap + level + pixl)) & 0x00ff) << 8) + (((unsigned int)*bufptr) & 0x00ff));
            }
          // And increment Tcount and the double buffer pointer.
          Tcount += Scale;
          bufptr += 320;
        }
    }
  else
    {
      level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + sliver_sptlt + sliver_zoneamb;
      if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1;
      level <<= 8;
      // Now, for the entire sliver run,
      for (y = sliver_start; y <= sliver_end ; ++y)
        {
          // Get the pixel value from the texture, using the whole part of Tcount as the index.
          pixl = *(base+(long)(Tcount>>16));
          // If the value isn't zero, then draw it.
          if ((pixl != 0))
            {
              if (sliver_usefog == 1)
                *bufptr =  *(Translucency + ((((unsigned int)*(FogMap + level + pixl)) & 0x00ff) << 8) + (((unsigned int)*bufptr) & 0x00ff));
              else
                *bufptr =  *(Translucency + ((((unsigned int)*(LightMap + level + pixl)) & 0x00ff) << 8) + (((unsigned int)*bufptr) & 0x00ff));
            }
          // And increment Tcount and the double buffer pointer.
          Tcount += Scale;
          bufptr += 320;
        }
    }
}

void draw_sliver(void)
{
  //
  // This function draws a sliver of wall texture. It reads from a texture (sliver_map) row
  // (sliver_row) and draws from the top down in the column (sliver_column). It is scaled
  // to (sliver_scale) and is centered on the screen vertically by the calling routing,
  // starting at (sliver_start).
  // Scaling is done using a pre-calculated array of fixed point fractions. These are added
  // to Tcount each time through the loop, and the whole part is used as the index into the
  // texture. (i.e. if Tcount = 0.5, then the texture is now 2x as large.)
  //
  int           level;
  extern int    sliver_column; // Screen column to draw in.
  extern int    sliver_start;  // Screen row to start drawing in.
  extern int    sliver_scale;  // how many pixels (rows) long the sliver is to be.
  extern int    sliver_row;    // Which row from the texture to read.
  extern int    sliver_map;    // Which texture to use.
  extern int    sliver_light;
  extern long   sliver_dist;
  extern int    sliver_sptlt;
  extern int    sliver_zoneamb;
  extern int    sliver_usefog;
  register int  y;             // Current screen row we're rendering
  register long Scale,         // Amount (fixed Pt) to increment Tcount each row.
                Tcount,        // The offset into the texture row (Fixed point)
                sliver_end;    // The last screen row to render
  char far      *base,         // Beginning of the current texture row in memory.
                *lightbase,
                *bufptr;       // Pointer to the beginning of screen memory for this sliver.
  register char lgt;

  // Reset Tcount for this run.
  Tcount = 0;
  // Preload the scaleing factor, and the pointer to the texture. (Faster this way).
  Scale = sliver_factor[sliver_scale];
  base = WallTextures[sliver_map]+(sliver_row<<7);
  if (sliver_light > -1) {
    if (sliver_light <= Num_Light_Tiles)
      lightbase = LightMapTiles[sliver_light]+(sliver_row<<7);
    else
      fprintf(stdout,"Error... Wrong light map number: %d \n",(int) sliver_light);
  }
  // Calculate the ending screen row, and clip it if necessary.
  sliver_end = sliver_start+sliver_scale;
  if (sliver_end > 149)
    sliver_end = 149;
  // Clip the beginning of the slice, if necessary. Adjust Tcount to it's new offset.
  if (sliver_start < 0)
    {
      Tcount = (((long)(-sliver_start)) * Scale);
      sliver_start = 0;
    }
  // Calculate the beginning point in the double buffer for the slice.
  bufptr = dblbuf+*(row+sliver_start)+sliver_column;
  if (sliver_light > -1)
    {
      // Now, for each screen row,
      for (y = sliver_start; y <= sliver_end; y ++)
        {
          lgt  = *(lightbase+(Tcount>>16));
          level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + lgt + sliver_zoneamb;
          if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1;
          level <<= 8;
          // Get the byte from the texture and render it.
          if (sliver_usefog == 1)
            *bufptr = *(FogMap + level + *(base + (Tcount>>16)));
          else
            *bufptr = *(LightMap + level + *(base + (Tcount>>16)));
          // Update Tcount and bufptr.
          Tcount += Scale;
          bufptr += 320;
        }
    }
  else
    {
      level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + sliver_sptlt + sliver_zoneamb;
      if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1;
      level <<= 8;
      // Now, for each screen row,
      for (y = sliver_start; y <= sliver_end; y ++)
        {
          // Get the byte from the texture and render it.
          if (sliver_usefog == 1)
            *bufptr = *(FogMap + level + *(base + (Tcount>>16)));
          else
            *bufptr = *(LightMap + level + *(base + (Tcount>>16)));
          // Update Tcount and bufptr.
          Tcount += Scale;
          bufptr += 320;
        }
    }
}

int Cast_X_Ray(long x, long y, long view_angle, long &yi_save, long &x_save, int &lighttile, int &zone)
{
  //
  //    This is the heart of the program. It casts a mathematical ray from the
  // players position along the view_angle. The ray stops when it hits a wall,
  // saving the x and y coordinates in yi_save and x_save, and returns the type
  // of wall hit.
  //    This routine finds the HORIZONTAL cells along the ray. (SEE TGPG).
  //
  register long x_bound,   // X intercept at the cell boundaries, in fine coords.
           x_delta,        // Amount to add to x_bound for each iteration.
           next_x_cell,    // Correction for "1-off" error for getting map cell.
           hit_type,       // Cell value at current intersection.
           ystep;          // Amount to add to yi for each iteration.
  long     yi;             // The Y intercept (at x_bound) in fine coords.

  // If the ray is going to the RIGHT,
  if ((view_angle < ANGLE_90) || (view_angle > ANGLE_270))
    {
      // Determine where the current cell edge is (in fine coords.)
      x_bound = (x & 0xff80)+CELL_X_SIZE;
      // This is the amount to add to x_bound for each step.
      x_delta = CELL_X_SIZE;
      // Amount to compensate for the "1-off" error.
      next_x_cell = 0;
    }
  // Else, if it's going to the LEFT.
  else
    {
      // Determine where the current cell edge is (in fine coords.)
      x_bound = (x & 0xff80);
      // This is the amount to add to x_bound for each step.
      x_delta = -CELL_X_SIZE;
      // Amount to compensate for the "1-off" error.
      next_x_cell = -1;
    }
  // The initial Y intercept, at x_bound.
  yi = ((long)x_bound-(long)x)*(*(tan_table+view_angle))+((long)y<<16);
  // The amount to add to yi for each step. (the same each time, so we bring it out here).
  ystep = *(y_step+view_angle);
  // Now we cast the ray...
  while(1)
    {
      // Calculate the map coordinates for the current intersection.
      cell_xx = ((x_bound+next_x_cell)>>7);
      cell_yx = ((long)(yi>>16)) >> 7;
      // Check boundaries.
      if (cell_xx > 63) cell_xx = 63;
      if (cell_yx > 63) cell_yx = 63;
      if (cell_xx < 0) cell_xx = 0;
      if (cell_yx < 0) cell_yx = 0;
      // Check to see if we hit a wall or not. (A wall is >= 1).
      if ((hit_type = *(world+(cell_yx<<6)+cell_xx))!=0)
        {
          // Save the fine coordinates sso we can return them...
          yi_save = (long)((yi+32768L)>>16);
          x_save = x_bound;
          lighttile = *(FloorLightMap+(cell_yx<<6)+cell_xx);
          zone = *(ZoneMap+(cell_yx<<6)+cell_xx);
          if(TextureFlags[hit_type].IsRecessed)
            {
              x_save += ( x_delta / 2 ) ;
              yi_save = (long) (((yi + ystep / 2 ) + 32768L ) >> 16 ) ;
            }
          // and return, with the texture number for the map cell.
          return hit_type;
        }
      // Otherwise, if we havn't hit a wall, increment to the next boundary intersection,
      // and go again.
      yi += ystep;
      x_bound += x_delta;
    }
}

int Cast_Y_Ray(long x, long y, long view_angle, long &xi_save, long &y_save, int &lighttile, int &zone)
{
  //
  //    This is the heart of the program. It casts a mathematical ray from the
  // players position along the view_angle. The ray stops when it hits a wall,
  // saving the x and y coordinates in xi_save and y_save, and returns the type
  // of wall hit.
  //    This routine finds the VERTICAL cells along the ray. (see TGPG)
  //

  register long y_bound,         // Y intercept at the cell boundaries, in fine coords.
                y_delta,         // Amount to add to y_bound each iteration of the loop.
                next_y_cell,     // Fixes the "1 off" error while calculating cell locations.
                hit_type,        // Texture value in the map cell.
                xstep;           // Amount to add to xi each iteration of the loop.
  long          xi;              // X intercept ay y_bound, in fine coords.


  // If the ray is looking UP,
  if ((view_angle > ANGLE_0) && (view_angle < ANGLE_180))
    {
      // Calculate the top cell boundary (Y intercept)
      y_bound = CELL_Y_SIZE + y & 0xff80;
      // Set y_delta. This is added to y_bound each time through the loop.
      y_delta = CELL_Y_SIZE;
      // and this fixes the "1 off" error in getting the map cell location (array x&y).
      next_y_cell = 0;
    }
  // Else, if we are looking DOWN...
  else
    {
      // Calculate the lower cell boundary.
      y_bound = y & 0xff80;
      // Set y_delta.
      y_delta = -CELL_Y_SIZE;
      // Again, this is to fix the "1 off" error.
      next_y_cell = -1;
    }
  // Now we calculate the x intercept on the Y boundary (See TGPG)
  xi =((long)y_bound-(long)y)*(*(inv_tan_table+view_angle))+((long)x<<16);
  // Get the value to increment xi from the x_step array.
  xstep = *(x_step+view_angle);
  // Now we enter into the main casting loop...
  while(1)
    {
      // Get the map array indices (X and Y)
      cell_yy = (y_bound+next_y_cell) >> 7;
      cell_xy = ((int)(xi>>16)) >> 7;
      // Check boundaries.
      if (cell_yy > 63) cell_yy = 63;
      if (cell_xy > 63) cell_xy = 63;
      if (cell_yy < 0) cell_yy = 0;
      if (cell_xy < 0) cell_xy = 0;
      // And determine if there is a texture in that cell.
      if ((hit_type = *(world+(cell_yy<<6)+cell_xy))!=0)
        {
          // If there is, let's save the location (fine coords),
          xi_save = (long)((xi+32768L)>>16);
          y_save = y_bound;
          lighttile = *(FloorLightMap+(cell_yy<<6)+cell_xy);
          zone = *(ZoneMap+(cell_yy<<6)+cell_xy);
          if(TextureFlags[hit_type].IsRecessed)
            {
              y_save += ( y_delta / 2 ) ;
              xi_save = (long) (((xi + xstep / 2 ) + 32768L ) >> 16 ) ;
            }
          // and indicate what type of wall we hit.
          return hit_type;
        }
      // If we didn't hit a wall, we increment the intercept variables, and check again.
      xi += xstep;
      y_bound += y_delta;
    }
}

void Texture_FloorCeil(long x, long y, long view_angle)
{
  //
  //    This function handles rendering the floor and ceiling. It is done in a somewhat
  // different manner than the walls. This routine will use similar triangles to find out
  // how far away a screen row (on which will be drawn the ceiling and floor) is from the
  // viewer's eye. The distance is then divided by the cosine of the view angle to find
  // out how far away a specific pixel in that row (corresponding to the angle) is from
  // the eye. The X and Y are then determined (using sine and cosine tables), and the
  // proper pixel is grabbed from the proper texture. The ceiling and floor mirror each
  // other most of the time, so we don't need to re-calculate. We do, a little bit though,
  // just to make sure we cover everything.
  //
  //    The next step would be to cast for the edges in each row, and linearly interpolate
  // across the row. I am currently experimenting with that, but I'm having some trouble.
  //
  register int   col,          // The screen column currently being processed.
                 ROW,          // The screen row currently being processed.
                 size;         // The row on the screen where the floor ends and the wall begins.
  int            flor_level,
                 ceil_level,
                 ZoneAmb;
  register long  LightDist,    // How far away the point is from the eye.
                 distance,     // How far away the point is from the eye (adjusted).
                 MapOff,       // Offset into the floor map of the current pixel. (see below)
                 TxtOff,       // Offset into the texture map of the current pixel. (see below)
                 inv_cost;     // Holds the inverse cosine. (22:10) It doesn't change by row, so we only get it once.
  long           xv,           // X coordinate of the screen pixel (ROW,COL) in texture space.
                 yv,           // Y coordinate of the screen pixel (ROW,COL) in texture space.
                 angle,        // the angle (absolute) the ray for the column is pointing.
                 sintab,       // The sine value for the current angle. (22:10 fixed)
                 costab;       // The cosine value for the current angle. (22:10 fixed)
  struct col_obj *line;        // This saves us a lot in array dereferencing! only have to do it once.
  unsigned char  *WallReflect; // This points to the wall/ceiling pixel we're going to reflect off the floor.
  int             Reflect = 0; // This flag tells us if the floor is to reflect or not.

  // For each column in the viewport (ANGLE_0 to ANGLE_60)
  for (col = 0; col < 240; ++col)
    {
      // Calculate the absolute angle of the ray. Since col=[0,240), col-120 = [-ANGLE_30, ANGLE_30)
      angle = view_angle+(col-120);
      // Check the bounds of the angle, and make it loop back. (mod with ANGLE_360).
      if (angle < ANGLE_0)
        angle += ANGLE_360;
      if (angle >= ANGLE_360)
        angle -= ANGLE_360;
      // Get the line structure for the current column, farthest wall.
      // We do this here to save time. Array dereferencing takes lots of time.
      line = &(scan_lines[col].line[scan_lines[col].num_objs-1]);
      // Now we point to the beginning of the wall where we start the reflection from.
      //       WallReflect = scrnbuf + col + *(row+line->scale);
      // Now get the trig values we need for this angle. This doesn't change with the
      // row, so we get it here to save time.
      inv_cost =*(Floor_inv_cos_table+col);
      sintab =*(Floor_sin_table+angle);
      costab =*(Floor_cos_table+angle);
      // Figure out where we can stop drawing the floor for this column. No sense in
      // drawing behind a solid wall.
      size = (line->top+line->scale);
      // Now, we cast from the bottom, up to the beginning of the farthest wall.
      for (ROW = 149; ROW >= size; --ROW)
        {
          // Get the distance of the floor pixel for this row, dead ahead, and then rotate it out
          // to the proper angle. (the inv_cost).
          LightDist = ((*(Floor_Row_Table+ROW))>>10);
          distance =((*(Floor_Row_Table+ROW)*inv_cost)>>10);
          // Figure out the x and y from trig, (hyp*sin=y, hyp*cos=x).
          yv = (long)((distance*sintab)>>20)+y;
          xv = (long)((distance*costab)>>20)+x;
          // Check boundaries.
          if (xv < 0) xv = 0;
          if (xv > 8191) xv = 8191;
          if (yv < 0) yv = 0;
          if (yv > 8191) yv = 8191;
          if (LightDist > 8191) LightDist = 8191;
          if (LightDist < 1) LightDist = 1;
          // We pre-calculate these to save time. Calculate once, use twice...
          MapOff = (((int)yv>>7)<<6)+((int)xv>>7);
          TxtOff = (((yv&127)<<7)+(xv&127));
          // If the texture at this map offset is reflective, then set reflection flag
          //if(TextureFlags[*(FloorMap + MapOff)].FloorReflect == 1)
          //Reflect = 1;
          //else
          // Reflect = 0;
          // Calculate the zone, and zone options.
          // light level for light sourcing
          ZoneAmb = ZoneAttr[*(ZoneMap+MapOff)].ambient;
          flor_level = (unsigned char)LightLevel[LightDist] + ambient_level + *(LightMapTiles[*(FloorLightMap+MapOff)]+TxtOff) + ZoneAmb;
          if (ZoneAttr[*(ZoneMap+MapOff)].inside == 1)
            ceil_level = (unsigned char)ambient_level + *(LightMapTiles[*(CeilLightMap+MapOff)]+TxtOff) + ZoneAmb;
          else
            ceil_level = (unsigned char)LightLevel[LightDist] + ambient_level + *(LightMapTiles[*(CeilLightMap+MapOff)]+TxtOff) + ZoneAmb;
          if(flor_level > MAX_LIGHT_LEVELS - 1) flor_level = MAX_LIGHT_LEVELS - 1;
          if(ceil_level > MAX_LIGHT_LEVELS - 1) ceil_level = MAX_LIGHT_LEVELS - 1;
          flor_level <<= 8;
          ceil_level <<= 8;
          if (ZoneAttr[*(ZoneMap+MapOff)].fog == 1)
            {
              *(dblbuf+*(row+ROW)+col) = *(FogMap + flor_level + *(WallTextures[*(FloorMap+MapOff)-1]+TxtOff));
              *(dblbuf+*(row+149-ROW)+col) = *(FogMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff));
            }
          else
            {
              *(dblbuf+*(row+ROW)+col) = *(LightMap + flor_level + *(WallTextures[*(FloorMap+MapOff)-1]+TxtOff));
              *(dblbuf+*(row+149-ROW)+col) = *(LightMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff));
            }
        }
      // Now we find out where the ceiling really ends. It isn't always even with the floor.
      size = line->top;
      // And we do the above rendering again. This time, just on the ceiling.
      for (ROW = 148-ROW; ROW <= size; ++ROW)
        {
          LightDist = ((*(Floor_Row_Table+ROW))>>10);
          distance =((*(Floor_Row_Table+ROW)*inv_cost)>>10);
          yv = (long)((distance*sintab)>>20)+y;
          xv = (long)((distance*costab)>>20)+x;
          // Check boundaries.
          if (xv < 0) xv = 0;
          if (xv > 8191) xv = 8191;
          if (yv < 0) yv = 0;
          if (yv > 8191) yv = 8191;
          MapOff = (((int)yv>>7)<<6)+((int)xv>>7);
          TxtOff = (((yv&127)<<7)+(xv&127));
          if (LightDist > 8191) LightDist = 8191;
          if (LightDist < 1) LightDist = 1;
          // light level for light sourcing
          if (ZoneAttr[*(ZoneMap+MapOff)].inside == 1)
            ceil_level = (unsigned char)ambient_level + *(LightMapTiles[*(CeilLightMap+MapOff)]+TxtOff) + ZoneAmb;
          else
            ceil_level = (unsigned char)LightLevel[LightDist] + ambient_level + *(LightMapTiles[*(CeilLightMap+MapOff)]+TxtOff) + ZoneAmb;
          if(ceil_level > MAX_LIGHT_LEVELS - 1) ceil_level = MAX_LIGHT_LEVELS - 1;
          ceil_level <<= 8;
          if (ZoneAttr[*(ZoneMap+MapOff)].fog == 1)
            *(dblbuf+*(row+ROW)+col) = *(FogMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff));
          else
            *(dblbuf+*(row+ROW)+col) = *(LightMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff));
        }
    }
}

void process_sprites(long x, long y, long view_angle)
{
  //
  //  This routine WILL handle the rendering calculations of the sprites. It is work
  // in progress.
  //
  int  Current_Sprite, // The current object we're rendering.
       Sprite_Num;     // The sprite used for the current object.
  long ang,            // The angle the sprite makes with the view_angle.
       ang_clipped,    // The relative angle, clipped to +- ANGLE_30.
       ang_saved,      // Saved copy of the angle. Used in scaling and fisheye fix.
       dist,           // The distance from the viewer.
       size,           // The size of the sprite in pixels (horiz and vert).
       left,           // The starting column of the sprite.
       right,          // The ending column of the sprite.
       sx,sy,          // The camera coordinates of the sprite.
       tx,ty,          // Temporary holding for the coords. of the sprite during the transform.
       VA_Neg,         // The negative of the player's view angle.
       Ref_ang,        // The angle we are viewing the sprite from.
       Scale_Factor;   // Fixed point value used in scaling the slices.
  long Frame,          // The frame of the sprite we are going to display.
       sliver,         // The column of the texture to draw for the scan line.
       col,            // The scan line (col) we are in.
       Col_Depth,      // The number of objects in the scan line buffer for this column.
       Indx;           // Loop index used to insert sprite sliver in scan line buffer.
  int  level,
       zoneamb,
       zonefog;
  register long MapOff,      // Offset into the floor map of the current pixel. (see below)
                TxtOff;      // Offset into the texture map of the current pixel. (see below)

  // We start adding sprites to the global WallTextures at 51.
  Sprite_Num = 50;
  // Loop through all the world's objects.
  for (Current_Sprite = 1; Current_Sprite <= Num_Objects; ++Current_Sprite)
    {
      // Step 1. Translate the position of the sprite by -[x,y] (Integer math)
      sx = Objects[Current_Sprite].x - x;
      sy = Objects[Current_Sprite].y - y;
      // Step 2. Rotate the sprite by -View_Angle. (Integer X Fixed = 22:10 fixed point)
      VA_Neg = ANGLE_360 - view_angle;
      tx = sx*Floor_cos_table[VA_Neg] - sy*Floor_sin_table[VA_Neg];
      ty = sx*Floor_sin_table[VA_Neg] + sy*Floor_cos_table[VA_Neg];
      sx = (long)(tx >> 10);
      sy =  (long)(ty >> 10);
      // Step 3. Now cull the sprite if behind viewer.
      if (sx < 1)
        continue;
      // Step 4. Get the angle of the sprite. (Fixed / Fixed = Integer)
      ang_clipped = ang_saved = ang = (sy * 226l)/sx;
      if (ang < ANGLE_0) ang += ANGLE_360;
      // Step 5 Cull the sprite if it's too far off the side of the screen
      if ((ang_saved < -ANGLE_45) || (ang_saved > ANGLE_45))
        continue;
      // Step 6 Get the distance, and size of the sprite (22:10 Fixed)
      dist = sx * inv_cos_table[ang] >> 13; //  Integer Result (Table = 19:13)
      // Step 6a. If it's too close, we can't draw it.
      if (dist < 5) continue;
      // Step 6b. Clip the angle to +/- ANGLE_30. The cos_table only goes that far.
      if (ang_clipped > ANGLE_30) ang_clipped = ANGLE_30;
      if (ang_clipped < -ANGLE_30) ang_clipped = -ANGLE_30;
      // Step 6c. Calculste the size of the bitmap on the screen.
      size = (int)((cos_table[ang_clipped+ ANGLE_30]/dist) >> 8) << 1 ; // Table is 25:7
      if (size > 600) size = 599;
      // Step 7. Now we calculate the starting and ending columns of the sprite, and get the scaling factor.
      left  = (120+ang_saved)-(size >> 1);
      right = (120+ang_saved)+(size >> 1)-1;
      Scale_Factor = sliver_factor[size];
      // Step 8 Determine the angle we are viewing the sprite from (Frame number).
      Frame = (((view_angle+ang_clipped) / ANGLE_45)+ 4) % 8 - (Objects[Current_Sprite].angle / ANGLE_45);
      if (Frame < 0) Frame += 8;
      if (Frame > 7) Frame -= 8;
      // Step 9. Now we sort the slices into the column buffer.
      Sprite_Num++; // Get the next available WallTextures slot.
      WallTextures[Sprite_Num] = Sprites[Objects[Current_Sprite].sprite_num].Frames[Frame];
      TextureFlags[Sprite_Num+1].IsTranslucent = Sprites[Objects[Current_Sprite].sprite_num].translucent;
      TextureFlags[Sprite_Num+1].IsTransparent = 1;
      TextureFlags[Sprite_Num+1].IsDoor = 0;
      // light level for light sourcing
      MapOff = (((int)sy>>7)<<6)+((int)sx>>7);
      TxtOff = (((sy&127)<<7)+(sx&127));
      level = LightLevel[dist] + ambient_level + *(LightMapTiles[*(FloorLightMap+MapOff)]+TxtOff);
      if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1;
      zoneamb = ZoneAttr[*(ZoneMap+MapOff)].ambient;
      zonefog = ZoneAttr[*(ZoneMap+MapOff)].fog;
      // Loop through all the slivers(columns) of the sprite's bitmap.
      for (sliver = 0, col=left; col <= right; ++col, sliver += Scale_Factor)  // Start at the left most slice of the sprite.
        {
          if ((col >= 0) && (col < 240))   //  If the slice is within the view window,
            // start at the beginning of the list. If the end is reached, break out. do not add the slice.
            for (Col_Depth = 0; Col_Depth <= scan_lines[col].num_objs; ++Col_Depth)
              // else if the depth of the current slice is greater than 'dist',
              {
                if (scan_lines[col].line[Col_Depth].dist > dist)
                  {
                    //  shift all info down, and insert slice.
                    for (Indx = scan_lines[col].num_objs; Indx > Col_Depth; --Indx)
                      {
                        scan_lines[col].line[Indx] = scan_lines[col].line[Indx-1];
                      }
                    scan_lines[col].line[Indx].top = 75 - (size >> 1);
                    scan_lines[col].line[Indx].col = col;
                    scan_lines[col].line[Indx].scale = size;
                    scan_lines[col].line[Indx].dist = dist;
                    scan_lines[col].line[Indx].row = (sliver >> 16);
                    scan_lines[col].line[Indx].texnum = Sprite_Num;
                    scan_lines[col].line[Indx].lightnum = -1;
                    scan_lines[col].line[Indx].sptlight = level;
                    scan_lines[col].line[Indx].zoneambient = zoneamb;
                    scan_lines[col].line[Indx].usefog = zonefog;
                    scan_lines[col].num_objs++;
                    break;
                  }
              }
        }
    }
}

void DrawAutomap(long Xpos, long Ypos, long view_angle)
{
  //    This function renders the automap screen to the double buffer. It is
  // positioned in the middle of the screen, and is 70x70 pixels in size.
  char far *StartAddr;
  long     x,y;

  // Frame in the automap.
  for (x=0; x<70; ++x)
    for (y=0; y<70; ++y)
      *(dblbuf+*(row+(20+y))+20+x) = *(AutoMapBkgd+y*70+x);

        // Draw the walls.
  for (x=0; x<64; ++x)
    for (y=0; y<64; ++y)
      if (*(world+((y<<6)+x)) != 0)
        if (TextureFlags[*(world+(y<<6)+(x))].IsDoor)
          {
            *(dblbuf+*(row+(23+y))+23+x) = 112;
          }
        else if (TextureFlags[*(world+(y<<6)+(x))].IsTransparent)
          {
            *(dblbuf+*(row+(23+y))+23+x) = 121;
          }
        else
          {
            *(dblbuf+*(row+(23+y))+23+x) = 2;
          }

  // Locate the player.
  *(dblbuf+*(row+(23+(Ypos>>7)))+23+(Xpos>>7)) = 198;
}

void Render_View(long x,long y,long view_angle)
{
  //
  //   Ok, here's where the magic comes together to render the full viewport...
  //   This function loops through all colums in the viewport. Each column (0-240) represents
  // an angle between -ANGLE_30 and ANGLE_30 (60 degree FOV). At each column, we cast 2 rays.
  // one tracks textures on horizontal boundaries, the other on vertical. Once both rays are cast,
  // we determine which is closer to the viewpoint, and store it in the column buffer. If the wall
  // was tagged as transparent, we 'erase' the texture from the map, keeping track of it, and go
  // again. (We keep casting until a SOLID wall is hit). When we hit a solid wall, the casting
  // stops, we replace the 'erase' textures, and go on to the next column. Once all the columns
  // have been cast, we process the sprites, and sort the slivers of thier bitmaps into the column
  // buffer.
  //   When all the setup is done, we bwgin drawing the viewport. We call a function to cast and
  // draw the floors and ceilings, and then we loop through the column buffer, sending each level's
  // texture and scaling info to the draw_sliver and draw_sliver_trans in reverse order (back to front).
  //
  extern int sliver_start;    // These are the Sliver renderer globals. They are global for
  extern int sliver_column;   // SPEED considerations. They have been described quite enough so far.
  extern int sliver_scale;
  extern int sliver_row;
  extern int sliver_map;
  extern int sliver_light;

  long ray,              // the current ray being cast 0-240 (Corresponds to both the screen col, and the angle.)
       x_hit_type,       // records the texture type that was found by the X caster.
       y_hit_type,       // records the texture type that was found by the Y caster.
       dist_x,           // The distance from the player to the texture found by the X caster.
       dist_y,           // The distance from the player to the texture found by the Y caster.
       xi,               // The X intercept, as calculated by the Y caster.
       yi,               // The Y intercept, as calculated by the X caster.
       xs,               // The X Boundary value, as found by the X caster.
       ys,               // The Y Boundary value, as found by the Y caster.
       view_angle_saved; // Saves the passed-in view angle, since we modify the original.
  int  x_light_type,
       y_light_type,
       x_zone,
       y_zone;

  struct erased_t { int x,y,type; } erased[30];    // Used to track 'erased' semi-transparent textures.
  struct col_obj *line;                            // Used to save time in array indexing.
  struct scan_line_t *s_line;                      // Used to save time in array indexing.
  int    numerased;                                // The number of 'erased' textures.
  int    indx;                                     // Loop coounter used when replacing erased blocks.

  // Keep a copy of view_angle, we're going to be changing it.
  view_angle_saved = view_angle;
  // Subtract ANGLE_30 from the view_angle (left most angle). Wrap it around if necessary.
  if ((view_angle-=ANGLE_30) < 0)
    view_angle=ANGLE_360 + view_angle;
  // Now we can start casting the rays from view_angle-ANGLE_30 to view_angle+ANGLE_30
  // For the current viewport size, that's 240 rays.
  for (ray=0; ray<240; ray++)
    {
      // We start out with no 'transparent' textures 'erased'.
      numerased = 0;
      // And no items in the column buffer.
      scan_lines[ray].num_objs = 0;
      // Keep going until we hit a solid wall, or fill the column buffer.
      while (1)
        {
          // If we're looking straight up and down, make dist_x so large, it won't be considered.
          if ((view_angle == ANGLE_90)||(view_angle == ANGLE_270))
            {
              dist_x = 1e8;
              x_hit_type = -1;
            }
          // Otherwise, let's cast some X rays...
          else
            {
              // First, we cast the ray.
              x_hit_type = Cast_X_Ray(x,y,view_angle,yi,xs,x_light_type,x_zone);
              // Use trig to get the distance (hyp = delta X / Cos(angle).
              dist_x  = (long)(((xs-x) * inv_cos_table[view_angle])>>13);
              // Here we chack for fixed point math overflow or underflow.
              if (dist_x == 0)
                dist_x = 1;
              if (dist_x < 0)
                dist_x = 1e8;
            }
          // Ok, now that we have the X distance, let's get the Y distance...
          // If we are looking due left or right, then make it so that we ignore the Y distance.
          if ((view_angle == ANGLE_0)||(view_angle == ANGLE_180))
            {
              dist_y=1e8;
              y_hit_type = -1;
            }
          // Else, let's cast some Y rays.
          else
            {
              // First, we cast the ray.
              y_hit_type = Cast_Y_Ray(x,y,view_angle,xi,ys,y_light_type,y_zone);
              // Then we calculate the distance to the boundary. (Trig again)
              dist_y  = (long)(((ys-y)*inv_sin_table[view_angle])>>13);
              // And then check for overflow or underflow.
              if (dist_y == 0)
                dist_y = 1;
              if (dist_y < 0)
                dist_y = 1e8;
            }

          // Now we determine which ray hit something closer, and save it the column buffer...
          if (dist_x < dist_y)
            {
              // The vertical wall (Horiz. distance and ray) was closer. Store it's info.
              // We stick it at the end of the column buffer.
              indx =scan_lines[ray].num_objs;
              // Get the address of the structure position. It's quicker to use a pointer than a big array.
              line = &(scan_lines[ray].line[indx]);
              // Calculate the scaling and correct for fish-eye.
              line->scale =(int)((cos_table[ray]/dist_x)>>8)<<1;
              // Crop the scaling to a max and min (for overflow/underflow)
              if (line->scale > 599) line->scale = 599;
              if (line->scale <= 0) line->scale = 1;
              // Store the texture row to use for the sliver
              line->row = ((int)yi)&127;
              // and it's position in the textures array.
              line->texnum = x_hit_type-1;
              line->lightnum = x_light_type;
              // Center the sliver about the middle of the viewport
              line->top = 75 - (line->scale>>1);
              // Store the column number
              line->col = ray;
              // and it's distance
              line->dist = (int)dist_x;
              // and the zone ambient light
              line->zoneambient = ZoneAttr[x_zone].ambient;
              // And the FOG flag.
              line->usefog = ZoneAttr[x_zone].fog;
              // and then increment the count.
              scan_lines[ray].num_objs++;
              // If the final texture was a transparent one, we have a little more to do...
              if (TextureFlags[x_hit_type].IsTransparent == 1)
                {
                  // We store the texture's map location and value in the erased array,
                  erased[numerased].x = cell_xx;
                  erased[numerased].y = cell_yx;
                  erased[numerased].type = x_hit_type;
                  // And clear it's entry in the map. This is so that the next ray will pass
                  // through this location, and continue on.
                  *(world+(cell_yx<<6)+cell_xx) = 0;
                  ++numerased;
                }
            }
          else
            {
              // The Horizontal wall (Vertical Ray) was closer, store it's info in the buffer.
              // This process is the same as the one above.
              indx =scan_lines[ray].num_objs;
              line = &(scan_lines[ray].line[indx]);
              line->scale =(int)((cos_table[ray]/dist_y)>>8)<<1;
              if (line->scale > 599) line->scale = 599;
              if (line->scale <= 0) line->scale = 1;
              line->row = ((int)xi)&127;
              // Note: If the other hit was a door, we use the ALT texture here...
              line->texnum = y_hit_type-1;
              line->lightnum = y_light_type;
              line->top = 75 - (line->scale>>1);
              line->col = ray;
              line->dist = (int)dist_y;
              // and the zone ambient light
              line->zoneambient = ZoneAttr[y_zone].ambient;
              // And the FOG flag.
              line->usefog = ZoneAttr[y_zone].fog;
              scan_lines[ray].num_objs++;
              if (TextureFlags[y_hit_type].IsTransparent == 1)
                {
                  erased[numerased].x = cell_xy;
                  erased[numerased].y = cell_yy;
                  erased[numerased].type = y_hit_type;
                  *(world+(cell_yy<<6)+cell_xy) = 0;
                  ++numerased;
                }
            }
          // Now, if we've hit a solid wall, we can quit with this cast, and go on with the
          // process. Else, we loop back and cast again for the wall behind the transparent one.
          if (TextureFlags[scan_lines[ray].line[indx].texnum+1].IsTransparent == 0) // Non-transparent Texture.
            break;
        }

      // Here's where we put the erased blocks back into the map array.
      for (indx = 0; indx < numerased; ++indx)
        *(world+(erased[indx].y<<6)+erased[indx].x) = erased[indx].type;

         // Now we increment the view angle, wraping back around at ANGLE_360.
      if (++view_angle>=ANGLE_360)
        view_angle=0;
      // And we loop back again to do the next screen column.
    }

  //   Process the sprites and sort them into the column array.
  process_sprites(x,y,view_angle_saved);

  // Here's where we render the display...
  // Cast and draw the floor and ceiling.
#ifdef CAST_FLOORCIEL
  Texture_FloorCeil(x,y,view_angle_saved);
#endif
  // Now, loop through all the screen columns stored in the col buffer.
  for (ray=0; ray<240; ray++)
    {
      // We take the address of the next entry here to save time.
      s_line = &scan_lines[ray];
      // Now we loop backwards (depth wise) theough the list for that column.
      for (indx = s_line->num_objs-1; indx >= 0; --indx)
        {
          // Now put the numbers into the globals.
          line = &(s_line->line[indx]);
          sliver_start = line->top;
          sliver_column = line->col;
          sliver_scale = line->scale;
          sliver_dist = line->dist;
          sliver_row = line->row;
          sliver_map = line->texnum;
          sliver_light = line->lightnum;
          sliver_zoneamb = line->zoneambient;
          sliver_usefog = line->usefog;
          if (sliver_light == -1) sliver_sptlt = line->sptlight;
          // And call the rendering routines.
          if (TextureFlags[sliver_map+1].IsTranslucent == 1)
            {
              if (TextureFlags[sliver_map+1].IsTransparent == 1)
                draw_sliver_trans();
            }
          else
            {
              if (TextureFlags[sliver_map+1].IsTransparent == 1)
                draw_sliver_transparent();
              else
                draw_sliver();
            }
        }
    }
}

// The last few functions perform the game logic...
void start_door(int x, int y)
{
  //
  //  This routine is called each time the player tries to open a door. It finds an empty
  // spot in the Opening_Doors struct to insert necessary information, Inserts the info,
  // allocates a new spot for the temporary texture info (which is animated), and sets
  // it's flags.
  //
  int i;                                  // Generic loop index variable.
  register int TxtNum;                    // Texture number of the original door.
  register struct Open_Doors_Info * Door; // Pointer to the used entry of Opening_Doors[].

  // Check to see if the specified block is in fact a real door...
  if (!TextureFlags[*(world+(((long)y)<<6)+x)].IsDoor)
    return;
  // Check to see if we are ALREADY opening the current door.
  for (i=0; i<10; ++i)
    if ((Opening_Doors[i].USED != 0) && (Opening_Doors[i].x == x) && (Opening_Doors[i].y == y))
      return;
  // Now, find an unused entry in the array.
  for (i=0; i<10; ++i)
    if (Opening_Doors[i].USED == 0)
      break;
  // If the array is full, we can't open it. Return adn make the user wait...
  if (i >= 10)
    return;
  // Get the address of the empty spot. Again, this saves time later on.
  Door = &Opening_Doors[i];
  // Get the map texture number for the original door.
  TxtNum = *(world+y*64+x);

  // Now, we start to fill in the Door info.
  Door->USED = 1;                           // This one is now used.
  Door->x = x;                              // It is located at these map coords.
  Door->y = y;
  Door->Orig_Txt_Num = TxtNum-1;            // And uses this texture.
  Door->New_Txt_Num = 40+i;                 // We're putting the animated texture here
  Door->Pcnt_opened = 0;                    // It's not open yet.
  Door->Num_Col_Opnd = 0;
  Door->Opening = 1;                        // But it's going to be.
  Door->delay = TextureFlags[TxtNum].delay; // Set the door's delay value.

  // OK. Now we make a new copy of the door.
  if (!(WallTextures[40+i] = (char far *)farmalloc(16384l)))
    {
      // If there was an error allocating, I'd like to know it.
      txtscreen();
      printf("Error allocating memory for opening door. \n");
      Opening_Doors[i].USED = 0;
      return;
    }
  // Copy the texture over.
  _fmemcpy(WallTextures[40+i],WallTextures[TxtNum-1],16384l);

  // Now, copy the textures parameters over...
  TextureFlags[i+41].IsTransparent = 1;               // It's definately transparent now.
  TextureFlags[i+41].CanWalkThru = 0;                 // Can't go through it yet.
  TextureFlags[i+41].IsDoor = 1;                      // It's a door for sure.
  TextureFlags[i+41].DoorType = TextureFlags[TxtNum].DoorType;
  TextureFlags[i+41].IsTranslucent = TextureFlags[TxtNum].IsTranslucent;
  TextureFlags[i+41].IsSecretDoor = TextureFlags[TxtNum].IsSecretDoor;
  TextureFlags[i+41].IsRecessed  = TextureFlags[TxtNum].IsRecessed;
  TextureFlags[i+41].bc = TextureFlags[TxtNum].bc;    // These define the rectangle to animate.
  TextureFlags[i+41].br = TextureFlags[TxtNum].br;
  TextureFlags[i+41].ec = TextureFlags[TxtNum].ec;
  TextureFlags[i+41].er = TextureFlags[TxtNum].er;
  TextureFlags[i+41].speed = TextureFlags[TxtNum].speed; // This is the amt. to move the door each frame.

  // Now we place the new texture information into the map.
  *(world+y*64+x) = 41+i;
}

void open_doors()
{
  //
  //   This function loops through the Opening_Doors aray, and opens each active door a
  // little bit more, based on it's speed. When 90% opened, it sets the "CanWalkThru"
  // flag. When 100% opened, it clears the Opening flag.
  //
  int i,          // Outer loop index variable.
      x,          // Loop variable for the animation routine.
      y,
      bc,         // These define the rectangle that is to be animated in the texture.
      br,
      ec,
      er,
      mc,
      spd;        // This is the amount to move the animated part of the texture.
  char far *base; // This points to the door's texture.
  struct TextureFlagsTYPE *TextUsed; // Points to the door's texture info.

  // For each entry in the Opening_Doors array,
  for (i=0; i<10; ++i)
    // If it is active, and opening...
    if ((Opening_Doors[i].USED) && (Opening_Doors[i].Opening) && (Opening_Doors[i].Pcnt_opened < 100))
      {
        // Get local copies of it's texture innfo. This is, again, faster.
        TextUsed = &(TextureFlags[Opening_Doors[i].New_Txt_Num+1]);
        bc = TextUsed->bc;
        br = TextUsed->br;
        ec = TextUsed->ec;
        er = TextUsed->er;
        mc = (ec-bc)/2 + bc;
        // Get a pointer to the bitmap.
        base = WallTextures[Opening_Doors[i].New_Txt_Num];
        // and it's speed.
        spd = TextureFlags[Opening_Doors[i].New_Txt_Num+1].speed;
        // Now animate the door.
        switch ( TextUsed->DoorType ) // 1=normal, 2=elevator, 3=garage
          {
          case 1: // Normal door.
            // Move bitmap of door area over by 1 column .
            for (x=ec; x >= bc+spd; --x)
              _fmemcpy(base+(x*128l)+br,base+br+((x-spd)*128l),er-br+1);
            // Clear out the edge, so we can see through it.
            for ( ; x>= bc; --x)
              _fmemset(base+(x*128l)+br,0,er-br);
            // Update the percent opened.
            Opening_Doors[i].Pcnt_opened += ((100*spd)/(ec-bc));
            break;
          case 2: // Elevator style door. Splits in the middle, scrolling to the sides.
            // Move bitmap towards left side from the middle...
            for (x=ec; x > mc+spd; --x)
              _fmemcpy(base+(x*128l)+br,base+br+((x-spd)*128l),er-br+1);
            // Clear out the middle, so we can see through it.
            for ( ; x > mc; --x)
              _fmemset(base+(x*128l)+br,0,er-br);
            // Move bitmap towards right side from the middle...
            for (x=bc; x <= mc-spd; ++x)
              _fmemcpy(base+(x*128l)+br,base+br+((x+spd)*128l),er-br+1);
            // Clear out the middle, so we can see through it.
            for ( ; x <= mc; ++x)
              _fmemset(base+(x*128l)+br,0,er-br);
            // Update the percent opened. (these open twice as fast)
            Opening_Doors[i].Pcnt_opened += ((200*spd)/(ec-bc));
            break;
          case 3: // Garage style door. Slides up from the bottom.
            // Move bitmap towards top side from the bottom...
            for (x=bc ; x <= ec; ++x)
              {
                for (y=br; y <= er - spd; ++y )
                  *(base+y+(x*128l)) = *(base+(y+spd)+(x*128l));
                for ( ; y <= er ; ++y)
                  *(base+y+(x*128l)) = 0;
              }
            // Update the percent opened.
            Opening_Doors[i].Pcnt_opened += ((100*spd)/(er-br));
            break;
          }
        // If it's 90% opened, let people walk through it.
        if (Opening_Doors[i].Pcnt_opened >= 90)
          TextUsed->CanWalkThru = 1;
        // If it's 100% open, mark it as closing.
        if (Opening_Doors[i].Pcnt_opened >= 100)
          Opening_Doors[i].Opening = 0;
      }
}

void close_doors()
{
  //
  //    This function closes all Active doors that are marked with "Opening=0". It copies
  //  part of the original bitmap back into the animated texture, and updates it's state.
  //  When the Percent_Opened is less than 90% it clears the CanWalkThru flag, which keeps
  //  anyone from ealking through the texture anymore. When it reaches 0%, the original door
  //  is put back into the map, and the animated texture space is deleted.
  //
  int  i,                    // Outer loop index variable.
       x,                    // Bitmap copy loop index variable.
       y,  
       bc,                   // These define the rectangle to be animated on the texture.
       br,
       ec,
       er,
       mc,
       spd;                 // This is how much to shift the door each time.
  char far *base,           // Pointer to the animated texture.
       far *oldbase;        // Pointer to the original texture.
  struct TextureFlagsTYPE *TextUsed; // Pointer to the texture info for the door.

  // For each entry in the Opening_Doors array...
  for (i=0; i<10; ++i)
        // If we are REALLY closing this door...
        if ((Opening_Doors[i].USED) && (!Opening_Doors[i].Opening) && (Opening_Doors[i].Pcnt_opened > 0))
         {
          // We check the delay first. Decrement it until it reaches zero first.
          if (Opening_Doors[i].delay > 0)
                --Opening_Doors[i].delay;
          // Afterwards, start animating the texture.
          else
                {
                 // We copy the texture's information into local variables to save time.
                  TextUsed = &(TextureFlags[Opening_Doors[i].New_Txt_Num+1]);
                  bc = TextUsed->bc;
                  br = TextUsed->br;
                  ec = TextUsed->ec;
                  er = TextUsed->er;
                  mc = (ec-bc)/2 + bc;
                  spd = TextUsed->speed;
                  base = WallTextures[Opening_Doors[i].New_Txt_Num];
                  oldbase = WallTextures[Opening_Doors[i].Orig_Txt_Num];

                  switch ( TextUsed->DoorType ) // 1=normal, 2=elevator, 3=garage
                    {
                    case 1: // Normal door.
                      // We copy part of the bitmap of the old door to the new door.
                      // The part is defined bu Num_Col_Opnd.
                      for (x = Opening_Doors[i].Num_Col_Opnd; x >= 0; --x)
                        memcpy(base+(ec-Opening_Doors[i].Num_Col_Opnd+x)*128l+br,oldbase+(x+bc)*128l+br,er-br);
                      // We update the size of the "copy wndow".
                      Opening_Doors[i].Num_Col_Opnd += spd;
                      // and clip it to a maximum amount (the size of the rectangle)
                      if (Opening_Doors[i].Num_Col_Opnd > (ec-bc))
                        Opening_Doors[i].Num_Col_Opnd = ec-bc;
                      // And we update the percent opened.
                      Opening_Doors[i].Pcnt_opened -= ((100 * spd)/(ec-bc));
                      break;
                    case 2: // Elevator style doors.
                      // We copy part of the bitmap of the old door to the new door.
                      // The part is defined bu Num_Col_Opnd.
                      for (x = 0 ; x <= Opening_Doors[i].Num_Col_Opnd; ++x)
                        {
                          memcpy(base+(ec-x)*128l+br,oldbase+(mc+Opening_Doors[i].Num_Col_Opnd-x)*128l+br,er-br);
                          memcpy(base+(bc+x)*128l+br,oldbase+(mc-Opening_Doors[i].Num_Col_Opnd+x)*128l+br,er-br);
                        }
                      // We update the size of the "copy wndow".
                      Opening_Doors[i].Num_Col_Opnd += spd;
                      // and clip it to a maximum amount (the size of a side door.)
                      if (Opening_Doors[i].Num_Col_Opnd > (ec-bc)/2 )
                        Opening_Doors[i].Num_Col_Opnd = (ec-bc)/2;
                      // And we update the percent opened.
                      Opening_Doors[i].Pcnt_opened -= ((200 * spd)/(ec-bc));
                      break;
                    case 3: // Garage style doors.
                      // We copy part of the bitmap of the old door to the new door.
                      // The part is defined bu Num_Col_Opnd.
                      for(x=bc; x<=ec ; ++x)
                        memcpy(base+x*128l+br,oldbase+x*128l+er-Opening_Doors[i].Num_Col_Opnd,Opening_Doors[i].Num_Col_Opnd+1);
                      // We update the size of the "copy wndow".
                      Opening_Doors[i].Num_Col_Opnd += spd;
                      // and clip it to a maximum amount (the size of a door.)
                      if (Opening_Doors[i].Num_Col_Opnd > (er-br))
                        Opening_Doors[i].Num_Col_Opnd = (er-br);
                      // And we update the percent opened.
                      Opening_Doors[i].Pcnt_opened -= ((100 * spd)/(er-br));
                      break;
                    }
                  // If we've closed enough, no one can walk through...
                  if (Opening_Doors[i].Pcnt_opened < 90)
                    TextureFlags[Opening_Doors[i].New_Txt_Num+1].CanWalkThru = 0;
                  // If we're completely closed, put the old door back.
                  if (Opening_Doors[i].Pcnt_opened <= 0)
                    {
                      farfree(base);
                      Opening_Doors[i].USED = 0; // Mark this array entry as available...
                      *(world+Opening_Doors[i].y*64+Opening_Doors[i].x) = Opening_Doors[i].Orig_Txt_Num+1;
                    }
                }
         }
}

void ProcessTrigger(long &x, long &y, long &view_angle, int TriggerNbr)
{
  switch (Triggers[TriggerNbr].Trigger_Type)
    {
    case 1: // Open door at location.
      start_door(Triggers[TriggerNbr].MapX,Triggers[TriggerNbr].Mapy);
      break;
    case 2: // Lock door at location.
      break;
    case 3: // Close door at location.
      break;
    case 4: // Start animated texture.
      Animations[TextureFlags[Triggers[TriggerNbr].TxtNbr].AnimNbr].Flagged = 1;
      break;
    case 5: // Stop animated texture.
      Animations[TextureFlags[Triggers[TriggerNbr].TxtNbr].AnimNbr].Flagged = 0;
      break;
    case 6: // Set ambient light level.
      ambient_level = Triggers[TriggerNbr].NewLightLvl;
      break;
    case 7: // Set zone light level.
      ZoneAttr[Triggers[TriggerNbr].ZoneNbr].ambient = Triggers[TriggerNbr].NewLightLvl;
      break;
    case 8: // Load new map.
      Allocate_World(x,y,view_angle,Triggers[TriggerNbr].NewFileName);
      // Now how about a special effect...
      _dos_setvect(0x09,OldInt9);
      fadeout(10);
      copyscrn(BkgBuffer);
      Render_View(x,y,view_angle);
      swapdblbuf();
      fadein(10);
      _dos_setvect(0x09,NewInt9);
      break;
    case 9: // Teleport player.
      x = Triggers[TriggerNbr].MapX;
      y = Triggers[TriggerNbr].Mapy;
      view_angle = Triggers[TriggerNbr].NewAngle;
      // Now how about a special effect...
      // End special effect.
      break;
    case 10: // Load new texture.
      break;
    case 11: // Change wall map at location.
      *(world + (Triggers[TriggerNbr].Mapy-1)*64l + Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr;
      break;
    case 12: // Change floor map at location.
      *(FloorMap + (Triggers[TriggerNbr].Mapy-1) * 64l + Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr;
      break;
    case 13: // Change ceiling map at location.
      *(CeilMap + (Triggers[TriggerNbr].MapX-1)*64l + Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr;
      break;
    case 14: // Change floor lighting map at location
      *(FloorLightMap+(Triggers[TriggerNbr].MapX-1)*64l+Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr;
      break;
    case 15: // Change ceiling lighting map at location.
      *(CeilLightMap+(Triggers[TriggerNbr].MapX-1)*64l+Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr;
      break;
    case 16: // Start animated light texture.
      Light_Animations[LightFlags[Triggers[TriggerNbr].TxtNbr].AnimNbr].Flagged = 1;
      break;
    case 17: // Stop animated light texture.
      Light_Animations[LightFlags[Triggers[TriggerNbr].TxtNbr].AnimNbr].Flagged = 0;
      break;
    }
}

void CheckTriggers(long &x, long &y, long &view_angle)
{
  // This function checks to see if the player (or other creature later on) sets off a trigger.
  // If not, then nothing happens. If so, then watch out...
  int Trigger_Nbr;
  int indx;

  if ((Trigger_Nbr = TriggerMap[x>>7][y>>7]) != 0)
    {
      for ( indx = ((Trigger_Nbr < 100)?Trigger_Nbr:1); indx <= Num_Triggers; ++indx)
        {
          // Is the player inside the area of the current trigger?
          if (Triggers[indx].Trigger_Area_Type == 1)
            {
              if (((x >=  Triggers[indx].X1) && (x <=  Triggers[indx].X2)) && ((y >=  Triggers[indx].Y1) && (y <=  Triggers[indx].Y2)))
                ProcessTrigger(x,y,view_angle,indx);
            }
          else
            if (((x - Triggers[indx].X1) * (x - Triggers[indx].X1) + (y - Triggers[indx].Y1) * (y - Triggers[indx].Y1)) <= Triggers[indx].Radius * Triggers[indx].Radius )
              ProcessTrigger(x,y,view_angle,indx);
          if (Trigger_Nbr < 100 )
            break;
        }
    }
}

int Rotate_Animations(void)
{
  //     This function loops through all of the animations loaded into the map,
  // updates all of the timers, and if needed, cycles the animation to the next frame.
  //
  int index;   // Generic loop index.

  //
  // Wall / Floor / Ceiling animations.
  //
  // Step 1. Loop through the animations array (for all defined animations)
  for (index = 0; index < Num_Animations; ++index)
    {
      // Step 1a. Is the current animation triggered?
      if ( Animations[index].Flagged == 1 )
        {
          // Step 1b. Decrement the timer for the current animation.
          if ( --Animations[index].Timer == 0)
            {
              // Step 1c. If = 0 then increment the frame counter.
              Animations[index].Curr_Frame++;
              // Step 1d. Make sure to reset to the 1st frame if we're at the end.
              if ( Animations[index].Curr_Frame >= Animations[index].Nbr_Frames )
                Animations[index].Curr_Frame = 0;
              // Step 1e. Set the new frame.
              WallTextures[Animations[index].Txt_Nbr] = Animations[index].Frames[Animations[index].Curr_Frame];
              // Step 1f. Reset the delay.
              Animations[index].Timer =  Animations[index].Delay;
            }
        }
    }
  //
  // Light Animations
  //
  for (index = 0; index < Num_LightAnimations; ++index)
    {
      // Step 1a. Is the current animation triggered?
      if ( Light_Animations[index].Flagged == 1 )
        {
          // Step 1b. Decrement the timer for the current animation.
          if ( --Light_Animations[index].Timer == 0)
            {
              // Step 1c. If = 0 then increment the frame counter.
              Light_Animations[index].Curr_Frame++;
              // Step 1d. Make sure to reset to the 1st frame if we're at the end.
              if ( Light_Animations[index].Curr_Frame >= Light_Animations[index].Nbr_Frames )
                Light_Animations[index].Curr_Frame = 0;
              // Step 1e. Set the new frame.
              LightMapTiles[Light_Animations[index].Txt_Nbr] = Light_Animations[index].Frames[Light_Animations[index].Curr_Frame];
              // Step 1f. Reset the delay.
              Light_Animations[index].Timer =  Light_Animations[index].Delay;
            }
        }
    }
  return 1;
}

void processblinklights(void)
{
  // This function will rotate the blinking lights through thier patterns...
  //

  int i;

  for (i=1; i <= Num_Light_Tiles; ++i)
    {
      ++LightFlags[i].CurrPatternIndx;
      if (LightFlags[i].LightType == 1)
        {
          if ( LightFlags[i].CurrPatternIndx < LightFlags[i].PulseWidth ) // Light is ON
            LightMapTiles[i] = LightFlags[i].OriginalBitmap;
          else
            LightMapTiles[i] = LightMapTiles[0];
          if ( LightFlags[i].CurrPatternIndx >= LightFlags[i].Period ) LightFlags[i].CurrPatternIndx = 0;
        }
      else if ( LightFlags[i].LightType == 2 )
        {
          if ( LightFlags[i].PulsePattern[LightFlags[i].CurrPatternIndx] == NULL ) LightFlags[i].CurrPatternIndx = 0;
          if ( LightFlags[i].PulsePattern[LightFlags[i].CurrPatternIndx] == '0' )
            LightMapTiles[i] = LightMapTiles[0];
          else
            LightMapTiles[i] = LightFlags[i].OriginalBitmap;
        }
    }
}

void AnimateFireScreen (void)
{
  unsigned int I;
  unsigned long x;
  unsigned long y;
  time_t   start;
  int      value;
  char far *Src,
           *MixBuf;

  randomize();
  Src = (char far *)farmalloc((unsigned)64321);
  if (Src == NULL)
    {
      txtscreen();
      printf("Unable to allocate the fire animation buffer.");
      free_tables();
      getch();
      exit(1);
    }
  MixBuf = (char far *)farmalloc((unsigned)64321);
  if (MixBuf == NULL)
    {
      txtscreen();
      printf("Unable to allocate the fire animation buffer.");
      farfree (Src);
      free_tables();
      getch();
      exit(1);
    }
  _fmemset(Src,0,64320);
  _fmemset(MixBuf,0,64320);

  start= clock();
  while ((KeyScan == 129) || (KeyScan == 57)) // Wait for the keys to clear.
    ;
  while ((clock() - start) < 10*CLK_TCK) // Start the delay, 10 seconds...
    {
      if ((KeyScan == 129) || (KeyScan == 57))// if space or ESC, break out.
        break;
      _fmemcpy(MixBuf,BkgBuffer,64000u);
      for (I=0; I < 319; ++I)                // Place a random bottom on Src.
        if ((rand() % 0x06) > 1) *(Src+64000+I) = 112;
        else *(Src+64000+I) = 0;
      for (y=140; y<200; ++y)                  // Average pixels, moving up 1 row
        for (x=1; x<319; ++x)                 // adding the decay.
          {
            value = (*(Src+x-1+(y * 320ul))+*(Src+x+1+(y * 320ul))
                     +*(Src+x-1+((y+1) * 320ul))+*(Src+x+((y+1) * 320ul))
                     +*(Src+x+1+((y+1) * 320ul )))/5 - 1;
            if ((value > 0) && y < 196)
              *(MixBuf+x+((y+4) * 320ul)) = value;
            *(Src+x+((y-1) * 320ul)) = value;
          }
      while(inportb(0x3da) & 0x08) // Wait for any current VBI to end.
        {};
      while(!(inportb(0x3da) & 0x08)) // Wait for the beginning of the next one.
        {};
      _fmemcpy(scrnbuf,MixBuf,64000u);
      while(inportb(0x3da) & 0x08) // Wait for any current VBI to end.
        {};
      while(!(inportb(0x3da) & 0x08)) // Wait for the beginning of the next one.
        {};

    }
  farfree (Src);
  farfree (MixBuf);
}

void main(void)
{
  //
  //  THIS is the function that drives it all. It begins by setting up the tables, the screen
  // the new keybard handler, loading the map, displaying the opening screens, processing user
  // input, checking the doors, collisions, etc. When done, it displays the closing screen, and
  // reports on the frames per seecond.

  int    done=0,               // This flag, when set, causes the outer While loop to terminate.
         i;                    // Generic loop index. Used for clearing some tables.
  char   AutomapFlag = 0,      // Automap state flag.
         TorchFlag = 0,
         TorchLevel = 8,
         FogState = 0;
  long   x,                    // The players X position, in fine coordinates.
         y,                    // The players Y position, in fine coordinates.
         view_angle,           // The angle which the player is facing.
         view_angle_90,        // The angle the player is facing + 90 degreess. (Used for collision & door processing)
         x_cell,               // The map cell COL the player is in. (Used for collision & door processing)
         y_cell,               // The map cell ROW the player is in.
         x_sub_cell,           // The fine coordinate inside the cell (0-63) of the player.
         y_sub_cell,           // The fine coordinate inside the cell (0-63) of the player.
         dx,                   // The amount to move the player in X this round.
         dy;                   // The amount to move the player in Y this round.
  float  fps;                  // Calculated frames per second.
  time_t initl;                // The time the game started. (Used in calculating FPS).
  unsigned long frames = 0,    // The cumulative number of frames displayed this game.
                InitMem = 0,
                MemLeft = 0;

  AutoMapBkgd = Load_PCX("automap.pcx",70*70,0);
  // OK, First thing we're going to do is clear out the palette table.
  InitMem = farcoreleft();
  // Now, initialize the screen, and set the proper graphics mode.
  initscreen(1);
  // Set up the new keyboard handler, saving the address of the old one for later.
  OldInt9 = _dos_getvect(0x09);
  _dos_setvect(0x09,NewInt9);
  // Fade the screen to black.
  fadeout(25);
  // And load in the first full screen graphic.
  load_bkgd("open0.PCX",0);
  // Copy it to the double buffer,
  copyscrn(BkgBuffer);
  // Move that to the screen,
  swapdblbuf();
  // And fade it in.
  fadein(25);
  // Now we wait 2 seconds. Esc and Space will cancel the delay.
  delay2 (2);
  // Do the same with the second picture,
  load_bkgd("open1.PCX",0);
  dissolveto(BkgBuffer);
  delay2 (7);
  // And the third.
  load_bkgd("open2.PCX",0);
  dissolveto(BkgBuffer);
  delay2 (7);
  fadeout(25);
  // Now we show the main title.
  load_bkgd("TITLE.PCX",0);
  copyscrn(BkgBuffer);
  swapdblbuf();
  fadein(25);
  AnimateFireScreen();
  fadeout(25);
  // temporarilly restore the OLD keyboard handler. Why? because we can die in Build_Tables
  // and Allocate_World.
  _dos_setvect(0x09,OldInt9);
  // make all the trig and support tables.
  Build_Tables();
  // Load in the map, textures, etc. and set the player's initital position.
  Allocate_World(x,y,view_angle,"raymap.dat");
  // and calculate the cell and sub-cell values.
  x_cell = x>>7;
  y_cell = y>>7;
  x_sub_cell = x & 127;
  y_sub_cell = y & 127;
  // Load the play screen background,
  load_bkgd("BKGD.PCX",0);
  // Copy it to the double buffer,
  copyscrn(BkgBuffer);
  // Render the 1st view, on top of the background,
  Render_View(x,y,view_angle);
  // Dump it to the screen,
  swapdblbuf();
  // And fade in..........  WE START....
  fadein(25);
  // Give us back our good kwyboard handler,
  OldInt9 = _dos_getvect(0x09);
  _dos_setvect(0x09,NewInt9);
  // and start counting up the Frames per second...
  frames = 0;
  initl = clock();

  // This is the MAIN GAME LOOP. EVERYTHING IN THE GAME HAPPENS HERE.
  while(!done)
    {
      // Step 1. Process the doors.
      open_doors(); // Open all opening doors a little more.
      close_doors(); // close all closing doors a little more.
      Rotate_Animations(); // Cycle the animations.
      processblinklights();
      // Check the triggers...
      CheckTriggers(x,y,view_angle);

      // Step 2. Check the keyboard for an ESC key (Quit).
      if (KeyScan == 129)
        done = 1;

          // Step 3. Check to see if the player wants to open a door. If so, then process it.
      if (KeyState & 0x10) // Check for opening a door...
        {
          // If the player is facing to the WEST
          if ((view_angle >  ANGLE_330) || (view_angle < ANGLE_30))
            {
              // If it really is a door right next to him (1 cell away),
              if (TextureFlags[*(world+(y_cell<<6)+x_cell+1)].IsDoor)
                // Then start it opening.
                start_door(x_cell+1,y_cell);
            }
          // Else see if it's to to the north.
          else if ((view_angle >  ANGLE_60) && (view_angle < ANGLE_120))
            {
              if (TextureFlags[*(world+((y_cell+1)<<6)+x_cell)].IsDoor)
                start_door(x_cell,y_cell+1);
            }
          // Else, see if it's to the west.
          else if ((view_angle >  ANGLE_150) && (view_angle < ANGLE_210))
            {
              if (TextureFlags[*(world+(y_cell<<6)+(x_cell-1))].IsDoor)
                start_door(x_cell-1,y_cell);
            }
          // Else, see if it's to the south.
          else if ((view_angle >  ANGLE_240) && (view_angle < ANGLE_300))
            {
              if (TextureFlags[*(world+((y_cell-1)<<6)+x_cell)].IsDoor)
                start_door(x_cell,y_cell-1);
            }
        }
      // Now check to see if the player wants the automap.
      if ((KeyState & 0x20) && (AutomapFlag == 0))
        {
          AutomapFlag = 1;
        }
      if (!(KeyState & 0x20) && (AutomapFlag == 1))
        {
          AutomapFlag = 2;
        }
      if ((KeyState & 0x20) && (AutomapFlag == 2))
        {
          AutomapFlag = 3;
        }
      if (!(KeyState & 0x20) && (AutomapFlag == 3))
        {
          AutomapFlag = 0;
        }

      // Now check to see if the player wants to change the torch level.
      if ((KeyState & 0x40) && (TorchFlag == 0))
        {
          TorchFlag = 1;
          --TorchLevel;
          if (TorchLevel == 0)
            TorchLevel = 8;
          CalcTorchLevel(TorchLevel);
        }
      if (!(KeyState & 0x40) && (TorchFlag == 1))
        {
          TorchFlag = 0;
        }

      // Step 4. Check for movement. The lower nibble of KeyState is for movement ONLY.
      if (KeyState & 0x0f)
        {
          // Reset dx and dy.
          dx=dy=0;
          // If the player is pressing the Right arrow,
          if (KeyState & 4)
            {
              // Decrement the view angle by 6 degrees, wrap at zero.
              if ((view_angle-=ANGLE_6)<ANGLE_0)
                view_angle+=ANGLE_360;
            }
          // Else, if the player is pressing the Left arrow.
          if (KeyState & 8)
            {
              // Increment the view angle by 6 degrees, Wrap at 360.
              if ((view_angle+=ANGLE_6)>=ANGLE_360)
                view_angle-=ANGLE_360;
            }
          // Else, if the player wants to go FORWARD...
          if (KeyState & 2)
            {
              // Set view_angle_90, and wrap it around at 360 deg.
              view_angle_90 = view_angle + ANGLE_90;
              if (view_angle_90 > ANGLE_360)
                view_angle_90 = view_angle_90 - ANGLE_360;
              // Calculate the dx and dy to move forward, at the current angle. (Trig)
              dx= (long)((sin_table[view_angle_90]*20)>>16);
              dy= (long)((sin_table[view_angle]*20)>>16);
            }
          // Else, if the player wants to move BACKWARDS
          if (KeyState & 1)
            {
              // Set view_angle_90, and wrap it around at 360 deg.
              view_angle_90 = view_angle + ANGLE_90;
              if (view_angle_90 > ANGLE_360)
                view_angle_90 = view_angle_90 - ANGLE_360;
              // Calculate the dx and dy to move backward, at the current angle. (Trig)
              dx= -(long)((sin_table[view_angle_90]*20)>>16);
              dy= -(long)((sin_table[view_angle]*20)>>16);
            }
          // Now , add the deltas to the current location,
          x+=dx;
          y+=dy;
          // Calculate the x and y cell and subcell values,
          x_cell = x>>7;
          y_cell = y>>7;
          x_sub_cell = x & 127;
          y_sub_cell = y & 127;

          // Now, we do collision detection... (Bumping into walls..)
          // If we're moving WEST,
          if (dx>0)
            {
              if ((*(world+(y_cell<<6)+x_cell+1) != 0) &&      // If there'a wall there,
                  (!TextureFlags[*(world+(y_cell<<6)+x_cell+1)].CanWalkThru) && // And we can't walk through it,
                  (x_sub_cell > (CELL_X_SIZE-OVERBOARD))) // And we're too close to it...
                x-= (x_sub_cell-(CELL_X_SIZE-OVERBOARD )); // then MOVE US BACK A BIT.
            }
          // Same as above, but going EAST.
          else
            {
              if ((*(world+(y_cell<<6)+x_cell-1) != 0) &&        // If there'a wall there,
                  (!TextureFlags[*(world+(y_cell<<6)+x_cell-1)].CanWalkThru) &&    // And we can't walk through it,
                  (x_sub_cell < (OVERBOARD)))   // And we're too close to it...
                x+= (OVERBOARD-x_sub_cell) ;     // then MOVE US BACK A BIT.
            }
          // If we're going NORTH,
          if (dy>0 )
            {
              if ((*(world+((y_cell+1)<<6)+x_cell) != 0) &&     // If there'a wall there,
                  (!TextureFlags[*(world+((y_cell+1)<<6)+x_cell)].CanWalkThru) &&   // And we can't walk through it,
                  (y_sub_cell > (CELL_Y_SIZE-OVERBOARD)))           // And we're too close to it...
                y-= (y_sub_cell-(CELL_Y_SIZE-OVERBOARD ));           // then MOVE US BACK A BIT.
            }
          // Else, if we're going SOUTH
          else
            {
              if ((*(world+((y_cell-1)<<6)+x_cell) != 0) &&       // If there'a wall there,
                  (!TextureFlags[*(world+((y_cell-1)<<6)+x_cell)].CanWalkThru) &&     // And we can't walk through it,
                  (y_sub_cell < (OVERBOARD)))         // And we're too close to it
                y+= (OVERBOARD-y_sub_cell);           // then MOVE US BACK A BIT.
            }
        }

      // Get ready to draw the screen again...
      // Copy the background to the double buffer.
      copyscrn(BkgBuffer);
      // Render the view plane
      Render_View(x,y,view_angle);
      // If the automap is on, then render it to the double buffer.
      if ((AutomapFlag == 1) || (AutomapFlag == 2))
        DrawAutomap(x,y,view_angle);
      // Send it to the screen.
      swapdblbuf();
      // Increment our frame count.
      ++frames;
      // Go back an ddo it all again....
    }
  MemLeft = farcoreleft();
  // Now, the game is over,
  // Calculate the frames per second.
  fps = (float)frames/((clock()-initl)/(float)CLK_TCK);
  // Fade the screen out.
  fadeout(25);
  // Load in the ending screen.
  load_bkgd("ending.PCX",0);
  // Copy it over to the double buffer.
  copyscrn(BkgBuffer);
  // Move it to the screen,
  swapdblbuf();
  // Fade it in.
  fadein(25);
  // Wait for 8 seconds, or until ESC or SPACE is pressed.
  delay2(8);
  // Give us back the normal keyboard handler
  _dos_setvect(0x09,OldInt9);
  // Go back to 80 col. text mode.
  txtscreen();
  // Print out the FPS message.
  fprintf(stderr,"Frames per second: %f\nStarting Memory: %ld\n Memory left: %ld\nTotal Used: %ld",fps,InitMem,MemLeft,(unsigned long)(InitMem-MemLeft));
}
// End Ray.cpp