/*************************************************************************/ /*** ***/ /*** 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); }