/*************************************************************************/
/*** ***/
/*** R A Y (C) 1995 NPS Software ***/
/*** ***/
/*** Writen By: Brad Broerman ***/
/*** Started: June 1994 ***/
/*** Last Modified: July 2, 2000 ***/
/*** 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. ***/
/*** ***/
/*************************************************************************/
/*** 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. ***/
/*** -- 4 years later -- ***/
/*** 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) (see triggers.txt) ***/
/*** Released Ray version 1.5 ***/
/*** 07/02/00 Did windows port of RAY. Added resize capability. ***/
/*** Thanks to Jorge for showin me how to do the Windows ***/
/*** port. ***/
/*** ***/
/*************************************************************************/
#include "WinRay.h"
//
// Initialize the double buffer and windows bitmaps that will be used to
// render the double buffer.
//
void init_double_buffer(void)
{
HDC hDC;
if ( BitMapInfo == NULL )
BitMapInfo = (BITMAPINFO *)malloc(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
hDC = GetDC(ghWnd);
BitMapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BitMapInfo->bmiHeader.biWidth = 320;
BitMapInfo->bmiHeader.biHeight = -200;
BitMapInfo->bmiHeader.biPlanes = 1;
BitMapInfo->bmiHeader.biBitCount = 8;
BitMapInfo->bmiHeader.biCompression = BI_RGB;
BitMapInfo->bmiHeader.biSizeImage = 0;
BitMapInfo->bmiHeader.biXPelsPerMeter = 0;
BitMapInfo->bmiHeader.biYPelsPerMeter = 0;
BitMapInfo->bmiHeader.biClrUsed = 0;
BitMapInfo->bmiHeader.biClrImportant = 0;
// Load the palette
for (int i= 0; i < 256; ++i)
{
BitMapInfo->bmiColors[i].rgbBlue = palette[i][2];
BitMapInfo->bmiColors[i].rgbGreen = palette[i][1];
BitMapInfo->bmiColors[i].rgbRed = palette[i][0];
}
if (DblBuff != NULL)
DeleteObject(DblBuff);
DblBuff = CreateDIBSection(hDC, (BITMAPINFO *)BitMapInfo, DIB_RGB_COLORS, (VOID **)&double_buffer, 0, 0);
ReleaseDC(ghWnd,hDC);
}
// This modifies the double buffers palette. It can be used for fading or flashing effects (whole screen)
//
void ChangePalette ( float percent )
{
HDC hDC, hMemDC;
HBITMAP DefaultBitmap;
RGBQUAD tmpPalette[256];
hDC = GetDC(ghWnd);
// Create memory device context compatible with the engines window dc
hMemDC = CreateCompatibleDC(GetDC(ghWnd));
// Save the default bitmap and select the engines double buffer into the memory dc
DefaultBitmap = SelectObject(hMemDC, DblBuff);
// Now get the palette for the selected bitmap.
GetDIBColorTable(hMemDC, 0, 256, tmpPalette);
// Modify the palette by the percentage.
for (int i=0; i<256; ++i)
{
tmpPalette[i].rgbBlue = (unsigned char )(tmpPalette[i].rgbBlue * percent);
tmpPalette[i].rgbGreen = (unsigned char )(tmpPalette[i].rgbGreen * percent);
tmpPalette[i].rgbRed = (unsigned char )(tmpPalette[i].rgbRed * percent);
}
// and now put it back into the bitmap.
SetDIBColorTable(hMemDC, 0, 256, tmpPalette);
// put back the default bitmap
SelectObject(hMemDC, DefaultBitmap);
// delete the memory dc
DeleteDC(hMemDC);
// delete the default bitmap
DeleteObject(DefaultBitmap);
// release the engines window dc
ReleaseDC(ghWnd,hDC);
}
//
// This copies the double buffer to the visible window. It also performs the stretching
// when the window is not the default size.
//
void blit(void)
{
HDC hDC, hMemDC;
HBITMAP DefaultBitmap;
hDC = GetDC(ghWnd);
// Create memory device context compatible with the engines window dc
hMemDC = CreateCompatibleDC(GetDC(ghWnd));
// Save the default bitmap and select the engines double buffer into the memory dc
DefaultBitmap = SelectObject(hMemDC, DblBuff);
// Copy double buffer to visible windows client area
StretchBlt(hDC, 0, 0, ClientWidth, ClientHeight, hMemDC, 0, 0, 320, 200, SRCCOPY);
// put back the default bitmap
SelectObject(hMemDC, DefaultBitmap);
// delete the memory dc
DeleteDC(hMemDC);
// delete the default bitmap
DeleteObject(DefaultBitmap);
// release the engines window dc
ReleaseDC(ghWnd,hDC);
}
//
// This method copies a loaded buffer (read PCX file) into the double buffer.
//
void copyscrn (char *Buffer)
{
memcpy(double_buffer, Buffer, 64000ul);
}
//
// This is a delay that can be cancelled by pressing SPACE or ESCape.
// It is used during the start-up screens.
//
void delay2(long time) // time is in seconds.
{
time_t start;
start= clock();
while (KEY_DOWN(VK_ESCAPE) || KEY_DOWN(VK_SPACE)) // Wait for the keys to clear.
;
while ((clock() - start) < time*CLK_TCK) // Start the delay
{
if (KEY_DOWN(VK_ESCAPE) || KEY_DOWN(VK_SPACE)) // if space or ESC, break out.
break;
}
}
//
// This function clears te screen to the color "C", by blasting the
// byte to the double buffer. This used to use a rep movsw, but now we're
// in windows, and I don't know if that will work anymore.
//
void clearscrn(char c)
{
memset(double_buffer, c, 64000ul);
}
//
// Deallocates all of the tables. Used when the engine is shut down.
//
void free_tables(void)
{
int i,j;
for (i = 0; i < Num_Animations; ++i)
WallTextures[Animations[i].Txt_Nbr] = NULL;
for (i = 0; i < Num_LightAnimations; ++i)
LightMapTiles[Light_Animations[i].Txt_Nbr] = NULL;
if (world != NULL)
free(world);
if (FloorMap != NULL)
free(FloorMap);
if (CeilMap != NULL)
free(CeilMap);
if (ZoneMap != NULL)
free(ZoneMap);
if (CeilLightMap != NULL)
free(CeilLightMap);
if (FloorLightMap != NULL)
free(FloorLightMap);
for (i=0; i<Num_Textures; ++i)
if (WallTextures[i] != NULL)
free (WallTextures[i]);
for (i=0; i<Num_Light_Tiles; ++i)
if (LightMapTiles != NULL)
free(LightMapTiles);
if (tan_table != NULL)
free(tan_table);
if (inv_tan_table != NULL)
free(inv_tan_table);
if (y_step != NULL)
free(y_step);
if (x_step != NULL)
free(x_step);
if (cos_table != NULL)
free(cos_table);
if (sin_table != NULL)
free(sin_table);
if (inv_cos_table != NULL)
free(inv_cos_table);
if (inv_sin_table != NULL)
free(inv_sin_table);
if (sliver_factor != NULL)
free(sliver_factor);
if (Floor_inv_cos_table != NULL)
free(Floor_inv_cos_table);
if (Floor_cos_table != NULL)
free(Floor_cos_table);
if (Floor_sin_table != NULL)
free(Floor_sin_table);
if (Floor_Dx_table != NULL)
free (Floor_Dx_table);
if (LightLevel != NULL)
free(LightLevel);
if (LightMap != NULL)
free(LightMap);
if (FogMap != NULL)
free(FogMap);
if (Translucency != NULL)
free(Translucency);
for (j = 0; j <= Num_Sprites; ++j)
for (i=0; i < 8; ++i)
if (Sprites[j].Frames[i] != NULL)
free (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)
free(Animations[j].Frames[i]);
for (i=0; i < Num_Light_Tiles ; ++i)
if (LightFlags[i].PulsePattern != NULL)
free(LightFlags[i].PulsePattern);
if (FireSrc != NULL)
free(FireSrc);
if (DblBuff != NULL)
DeleteObject(DblBuff);
}
//
// This prints an error message, clears all the tables, and informs the
// message loop that we need to exit the program.
//
int Die(char *string)
{
free_tables();
MessageBox(ghWnd, string, lpszTitle, MB_APPLMODAL | MB_ICONERROR | MB_OK );
SendMessage(ghWnd, WM_DESTROY, 0, 0);
return -1;
}
//
// Here is were we load 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 Build_Tables(void)
{
int tmp;
int i;
FILE *handle;
// Step 1. Initialize static arrays, pointers, and counters.
// Clear the global palette.
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 and the tables.
if (!(world = (char *)malloc(4096)))
return Die("Allocation Error while creating world map");
if (!(FloorMap = (char *)malloc(4096)))
return Die("Allocation Error while creating floor map");
if (!(CeilMap = (char *)malloc(4096)))
return Die("Allocation Error while creating ceiling map");
if (!(ZoneMap = (char *)malloc(4096)))
return Die("Allocation Error while creating zone map");
if (!(CeilLightMap = (char *)malloc(4096)))
return Die("Allocation Error while creating ceiling light map");
if (!(FloorLightMap = (char *)malloc(4096)))
return Die("Allocation Error while creating floor light map");
if (!(tan_table = (long *)malloc(sizeof(long) * (ANGLE_360+1))))
return Die("Allocation Error while creating tangent table");
if (!(inv_tan_table = (long *)malloc(sizeof(long) * (ANGLE_360+1))))
return Die("Allocation Error while creating inverse tangent table");
if (!(y_step = (long *)malloc(sizeof(long) * (ANGLE_360+1))))
return Die("Allocation Error while creating y step table");
if (!(x_step = (long *)malloc(sizeof(long) * (ANGLE_360+1))))
return Die("Allocation Error while creating x step table");
if (!(cos_table = (long *)malloc(sizeof(long) * (ANGLE_360+1))))
return Die("Allocation Error while creating cosine table");
if (!(sin_table = (long *)malloc(sizeof(long) * (ANGLE_360+1))))
return Die("Allocation Error while creating sine table");
if (!(inv_cos_table = (long *)malloc(sizeof(long) * (ANGLE_360+1))))
return Die("Allocation Error while creating inverse cosine table");
if (!(inv_sin_table = (long *)malloc(sizeof(long) * (ANGLE_360+1))))
return Die("Allocation Error while creating inverse sine table");
if (!(sliver_factor = (long *)malloc(sizeof(long)*600)))
return Die("Allocation Error while creating sliver scaling table");
if (!(Floor_inv_cos_table = (long *)malloc(sizeof(long)*(ANGLE_360+1))))
return Die("Allocation Error while creating Floor inverse cosine table");
if (!(Floor_cos_table = (long *)malloc(sizeof(long)*(ANGLE_360+1))))
return Die("Allocation Error while creating Floor cosine table");
if (!(Floor_sin_table = (long *)malloc(sizeof(long)*(ANGLE_360+1))))
return Die("Allocation Error while creating Floor sine table");
if (!(Floor_Dx_table = (long *)malloc(sizeof(long)*10000L)))
return Die("Allocation Error while creating Floor DX table");
if (!(LightLevel = (char *)malloc(sizeof(char)* MAX_DISTANCE ))) // Maximum distance.
return Die("Allocation Error while creating Light levels");
if (!(LightMap = (char *)malloc(sizeof(char)*(PALETTE_SIZE*MAX_LIGHT_LEVELS)))) // 128 levels of 256 colors.
return Die("Allocation Error while creating Light map");
if (!(FogMap = (char *)malloc(sizeof(char)*(PALETTE_SIZE*MAX_LIGHT_LEVELS)))) // 128 levels of 256 colors.
return Die("Allocation Error while creating Fog Map");
if (!(Translucency = (char *)malloc(65536ul))) // Mixing 256 colors with 256 colors.
return Die("Allocation Error while creating Translucency Map");
// Step 3. Read the tables from the data file...
if((handle=fopen("tables.dat","rb")) == NULL)
return Die("Error reading data tables.\n");
fread(sliver_factor,1l,(sizeof(long)*600L),handle);
fread(Floor_Row_Table,1l,(sizeof(long)*151L),handle);
fread(Floor_Dx_table,1,(sizeof(long)*10000L),handle);
fread(row,1,(sizeof(long)*200L),handle);
fread(Sprite_Frame,1,(sizeof(int)*361L),handle);
fread(tan_table,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(inv_tan_table,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(Floor_cos_table,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(Floor_sin_table,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(y_step,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(x_step,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(inv_cos_table,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(inv_sin_table,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(sin_table,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(Floor_inv_cos_table,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(cos_table,1,(sizeof(long) * (ANGLE_360+1L)),handle);
fread(LightLevel,1,(sizeof(char)* MAX_DISTANCE ), handle);
fread(FogMap, MAX_LIGHT_LEVELS, PALETTE_SIZE, handle);
fread(LightMap,PALETTE_SIZE,MAX_LIGHT_LEVELS, handle);
fread(Translucency,1,65535l, handle);
fclose(handle);
return 1;
}
//
// This method re-calculates the light level table based on the current torch level. When the
// user changes the torch level, this method re-calculates the lighting table.
//
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 * 2.0; // Normal intensity is mid-range.
if (ratio > 1.0) ratio = 1.0;
*(LightLevel + dist) = (char)( ratio * MAX_LIGHT_LEVELS );
}
}
//
// This function saves a 256 color PCX file, of a specified filename and size, from the
// indicated buffer (usually the double buffer).
//
// Return Codes: 0 - Success
// -1 - Error
//
int Save_Pcx(char *filename, unsigned char *Buffer, unsigned long width, unsigned long height)
{
unsigned long count, // the count for the RLE run.
bcount, // the count for the line.
index, // Overall byte count.
TotalSize;
unsigned char data; // The data byte written.
FILE *outfile; // The file pointer.
PCX_HEADER header; // The PCX header info.
outfile = fopen(filename, "wb");
if(!outfile) {
return -1;
}
// Build a PCX file header. Must be little endian, so beware
header.manufacturer = 10;
header.version = 5;
header.encoding = 1;
header.bits_per_pixel = 8;
header.x = 0;
header.y = 0;
header.width = width-1;
header.height = height-1;
header.horiz_rez = width;
header.vert_rez = height;
header.Num_Planes = 1;
header.bytes_per_line = width;
header.palette_type = 1;
// Write the PCX file header
fwrite((void *)&header,sizeof(PCX_HEADER),1,outfile);
// Do run length encoding for pixel runs of 2 to 63. Add 192 to the
// run count, and write it before the pixel to indicate how many are
// present. Runs are not encoded across line boundaries, so
// bcount keeps track of how many bytes have been written per line,
// and when it hits "width", the run is written even if less than 63.
bcount = 0;
index = 0;
TotalSize = width * height;
while (index < TotalSize)
{
if((index < TotalSize-1) && (Buffer[index] == Buffer[index + 1]) && (bcount < width-1))
{
// We have a run of two or more
data = Buffer[index];
count = 2;
while((index + count < TotalSize) &&
(data == Buffer[index + count]) &&
(count < 63) && (bcount + count < width) )
count++;
index = index + count;
bcount = bcount + count;
putc((char)(count + 192), outfile);
putc(data, outfile);
}
else
{
// We are writing one byte. If byte is 192-255, put a run count of 1 (193) in front of it
if(Buffer[index] >= 192)
putc((char)193, outfile);
putc((char)Buffer[index], outfile);
index++;
bcount++;
}
if(bcount == width)
bcount = 0;
}
// Write the palette
// 256 color palette must be prefixed with this byte
putc((char)12, outfile);
for(index=0; index<256; index++)
{
putc((char)palette[index][0], outfile);
putc((char)palette[index][1], outfile);
putc((char)palette[index][2], outfile);
}
fclose(outfile);
return 1;
}
//
// 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.
//
char *Load_PCX(char *filename, unsigned long size, int load_pal)
{
unsigned long count, // the count for the RLE run.
index; // Overall byte count. main FOR loop.
unsigned char data; // The data byte read in.
FILE *infile; // The file pointer.
PCX_HEADER header; // The PCX header info.
char *Pointer; // Points to the bitmap data.
char errorMsg[128];
// Step 1. Allocate a new bit of memory for the bitmap.
if (!(Pointer = (char *)malloc(size)))
{
// If there was a problem, quit and complain!
sprintf(errorMsg,"Memory error loading PCX File '%s'. \n", filename);
Die(errorMsg);
return NULL;
}
// Step 2: Open the file
infile = fopen(filename,"rb");
if (!infile)
{
sprintf(errorMsg,"Error opening PCX File '%s'. \n", filename);
Die(errorMsg);
return NULL;
}
// 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)
{
sprintf(errorMsg,"Error opening PCX File. Wrong file type '%s'. \n", filename);
Die(errorMsg);
return NULL;
}
// 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) && (index < size)) // 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);
palette[index][1] = getc(infile);
palette[index][2] = getc(infile);
}
}
// Step 5: Close the file.
fclose(infile);
return Pointer;
}
//
// 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 transpose(char *Bitmap)
{
int rows, // These two are loop counters.
cols;
char *temp; // A temporary holding buffer.
temp = (char *)malloc(16385);
if (temp == NULL)
return Die("Memory error transposing PCX File \n");
// 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);
free(temp);
return 1;
}
//
// This function loads in a 320x200, 256 color PCX file, and
// places it into the BkgBuffer.
//
int load_bkgd(char *filename)
{
// Step 1. Clear the background buffer.
if (BkgBuffer)
free(BkgBuffer);
// Step 2. Load the PCX file.
BkgBuffer = Load_PCX(filename,64000l,1);
if (NULL == BkgBuffer)
return -1;
return 1;
}
//
// This function loads in a 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.
//
// filename is the name of the PCX file on disk.
// offset is the texture number (as specified in the map file.
//
int Load_Wall(char *filename, int offset)
{
// 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)
free(WallTextures[offset]);
// Step 3. Load the PCX file.
WallTextures[offset] = Load_PCX(filename,16384l,0);
if (NULL == WallTextures[offset])
return -1;
// 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).
return transpose(WallTextures[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.)
//
int Load_Light_Tile(char *filename,int offset)
{
// Step 1. Check to see if a texture is already there. If so, then delete it.
if (LightMapTiles[offset] != NULL)
free(LightMapTiles[offset]);
// Step 2. Load the PCX file.
LightMapTiles[offset] = Load_PCX(filename,16384l,0);
if (NULL == LightMapTiles[offset])
{
return -1;
}
// 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).
return transpose(LightMapTiles[offset]);
}
//
// 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 Load_Map ( ). 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 *Load_Sprite (char *filename)
{
char *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).
if (Pointer != NULL)
transpose(Pointer);
// Step 3. Return the pointer to the bitmap.
return Pointer;
}
//
// Translates a character in a map definition to the numeric value that is to be stored in
// the map table. 0-9 are 0-9, A-Z are 10 - 34, and space is also 0.
//
unsigned char translateChar(unsigned char ch)
{
if (ch == ' ') // Space maps to zone 0.
return 0;
else if ((ch >= '0') && (ch <= '9')) // 0-9 is 0-9
return ch - '0';
else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 10-34
return ch - 'A' + 10;
// Default, this shold never happen.
return 0;
}
//
// Clears out the previously loaded map, and prepares the system for a new one.
//
void Clear_Map(void)
{
int i,j; // Generic loop counters.
// Step 1. Reset the triggers and objects.
Num_Triggers = 0;
Num_Objects = 0;
// Step 2. Reset the sprites, and free the memory for the frames.
for (j = 0; j < Num_Sprites; ++j)
for (i=1; i<= 8; ++i)
if (Sprites[j].Frames[i-1] != NULL)
{
free(Sprites[j].Frames[i-1]);
Sprites[j].Frames[i-1] = NULL;
}
Num_Sprites = 0;
// Step 3. Clear the wall / door textures, their animations, texture flags, etc.
for (i = 0; i < Num_Animations; ++i)
WallTextures[Animations[i].Txt_Nbr] = NULL;
for (i = 0; i < Num_LightAnimations; ++i)
LightMapTiles[Light_Animations[i].Txt_Nbr] = NULL;
for (j = 0; j < Num_Textures; ++j)
{
if (WallTextures[j] != NULL)
{
free(WallTextures[j]);
WallTextures[j] = NULL;
}
if (TextureFlags[j].AnimNbr > -1)
{
if (Animations[TextureFlags[j].AnimNbr].Frames != NULL )
{
for (i = 0; i < Animations[TextureFlags[j].AnimNbr].Nbr_Frames; ++i)
if (Animations[TextureFlags[j].AnimNbr].Frames[i] != NULL)
{
free(Animations[TextureFlags[j].AnimNbr].Frames[i]);
Animations[TextureFlags[j].AnimNbr].Frames[i] = NULL;
}
free(Animations[TextureFlags[j].AnimNbr].Frames);
Animations[TextureFlags[j].AnimNbr].Frames = NULL;
}
}
}
Num_Textures = 0;
// Step 4. Clear the lighting tiles, and the animations, etc.
for (j = 0; j <= Num_Light_Tiles ; ++j)
{
if (LightMapTiles[j] != NULL)
{
free(LightMapTiles[j]);
LightMapTiles[j] = NULL;
}
if (LightFlags[j].PulsePattern != NULL)
{
free(LightFlags[j].PulsePattern);
LightFlags[j].PulsePattern = NULL;
}
if (LightFlags[j].AnimNbr > -1)
{
if (Light_Animations[LightFlags[j].AnimNbr].Frames != NULL )
{
for (i = 0; i < Light_Animations[LightFlags[j].AnimNbr].Nbr_Frames; ++i)
if (Light_Animations[LightFlags[j].AnimNbr].Frames[i] != NULL)
{
free(Light_Animations[LightFlags[j].AnimNbr].Frames[i]);
Light_Animations[LightFlags[j].AnimNbr].Frames[i] = NULL;
}
free(Light_Animations[LightFlags[j].AnimNbr].Frames);
Light_Animations[LightFlags[j].AnimNbr].Frames = NULL;
}
}
}
Num_Light_Tiles = 0;
// Step 5. Clear out the zone map, and set up the default zone.
memset(ZoneMap,0,4096);
ZoneAttr[0].ambient = 16;
ZoneAttr[0].inside = 1;
ZoneAttr[0].fog = 0;
return;
}
//
// This routine reads and translates the map file, creates and fills the map arrays,
// and loads the textures into memory.
//
int Load_Map(long &initx, long &inity, long &initang, char *MapFileName)
{
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, // The following is Door info, and has already been described in detail.
rs,
ce,
re,
de,
tp,
tl,
sec,
type,
sidetxt,
spd,
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 1. Clear out the old map.
Clear_Map();
// Step 2. Set up the 1st light map (map 0). This is used as the default.
if (LightMapTiles[0] != NULL)
free(LightMapTiles[0]);
LightMapTiles[0] = (char *)calloc(1,16384l);
// Step 3. Read in world data.
// Step 3a. Open the map file.
if ((fp = fopen(MapFileName,"r")) == NULL)
return Die("Error loading map file.");
// Step 3b. Read the file in, line by line.
fgets(line,256,fp);
while (!feof(fp))
{
memset(typ,0,100);
memset(trg_parms,0,100);
sscanf(line,"%s",typ); // This step cleans up much garbage that can be present.
// Step 3c. Skip over any comment lines.
if (!strcmpi(typ,"//"))
{
fgets(line,256,fp); // Get the next line here.. (We test for EOF at the beginning..)
continue;
}
// Step 3d. Load the player's initial position and orientation
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;
}
// 3e. Load in a sprite definition..
else if (!strcmpi(typ,"S"))
{
// Parameters are: [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)
free(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;
}
// 3f. Load in an object definition. (an instance of a sprite)
else if (!strcmpi(typ,"O"))
{
// Parameters are: [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 3g. Load a texture definition.
else if (!strcmpi(typ,"T"))
{
// Parameters are: [NUMBER] [Filename] [transparent] [translucent] [recessed] [Walk Through]
sscanf(line,"%*s %d %s %d %d %d %d",&txtnum,typ,&tp,&tl,&re,&de);
// 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].AnimNbr = -1;
// Load the bitmap.
Load_Wall(typ,txtnum);
}
// Step 3h. Load a door definition.
else if (!strcmpi(typ,"D"))
{
// Parameters are: [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);
}
// Step 3i. Load in the actual wall 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))
Die("Invalid map size.\n");
// 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
{}
*(world+(row<<6)+column) = translateChar(ch); // Place the converted value in the map.
}
}
}
// Step 3j. Load in the Floor texture map.
else if (!strcmpi(typ,"F"))
{
// The floor must have the same dimensions as the walls.
for (row=0; row<WORLD_ROWS; row++)
{
for (column=0; column<WORLD_COLUMNS; column++)
{
while((ch = getc(fp))==10) // filter out CR
{}
*(FloorMap+(row<<6)+column) = translateChar(ch); // Add it to the floor map.
}
}
}
// Step 3k. Load in the ceiling texture map.
else if (!strcmpi(typ,"C"))
{
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
{}
*(CeilMap+(row<<6)+column) = translateChar(ch); // Add it to the ceiling map.
}
}
}
// Step 3l. Load in the zone map.
else if (!strcmpi(typ,"Z"))
{
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
{}
*(ZoneMap+(row<<6)+column) = translateChar(ch); // Add it to the ceiling map.
}
}
}
// Step 3m. Load in the Floor Lighting map.
else if (!strcmpi(typ,"L"))
{
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
{}
*(FloorLightMap+(row<<6)+column) = translateChar(ch); // Add it to the ceiling map.
}
}
}
// Step 3n. Load in the Ceiling Lighting Map.
else if (!strcmpi(typ,"I"))
{
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
{}
*(CeilLightMap+(row<<6)+column) = translateChar(ch); // Add it to the ceiling map.
}
}
}
// Step 3o. Load in a lighting tile definition.
else if (!strcmpi(typ,"G"))
{
// Parameters are: [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)
free(LightFlags[txtnum].PulsePattern);
LightFlags[txtnum].PulsePattern = (char *)malloc(strlen(typ)+1);
strcpy(LightFlags[txtnum].PulsePattern,typ);
}
}
}
// Step 3p. Load in a Zone Option.
else if (!strcmpi(typ,"N")) // Load in the zone options.
{
// N [NUMBER] [Ambient] [Inside] [Fog]
sscanf(line,"%*s %d %d %d",&txtnum, &de, &tp, &re);
ZoneAttr[txtnum].ambient = de;
ZoneAttr[txtnum].inside = tp;
ZoneAttr[txtnum].fog = re;
}
// Step 3q. Load in an animated texture.
else if (!strcmpi(typ,"A"))
{
// 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)
{
free(Animations[TextureFlags[sp_num].AnimNbr].Frames[i]);
Animations[TextureFlags[sp_num].AnimNbr].Frames[i] = NULL;
}
free(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 **)malloc((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;
}
// Step 3r. Load in an animated light tile.
else if (!strcmpi(typ,"H"))
{
// [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)
{
free(Light_Animations[LightFlags[sp_num].AnimNbr].Frames[i]);
Light_Animations[LightFlags[sp_num].AnimNbr].Frames[i] = NULL;
}
free(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 **)malloc((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;
}
// Step 3s. Load in a trigger.
else if (!strcmpi(typ,"R"))
{
// [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 %98c",&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 %98c",&cs,&rs,&ce,&tl,trg_parms);
Triggers[Num_Triggers].X1 = cs;
Triggers[Num_Triggers].Y1 = rs;
Triggers[Num_Triggers].Radius = ce;
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;
// 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;
}
}
// Step 3t. Load in the ambient light level.
else if (!strcmpi(typ,"B"))
{
sscanf(line,"%*s %d",&sp_num);
ambient_level = sp_num;
}
// Finally, This quits... along with EOF.
else if (!strcmpi(typ,"#END"))
break;
// Step 4. Get the next line.
fgets(line,256,fp);
}
fclose(fp); // Close the file,
return 1; // and return.
}
// The following functions render the world...
//
// 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.)
//
void draw_sliver_transparent(void)
{
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 *base, // Beginning of the current texture row in memory.
*lightbase; // Beginning of the current light tile row in memory.
unsigned char *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 scaling 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);
// 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 = double_buffer+*(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;
}
}
}
//
// 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.)
//
void draw_sliver_trans(void)
{
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 *base, // Beginning of the current texture row in memory.
*lightbase;
unsigned char *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);
// 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 = double_buffer+*(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;
}
}
}
//
// 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.)
//
void draw_sliver(void)
{
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 *base, // Beginning of the current texture row in memory.
*lightbase;
unsigned char *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);
// 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 = double_buffer+*(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;
}
}
}
//
// 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.
//
int Cast_X_Ray(long x, long y, long view_angle, long &yi_save, long &x_save, int &lighttile, int &zone)
{
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;
}
}
//
// 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.
//
int Cast_Y_Ray(long x, long y, long view_angle, long &xi_save, long &y_save, int &lighttile, int &zone)
{
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;
}
}
//
// 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.
//
void Texture_FloorCeil(long x, long y, long view_angle)
{
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.
unsigned int flor_level,
ceil_level,
ZoneAmb;
register long LightDist, // How away the point is from the eye.
distance, // How 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.
// 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.
if (angle < ANGLE_0)
angle += ANGLE_360;
if (angle >= ANGLE_360)
angle -= ANGLE_360;
// Get the line structure for the current column.
// 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 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 the 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));
// 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;
// Now we draw the ceiling and floor at the same time.
if (ZoneAttr[*(ZoneMap+MapOff)].fog == 1)
{
*(double_buffer+*(row+ROW)+col) = *(FogMap + flor_level + *(WallTextures[*(FloorMap+MapOff)-1]+TxtOff));
*(double_buffer+*(row+149-ROW)+col) = *(FogMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff));
}
else
{
*(double_buffer+*(row+ROW)+col) = *(LightMap + flor_level + *(WallTextures[*(FloorMap+MapOff)-1]+TxtOff));
*(double_buffer+*(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 > 0); ++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)
*(double_buffer+*(row+ROW)+col) = *(FogMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff));
else
*(double_buffer+*(row+ROW)+col) = *(LightMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff));
}
}
}
//
// This routine handles the rendering calculations of the sprites. It is work
// in progress.
//
void process_sprites(long x, long y, long view_angle)
{
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.
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 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 .
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;
}
}
}
}
}
//
// 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.
//
void DrawAutomap(long Xpos, long Ypos)
{
long x,y;
// Frame in the automap.
for (x=0; x<70; ++x)
for (y=0; y<70; ++y)
*(double_buffer+*(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)
{
*(double_buffer+*(row+(23+y))+23+x) = 112;
}
else if (TextureFlags[*(world+(y<<6)+(x))].IsTransparent)
{
*(double_buffer+*(row+(23+y))+23+x) = 121;
}
else
{
*(double_buffer+*(row+(23+y))+23+x) = 2;
}
// Locate the player.
*(double_buffer+*(row+(23+(Ypos>>7)))+23+(Xpos>>7)) = 198;
}
//
// 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 begin 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).
//
void Render_View(long x,long y,long view_angle)
{
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 .
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.
// printf("Drawing ray %d...",(int)ray);
for (indx = s_line->num_objs-1; indx >= 0; --indx)
{
// printf("%d..",(int) 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...
//
// 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 start_door(int x, int y)
{
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 1;
// 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 1;
// 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 1;
// 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 *)malloc(16384l)))
return Die("Error allocating memory for opening door. \n");
// Copy the texture over.
memcpy(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;
return 1;
}
//
// 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.
//
void open_doors()
{
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 *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)
memcpy(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)
memset(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)
memcpy(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)
memset(base+(x*128l)+br,0,er-br);
// Move bitmap towards right side from the middle...
for (x=bc; x <= mc-spd; ++x)
memcpy(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)
memset(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;
}
}
//
// 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.
//
void close_doors()
{
int i, // Outer loop index variable.
x, // Bitmap copy loop index variable.
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 *base, // Pointer to the animated texture.
*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)
{
free(base);
WallTextures[Opening_Doors[i].New_Txt_Num] = NULL;
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;
}
}
}
}
//
// This method performs the action associated with a trigger that has
// been activated. This is still an area of expansion.
//
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.
Load_Map(x,y,view_angle,Triggers[TriggerNbr].NewFileName);
// Now how about a special effect...
copyscrn(BkgBuffer);
Render_View(x,y,view_angle);
blit();
break;
case 9: // Teleport player.
x = Triggers[TriggerNbr].MapX;
y = Triggers[TriggerNbr].Mapy;
view_angle = Triggers[TriggerNbr].NewAngle;
// Now how about a 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;
}
}
//
// 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...
//
void CheckTriggers(long &x, long &y, long &view_angle)
{
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;
}
}
}
//
// 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 Rotate_Animations(void)
{
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;
}
//
// This function will rotate the blinking lights through thier patterns...
//
void processblinklights(void)
{
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;
}
}
}
//
// This method performs the user movement and collision detection.
//
void ProccessUserMovement( void )
{
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.
x = Player_1.x;
y = Player_1.y;
view_angle = Player_1.view_angle;
x_cell = Player_1.x>>7;
y_cell = Player_1.y>>7;
x_sub_cell = Player_1.x & 127;
y_sub_cell = Player_1.y & 127;
// Step 3. Check for movement. The lower nibble of KeyState is for movement ONLY. ( Arrow Keys )
if ( KEY_DOWN(VK_LEFT) || KEY_DOWN(VK_RIGHT) || KEY_DOWN(VK_UP) || KEY_DOWN(VK_DOWN) )
{
// Reset dx and dy.
dx=dy=0;
// If the player is pressing the Right arrow,
if (KEY_DOWN(VK_LEFT))
{
// 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 (KEY_DOWN(VK_RIGHT))
{
// 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 (KEY_DOWN(VK_UP))
{
// 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 (KEY_DOWN(VK_DOWN))
{
// 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;
// Step 4. 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-TOOCLOSE ) && (!TextureFlags[*(world+(y_cell<<6)+x_cell+1)].IsRecessed)) || //And we're too close to it,
((x_sub_cell > CELL_X_SIZE-TOOCLOSE + 30 ) && (TextureFlags[*(world+(y_cell<<6)+x_cell+1)].IsRecessed)))) // (don't forget recessed panels)
{
if (TextureFlags[*(world+(y_cell<<6)+x_cell+1)].IsRecessed)
x-= (x_sub_cell-(CELL_X_SIZE-TOOCLOSE +30 )); // then MOVE US BACK A BIT.
else
x-= (x_sub_cell-(CELL_X_SIZE-TOOCLOSE )); // 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 < TOOCLOSE ) && (!TextureFlags[*(world+(y_cell<<6)+x_cell-1)].IsRecessed)) || //And we're too close to it,
((x_sub_cell < TOOCLOSE - 30 ) && (TextureFlags[*(world+(y_cell<<6)+x_cell-1)].IsRecessed)))) // (don't forget recessed panels)
{
if (TextureFlags[*(world+(y_cell<<6)+x_cell-1)].IsRecessed)
x+= (TOOCLOSE - x_sub_cell - 30) ; // then MOVE US BACK A BIT.
else
x+= (TOOCLOSE - 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-TOOCLOSE ) && (!TextureFlags[*(world+((y_cell+1)<<6)+x_cell)].IsRecessed)) || //And we're too close to it,
((y_sub_cell > CELL_Y_SIZE-TOOCLOSE + 30 ) && (TextureFlags[*(world+((y_cell+1)<<6)+x_cell)].IsRecessed)))) // (don't forget recessed panels)
{
if (TextureFlags[*(world+((y_cell+1)<<6)+x_cell)].IsRecessed)
y-= (y_sub_cell-(CELL_Y_SIZE-TOOCLOSE + 30 )); // then MOVE US BACK A BIT.
else
y-= (y_sub_cell-(CELL_Y_SIZE-TOOCLOSE )); // 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 < TOOCLOSE ) && (!TextureFlags[*(world+((y_cell-1)<<6)+x_cell)].IsRecessed)) || //And we're too close to it,
((y_sub_cell < TOOCLOSE - 30 ) && (TextureFlags[*(world+((y_cell-1)<<6)+x_cell)].IsRecessed)))) // (don't forget recessed panels)
{
if (TextureFlags[*(world+((y_cell-1)<<6)+x_cell)].IsRecessed)
y+= (TOOCLOSE-y_sub_cell - 30); // then MOVE US BACK A BIT.
else
y+= (TOOCLOSE-y_sub_cell); // then MOVE US BACK A BIT.
}
}
}
Player_1.x = x;
Player_1.y = y;
Player_1.view_angle = view_angle;
return;
}
//
// This method sets up the rendering engine.
//
int StartEngine(void)
{
// make all the trig and support tables.
if ( Build_Tables() < 0 )
return -1;
// Now, load the automap image.
AutoMapBkgd = Load_PCX("automap.pcx",70*70,0);
if(NULL == AutoMapBkgd )
return -1;
// Load in the map, textures, etc. and set the player's initital position.
if ( Load_Map(Player_1.x,Player_1.y,Player_1.view_angle,"raymap.dat") < 0 )
return -1;
// Load the background image.
if ( load_bkgd("BKGD.PCX") < 0)
return -1;
//Initialize the double buffer
init_double_buffer();
// Copy it to the double buffer,
copyscrn(BkgBuffer);
// Dump it to the screen,
blit();
return 1;
}
int InitTitleAnimation (void)
{
// Load the game title.
if ( load_bkgd("TITLE.PCX") < 0)
return -1;
//Initialize the double buffer
init_double_buffer();
// Copy it to the double buffer,
copyscrn(BkgBuffer);
// Dump it to the screen,
blit();
// Allocate memory for the fire animation.
FireSrc = (char far *)malloc((unsigned)64321);
if (FireSrc == NULL)
{
Die("Unable to allocate the fire animation buffer.");
return -1;
}
memset(FireSrc,0,64320);
// Seed the random number generator.
srand( (unsigned)time( NULL ) );
return 1;
}
int EndTitleAnimation (void )
{
if (FireSrc != NULL)
free(FireSrc);
return 1;
}
void AnimateFireScreen (void)
{
unsigned short I;
unsigned long x;
unsigned long y;
int value;
copyscrn(BkgBuffer);
for (I=0; I < 319; ++I) // Place a random bottom on Src.
{
if ((rand() % 6) > 1)
*(FireSrc+64000+I) = 112;
else
*(FireSrc+64000+I) = 0;
}
for (y=100; y<200; ++y) // Average pixels, moving up 1 row
{
for (x=1; x<319; ++x) // adding the decay.
{
value = (*(FireSrc+x-1+(y * 320ul))+*(FireSrc+x+1+(y * 320ul))
+*(FireSrc+x-1+((y+1) * 320ul))+*(FireSrc+x+((y+1) * 320ul))
+*(FireSrc+x+1+((y+1) * 320ul )))/5 - 1;
if ((value > 0) && y < 196)
*(double_buffer+x+((y+4) * 320ul)) = value;
*(FireSrc+x+((y-1) * 320ul)) = value;
}
}
blit();
}
//
// This method performs the main game loop. It calls a set of methods to do background processing,
// then it performs the user movement, and then processing of triggers. Finally, it renders the view.
//
void MainLoop(void)
{
// Step 1. Process background things (doors, animations, lights, etc.)
open_doors(); // Open all opening doors a little more.
close_doors(); // close all closing doors a little more.
Rotate_Animations(); // Cycle the animations.
processblinklights(); // Do the next frame in linking lights.
CheckTriggers(Player_1.x,Player_1.y,Player_1.view_angle); // Check the triggers, and perform any actions.
// Step 2. Allow the user to move
ProccessUserMovement();
// Step 3. Render the new view.
copyscrn(BkgBuffer); // Clear the double buffer to the blank background.
Render_View(Player_1.x,Player_1.y,Player_1.view_angle); // Render the view on top of the background.
if (ShowAutomap) DrawAutomap(Player_1.x,Player_1.y); // Render the automap, if selected.
blit();// Send it all to the screen.
return;
}
//
// The main entry function. It creates the main widow, and calls the necessary functions to
// initialize the engine, and then enters the main message loop.
//
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
MSG msg;
HWND hWnd;
WNDCLASSEX wc;
bIsActive = FALSE;
bRunGame = FALSE;
// Register the main application window class.
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, lpszTitle);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = lpszAppName;
wc.lpszClassName = lpszAppName;
wc.cbSize = sizeof(WNDCLASSEX);
wc.hIconSm = LoadImage(hInstance, lpszTitle, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
if(!RegisterClassEx(&wc))
{
return(FALSE);
}
hInst = hInstance;
// Create the main application window.
hWnd = CreateWindowEx(WS_OVERLAPPED, lpszAppName, lpszTitle, WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME,
50, 50, (320 + 6), (200 + 44), NULL, NULL, hInstance, NULL);
if(!hWnd)
{
return(FALSE);
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
ghWnd = hWnd;
DblBuff = NULL;
// Prepare for the title screen and the animation.
if ( InitTitleAnimation() < 0 )
return(-1);
// We're going to try and use the windows timer to set the upper limit to 60 frames / sec.
SetTimer(ghWnd, 1, 20, NULL);
// enter main event loop
while(1)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// test if this is a quit
if(msg.message == WM_QUIT)
{
break;
}
// translate any accelerator keys
TranslateMessage(&msg);
// send the message to the window proc
DispatchMessage(&msg);
}
}
KillTimer(ghWnd, 1);
free_tables();
return(msg.wParam);
}
//
// The winproc for the main window handles messages from the menu bar, and from user input.
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
long x_cell;
long y_cell;
switch(message){
case WM_ACTIVATEAPP:
bIsActive = (BOOL) wParam;
break;
case WM_COMMAND:
switch (wParam)
{
case IDM_ABOUT:
DialogBox(hInst,"AboutBox", hWnd,(DLGPROC)About);
break;
case IDM_SHOWKEYS:
DialogBox(hInst, "KeysList", hWnd,(DLGPROC)About);
break;
case IDM_RUN:
StartEngine() ;
bRunGame = TRUE;
break;
case IDM_EXIT:
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
case IDM_DISPLAY_320:
MoveWindow(ghWnd, ClientxPos, ClientyPos, 320, 240, TRUE);
break;
case IDM_DISPLAY_640:
MoveWindow(ghWnd, ClientxPos, ClientyPos, 640, 480, TRUE);
break;
case IDM_DISPLAY_960:
MoveWindow(ghWnd, ClientxPos, ClientyPos, 960, 720, TRUE);
break;
}
break;
case WM_KEYDOWN:
// Check to see if the user wants to see the auto map.
if (((int)wParam == VK_TAB ) && (bRunGame == TRUE ))
ShowAutomap = !ShowAutomap;
// Check to see if the user wants to quit (ESC KEY).
if((int)wParam == VK_ESCAPE)
PostMessage(ghWnd,WM_CLOSE,0,0);
// Check to se if the user is requesting a screen shot ( F1 Key ).
if ((int)wParam == VK_F1)
Save_Pcx("scrnsht.pcx",double_buffer,320ul,200ul);
// Now check to see if the player wants to change the torch level ( F2 Key ).
if (((int)wParam == VK_F2) && (bRunGame == TRUE ))
{
--TorchLevel;
if (TorchLevel == 0)
TorchLevel = 8;
CalcTorchLevel(TorchLevel);
}
// Check to see if the player wants to open a door ( SPACE key ).
if (((int)wParam == VK_SPACE) && (bRunGame == TRUE ))
{
x_cell = Player_1.x>>7;
y_cell = Player_1.y>>7;
// If the player is facing to the WEST
if ((Player_1.view_angle > ANGLE_330) || (Player_1.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 ((Player_1.view_angle > ANGLE_60) && (Player_1.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 ((Player_1.view_angle > ANGLE_150) && (Player_1.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 ((Player_1.view_angle > ANGLE_240) && (Player_1.view_angle < ANGLE_300))
{
if (TextureFlags[*(world+((y_cell-1)<<6)+x_cell)].IsDoor)
start_door(x_cell,y_cell-1);
}
}
break;
case WM_SIZE:
ClientWidth = LOWORD(lParam);
ClientHeight = HIWORD(lParam);
break;
case WM_MOVE :
ClientxPos = (int) LOWORD(lParam);
ClientyPos = (int) HIWORD(lParam);
break;
case WM_PAINT:
BeginPaint(hWnd, &ps );
EndPaint(hWnd, &ps );
break;
case WM_TIMER:
if (bRunGame == TRUE )
MainLoop(); // call main logic module.
else
AnimateFireScreen(); // Call title screen animation module.
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
//
// The windproc for the ABOUT box.
//
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_INITDIALOG:
return(TRUE);
break;
case WM_COMMAND:
if(LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, TRUE);
return(TRUE);
}
break;
}
return(FALSE);
}