/*************************************************************************/
/*** ***/
/*** 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