/*************************************************************************/ /*** ***/ /*** R A Y . C P P (C) 1995 NPS Software ***/ /*** ***/ /*** Writen By: Brad Broerman ***/ /*** Started: June 1994 ***/ /*** Last Modified: March 13, 1999 ***/ /*** Version: 1.5 ***/ /*** ***/ /*** ***/ /*** A 3-D Raycasting graphics engine with up to a 64x64 cell map, ***/ /*** 35 textures (256 color 128x128 PCX files), textured floor and ***/ /*** ceiling, sprites, 3 types of animated doors, 20 levels of ***/ /*** transparency, translucency, animated textures,lighting and ***/ /*** depth cueing (with fog effects), zone maps, and triggers. ***/ /*** ***/ /*** To be added: 2 more door types, door side texturing, door api, ***/ /*** sprite logic api, lighting api, var. height floors, look up/down,***/ /*** animated palette section, main menu, stats updating, trigger api,***/ /*** better kbd map. ***/ /*** ***/ /*** Clean up: Split functionality in to multiple source and header ***/ /*** files, Optimize rendering routines (convert to assembly), ***/ /*** move collision detection functionality from main() to separate ***/ /*** functions. ***/ /*** ***/ /*************************************************************************/ /*** Change Log: ***/ /*** ***/ /*** 06/15/94 Started RAY project ***/ /*** 06/30/94 Basic raycaster engine finished. ***/ /*** 07/30/94 Base engine optimized. ***/ /*** 09/10/94 Floor / ceiling texturing added. ***/ /*** 09/15/94 RAY first distributed. ***/ /*** ***/ /*** 09/25/98 Started working on Ray again... ***/ /*** 10/03/98 Added depth cueing/fog effects ***/ /*** 10/06/98 Added garage and elevator door styles ***/ /*** 10/10/98 Fixed doors, now recessed 1/2 way back ***/ /*** 10/14/98 Fixed array overruns ... Thx to Jorge A. Martin ***/ /*** 10/29/98 Added animated textures, translucency, and ***/ /*** recesed tile flags. Combined T & X tiles. ***/ /*** 11/01/98 Finally got it to run in Protected Mode! ***/ /*** 11/10/98 Added tiled light maps. ***/ /*** 12/05/98 Added animated lighting ***/ /*** 01/11/99 Increased PCX file resolution to 128x128 ***/ /*** 02/24/99 Added zone maps and zone options. ***/ /*** 03/11/99 Added Triggers (except 2,3,&10) ***/ /*** ***/ /*************************************************************************/ #include <time.h> #include <io.h> #include <conio.h> #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <bios.h> #include <fcntl.h> #include <memory.h> #include <malloc.h> #include <math.h> #include <string.h> // If defined, then floor and ceiling are rendered. #define CAST_FLOORCIEL // How close you can get to a wall. #define OVERBOARD 60 // Our definitions for the angles. ANGLE_60 = width of viewport. (60 deg. FOV) // All the other angles are based on this. #define ANGLE_0 0 #define ANGLE_1 4 #define ANGLE_2 8 #define ANGLE_4 16 #define ANGLE_5 20 #define ANGLE_6 24 #define ANGLE_10 40 #define ANGLE_15 60 #define ANGLE_20 80 #define ANGLE_30 120 #define ANGLE_45 180 #define ANGLE_60 240 #define ANGLE_90 360 #define ANGLE_120 480 #define ANGLE_135 540 #define ANGLE_150 600 #define ANGLE_180 720 #define ANGLE_210 840 #define ANGLE_225 900 #define ANGLE_240 960 #define ANGLE_270 1080 #define ANGLE_300 1200 #define ANGLE_315 1260 #define ANGLE_330 1320 #define ANGLE_360 1440 // The size of each cell. 128 makes the math easier. (also textures are 128 pixels wide) #define CELL_X_SIZE 128 #define CELL_Y_SIZE 128 // The maximum size of our world 64x64 cells. #define MAX_WORLD_ROWS 64 #define MAX_WORLD_COLS 64 // These defines are used for lighting. #define MAX_LIGHT_LEVELS 128 #define PALETTE_SIZE 256ul #define MAX_DISTANCE (MAX_WORLD_ROWS * MAX_WORLD_COLS) // We need a large stack. extern unsigned _stklen = 20480U; // The generic PCX header structure. I've listed some important fields, but // I won't check for them until this code is "officially" released. typedef struct PCX_HEADER_TYPE { char manufacturer; // We don't care about these... char version; char encoding; char bits_per_pixel; // We want an 8 here... int x,y; // We ignore these. int width,height; // Will be either 64x64 or 320x200 int horiz_rez; // Forget about these... int vert_rez; char EGA_palette[48]; // We won't even touch this. char reserved; // or this. char Num_Planes; // We want 1 int bytes_per_line; // Either 64 or 320 int palette_type; // We can forget about these too.. char padding[58]; } PCX_HEADER; // Stores slice information for rendering. This is used so that we can // add sprites, transparency, etc. and have the rendering engine draw them too. struct col_obj { int top, // Wihch row to begin drawing this slice. col, // Which screen column this slice is in. scale, // How much do I scale this slice row, // Which texture column to render for this slice. texnum, // The number of the wall/sprite texture to use. lightnum, // The number of the lighting tile to use. If -1, then use sptlight as the light for the whole sliver. sptlight, // Flat light value (if used). zoneambient, // Ambient light for the zone the sliver is in. usefog; // Use the fog table or the darkness table for the sliver. long dist; // How far away is it (Used for sorting in sprites and light sourcing.) }; // This implements the multiple levels of transparency we can use. The // objecs are ordered from nearest to farthest. struct scan_line_t { col_obj line[10]; // We can go to 10 levels of transparency, sprites, etc. int num_objs; // The number of objects in 'line'. }; // This structure is kept for every texture map loaded into the game. It is // used (mainly) for doors and transparent items. struct TextureFlagsTYPE { char IsTransparent, // 1=transparent, 0=not. IsTranslucent, // 1=Translucent, 0=not. IsDoor, // 1= door, 0= regular wall. DoorType, // 1=normal, 2=elevator, 3=garage IsSecretDoor, // 1=Secret door, 0=Normal door. IsRecessed, // 1=Recessed texture(walls), 0=Not recessed DoorSideTxt, // This holds the texture number for the door sides. (Not used yet.) CanWalkThru, // 1 = you can walk trough it, 0 = Can not walk through it. speed, // How fast the door opens and closes. (cols per frame) delay, // How many frames to wait before closing the door. bc, // Upper left corner of the door br, ec, // Lower right corner of the door er, FloorReflect; int AnimNbr; }; // This structure keeps the parameters for the lights, including light type, // and blink parameters. struct LightFlagsTYPE { char LightType; // Either a 1, or a 2. 1: Regular blink, 2: Custom blink. int PulseWidth, // For a regular blink, defines the pulse. Period, CurrPatternIndx; // counter... see ProcessBlinkLights() for usage. char *PulsePattern; // For a custom blinking light, this is the pattern: 1 - on, 0= off. char far *OriginalBitmap; // Used to track the original bitmap. see ProcessBlinkLights() for usage. int AnimNbr; }; // This structure keeps track of all the doors that are opening and closing // at any particular time. struct Open_Doors_Info { char USED; // Used in finding an empty spot to use. See the code. int x, // Map location of the door. y, delay, // The door's delay from above. Orig_Txt_Num, // The texture number of the original (un-opened) door. New_Txt_Num, // Points to the working copy of the door tex. Pcnt_opened, // Percent opened. Num_Col_Opnd, // The number of columns the door is opening. Opening; // 1=opening, 0=closing. }; // This structure keeps track of information associated with all doors on the map. // Work in progress. struct Door_Info { int x, // Map location of the door. y, locked, // Can I open it or not? 1 = Locked , 0 = Un-Locked. opening; // lets up know if the texture is not the original one. (!= 0) --> Don't touch! }; // This is the basic structure for the sprites. struct Sprite_Type { char *Frames[8]; // Stores the views (angles object can be viewed from) int translucent; // Specifies wether or not this sprites texture is translucent. (Sprites are always transparent) }; // Sprite instance variables. // Work in progrss, struct object { int sprite_num, // The sprite array object we're using. x, // The sprites position in the map (fine coordiates) y, angle, // The angle the sprite is facing. state; // The sprites state (To be used later) }; // This structure holds information about anumated textures. This includes the // frames, and counter / timing information. struct Animations_Typ { char far **Frames; // Array of pointers to the frames. int Flagged, // 1=Animate this texture, 0=Show only 1st frame. Txt_Nbr, // Which texture in the map this applies to. Delay, // Delay time between frames. Timer, // Counts the delay for this texture. Curr_Frame, // The frame we are currently showing. Nbr_Frames; // The number of frames. }; // This structure holds information about the various defined zones. These zones // could be rooms, the outside, etc. struct zone_info { char ambient, // Ambient light level 0-64 for this zone. inside, // 1=inside, 0=outside (turns on/off ceiling lighting) fog; // 1=Use fog, 0=use normal depth cueing. }; // This structure is used to record trigger information. // Work in progrss... struct trigger_info { int Trigger_Nbr, Trigger_Type, Trigger_Area_Type, X1, Y1, X2, Y2, Radius, MapX, Mapy, ZoneNbr, NewLightLvl, NewAngle, TxtNbr; char NewFileName[13]; }; // These globals are used for speeding up the tracing and rendering parts. // These are copied from the col_obj structure above. long sliver_dist; int sliver_start, // Where (y) to begin drawing the slice. sliver_column, // Screen column to draw in. sliver_scale, // Height of the sliver (pixels). sliver_row, // Column of the texture to draw. sliver_map, // Which texture to use. sliver_light, // Which lighting tile to use. sliver_sptlt, // Lighting level for sprites. sliver_zoneamb,// Ambient light delta for the current zone. sliver_usefog, // If 1, use fog table, else use darkness table. // These are used in the tracing process. They are global for the same reason as above. cell_xx, // These store the coordinates for the horiz. trace. cell_yx, // cell_xy, // These store the coordinates for the vert. trace. cell_yy; // // These are for the new keyboard ISR. // They are globals BECAUSE they are used in the ISR. void interrupt (*OldInt9)(...); int volatile KeyScan = 0; int volatil KeyState = 0; char volatile inProgress = 0; // These are global because they're used all over, and I wanted to generate them ONLY // at the beginning of the program. It also saves us on stack usage. char far *AutoMapBkgd; char far *world = NULL; // pointer to matrix of cells that make up world char far *FloorMap = NULL; // pointer to the matrix of cells that define the floor textures. char far *CeilMap = NULL; // pointer to the matrix of cells that define the ceiling textures. char far *ZoneMap = NULL; // pointer to the matrix of calls that define the 'zones' for various effects. char far TriggerMap[64][64]; // Assists in quick detection of simple triggers. Maps location to trigger number. char far *CeilLightMap = NULL; // pointer to the ceiling light map. char far *FloorLightMap = NULL; // pointer to the floor light map. int Num_Textures = 0; // The number of textures we've loaded. char far *WallTextures[80]; // The 64x64 wall textures, door textures, and visible sprites. int Num_Light_Tiles = 0; // The number of lighting tiles for the floor/ceiling light maps. char far *LightMapTiles[30]; // The 64x64 lighting map tiles. char far *LightLevel = NULL; // Gives the lighting level as a function of distance. char far *LightMap = NULL; // Maps a color/light pair into a pallete value for depth cueing. 32*256 in size. char far *FogMap = NULL; // Maps a color/light pair into a pallete value for fog effects. 32*256 in size. char far *Translucency = NULL; // Trans. lookup. Blends a color/color pair with 10% color 1, 90% color 2. Gives palette value. char far *BkgBuffer = NULL; // The background picture. long far *tan_table = NULL; // tangent tables used to compute initial long far *inv_tan_table = NULL; // intersections with ray long far *y_step = NULL; // x and y steps, used to find intersections long far *x_step = NULL; // after initial one is found long far *cos_table = NULL; // used to cacell out fishbowl effect long far *sin_table = NULL; // Used for movement in different directions. long far *inv_cos_table = NULL; // used to compute distances by calculating long far *inv_sin_table = NULL; // the hypontenuse long far *sliver_factor = NULL; // Scale factor for texture slivers. long Floor_Row_Table[151]; // Pre-calculated row start values. long far *Floor_inv_cos_table = NULL; // The following tables are used in the math for the floor/ceiling. long far *Floor_cos_table = NULL; long far *Floor_sin_table = NULL; long far *Floor_Dx_table = NULL; long row[200]; // Pre-calculates 320*row. int oldmode; // Original grahics/text mode when the game starts. char far *scrnbuf, // Pointer to video memory *dblbuf; // Pointer to the double buffer ram. int WORLD_ROWS = 64; // Current size of the map. int WORLD_COLUMNS = 64; long WORLD_X_SIZE = 8192; // Total size of the world (64cells x 128 per cell) long WORLD_Y_SIZE = 8192; short ambient_level = 3; // The global ambient light level. char palette[256][3]; // The current palette. int Num_Sprites = 0; // The number of sprites defined. int Num_Objects = 0; // The number of objects defined. int Sprite_Frame[361]; // Determines frame to display for sprites. int Num_Animations = 0; // The number of animated textures we are using. int Num_LightAnimations = 0; // The number of animated lighting tiles we are using. int Num_Triggers = 0; // The number of defined triggers. struct scan_line_t scan_lines[240]; // Stores the scan-line info. struct TextureFlagsTYPE TextureFlags[81]; // Stores the texture flags for each loaded texture. struct LightFlagsTYPE LightFlags[30]; // Stores the flags for the lights. struct Open_Doors_Info Opening_Doors[10]; // Moving door info. struct zone_info ZoneAttr[20]; // Attributes for the zones. Currently: ambient light, fog, and inside/outside struct Sprite_Type Sprites[10]; // The sprites. These are the bitmaps. struct object Objects[30]; // The actual objects that use the sprites. struct Animations_Typ Animations[10]; // Up to 10 animated textures. struct Animations_Typ Light_Animations[10]; // Up to 10 animated lighting tiles. struct trigger_info Triggers[10]; // The loaded triggers. // Graphics functions... void initscreen(int buf) { // Initialize the grapphics display, and the double buffer. // IF buf = 0, then NO double buffering. int om; // The old mode - from the BIOS call. dblbuf = NULL; scrnbuf=(char far *)MK_FP(__SegA000,0x0000); // Make a pointer to screen memory. if (buf) // If we want one, allocate a double buffer. { dblbuf =(char far *)farmalloc(64000l); if (!dblbuf) { printf("Error allocating screen buffer. Exiting. \n"); exit(1); } } else // Otherwise, just skip it. dblbuf = scrnbuf; // Now we get the old graphics mode, and set the new one. asm { mov ah,0xf int 0x10 xor ah,ah mov om,ax mov ax,0x0013 int 0x10 } oldmode = om; return; } void txtscreen(void) { // This function resets the VGA to the old mode. char om = oldmode&0xff; asm { mov ah,0x00 mov al,om int 0x10 int 0x10 } farfree(dblbuf); } void clearscrn(int c) { // This function clears te screen to the color "C", by blasting the // byte to the double buffer as fast as possible! unsigned int Sg, // These are declared here for the asm call. Of, color; Sg = FP_SEG(dblbuf); // Get the address of the double buffer. Of = FP_OFF(dblbuf); color = (c<<8)|c; // Replicate the color value in the upper byte. asm { mov ax,Sg // Now blast it to the buffer. mov es,ax mov di,Of mov ax,color mov cx,32000 rep stosw } } void copyscrn (char far *Buffer) { // This function copies a bitmap from a buffer to the double buffer. unsigned int Sg, Of, Bsg, Bof; Sg = FP_SEG(dblbuf); // Get the adderess of the double buffer. Of = FP_OFF(dblbuf); Bsg = FP_SEG(Buffer); // Get the address of the input buffer. Bof = FP_OFF(Buffer); // Slam it to the doubl buffer. asm { push ds mov ax,Bof mov si,ax mov ax,Bsg mov ds,ax mov ax,Sg mov es,ax mov di,Of mov cx,32000 rep movsw pop ds } } void dissolveto(char far *Buffer) { // This routine does a "dissolve" from the curent screen to that stored // in Buffer. It writes directly to the screen. unsigned long index, x,y, offset; // Do LOTS of copies at random locations.. // (We only need the top 1/2 of the screen, IE Dirty animation...) for (index=0; index<=500000; index++) { x = rand()%320; y = rand()%100; offset = x+(y<<6)+(y<<8); *(scrnbuf+offset) = *(Buffer+offset); } } void swapdblbuf() { // This routine copies the double buffer to the screen. unsigned int Sg, Of, vidseg; Sg = FP_SEG(dblbuf); Of = FP_OFF(dblbuf); vidseg = __SegA000; // This snippet waits for the beginning of a verticle blank interval. // while(inportb(0x3da) & 0x08) // Wait for any current VBI to end. // {}; // while(!(inportb(0x3da) & 0x08)) // Wait for the beginning of the next one. // {}; // Now we can blast the buffer to the screen. asm { push ds mov ax,Of mov si,ax mov ax,Sg mov ds,ax mov ax,vidseg mov es,ax xor di,di mov cx,32000 rep movsw pop ds } } void fadeout(int del) { // This function fades the picture on the screen to black, using the // palette. // int r,g,b, // The color values pl, // The pallete entry number index; // Loop counter... used as a divisor. // Loop through 16 stages of darkening... for (index = 1; index <= 16; ++index) { // And do it for all 256 colors... for (pl = 0; pl < 256; ++pl) { // Divide the current palette entry by the divisor, r = palette[pl][0]/index; g = palette[pl][1]/index; b = palette[pl][2]/index; // And stick it in the VGA outportb(0x3c6,0xff); outportb(0x3c8,pl); outportb(0x3c9,r); outportb(0x3c9,g); outportb(0x3c9,b); } // Without this, the whole thing's too fast... delay(del); } // Finally, we black it all out. for (pl = 0; pl < 256; ++pl) { outportb(0x3c6,0xff); outportb(0x3c8,pl); outportb(0x3c9,0); outportb(0x3c9,0); outportb(0x3c9,0); } } void fadein(int del) { // // This function fades in from Black to the current palette values. // int r,g,b, // The color values to plug into the VGA pl, // The index into the palette structure. index; // loop counter, used as the divisor... // Make sure we're starting out from black. for (pl = 0; pl < 256; ++pl) { outportb(0x3c6,0xff); outportb(0x3c8,pl); outportb(0x3c9,0); outportb(0x3c9,0); outportb(0x3c9,0); } // Now, go through all 16 levels of darkness. for (index = 16; index >= 1; --index) { for (pl = 0; pl < 256; ++pl) { r = palette[pl][0]/index; g = palette[pl][1]/index; b = palette[pl][2]/index; outportb(0x3c6,0xff); outportb(0x3c8,pl); outportb(0x3c9,r); outportb(0x3c9,g); outportb(0x3c9,b); } delay(del); } } // New interrupt handler... void interrupt NewInt9(...) { register keybyte; if (!inProgress) // Let's not do is twice at the same time... { inProgress = 1; KeyScan =inp(0x60); // Get the scan code. keybyte =inp(0x61); // Get the byte code. outp(0x61,(keybyte|0x80)); // Clear the flag. outp(0x61,keybyte); switch(KeyScan) // Now process it... { case 59: KeyState |= 0x80; break; // Pressed the F1 key. ---> Screen Shot. case 187: KeyState &= 0x7f; break; case 60: KeyState |= 0x40; break; // Pressed the F2 key. ---> Torch level. case 188: KeyState &= 0xbf; break; case 15: KeyState |= 0x20; break; // Pressed the Tab key. ---> Automap on/off. case 143: KeyState &= 0xdf; break; case 57: KeyState |= 0x10; break; // Pressed the space bar. ---> Activate door / trigger case 185: KeyState &= 0xef; break; case 77: KeyState |= 0x08; break; // Pressed left ---> Turn left. case 205: KeyState &= 0xf7; break; case 75: KeyState |= 0x04; break; // Pressed right ---> Turn Right. case 203: KeyState &= 0xfb; break; case 72: KeyState |= 0x02; break; // Pressed up ---> Move Forward. case 200: KeyState &= 0xfd; break; case 80: KeyState |= 0x01; break; // Pressed down ---> Move Backwards. case 208: KeyState &= 0xfe; break; } inProgress = 0; } outp(0x20,0x20); // Signal the end of the interrupt. } // Misc. Functions. void delay2(long time) // time is in seconds. { // This is a delay that can be cancelled by pressing SPACE or ESCape. // It is used during the start-up screens. time_t start; start= clock(); while ((KeyScan == 129) || (KeyScan == 57)) // Wait for the keys to clear. ; while ((clock() - start) < time*CLK_TCK) // Start the delay { if ((KeyScan == 129) || (KeyScan == 57))// if space or ESC, break out. break; } } // The following functions set up the world... void free_tables(void) { // Freee all the tables, if they were allocated. int i,j; if (world != NULL) farfree(world); if (FloorMap != NULL) farfree(FloorMap); if (CeilMap != NULL) farfree(CeilMap); if (ZoneMap != NULL) farfree(ZoneMap); if (CeilLightMap != NULL) farfree(CeilLightMap); if (FloorLightMap != NULL) farfree(FloorLightMap); for (i=0; i<Num_Textures; ++i) if (WallTextures[i] != NULL) farfree (WallTextures[i]); for (i=0; i<Num_Light_Tiles; ++i) if (LightMapTiles != NULL) farfree(LightMapTiles); if (tan_table != NULL) farfree(tan_table); if (inv_tan_table != NULL) farfree(inv_tan_table); if (y_step != NULL) farfree(y_step); if (x_step != NULL) farfree(x_step); if (cos_table != NULL) farfree(cos_table); if (sin_table != NULL) farfree(sin_table); if (inv_cos_table != NULL) farfree(inv_cos_table); if (inv_sin_table != NULL) farfree(inv_sin_table); if (sliver_factor != NULL) farfree(sliver_factor); if (Floor_inv_cos_table != NULL) farfree(Floor_inv_cos_table); if (Floor_cos_table != NULL) farfree(Floor_cos_table); if (Floor_sin_table != NULL) farfree(Floor_sin_table); if (Floor_Dx_table != NULL) farfree (Floor_Dx_table); if (LightLevel != NULL) farfree(LightLevel); if (LightMap != NULL) farfree(LightMap); if (FogMap != NULL) farfree(FogMap); if (Translucency != NULL) farfree(Translucency); for (j = 0; j <= Num_Sprites; ++j) for (i=0; i < 8; ++i) if (Sprites[j].Frames[i] != NULL) farfree (Sprites[j].Frames[i]); for (j = 0; j < Num_Animations; ++j) for (i=0; i < Animations[j].Nbr_Frames; ++ i) if (Animations[j].Frames[i] != NULL) farfree(Animations[j].Frames[i]); for (i=0; i < Num_Light_Tiles ; ++i) if (LightFlags[i].PulsePattern != NULL) farfree(LightFlags[i].PulsePattern); } void HndleAllocError(char *string) { // If an allocation error was found, this function is called. It informs us of the // error, frees the tables, and quits. txtscreen(); fprintf(stderr,"Memory allocation error while allocating '%s'\n",string); free_tables(); getch(); exit(1); } void HndleAllocError() { // If an allocation error was found, this function is called. It informs us of the // error, frees the tables, and quits. txtscreen(); fprintf(stderr,"General memory allocation error.\n"); free_tables(); getch(); exit(1); } void Build_Tables(void) { // Here is were we build all the math tables. This is MUCH faster than // using the trig functions. We will also pre-calculate a couple of other // things here as well. // int ang; // The angle (from ANGLE_0 to ANGLE_360) int tmp; int i; long dist; // Distance value. Used in lighting table calculation. float ratio; // Light Ratio. Used in lighting table calculation. float rad_angle; // The angle in radians. FILE *handle; // Step 1. Initialize static arrays pointers and counters. for (i = 0; i < 256; ++i) { palette[i][0] = 0; palette[i][1] = 0; palette[i][2] = 0; } // Then set up the WallTextures table. for (i=0; i < 80; ++i) { WallTextures[i] = NULL; TextureFlags[i].AnimNbr = -1; } // Then set up the LightMapTextures table. for (i=0; i < 30; ++i) { LightMapTiles[i] = NULL; LightFlags[i].AnimNbr = -1; } // And clean up the OpeningDoors table. for (i=0; i<10; ++i) Opening_Doors[i].USED = 0; // Initialize the light flags pattern param. for (i=0; i < 30 ; ++i) { LightFlags[i].PulsePattern = NULL; LightFlags[i].CurrPatternIndx = 0; } ZoneAttr[0].inside = 0; ZoneAttr[0].ambient = 0; ZoneAttr[0].fog = 0; for (tmp = 0; tmp <10 ; ++tmp) for (i=0; i < 8; ++i) Sprites[tmp].Frames[i] = NULL; // Step 2. Allocate the maps. 4K each. if (!(world = (char far*)farmalloc(4096))) HndleAllocError("world map"); if (!(FloorMap = (char far*)farmalloc(4096))) HndleAllocError("floor map"); if (!(CeilMap = (char far *)farmalloc(4096))) HndleAllocError("ceiling map"); if (!(ZoneMap = (char far *)farmalloc(4096))) HndleAllocError("zone map"); if (!(CeilLightMap = (char far *)farmalloc(4096))) HndleAllocError("ceiling light map"); if (!(FloorLightMap = (char far *)farmalloc(4096))) HndleAllocError("floor light map"); if (!(tan_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1)))) HndleAllocError("tangent table"); if (!(inv_tan_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1)))) HndleAllocError("inverse tangent table"); if (!(y_step = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1)))) HndleAllocError("y step table"); if (!(x_step = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1)))) HndleAllocError("x step table"); if (!(cos_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1)))) HndleAllocError("cosine table"); if (!(sin_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1)))) HndleAllocError("sine table"); if (!(inv_cos_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1)))) HndleAllocError("inverse cosine table"); if (!(inv_sin_table = (long far *)farmalloc(sizeof(long) * (ANGLE_360+1)))) HndleAllocError("inverse sine table"); if (!(sliver_factor = (long far *)farmalloc(sizeof(long)*600))) HndleAllocError("sliver scaling table"); if (!(Floor_inv_cos_table = (long far *)farmalloc(sizeof(long)*(ANGLE_360+1)))) HndleAllocError("Floor inverse cosine table"); if (!(Floor_cos_table = (long far *)farmalloc(sizeof(long)*(ANGLE_360+1)))) HndleAllocError("Floor cosine table"); if (!(Floor_sin_table = (long far *)farmalloc(sizeof(long)*(ANGLE_360+1)))) HndleAllocError("Floor sine table"); if (!(Floor_Dx_table = (long far *)farmalloc(sizeof(long)*10000L))) HndleAllocError("Floor DX table"); if (!(LightLevel = (char far *)farmalloc(sizeof(char)* MAX_DISTANCE ))) // Maximum distance. HndleAllocError("Light levels"); if (!(LightMap = (char far *)farmalloc(sizeof(char)*(PALETTE_SIZE*MAX_LIGHT_LEVELS)))) // 128 levels of 256 colors. HndleAllocError("Light map"); if (!(FogMap = (char far *)farmalloc(sizeof(char)*(PALETTE_SIZE*MAX_LIGHT_LEVELS)))) // 128 levels of 256 colors. HndleAllocError(); if (!(Translucency = (char far *)farmalloc(65536ul))) // Mixing 256 colors with 256 colors. HndleAllocError("Translucency"); // Step 3: Calculate scaling tables, and row tables. 16:16 and 22:10 fixed point for (ang=1; ang < 600; ++ang) sliver_factor[ang] = (long)(((float)128/ang)*65536l); // Used in scaling the slivers of texture for (ang = 150; ang > 76 ; --ang) Floor_Row_Table[ang]=(long)((11476.28125*1024.0)/(ang-75.0)); // Distance of floor row, dead ahead center. for (ang = 74; ang >=0 ; --ang) Floor_Row_Table[ang]=(long)((11476.28125*1024.0)/(75.0-ang)); // Distance of ceiling row, dead ahead center. for (ang = 0; ang < 10000; ++ang) Floor_Dx_table[ang] = (long)((1024.0 * ang)/239); // Not used yet. Will be for linear interp. of floor/ceil row rendering for (ang = 0; ang < 200; ++ang) row[ang] = ang*320; // Pre-calculated beginning of the row in screen memory. // Determine the sprite frame to display based on the angle. for (ang=0; ang < 361; ++ang) { if ((ang > 157) && (ang <= 202)) Sprite_Frame[ang] = 0; if ((ang > 202) && (ang <= 247)) Sprite_Frame[ang] = 1; if ((ang > 247) && (ang <= 292)) Sprite_Frame[ang] = 2; if ((ang > 292) && (ang <= 337)) Sprite_Frame[ang] = 3; if ((ang > 337) && (ang <= 22)) Sprite_Frame[ang] = 4; if ((ang > 22) && (ang <= 67)) Sprite_Frame[ang] = 5; if ((ang > 67) && (ang <= 112)) Sprite_Frame[ang] = 6; if ((ang > 112) && (ang <= 157)) Sprite_Frame[ang] = 7; } // Step 4: Now calculate the trig tables. for (ang=ANGLE_0; ang<=ANGLE_360; ang++) { // Calculate the angle n radians (for the trig funcions... rad_angle = (3.272e-4)+ang*2*3.141592654/ANGLE_360; // Make the basic trig tables. tan_table[ang] = (long)(tan(rad_angle)*65536L); // Used to get the y step for a specific angle. inv_tan_table[ang] = (long)(65536L/tan(rad_angle)); // Used to get the x step for a specific angle. Floor_cos_table[ang] = (long) (1024L*cos(rad_angle)); // Used to get the Y position for the angle and distance. Floor_sin_table[ang] = (long) (1024L*sin(rad_angle)); // Used to get the X position for the angle and distance. // Make the X and Y step tables, used while casting the rays. if (ang>=ANGLE_0 && ang<ANGLE_180) { y_step[ang] = (tan_table[ang])*CELL_Y_SIZE; if (y_step[ang] < 0) y_step[ang] = -y_step[ang]; } else { y_step[ang] = (tan_table[ang])*CELL_Y_SIZE; if (y_step[ang] > 0) y_step[ang] = -y_step[ang]; } if (ang>=ANGLE_90 && ang<ANGLE_270) { x_step[ang] = -(inv_tan_table[ang])*CELL_X_SIZE; if (x_step[ang] > 0) x_step[ang] = -x_step[ang]; } else { x_step[ang] = inv_tan_table[ang]*CELL_X_SIZE; if (x_step[ang] < 0) x_step[ang] = -x_step[ang]; } // Back to the basic tables... inv_cos_table[ang] = (long)(8192L/cos(rad_angle)); // Get Y for wall position from angle and distance. inv_sin_table[ang] = (long)(8192L/sin(rad_angle)); // Get X for wall position from andle and distance. sin_table[ang] = (long)(65536L*sin(rad_angle)); // Used for movement. % to move in X and Y for certain angle. } // These tables arre for scaling and the anti-fisheye stuff. for (ang=-ANGLE_30; ang<=ANGLE_30; ang++) { rad_angle = (3.272e-4)+ang*2*3.141592654/ANGLE_360; Floor_inv_cos_table[ang+ANGLE_30] = (long)(1024.0/cos(rad_angle)); // Anti-fisheye for floor. cos_table[ang+ANGLE_30] = (long)((11476.28125*256.0)/cos(rad_angle)); // Anti-fisheye and scaling for walls. } // Now we calculate the lighting tables... // Calculate light intensity table. for (dist = 1; dist < MAX_DISTANCE; dist++) { ratio = (float)(MAX_LIGHT_LEVELS/2.0) / dist * 2.0; // Normal intensity is mid-range. if (ratio > 1.0) ratio = 1.0; *(LightLevel + dist) = (char)( ratio * MAX_LIGHT_LEVELS ); } // Now we load the palette for the lighting effects. if((handle=fopen("darkness.pal","rb")) == NULL) { txtscreen(); fprintf(stdout,"Error loading depth palette\n"); free_tables(); getch(); exit(1); } fread(LightMap, MAX_LIGHT_LEVELS, PALETTE_SIZE, handle); fclose(handle); // Now we load the palette for the fog mapping if((handle=fopen("fog.pal","rb")) == NULL) { txtscreen(); fprintf(stdout,"Error loading fog palette\n"); free_tables(); getch(); exit(1); } fread(FogMap, MAX_LIGHT_LEVELS, PALETTE_SIZE, handle); fclose(handle); // // Now we load the palette for translucency if((handle=fopen("trans.pal","rb")) == NULL) { txtscreen(); fprintf(stdout,"Error loading fog palette\n"); free_tables(); getch(); exit(1); } fread(Translucency,1,65535l, handle); fclose(handle); _fmemset(ZoneMap,0,4096); } void CalcTorchLevel(int level) { long dist; float ratio; // There are 8 levels of torch. (1 = blackness, 8 = full) if ((level < 1) || (level > 8)) return; for (dist = 1; dist < MAX_DISTANCE; dist++) { ratio = (float)(MAX_LIGHT_LEVELS/(2.0 * (9 - level))) / dist * 1.5; // Normal intensity is mid-range. if (ratio > 1.0) ratio = 1.0; *(LightLevel + dist) = (char)( ratio * MAX_LIGHT_LEVELS ); } } char far *Load_PCX(char *filename, unsigned long size, int load_pal) { // This function loads in a 256 color PCX file, of a specified size, and // returns a pointer to it. If load_pal > 0 then it loads the palette as well. unsigned long count, // the count for the RLE run. index; // Overall byte count. main FOR loop. unsigned char data, // The data byte read in. r,g,b; // Color data. FILE *infile; // The file pointer. FILE *outfile; PCX_HEADER header; // The PCX header info. char far *Pointer; // Points to the bitmap data. // Step 1. Allocate a new bit of memory for the bitmap. if (!(Pointer = (char far *)farmalloc(size))) { // If there was a problem, quit and complain! txtscreen(); printf("Memory error loading PCX File '%s'. \n", filename); free_tables(); getch(); exit(1); } // Step 2: Open the file infile = fopen(filename,"rb"); if (!infile) { txtscreen(); printf("Error opening PCX File '%s'. \n", filename); free_tables(); farfree(Pointer); getch(); exit(1); } // Step 3: Read in the header, and get it out of the way... fread((char *)&header,128,1,infile); // Step 4. Check the header to make sure we're reading the correct file type... if (( header.bits_per_pixel != 8) || (header.Num_Planes != 1)) { fclose(infile); txtscreen(); printf("Error opening PCX File. Wrong file type '%s'. \n", filename); farfree(Pointer); free_tables(); getch(); exit(1); } // Step 5: Read in the bitmap data. index = 0; while(index < size) // for each byte in the bitmap (WxL), { data = getc(infile); // Get the byte. if (data >= 192) // If it's an RLE run, { count = data-192; // Get the count, data = getc(infile); // and the data, while (count-- > 0) // and blast it out. { *(Pointer+index) = (char)data; ++index; } } else // Else if it isn't an RLE, { *(Pointer+index) = (char)data; // just write it. ++index; } } // If specified, load in the palette. if (load_pal > 0) { // Now seek to the palette data, and read that in. fseek(infile,-768l,SEEK_END); for (index = 0; index < 256; ++index) { // Get the data, palette[index][0] = getc(infile)>>2; palette[index][1] = getc(infile)>>2; palette[index][2] = getc(infile)>>2; } } // Step 5: Close the file. fclose(infile); return Pointer; } void load_bkgd(char *filename, int i) { // This function loads in a 320x200, 256 color PCX file, and // places it into the BkgBuffer. unsigned long index; // loop counter. // Step 1. Clear the background buffer. if (BkgBuffer) farfree(BkgBuffer); // Step 2. Load the PCX file. BkgBuffer = Load_PCX(filename,64000l,1); // Step 3. And set the VGA palette if requested. if (i) { for (index = 0; index < 256; ++ index) { outportb(0x3c6,0xff); outportb(0x3c8,index); outportb(0x3c9,palette[index][0]); outportb(0x3c9,palette[index][1]); outportb(0x3c9,palette[index][2]); } } } void transpose(char far *Bitmap) { // // This function takes a bitmap and transposes the columns with the rows. This is // because the sliver renderer reads across a row of the bitmap, and draws a column. // int rows, // These two are loop counters. cols; char far *temp; // A temporary holding buffer. temp = (char far *)farmalloc(16385); if (temp == NULL) { printf("Memory error transposing PCX File \n"); free_tables(); getch(); exit(1); } // Copy the bitmap into the temp array, transposed. for (rows = 0; rows < 128; ++rows) for (cols = 0; cols < 128; ++cols) temp[(128*rows)+cols] = Bitmap[(128*cols)+rows]; // Now, copy it back... // memcpy(Bitmap,temp,16384); farfree(temp); } void Load_Wall(char *filename,int offset) { // // This function loads in the wall texture map. This will be a 256 color .PCX // file. The palette must be the same as that which is loaded in the background // The routine also calls Transpose, to order the rows and cols correctly. // // offset is the texture number (as specified in the map file. // // Step 1. Decrement the offset since the array starts at 0. --offset; // Step 2. Check to see if a texture is already there. If so, then delete it. if (WallTextures[offset] != NULL) farfree(WallTextures[offset]); // Step 3. Load the PCX file. WallTextures[offset] = Load_PCX(filename,16384l,0); // Step 4. We need to transpose the rows and coumns now, because the renderer // traverses a ROW of the texture to draw a COLUMN of the screen (faster). transpose(WallTextures[offset]); } void Load_Light_Tile(char *filename,int offset) { // // This function loads in the light tile bitmap. This will be a 256 color .PCX // file. The palette is unimportant. The color numbers will add to the lighting of the area. // offset is the bitmap number (as specified in the map file.) // // Step 1. Check to see if a texture is already there. If so, then delete it. if (LightMapTiles[offset] != NULL) farfree(LightMapTiles[offset]); // Step 2. Load the PCX file. LightMapTiles[offset] = Load_PCX(filename,16384l,0); // Step 3. We need to transpose the rows and coumns now, because the renderer // traverses a ROW of the texture to draw a COLUMN of the screen (faster). transpose(LightMapTiles[offset]); } char far *Load_Sprite (char *filename) { // Loads the bitmaps for a sprite object. The files are called [Filename][1-8].pcx // where [Filename] is the base filename (up to 7 characters), and [1-8] is a // single digit from 1 to 8 that is appended to the filename which represents the // frame number (each frame represents a 45 degree view of the object. This name formatting // is not done here, but in Allocate_World ( ). This routine just loads in 1 bitmap. // The function malloc's space for the bitmap, loads it in, and returns a pointer to it. char far *Pointer; // Points to the sprite data. // Step 1. Load the PCX file. Pointer = Load_PCX(filename,16384l,0); // Step 2. We need to transpose the rows and coumns now, because the renderer // traverses a ROW of the texture to draw a COLUMN of the screen (faster). transpose(Pointer); // Step 3. Return the pointer to the bitmap. return Pointer; } int Allocate_World(long &initx, long &inity, long &initang, char *MapFileName) { // // This routine reads and translates the map file, creates and fills the map arrays, // and loads the textures into memory. // FILE *fp; // Filepointer for the map file. int row, // Row number that we are readng in. column, // Column number that we are reading in. txtnum, // The texture to be assigned to the cell. cs, // Te following is Door info, and has already been described in detail. rs, ce, re, de, tp, tl, sec, type, sidetxt, spd, fr, i, // Generic loop index. sp_num, // The number of the sprite we're loading. sp_txt; long sp_initx, // The sprite's initial X coordinate. sp_inity, // The sprite's initial Y coordinate. sp_initang; // The sprite;s initial angle. char line[257], // Temporary buffer to hold the current line of the map file. typ[100], // This holds string data we're reading from the line. ch, // Temporary holding var. for cell data during translation. trg_parms[100], sp_basename[12]; // Base filename for the sprite's PCX files. // Step 0. Clear out the old map settings. Num_Triggers = 0; Num_Objects = 0; for (fr = 0; fr < Num_Sprites; ++fr) for (i=1; i<= 8; ++i) if (Sprites[fr].Frames[i-1] != NULL) { farfree(Sprites[fr].Frames[i-1]); Sprites[fr].Frames[i-1] = NULL; } Num_Sprites = 0; for (fr = 0; fr < Num_Textures; ++fr) { if (WallTextures[fr] != NULL) { farfree(WallTextures[fr]); WallTextures[fr] = NULL; } if (LightFlags[fr].PulsePattern != NULL) { farfree(LightFlags[fr].PulsePattern); LightFlags[fr].PulsePattern = NULL; } if (LightMapTiles[fr] != NULL) { farfree(LightMapTiles[fr]); LightMapTiles[fr] = NULL; } if (TextureFlags[fr].AnimNbr > -1) { if (Animations[TextureFlags[fr].AnimNbr].Frames != NULL ) { for (i = 0; i < Animations[TextureFlags[fr].AnimNbr].Nbr_Frames; ++i) if (Animations[TextureFlags[fr].AnimNbr].Frames[i] != NULL) { farfree(Animations[TextureFlags[fr].AnimNbr].Frames[i]); Animations[TextureFlags[fr].AnimNbr].Frames[i] = NULL; } farfree(Animations[TextureFlags[fr].AnimNbr].Frames); Animations[TextureFlags[fr].AnimNbr].Frames = NULL; } } } Num_Textures = 0; for (fr = 0; fr <= Num_Light_Tiles ; ++fr) { if (LightFlags[fr].PulsePattern != NULL) { farfree(LightFlags[fr].PulsePattern); LightFlags[fr].PulsePattern = NULL; } if (LightMapTiles[fr] != NULL) { farfree(LightMapTiles[fr]); LightMapTiles[fr] = NULL; } if (LightFlags[fr].AnimNbr > -1) { if (Light_Animations[LightFlags[fr].AnimNbr].Frames != NULL ) { for (i = 0; i < Light_Animations[LightFlags[fr].AnimNbr].Nbr_Frames; ++i) if (Light_Animations[LightFlags[fr].AnimNbr].Frames[i] != NULL) { farfree(Light_Animations[LightFlags[fr].AnimNbr].Frames[i]); Light_Animations[LightFlags[fr].AnimNbr].Frames[i] = NULL; } farfree(Light_Animations[LightFlags[fr].AnimNbr].Frames); Light_Animations[LightFlags[fr].AnimNbr].Frames = NULL; } } } Num_Light_Tiles = 0; // Step 1. Set up the 1st light map (map 0). if (LightMapTiles[0] != NULL) farfree(LightMapTiles[0]); LightMapTiles[0] = (char far *)farcalloc(1,16384l); // Step 2. Open the map file, and if ((fp = fopen(MapFileName,"r")) == NULL) { txtscreen(); fprintf(stderr,"Error loading map file '%s'.",MapFileName); free_tables(); getch(); exit(1); } // Read in world data. fgets(line,256,fp); while (!feof(fp)) { memset(typ,0,80); sscanf(line,"%s",typ); // This step cleans up much garbage that can be present. // Step 2a. Load the player's initial position and orientation if (!strcmpi(typ,"//")) // Comments. { fgets(line,256,fp); // Get the next line here.. (We test for EOF at the beginning..) continue; } if (!strcmpi(typ,"P")) { sscanf(line,"%*s %ld %ld %ld",&initx, &inity, &initang); // Translate into the fine coordinate system. initx = initx * 128+64; inity = inity * 128+64; initang *= 4; } // 2b. Load in a sprite definition.. else if (!strcmpi(typ,"S")) { // [Number] [Base Filename] [Translucent] sscanf(line,"%*s %d %7s %d",&sp_num, sp_basename,&tp); for (i=1; i<= 8; ++i) { sprintf(typ,"%s%d.pcx",sp_basename,i); if (Sprites[sp_num].Frames[i-1] != NULL) farfree(Sprites[sp_num].Frames[i-1]); Sprites[sp_num].Frames[i-1] = Load_Sprite(typ); } Sprites[sp_num].translucent = tp; if (sp_num > Num_Sprites) Num_Sprites = sp_num; } // 2c. Load in an object definition. else if (!strcmpi(typ,"O")) { // [Number] [Initial X] [Initial Y] [Initial Angle] sscanf(line,"%*s %d %d %d %d %d ",&sp_num, &sp_txt, &sp_initx, &sp_inity, &sp_initang ); Objects[sp_num].sprite_num = sp_txt; Objects[sp_num].x = sp_initx*128+64; Objects[sp_num].y = sp_inity*128+64; Objects[sp_num].angle = sp_initang * 4; Objects[sp_num].state = 0; if (sp_num > Num_Objects) Num_Objects = sp_num; } // Step 2d. Load a texture definition. else if (!strcmpi(typ,"T")) { // [NUMBER] [Filename] [transparent] [translucent] [recessed] [Walk Through] [Floor Reflect] sscanf(line,"%*s %d %s %d %d %d %d %d",&txtnum,typ,&tp,&tl,&re,&de,&fr); // Track how many we've loaded. if (txtnum > Num_Textures) Num_Textures = txtnum; // We need to clear these, since this is normal texture. TextureFlags[txtnum].IsTransparent = tp; TextureFlags[txtnum].IsTranslucent = tl; TextureFlags[txtnum].IsRecessed = re; TextureFlags[txtnum].CanWalkThru = de; TextureFlags[txtnum].IsDoor = 0; TextureFlags[txtnum].FloorReflect = fr; TextureFlags[txtnum].AnimNbr = -1; // Load the bitmap. Load_Wall(typ,txtnum); } // Step 2f. Load a door definition. else if (!strcmpi(typ,"D")) { // Door format is: D [Number] [Door Filename] [Side Number] [Col Start] [Row Start] [Col End] [Row End] [Speed] [Delay] [transparent] [Translucent] [Secret] sscanf(line,"%*s %d %s %d %d %d %d %d %d %d %d %d %d %d",&txtnum,typ,&sidetxt,&type,&cs,&rs,&ce,&re,&spd,&de,&tp,&tl,&sec); // Still, keep trck of the number, if (txtnum > Num_Textures) Num_Textures = txtnum; // Since this is a door, we have lots more to set... TextureFlags[txtnum].IsTransparent = tp; TextureFlags[txtnum].CanWalkThru = 0; TextureFlags[txtnum].IsDoor = 1; TextureFlags[txtnum].DoorType = type; TextureFlags[txtnum].DoorSideTxt = sidetxt; TextureFlags[txtnum].bc = cs; TextureFlags[txtnum].br = rs; TextureFlags[txtnum].ec = ce; TextureFlags[txtnum].er = re; TextureFlags[txtnum].delay = de; TextureFlags[txtnum].speed = spd; TextureFlags[txtnum].IsTranslucent = tl; TextureFlags[txtnum].IsSecretDoor = sec; TextureFlags[txtnum].IsRecessed = (sec == 1?0:1); TextureFlags[sp_num].AnimNbr = -1; Load_Wall(typ,txtnum); } // Load in the actual wall map data. else if (!strcmpi(typ,"M")) { sscanf(line,"%*s %d %d",&WORLD_ROWS,&WORLD_COLUMNS); // Make sure the map size is OK. We set a maximum value above... if ((WORLD_ROWS < 10) || (WORLD_ROWS > MAX_WORLD_ROWS) || (WORLD_COLUMNS < 0) || (WORLD_COLUMNS > MAX_WORLD_COLS)) { txtscreen(); fprintf(stderr,"Invalid map size.\n"); free_tables(); getch(); exit(1); } // Convert to fine coordinates. WORLD_X_SIZE = 128l*WORLD_COLUMNS; WORLD_Y_SIZE = 128l*WORLD_ROWS; // Now read in the cell data. for (row=0; row<WORLD_ROWS; row++) // For all rows... { for (column=0; column<WORLD_COLUMNS; column++) // For all columns... { while((ch = getc(fp))==10) // filter out CR {} if (ch == ' ') // If blank, default to zero (No wall) ch=0; else if (ch == '0') // zero is ten ch = 10; else if ((ch > '0') && (ch <= '9')) // 1 - 9 is 1 - 9 ch = ch - '0'; else if ((ch >= 'A') && (ch <= 'Z')) // A - Z is 11 - 35 ch = ch - 'A' + 11; *(world+(row<<6)+column) = ch; // Place the converted value in the map. } } } else if (!strcmpi(typ,"F")) // Load in the Floor map. { for (row=0; row<WORLD_ROWS; row++) // The floor must have the same dimensions as the walls. { for (column=0; column<WORLD_COLUMNS; column++) { while((ch = getc(fp))==10) // filter out CR {} if (ch == ' ') // Spaces map out to 'no texture' (default Bkg color) ch=0; else if (ch == '0') // 0 is 10 ch = 10; else if ((ch > '0') && (ch <= '9')) // 1-9 is 1-9 ch = ch - '0'; else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 11-35 ch = ch - 'A' + 11; *(FloorMap+(row<<6)+column) = ch; // Add it to the floor map. } } } else if (!strcmpi(typ,"C")) // Load in the ceiling map. It must be the same size as the wall map. { for (row=0; row<WORLD_ROWS; row++) // For every row { for (column=0; column<WORLD_COLUMNS; column++) // For every column { while((ch = getc(fp))==10) // read in a char, and filter out CR {} if (ch == ' ') // Space maps to 'no texture', default Bkg color. ch=0; else if (ch == '0') // 0 is 10 ch = 10; else if ((ch > '0') && (ch <= '9')) // 1-9 is 1-9 ch = ch - '0'; else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 11-35 ch = ch - 'A' + 11; *(CeilMap+(row<<6)+column) = ch; // Add it to the ceiling map. } } } else if (!strcmpi(typ,"Z")) // Load in the zone map. It must be the same size as the wall map. { for (row=0; row<WORLD_ROWS; row++) // For every row { for (column=0; column<WORLD_COLUMNS; column++) // For every column { while((ch = getc(fp))==10) // read in a char, and filter out CR {} if (ch == ' ') // Space maps to zone 0. ch=0; else if (ch == '0') // 0 is 10 ch = 10; else if ((ch > '0') && (ch <= '9')) // 1-9 is 1-9 ch = ch - '0'; else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 11-35 ch = ch - 'A' + 11; *(ZoneMap+(row<<6)+column) = ch; // Add it to the ceiling map. } } } else if (!strcmpi(typ,"L")) // Load in the floor light map. It must be the same size as the wall map. { for (row=0; row<WORLD_ROWS; row++) // For every row { for (column=0; column<WORLD_COLUMNS; column++) // For every column { while((ch = getc(fp))==10) // read in a char, and filter out CR {} if (ch == ' ') // Space maps to zone 0. ch=0; else if ((ch >= '0') && (ch <= '9')) // 0-9 is 0-9 ch = ch - '0'; else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 10-34 ch = ch - 'A' + 10; *(FloorLightMap+(row<<6)+column) = ch; // Add it to the ceiling map. } } } else if (!strcmpi(typ,"I")) // Load in the ceiling light map. It must be the same size as the wall map. { for (row=0; row<WORLD_ROWS; row++) // For every row { for (column=0; column<WORLD_COLUMNS; column++) // For every column { while((ch = getc(fp))==10) // read in a char, and filter out CR {} if (ch == ' ') // Space maps to zone 0. ch=0; else if ((ch >= '0') && (ch <= '9')) // 0-9 is 0-9 ch = ch - '0'; else if ((ch >= 'A') && (ch <= 'Z')) // A-Z is 10-34 ch = ch - 'A' + 10; *(CeilLightMap+(row<<6)+column) = ch; // Add it to the ceiling map. } } } else if (!strcmpi(typ,"G")) // Load a lighting tile. { // G [Tile Number] [Filename] [Pulsed] [Pulse type] [Args] (1 or 2 args) // Pulse type: regular -> args is [pulse width] and [Period] // custom -> arg is : [pattern] (all 1s and 0s) sscanf(line,"%*s %d %s %d",&txtnum,typ,&tp); // Track how many we've loaded. if (txtnum > Num_Light_Tiles) Num_Light_Tiles = txtnum; // Load the bitmap. Load_Light_Tile(typ,txtnum); LightFlags[txtnum].OriginalBitmap = LightMapTiles[txtnum]; LightFlags[txtnum].AnimNbr = -1; // Is the light a pulsed light? if (tp == 1) { // Get the pulse type. sscanf(line,"%*s %*d %*s %*d %d",&tp); // Pulse type will be either 1 or 2 if ( tp == 1) // Regular pulse. Get pulse width and period... { sscanf(line,"%*s %*d %*s %*d %*d %d %d",&tp,&re); LightFlags[txtnum].LightType = 1; LightFlags[txtnum].PulseWidth = tp; LightFlags[txtnum].Period = re; } else if ( tp == 2 ) // Custom pulse type. Get the mask. { sscanf(line,"%*s %*d %*s %*d %*d %s",typ); LightFlags[txtnum].LightType = 2; if (LightFlags[txtnum].PulsePattern != NULL) farfree(LightFlags[txtnum].PulsePattern); LightFlags[txtnum].PulsePattern = (char far *)farmalloc(strlen(typ)+1); strcpy(LightFlags[txtnum].PulsePattern,typ); } } } else if (!strcmpi(typ,"N")) // Load in the zone options. { // N [NUMBER] [Ambient] [Inside] [Fog] sscanf(line,"%*s %d %d %d %d",&txtnum, &de, &tp, &re); ZoneAttr[txtnum].ambient = de; ZoneAttr[txtnum].inside = tp; ZoneAttr[txtnum].fog = re; } else if (!strcmpi(typ,"A")) // Load in the animated textures { // A [Number] [Base File name] [Number of Textures] [Delay] [Triggered] [Transparent] [Translucent] [Recessed] [Walk Through] sscanf(line,"%*s %d %7s %d %d %d %d %d %d %d",&sp_num, sp_basename,&tp,&de,&re,&cs,&ce,&rs,&tl); // Now, clear out any previously loaded animation for this texture... if ((TextureFlags[sp_num].AnimNbr > -1) && (Animations[TextureFlags[sp_num].AnimNbr].Frames != NULL )) { for (i = 0; i < Animations[TextureFlags[sp_num].AnimNbr].Nbr_Frames; ++i) if (Animations[TextureFlags[sp_num].AnimNbr].Frames[i] != NULL) { farfree(Animations[TextureFlags[sp_num].AnimNbr].Frames[i]); Animations[TextureFlags[sp_num].AnimNbr].Frames[i] = NULL; } farfree(Animations[TextureFlags[sp_num].AnimNbr].Frames); Animations[TextureFlags[sp_num].AnimNbr].Frames = NULL; } Animations[Num_Animations].Flagged = re; Animations[Num_Animations].Txt_Nbr = sp_num-1; Animations[Num_Animations].Delay = de; Animations[Num_Animations].Timer = de; Animations[Num_Animations].Curr_Frame = 0; Animations[Num_Animations].Nbr_Frames = tp; TextureFlags[sp_num].IsTransparent = cs; TextureFlags[sp_num].CanWalkThru = tl; TextureFlags[sp_num].IsDoor = 0; TextureFlags[sp_num].delay = de; TextureFlags[sp_num].IsTranslucent = ce; TextureFlags[sp_num].IsSecretDoor = 0; TextureFlags[sp_num].IsRecessed = rs; TextureFlags[sp_num].AnimNbr = Num_Animations; // Allocate space for the pointers. **Frames Animations[Num_Animations].Frames = (char far **)farmalloc((sizeof(char *))*tp); // Load the bitmaps. for (i=1; i<= tp; ++i) { sprintf(typ,"%s%d.pcx",sp_basename,i); Animations[Num_Animations].Frames[i-1] = Load_Sprite(typ); } WallTextures[sp_num-1] = Animations[Num_Animations].Frames[0]; ++Num_Animations; } else if (!strcmpi(typ,"H")) // Load in the animated light tile. { // [Txt Number] [Base File name] [Number of Textures] [Delay] [Triggered] sscanf(line,"%*s %d %7s %d %d %d",&sp_num, sp_basename,&tp,&de,&re); // Now, clear out any previously loaded animation for this texture... if ((LightFlags[sp_num].AnimNbr > -1) && (Light_Animations[LightFlags[sp_num].AnimNbr].Frames != NULL )) { for (i = 0; i < Light_Animations[LightFlags[sp_num].AnimNbr].Nbr_Frames; ++i) if (Light_Animations[LightFlags[sp_num].AnimNbr].Frames[i] != NULL) { farfree(Light_Animations[LightFlags[sp_num].AnimNbr].Frames[i]); Light_Animations[LightFlags[sp_num].AnimNbr].Frames[i] = NULL; } farfree(Light_Animations[LightFlags[sp_num].AnimNbr].Frames); Light_Animations[LightFlags[sp_num].AnimNbr].Frames = NULL; } Light_Animations[Num_LightAnimations].Flagged = re; Light_Animations[Num_LightAnimations].Txt_Nbr = sp_num; Light_Animations[Num_LightAnimations].Delay = de; Light_Animations[Num_LightAnimations].Timer = de; Light_Animations[Num_LightAnimations].Curr_Frame = 0; Light_Animations[Num_LightAnimations].Nbr_Frames = tp; // Allocate space for the pointers. **Frames Light_Animations[Num_LightAnimations].Frames = (char far **)farmalloc((sizeof(char *))*tp); LightFlags[txtnum].AnimNbr = Num_LightAnimations; // Load the bitmaps. for (i=1; i<= tp; ++i) { sprintf(typ,"%s%d.pcx",sp_basename,i); Light_Animations[Num_LightAnimations].Frames[i-1] = Load_Sprite(typ); } LightMapTiles[sp_num] = Light_Animations[Num_LightAnimations].Frames[0]; ++Num_LightAnimations; } else if (!strcmpi(typ,"R")) // Load in a trigger. { // [Trgr Nbr] [Trgr Area Type] [Area Params]... [Space Flag] [Effect Type] [Effect Params]... sscanf(line,"%*s %d %d ",&sp_num,&tp); Num_Triggers++; Triggers[Num_Triggers].Trigger_Nbr = sp_num; Triggers[Num_Triggers].Trigger_Area_Type = tp; // Now depending on the trigger area type, we have 3 types of area parameters. switch (tp) { case 1: // Rectangle sscanf(line,"%*s %*d %*d %d %d %d %d %d %99c",&cs,&rs,&ce,&re,&tl,trg_parms); Triggers[Num_Triggers].X1 = cs; Triggers[Num_Triggers].Y1 = rs; Triggers[Num_Triggers].X2 = ce; Triggers[Num_Triggers].Y2 = re; break; case 2: // Circle sscanf(line,"%*s %*d %*d %d %d %d %d %99c",&cs,&rs,&ce,&tl,trg_parms); Triggers[Num_Triggers].X1 = cs; Triggers[Num_Triggers].Y1 = rs; Triggers[Num_Triggers].Radius = ce; printf("this: %s \n",trg_parms); break; case 3: // Time --- Not implemented yet... break; } // Now we need to mark the Trigger Map with the location of this trigger. if (Triggers[Num_Triggers].Trigger_Area_Type == 1) for (column = Triggers[Num_Triggers].X1>>7; column <= Triggers[Num_Triggers].X2>>7; ++ column) for (row = Triggers[Num_Triggers].Y1>>7; row <= Triggers[Num_Triggers].Y2>>7; ++row) if (TriggerMap[column][row] != 0) TriggerMap[column][row] = 100; else TriggerMap[column][row] = Triggers[Num_Triggers].Trigger_Nbr; else if (Triggers[Num_Triggers].Trigger_Area_Type == 2) for (column = (Triggers[Num_Triggers].X1 - Triggers[Num_Triggers].Radius )>>7; column <= (Triggers[Num_Triggers].X1 + Triggers[Num_Triggers].Radius )>>7; ++ column) for (row = (Triggers[Num_Triggers].Y1-Triggers[Num_Triggers].Radius)>>7; row <= (Triggers[Num_Triggers].Y1+Triggers[Num_Triggers].Radius)>>7; ++row) if (TriggerMap[column][row] != 0) TriggerMap[column][row] = 100; else TriggerMap[column][row] = Triggers[Num_Triggers].Trigger_Nbr; for(column = 0; column < 64; ++column) { for(row = 0; row < 64; ++row) printf("%d",(int)TriggerMap[row][column]); printf("\n"); } // Now, depending on the trigger type, read in the rest of the parameters... Triggers[Num_Triggers].Trigger_Type = tl; switch(tl) { case 1: case 2: case 3: // X/Y Coords. ( open / close / lock door ) sscanf(trg_parms,"%d %d",&cs,&rs); Triggers[Num_Triggers].MapX = cs; Triggers[Num_Triggers].Mapy = rs; break; case 4: case 5: case 16: case 17: // Texture Number ( Start / stop animated texture / Light ) sscanf(trg_parms,"%d",&cs); Triggers[Num_Triggers].TxtNbr = cs; break; case 6: // Change ambient light level. sscanf(trg_parms,"%d",&cs); Triggers[Num_Triggers].NewLightLvl = cs; break; case 7: // Change Zone light level. sscanf(trg_parms,"%d %d",&cs, &rs); Triggers[Num_Triggers].ZoneNbr = cs; Triggers[Num_Triggers].NewLightLvl = rs; break; case 8: // Load new map. sscanf(trg_parms,"%s",sp_basename); strncpy(Triggers[Num_Triggers].NewFileName,sp_basename,13); *(Triggers[Num_Triggers].NewFileName + 13) = 0x00; break; case 9: // Teleport player. sscanf(trg_parms,"%d %d %d",&cs, &rs, &ce); Triggers[Num_Triggers].MapX = cs; Triggers[Num_Triggers].Mapy = rs; Triggers[Num_Triggers].NewAngle = ce; break; case 10: // Load new texture. --- Not implemented yet. break; case 11: case 12: case 13: case 14: case 15: // Change Map. sscanf(trg_parms,"%d %d %d",&cs, &rs, &ce); Triggers[Num_Triggers].MapX = cs; Triggers[Num_Triggers].Mapy = rs; Triggers[Num_Triggers].TxtNbr = ce; break; } } else if (!strcmpi(typ,"B")) // Load in the ambient light level. { sscanf(line,"%*s %d",&sp_num); ambient_level = sp_num; } else if (!strcmpi(typ,"#END")) // This quits... along with EOF. break; fgets(line,256,fp); // Get the next line here.. (We test for EOF at the beginning..) } fclose(fp); // Close the file, return 1; // and return. } // The following functions render the world... void draw_sliver_transparent(void) { // // This function draws a sliver of wall texture. It reads from a texture (sliver_map) row // (sliver_row) and draws from the top down in the column (sliver_column). It is scaled // to (sliver_scale) and is centered on the screen vertically by the calling routing, // starting at (sliver_start). All bytes that are 0 (zero) are transparent. // Scaling is done using a pre-calculated array of fixed point fractions. These are added // to Tcount each time through the loop, and the whole part is used as the index into the // texture. (i.e. if Tcount = 0.5, then the texture is now 2x as large.) // int level; extern int sliver_column; // Screen column to draw in. extern int sliver_start; // Screen row to start drawing in. extern int sliver_scale; // how many pixels (rows) long the sliver is to be. extern int sliver_row; // Which row from the texture to read. extern int sliver_map; // Which texture to use. extern int sliver_light; extern long sliver_dist; extern int sliver_sptlt; extern int sliver_zoneamb; extern int sliver_usefog; register int y; // Current screen row we're rendering register long Scale, // Amount (fixed Pt) to increment Tcount each row. Tcount, // The offset into the texture / lightmap row (Fixed point) sliver_end; // The last screen row to render char far *base, // Beginning of the current texture row in memory. *lightbase, // Beginning of the current light tile row in memory. *bufptr; // Pointer to the beginning of screen memory for this sliver. register char pixl, // The pixel value we're going to plot. lgt; // The lighting value for the current pixel. // Reset Tcount Tcount = 0; // Preload the scaleing factor, and the pointer to the texture. (Faster this way). Scale = sliver_factor[sliver_scale]; base = WallTextures[sliver_map]+(sliver_row<<7); if (sliver_light > -1) { if (sliver_light <= Num_Light_Tiles) lightbase = LightMapTiles[sliver_light]+(sliver_row<<7); else fprintf(stdout,"Error... Wrong light map number: %d \n",(int) sliver_light); } // Calculate the end screen row, and clip it if necessary. sliver_end = sliver_start+sliver_scale; if (sliver_end > 149) sliver_end = 149; // Clip the top of the sliver, and adjust Tcount appropriately. if (sliver_start < 0) { Tcount = (((long)(-sliver_start)) * Scale); sliver_start = 0; } // Set the double buffer pointer to the starting row and column. bufptr = dblbuf+*(row+sliver_start)+sliver_column; // Are we using constant shading, or are we using the tile ? if (sliver_light > -1) { // Here, were using a lighting tile... // Now, for the entire sliver run, for (y = sliver_start; y <= sliver_end ; ++y) { // Get the pixel value from the texture, using the whole part of Tcount as the index. pixl = *(base+(long)(Tcount>>16)); // If the value isn't zero, then draw it. if ((pixl != 0)) { lgt = *(lightbase+(Tcount>>16)); level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + lgt + sliver_zoneamb; if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1; level <<= 8; if (sliver_usefog == 1) *bufptr = *(FogMap + level + pixl); else *bufptr = *(LightMap + level + pixl); } // And increment Tcount and the double buffer pointer. Tcount += Scale; bufptr += 320; } } else // sliver_light = -1, constant shading... { // Here we're using constant lighting for the whole run... level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + sliver_sptlt + sliver_zoneamb; if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1; level <<= 8; // Now, for the entire sliver run, for (y = sliver_start; y <= sliver_end ; ++y) { // Get the pixel value from the texture, using the whole part of Tcount as the index. pixl = *(base+(long)(Tcount>>16)); // If the value isn't zero, then draw it. if ((pixl != 0)) if (sliver_usefog == 1) *bufptr = *(FogMap + level + pixl); else *bufptr = *(LightMap + level + pixl); // And increment Tcount and the double buffer pointer. Tcount += Scale; bufptr += 320; } } } void draw_sliver_trans(void) { // // This function draws a sliver of wall texture. It reads from a texture (sliver_map) row // (sliver_row) and draws from the top down in the column (sliver_column). It is scaled // to (sliver_scale) and is centered on the screen vertically by the calling routing, // starting at (sliver_start). All bytes that are 0 (zero) are transparent. // Scaling is done using a pre-calculated array of fixed point fractions. These are added // to Tcount each time through the loop, and the whole part is used as the index into the // texture. (i.e. if Tcount = 0.5, then the texture is now 2x as large.) // int level; unsigned int lvl; extern int sliver_column; // Screen column to draw in. extern int sliver_start; // Screen row to start drawing in. extern int sliver_scale; // how many pixels (rows) long the sliver is to be. extern int sliver_row; // Which row from the texture to read. extern int sliver_map; // Which texture to use. extern int sliver_light; extern long sliver_dist; extern int sliver_sptlt; extern int sliver_zoneamb; extern int sliver_usefog; register int y; // Current screen row we're rendering register long Scale, // Amount (fixed Pt) to increment Tcount each row. Tcount, // The offset into the texture row (Fixed point) sliver_end; // The last screen row to render char far *base, // Beginning of the current texture row in memory. *lightbase, *bufptr; // Pointer to the beginning of screen memory for this sliver. register char pixl, // The pixel value we're going to plot. lgt; // Reset Tcount Tcount = 0; // Preload the scaleing factor, and the pointer to the texture. (Faster this way). Scale = sliver_factor[sliver_scale]; base = WallTextures[sliver_map]+(sliver_row<<7); if (sliver_light > -1) { if (sliver_light <= Num_Light_Tiles) lightbase = LightMapTiles[sliver_light]+(sliver_row<<7); else fprintf(stdout,"Error... Wrong light map number: %d \n",(int) sliver_light); } // Calculate the end screen row, and clip it if necessary. sliver_end = sliver_start+sliver_scale; if (sliver_end > 149) sliver_end = 149; // Clip the top of the sliver, and adjust Tcount appropriately. if (sliver_start < 0) { Tcount = (((long)(-sliver_start)) * Scale); sliver_start = 0; } // Set the double buffer pointer to the starting row and column. bufptr = dblbuf+*(row+sliver_start)+sliver_column; if (sliver_light > -1) { // Now, for the entire sliver run, for (y = sliver_start; y <= sliver_end ; ++y) { // Get the pixel value from the texture, using the whole part of Tcount as the index. pixl = *(base+(long)(Tcount>>16)); // If the value isn't zero, then draw it. if ((pixl != 0)) { lgt = *(lightbase+(Tcount>>16)); level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + lgt + sliver_zoneamb; if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1; level <<= 8; if (sliver_usefog == 1) *bufptr = *(Translucency + ((((unsigned int)*(FogMap + level + pixl)) & 0x00ff) << 8) + (((unsigned int)*bufptr) & 0x00ff)); else *bufptr = *(Translucency + ((((unsigned int)*(LightMap + level + pixl)) & 0x00ff) << 8) + (((unsigned int)*bufptr) & 0x00ff)); } // And increment Tcount and the double buffer pointer. Tcount += Scale; bufptr += 320; } } else { level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + sliver_sptlt + sliver_zoneamb; if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1; level <<= 8; // Now, for the entire sliver run, for (y = sliver_start; y <= sliver_end ; ++y) { // Get the pixel value from the texture, using the whole part of Tcount as the index. pixl = *(base+(long)(Tcount>>16)); // If the value isn't zero, then draw it. if ((pixl != 0)) { if (sliver_usefog == 1) *bufptr = *(Translucency + ((((unsigned int)*(FogMap + level + pixl)) & 0x00ff) << 8) + (((unsigned int)*bufptr) & 0x00ff)); else *bufptr = *(Translucency + ((((unsigned int)*(LightMap + level + pixl)) & 0x00ff) << 8) + (((unsigned int)*bufptr) & 0x00ff)); } // And increment Tcount and the double buffer pointer. Tcount += Scale; bufptr += 320; } } } void draw_sliver(void) { // // This function draws a sliver of wall texture. It reads from a texture (sliver_map) row // (sliver_row) and draws from the top down in the column (sliver_column). It is scaled // to (sliver_scale) and is centered on the screen vertically by the calling routing, // starting at (sliver_start). // Scaling is done using a pre-calculated array of fixed point fractions. These are added // to Tcount each time through the loop, and the whole part is used as the index into the // texture. (i.e. if Tcount = 0.5, then the texture is now 2x as large.) // int level; extern int sliver_column; // Screen column to draw in. extern int sliver_start; // Screen row to start drawing in. extern int sliver_scale; // how many pixels (rows) long the sliver is to be. extern int sliver_row; // Which row from the texture to read. extern int sliver_map; // Which texture to use. extern int sliver_light; extern long sliver_dist; extern int sliver_sptlt; extern int sliver_zoneamb; extern int sliver_usefog; register int y; // Current screen row we're rendering register long Scale, // Amount (fixed Pt) to increment Tcount each row. Tcount, // The offset into the texture row (Fixed point) sliver_end; // The last screen row to render char far *base, // Beginning of the current texture row in memory. *lightbase, *bufptr; // Pointer to the beginning of screen memory for this sliver. register char lgt; // Reset Tcount for this run. Tcount = 0; // Preload the scaleing factor, and the pointer to the texture. (Faster this way). Scale = sliver_factor[sliver_scale]; base = WallTextures[sliver_map]+(sliver_row<<7); if (sliver_light > -1) { if (sliver_light <= Num_Light_Tiles) lightbase = LightMapTiles[sliver_light]+(sliver_row<<7); else fprintf(stdout,"Error... Wrong light map number: %d \n",(int) sliver_light); } // Calculate the ending screen row, and clip it if necessary. sliver_end = sliver_start+sliver_scale; if (sliver_end > 149) sliver_end = 149; // Clip the beginning of the slice, if necessary. Adjust Tcount to it's new offset. if (sliver_start < 0) { Tcount = (((long)(-sliver_start)) * Scale); sliver_start = 0; } // Calculate the beginning point in the double buffer for the slice. bufptr = dblbuf+*(row+sliver_start)+sliver_column; if (sliver_light > -1) { // Now, for each screen row, for (y = sliver_start; y <= sliver_end; y ++) { lgt = *(lightbase+(Tcount>>16)); level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + lgt + sliver_zoneamb; if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1; level <<= 8; // Get the byte from the texture and render it. if (sliver_usefog == 1) *bufptr = *(FogMap + level + *(base + (Tcount>>16))); else *bufptr = *(LightMap + level + *(base + (Tcount>>16))); // Update Tcount and bufptr. Tcount += Scale; bufptr += 320; } } else { level = ((unsigned char)*(LightLevel+sliver_dist)) + ambient_level + sliver_sptlt + sliver_zoneamb; if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1; level <<= 8; // Now, for each screen row, for (y = sliver_start; y <= sliver_end; y ++) { // Get the byte from the texture and render it. if (sliver_usefog == 1) *bufptr = *(FogMap + level + *(base + (Tcount>>16))); else *bufptr = *(LightMap + level + *(base + (Tcount>>16))); // Update Tcount and bufptr. Tcount += Scale; bufptr += 320; } } } int Cast_X_Ray(long x, long y, long view_angle, long &yi_save, long &x_save, int &lighttile, int &zone) { // // This is the heart of the program. It casts a mathematical ray from the // players position along the view_angle. The ray stops when it hits a wall, // saving the x and y coordinates in yi_save and x_save, and returns the type // of wall hit. // This routine finds the HORIZONTAL cells along the ray. (SEE TGPG). // register long x_bound, // X intercept at the cell boundaries, in fine coords. x_delta, // Amount to add to x_bound for each iteration. next_x_cell, // Correction for "1-off" error for getting map cell. hit_type, // Cell value at current intersection. ystep; // Amount to add to yi for each iteration. long yi; // The Y intercept (at x_bound) in fine coords. // If the ray is going to the RIGHT, if ((view_angle < ANGLE_90) || (view_angle > ANGLE_270)) { // Determine where the current cell edge is (in fine coords.) x_bound = (x & 0xff80)+CELL_X_SIZE; // This is the amount to add to x_bound for each step. x_delta = CELL_X_SIZE; // Amount to compensate for the "1-off" error. next_x_cell = 0; } // Else, if it's going to the LEFT. else { // Determine where the current cell edge is (in fine coords.) x_bound = (x & 0xff80); // This is the amount to add to x_bound for each step. x_delta = -CELL_X_SIZE; // Amount to compensate for the "1-off" error. next_x_cell = -1; } // The initial Y intercept, at x_bound. yi = ((long)x_bound-(long)x)*(*(tan_table+view_angle))+((long)y<<16); // The amount to add to yi for each step. (the same each time, so we bring it out here). ystep = *(y_step+view_angle); // Now we cast the ray... while(1) { // Calculate the map coordinates for the current intersection. cell_xx = ((x_bound+next_x_cell)>>7); cell_yx = ((long)(yi>>16)) >> 7; // Check boundaries. if (cell_xx > 63) cell_xx = 63; if (cell_yx > 63) cell_yx = 63; if (cell_xx < 0) cell_xx = 0; if (cell_yx < 0) cell_yx = 0; // Check to see if we hit a wall or not. (A wall is >= 1). if ((hit_type = *(world+(cell_yx<<6)+cell_xx))!=0) { // Save the fine coordinates sso we can return them... yi_save = (long)((yi+32768L)>>16); x_save = x_bound; lighttile = *(FloorLightMap+(cell_yx<<6)+cell_xx); zone = *(ZoneMap+(cell_yx<<6)+cell_xx); if(TextureFlags[hit_type].IsRecessed) { x_save += ( x_delta / 2 ) ; yi_save = (long) (((yi + ystep / 2 ) + 32768L ) >> 16 ) ; } // and return, with the texture number for the map cell. return hit_type; } // Otherwise, if we havn't hit a wall, increment to the next boundary intersection, // and go again. yi += ystep; x_bound += x_delta; } } int Cast_Y_Ray(long x, long y, long view_angle, long &xi_save, long &y_save, int &lighttile, int &zone) { // // This is the heart of the program. It casts a mathematical ray from the // players position along the view_angle. The ray stops when it hits a wall, // saving the x and y coordinates in xi_save and y_save, and returns the type // of wall hit. // This routine finds the VERTICAL cells along the ray. (see TGPG) // register long y_bound, // Y intercept at the cell boundaries, in fine coords. y_delta, // Amount to add to y_bound each iteration of the loop. next_y_cell, // Fixes the "1 off" error while calculating cell locations. hit_type, // Texture value in the map cell. xstep; // Amount to add to xi each iteration of the loop. long xi; // X intercept ay y_bound, in fine coords. // If the ray is looking UP, if ((view_angle > ANGLE_0) && (view_angle < ANGLE_180)) { // Calculate the top cell boundary (Y intercept) y_bound = CELL_Y_SIZE + y & 0xff80; // Set y_delta. This is added to y_bound each time through the loop. y_delta = CELL_Y_SIZE; // and this fixes the "1 off" error in getting the map cell location (array x&y). next_y_cell = 0; } // Else, if we are looking DOWN... else { // Calculate the lower cell boundary. y_bound = y & 0xff80; // Set y_delta. y_delta = -CELL_Y_SIZE; // Again, this is to fix the "1 off" error. next_y_cell = -1; } // Now we calculate the x intercept on the Y boundary (See TGPG) xi =((long)y_bound-(long)y)*(*(inv_tan_table+view_angle))+((long)x<<16); // Get the value to increment xi from the x_step array. xstep = *(x_step+view_angle); // Now we enter into the main casting loop... while(1) { // Get the map array indices (X and Y) cell_yy = (y_bound+next_y_cell) >> 7; cell_xy = ((int)(xi>>16)) >> 7; // Check boundaries. if (cell_yy > 63) cell_yy = 63; if (cell_xy > 63) cell_xy = 63; if (cell_yy < 0) cell_yy = 0; if (cell_xy < 0) cell_xy = 0; // And determine if there is a texture in that cell. if ((hit_type = *(world+(cell_yy<<6)+cell_xy))!=0) { // If there is, let's save the location (fine coords), xi_save = (long)((xi+32768L)>>16); y_save = y_bound; lighttile = *(FloorLightMap+(cell_yy<<6)+cell_xy); zone = *(ZoneMap+(cell_yy<<6)+cell_xy); if(TextureFlags[hit_type].IsRecessed) { y_save += ( y_delta / 2 ) ; xi_save = (long) (((xi + xstep / 2 ) + 32768L ) >> 16 ) ; } // and indicate what type of wall we hit. return hit_type; } // If we didn't hit a wall, we increment the intercept variables, and check again. xi += xstep; y_bound += y_delta; } } void Texture_FloorCeil(long x, long y, long view_angle) { // // This function handles rendering the floor and ceiling. It is done in a somewhat // different manner than the walls. This routine will use similar triangles to find out // how far away a screen row (on which will be drawn the ceiling and floor) is from the // viewer's eye. The distance is then divided by the cosine of the view angle to find // out how far away a specific pixel in that row (corresponding to the angle) is from // the eye. The X and Y are then determined (using sine and cosine tables), and the // proper pixel is grabbed from the proper texture. The ceiling and floor mirror each // other most of the time, so we don't need to re-calculate. We do, a little bit though, // just to make sure we cover everything. // // The next step would be to cast for the edges in each row, and linearly interpolate // across the row. I am currently experimenting with that, but I'm having some trouble. // register int col, // The screen column currently being processed. ROW, // The screen row currently being processed. size; // The row on the screen where the floor ends and the wall begins. int flor_level, ceil_level, ZoneAmb; register long LightDist, // How far away the point is from the eye. distance, // How far away the point is from the eye (adjusted). MapOff, // Offset into the floor map of the current pixel. (see below) TxtOff, // Offset into the texture map of the current pixel. (see below) inv_cost; // Holds the inverse cosine. (22:10) It doesn't change by row, so we only get it once. long xv, // X coordinate of the screen pixel (ROW,COL) in texture space. yv, // Y coordinate of the screen pixel (ROW,COL) in texture space. angle, // the angle (absolute) the ray for the column is pointing. sintab, // The sine value for the current angle. (22:10 fixed) costab; // The cosine value for the current angle. (22:10 fixed) struct col_obj *line; // This saves us a lot in array dereferencing! only have to do it once. unsigned char *WallReflect; // This points to the wall/ceiling pixel we're going to reflect off the floor. int Reflect = 0; // This flag tells us if the floor is to reflect or not. // For each column in the viewport (ANGLE_0 to ANGLE_60) for (col = 0; col < 240; ++col) { // Calculate the absolute angle of the ray. Since col=[0,240), col-120 = [-ANGLE_30, ANGLE_30) angle = view_angle+(col-120); // Check the bounds of the angle, and make it loop back. (mod with ANGLE_360). if (angle < ANGLE_0) angle += ANGLE_360; if (angle >= ANGLE_360) angle -= ANGLE_360; // Get the line structure for the current column, farthest wall. // We do this here to save time. Array dereferencing takes lots of time. line = &(scan_lines[col].line[scan_lines[col].num_objs-1]); // Now we point to the beginning of the wall where we start the reflection from. // WallReflect = scrnbuf + col + *(row+line->scale); // Now get the trig values we need for this angle. This doesn't change with the // row, so we get it here to save time. inv_cost =*(Floor_inv_cos_table+col); sintab =*(Floor_sin_table+angle); costab =*(Floor_cos_table+angle); // Figure out where we can stop drawing the floor for this column. No sense in // drawing behind a solid wall. size = (line->top+line->scale); // Now, we cast from the bottom, up to the beginning of the farthest wall. for (ROW = 149; ROW >= size; --ROW) { // Get the distance of the floor pixel for this row, dead ahead, and then rotate it out // to the proper angle. (the inv_cost). LightDist = ((*(Floor_Row_Table+ROW))>>10); distance =((*(Floor_Row_Table+ROW)*inv_cost)>>10); // Figure out the x and y from trig, (hyp*sin=y, hyp*cos=x). yv = (long)((distance*sintab)>>20)+y; xv = (long)((distance*costab)>>20)+x; // Check boundaries. if (xv < 0) xv = 0; if (xv > 8191) xv = 8191; if (yv < 0) yv = 0; if (yv > 8191) yv = 8191; if (LightDist > 8191) LightDist = 8191; if (LightDist < 1) LightDist = 1; // We pre-calculate these to save time. Calculate once, use twice... MapOff = (((int)yv>>7)<<6)+((int)xv>>7); TxtOff = (((yv&127)<<7)+(xv&127)); // If the texture at this map offset is reflective, then set reflection flag //if(TextureFlags[*(FloorMap + MapOff)].FloorReflect == 1) //Reflect = 1; //else // Reflect = 0; // Calculate the zone, and zone options. // light level for light sourcing ZoneAmb = ZoneAttr[*(ZoneMap+MapOff)].ambient; flor_level = (unsigned char)LightLevel[LightDist] + ambient_level + *(LightMapTiles[*(FloorLightMap+MapOff)]+TxtOff) + ZoneAmb; if (ZoneAttr[*(ZoneMap+MapOff)].inside == 1) ceil_level = (unsigned char)ambient_level + *(LightMapTiles[*(CeilLightMap+MapOff)]+TxtOff) + ZoneAmb; else ceil_level = (unsigned char)LightLevel[LightDist] + ambient_level + *(LightMapTiles[*(CeilLightMap+MapOff)]+TxtOff) + ZoneAmb; if(flor_level > MAX_LIGHT_LEVELS - 1) flor_level = MAX_LIGHT_LEVELS - 1; if(ceil_level > MAX_LIGHT_LEVELS - 1) ceil_level = MAX_LIGHT_LEVELS - 1; flor_level <<= 8; ceil_level <<= 8; if (ZoneAttr[*(ZoneMap+MapOff)].fog == 1) { *(dblbuf+*(row+ROW)+col) = *(FogMap + flor_level + *(WallTextures[*(FloorMap+MapOff)-1]+TxtOff)); *(dblbuf+*(row+149-ROW)+col) = *(FogMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff)); } else { *(dblbuf+*(row+ROW)+col) = *(LightMap + flor_level + *(WallTextures[*(FloorMap+MapOff)-1]+TxtOff)); *(dblbuf+*(row+149-ROW)+col) = *(LightMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff)); } } // Now we find out where the ceiling really ends. It isn't always even with the floor. size = line->top; // And we do the above rendering again. This time, just on the ceiling. for (ROW = 148-ROW; ROW <= size; ++ROW) { LightDist = ((*(Floor_Row_Table+ROW))>>10); distance =((*(Floor_Row_Table+ROW)*inv_cost)>>10); yv = (long)((distance*sintab)>>20)+y; xv = (long)((distance*costab)>>20)+x; // Check boundaries. if (xv < 0) xv = 0; if (xv > 8191) xv = 8191; if (yv < 0) yv = 0; if (yv > 8191) yv = 8191; MapOff = (((int)yv>>7)<<6)+((int)xv>>7); TxtOff = (((yv&127)<<7)+(xv&127)); if (LightDist > 8191) LightDist = 8191; if (LightDist < 1) LightDist = 1; // light level for light sourcing if (ZoneAttr[*(ZoneMap+MapOff)].inside == 1) ceil_level = (unsigned char)ambient_level + *(LightMapTiles[*(CeilLightMap+MapOff)]+TxtOff) + ZoneAmb; else ceil_level = (unsigned char)LightLevel[LightDist] + ambient_level + *(LightMapTiles[*(CeilLightMap+MapOff)]+TxtOff) + ZoneAmb; if(ceil_level > MAX_LIGHT_LEVELS - 1) ceil_level = MAX_LIGHT_LEVELS - 1; ceil_level <<= 8; if (ZoneAttr[*(ZoneMap+MapOff)].fog == 1) *(dblbuf+*(row+ROW)+col) = *(FogMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff)); else *(dblbuf+*(row+ROW)+col) = *(LightMap + ceil_level + *(WallTextures[*(CeilMap+MapOff)-1]+TxtOff)); } } } void process_sprites(long x, long y, long view_angle) { // // This routine WILL handle the rendering calculations of the sprites. It is work // in progress. // int Current_Sprite, // The current object we're rendering. Sprite_Num; // The sprite used for the current object. long ang, // The angle the sprite makes with the view_angle. ang_clipped, // The relative angle, clipped to +- ANGLE_30. ang_saved, // Saved copy of the angle. Used in scaling and fisheye fix. dist, // The distance from the viewer. size, // The size of the sprite in pixels (horiz and vert). left, // The starting column of the sprite. right, // The ending column of the sprite. sx,sy, // The camera coordinates of the sprite. tx,ty, // Temporary holding for the coords. of the sprite during the transform. VA_Neg, // The negative of the player's view angle. Ref_ang, // The angle we are viewing the sprite from. Scale_Factor; // Fixed point value used in scaling the slices. long Frame, // The frame of the sprite we are going to display. sliver, // The column of the texture to draw for the scan line. col, // The scan line (col) we are in. Col_Depth, // The number of objects in the scan line buffer for this column. Indx; // Loop index used to insert sprite sliver in scan line buffer. int level, zoneamb, zonefog; register long MapOff, // Offset into the floor map of the current pixel. (see below) TxtOff; // Offset into the texture map of the current pixel. (see below) // We start adding sprites to the global WallTextures at 51. Sprite_Num = 50; // Loop through all the world's objects. for (Current_Sprite = 1; Current_Sprite <= Num_Objects; ++Current_Sprite) { // Step 1. Translate the position of the sprite by -[x,y] (Integer math) sx = Objects[Current_Sprite].x - x; sy = Objects[Current_Sprite].y - y; // Step 2. Rotate the sprite by -View_Angle. (Integer X Fixed = 22:10 fixed point) VA_Neg = ANGLE_360 - view_angle; tx = sx*Floor_cos_table[VA_Neg] - sy*Floor_sin_table[VA_Neg]; ty = sx*Floor_sin_table[VA_Neg] + sy*Floor_cos_table[VA_Neg]; sx = (long)(tx >> 10); sy = (long)(ty >> 10); // Step 3. Now cull the sprite if behind viewer. if (sx < 1) continue; // Step 4. Get the angle of the sprite. (Fixed / Fixed = Integer) ang_clipped = ang_saved = ang = (sy * 226l)/sx; if (ang < ANGLE_0) ang += ANGLE_360; // Step 5 Cull the sprite if it's too far off the side of the screen if ((ang_saved < -ANGLE_45) || (ang_saved > ANGLE_45)) continue; // Step 6 Get the distance, and size of the sprite (22:10 Fixed) dist = sx * inv_cos_table[ang] >> 13; // Integer Result (Table = 19:13) // Step 6a. If it's too close, we can't draw it. if (dist < 5) continue; // Step 6b. Clip the angle to +/- ANGLE_30. The cos_table only goes that far. if (ang_clipped > ANGLE_30) ang_clipped = ANGLE_30; if (ang_clipped < -ANGLE_30) ang_clipped = -ANGLE_30; // Step 6c. Calculste the size of the bitmap on the screen. size = (int)((cos_table[ang_clipped+ ANGLE_30]/dist) >> 8) << 1 ; // Table is 25:7 if (size > 600) size = 599; // Step 7. Now we calculate the starting and ending columns of the sprite, and get the scaling factor. left = (120+ang_saved)-(size >> 1); right = (120+ang_saved)+(size >> 1)-1; Scale_Factor = sliver_factor[size]; // Step 8 Determine the angle we are viewing the sprite from (Frame number). Frame = (((view_angle+ang_clipped) / ANGLE_45)+ 4) % 8 - (Objects[Current_Sprite].angle / ANGLE_45); if (Frame < 0) Frame += 8; if (Frame > 7) Frame -= 8; // Step 9. Now we sort the slices into the column buffer. Sprite_Num++; // Get the next available WallTextures slot. WallTextures[Sprite_Num] = Sprites[Objects[Current_Sprite].sprite_num].Frames[Frame]; TextureFlags[Sprite_Num+1].IsTranslucent = Sprites[Objects[Current_Sprite].sprite_num].translucent; TextureFlags[Sprite_Num+1].IsTransparent = 1; TextureFlags[Sprite_Num+1].IsDoor = 0; // light level for light sourcing MapOff = (((int)sy>>7)<<6)+((int)sx>>7); TxtOff = (((sy&127)<<7)+(sx&127)); level = LightLevel[dist] + ambient_level + *(LightMapTiles[*(FloorLightMap+MapOff)]+TxtOff); if(level > MAX_LIGHT_LEVELS - 1) level = MAX_LIGHT_LEVELS - 1; zoneamb = ZoneAttr[*(ZoneMap+MapOff)].ambient; zonefog = ZoneAttr[*(ZoneMap+MapOff)].fog; // Loop through all the slivers(columns) of the sprite's bitmap. for (sliver = 0, col=left; col <= right; ++col, sliver += Scale_Factor) // Start at the left most slice of the sprite. { if ((col >= 0) && (col < 240)) // If the slice is within the view window, // start at the beginning of the list. If the end is reached, break out. do not add the slice. for (Col_Depth = 0; Col_Depth <= scan_lines[col].num_objs; ++Col_Depth) // else if the depth of the current slice is greater than 'dist', { if (scan_lines[col].line[Col_Depth].dist > dist) { // shift all info down, and insert slice. for (Indx = scan_lines[col].num_objs; Indx > Col_Depth; --Indx) { scan_lines[col].line[Indx] = scan_lines[col].line[Indx-1]; } scan_lines[col].line[Indx].top = 75 - (size >> 1); scan_lines[col].line[Indx].col = col; scan_lines[col].line[Indx].scale = size; scan_lines[col].line[Indx].dist = dist; scan_lines[col].line[Indx].row = (sliver >> 16); scan_lines[col].line[Indx].texnum = Sprite_Num; scan_lines[col].line[Indx].lightnum = -1; scan_lines[col].line[Indx].sptlight = level; scan_lines[col].line[Indx].zoneambient = zoneamb; scan_lines[col].line[Indx].usefog = zonefog; scan_lines[col].num_objs++; break; } } } } } void DrawAutomap(long Xpos, long Ypos, long view_angle) { // This function renders the automap screen to the double buffer. It is // positioned in the middle of the screen, and is 70x70 pixels in size. char far *StartAddr; long x,y; // Frame in the automap. for (x=0; x<70; ++x) for (y=0; y<70; ++y) *(dblbuf+*(row+(20+y))+20+x) = *(AutoMapBkgd+y*70+x); // Draw the walls. for (x=0; x<64; ++x) for (y=0; y<64; ++y) if (*(world+((y<<6)+x)) != 0) if (TextureFlags[*(world+(y<<6)+(x))].IsDoor) { *(dblbuf+*(row+(23+y))+23+x) = 112; } else if (TextureFlags[*(world+(y<<6)+(x))].IsTransparent) { *(dblbuf+*(row+(23+y))+23+x) = 121; } else { *(dblbuf+*(row+(23+y))+23+x) = 2; } // Locate the player. *(dblbuf+*(row+(23+(Ypos>>7)))+23+(Xpos>>7)) = 198; } void Render_View(long x,long y,long view_angle) { // // Ok, here's where the magic comes together to render the full viewport... // This function loops through all colums in the viewport. Each column (0-240) represents // an angle between -ANGLE_30 and ANGLE_30 (60 degree FOV). At each column, we cast 2 rays. // one tracks textures on horizontal boundaries, the other on vertical. Once both rays are cast, // we determine which is closer to the viewpoint, and store it in the column buffer. If the wall // was tagged as transparent, we 'erase' the texture from the map, keeping track of it, and go // again. (We keep casting until a SOLID wall is hit). When we hit a solid wall, the casting // stops, we replace the 'erase' textures, and go on to the next column. Once all the columns // have been cast, we process the sprites, and sort the slivers of thier bitmaps into the column // buffer. // When all the setup is done, we bwgin drawing the viewport. We call a function to cast and // draw the floors and ceilings, and then we loop through the column buffer, sending each level's // texture and scaling info to the draw_sliver and draw_sliver_trans in reverse order (back to front). // extern int sliver_start; // These are the Sliver renderer globals. They are global for extern int sliver_column; // SPEED considerations. They have been described quite enough so far. extern int sliver_scale; extern int sliver_row; extern int sliver_map; extern int sliver_light; long ray, // the current ray being cast 0-240 (Corresponds to both the screen col, and the angle.) x_hit_type, // records the texture type that was found by the X caster. y_hit_type, // records the texture type that was found by the Y caster. dist_x, // The distance from the player to the texture found by the X caster. dist_y, // The distance from the player to the texture found by the Y caster. xi, // The X intercept, as calculated by the Y caster. yi, // The Y intercept, as calculated by the X caster. xs, // The X Boundary value, as found by the X caster. ys, // The Y Boundary value, as found by the Y caster. view_angle_saved; // Saves the passed-in view angle, since we modify the original. int x_light_type, y_light_type, x_zone, y_zone; struct erased_t { int x,y,type; } erased[30]; // Used to track 'erased' semi-transparent textures. struct col_obj *line; // Used to save time in array indexing. struct scan_line_t *s_line; // Used to save time in array indexing. int numerased; // The number of 'erased' textures. int indx; // Loop coounter used when replacing erased blocks. // Keep a copy of view_angle, we're going to be changing it. view_angle_saved = view_angle; // Subtract ANGLE_30 from the view_angle (left most angle). Wrap it around if necessary. if ((view_angle-=ANGLE_30) < 0) view_angle=ANGLE_360 + view_angle; // Now we can start casting the rays from view_angle-ANGLE_30 to view_angle+ANGLE_30 // For the current viewport size, that's 240 rays. for (ray=0; ray<240; ray++) { // We start out with no 'transparent' textures 'erased'. numerased = 0; // And no items in the column buffer. scan_lines[ray].num_objs = 0; // Keep going until we hit a solid wall, or fill the column buffer. while (1) { // If we're looking straight up and down, make dist_x so large, it won't be considered. if ((view_angle == ANGLE_90)||(view_angle == ANGLE_270)) { dist_x = 1e8; x_hit_type = -1; } // Otherwise, let's cast some X rays... else { // First, we cast the ray. x_hit_type = Cast_X_Ray(x,y,view_angle,yi,xs,x_light_type,x_zone); // Use trig to get the distance (hyp = delta X / Cos(angle). dist_x = (long)(((xs-x) * inv_cos_table[view_angle])>>13); // Here we chack for fixed point math overflow or underflow. if (dist_x == 0) dist_x = 1; if (dist_x < 0) dist_x = 1e8; } // Ok, now that we have the X distance, let's get the Y distance... // If we are looking due left or right, then make it so that we ignore the Y distance. if ((view_angle == ANGLE_0)||(view_angle == ANGLE_180)) { dist_y=1e8; y_hit_type = -1; } // Else, let's cast some Y rays. else { // First, we cast the ray. y_hit_type = Cast_Y_Ray(x,y,view_angle,xi,ys,y_light_type,y_zone); // Then we calculate the distance to the boundary. (Trig again) dist_y = (long)(((ys-y)*inv_sin_table[view_angle])>>13); // And then check for overflow or underflow. if (dist_y == 0) dist_y = 1; if (dist_y < 0) dist_y = 1e8; } // Now we determine which ray hit something closer, and save it the column buffer... if (dist_x < dist_y) { // The vertical wall (Horiz. distance and ray) was closer. Store it's info. // We stick it at the end of the column buffer. indx =scan_lines[ray].num_objs; // Get the address of the structure position. It's quicker to use a pointer than a big array. line = &(scan_lines[ray].line[indx]); // Calculate the scaling and correct for fish-eye. line->scale =(int)((cos_table[ray]/dist_x)>>8)<<1; // Crop the scaling to a max and min (for overflow/underflow) if (line->scale > 599) line->scale = 599; if (line->scale <= 0) line->scale = 1; // Store the texture row to use for the sliver line->row = ((int)yi)&127; // and it's position in the textures array. line->texnum = x_hit_type-1; line->lightnum = x_light_type; // Center the sliver about the middle of the viewport line->top = 75 - (line->scale>>1); // Store the column number line->col = ray; // and it's distance line->dist = (int)dist_x; // and the zone ambient light line->zoneambient = ZoneAttr[x_zone].ambient; // And the FOG flag. line->usefog = ZoneAttr[x_zone].fog; // and then increment the count. scan_lines[ray].num_objs++; // If the final texture was a transparent one, we have a little more to do... if (TextureFlags[x_hit_type].IsTransparent == 1) { // We store the texture's map location and value in the erased array, erased[numerased].x = cell_xx; erased[numerased].y = cell_yx; erased[numerased].type = x_hit_type; // And clear it's entry in the map. This is so that the next ray will pass // through this location, and continue on. *(world+(cell_yx<<6)+cell_xx) = 0; ++numerased; } } else { // The Horizontal wall (Vertical Ray) was closer, store it's info in the buffer. // This process is the same as the one above. indx =scan_lines[ray].num_objs; line = &(scan_lines[ray].line[indx]); line->scale =(int)((cos_table[ray]/dist_y)>>8)<<1; if (line->scale > 599) line->scale = 599; if (line->scale <= 0) line->scale = 1; line->row = ((int)xi)&127; // Note: If the other hit was a door, we use the ALT texture here... line->texnum = y_hit_type-1; line->lightnum = y_light_type; line->top = 75 - (line->scale>>1); line->col = ray; line->dist = (int)dist_y; // and the zone ambient light line->zoneambient = ZoneAttr[y_zone].ambient; // And the FOG flag. line->usefog = ZoneAttr[y_zone].fog; scan_lines[ray].num_objs++; if (TextureFlags[y_hit_type].IsTransparent == 1) { erased[numerased].x = cell_xy; erased[numerased].y = cell_yy; erased[numerased].type = y_hit_type; *(world+(cell_yy<<6)+cell_xy) = 0; ++numerased; } } // Now, if we've hit a solid wall, we can quit with this cast, and go on with the // process. Else, we loop back and cast again for the wall behind the transparent one. if (TextureFlags[scan_lines[ray].line[indx].texnum+1].IsTransparent == 0) // Non-transparent Texture. break; } // Here's where we put the erased blocks back into the map array. for (indx = 0; indx < numerased; ++indx) *(world+(erased[indx].y<<6)+erased[indx].x) = erased[indx].type; // Now we increment the view angle, wraping back around at ANGLE_360. if (++view_angle>=ANGLE_360) view_angle=0; // And we loop back again to do the next screen column. } // Process the sprites and sort them into the column array. process_sprites(x,y,view_angle_saved); // Here's where we render the display... // Cast and draw the floor and ceiling. #ifdef CAST_FLOORCIEL Texture_FloorCeil(x,y,view_angle_saved); #endif // Now, loop through all the screen columns stored in the col buffer. for (ray=0; ray<240; ray++) { // We take the address of the next entry here to save time. s_line = &scan_lines[ray]; // Now we loop backwards (depth wise) theough the list for that column. for (indx = s_line->num_objs-1; indx >= 0; --indx) { // Now put the numbers into the globals. line = &(s_line->line[indx]); sliver_start = line->top; sliver_column = line->col; sliver_scale = line->scale; sliver_dist = line->dist; sliver_row = line->row; sliver_map = line->texnum; sliver_light = line->lightnum; sliver_zoneamb = line->zoneambient; sliver_usefog = line->usefog; if (sliver_light == -1) sliver_sptlt = line->sptlight; // And call the rendering routines. if (TextureFlags[sliver_map+1].IsTranslucent == 1) { if (TextureFlags[sliver_map+1].IsTransparent == 1) draw_sliver_trans(); } else { if (TextureFlags[sliver_map+1].IsTransparent == 1) draw_sliver_transparent(); else draw_sliver(); } } } } // The last few functions perform the game logic... void start_door(int x, int y) { // // This routine is called each time the player tries to open a door. It finds an empty // spot in the Opening_Doors struct to insert necessary information, Inserts the info, // allocates a new spot for the temporary texture info (which is animated), and sets // it's flags. // int i; // Generic loop index variable. register int TxtNum; // Texture number of the original door. register struct Open_Doors_Info * Door; // Pointer to the used entry of Opening_Doors[]. // Check to see if the specified block is in fact a real door... if (!TextureFlags[*(world+(((long)y)<<6)+x)].IsDoor) return; // Check to see if we are ALREADY opening the current door. for (i=0; i<10; ++i) if ((Opening_Doors[i].USED != 0) && (Opening_Doors[i].x == x) && (Opening_Doors[i].y == y)) return; // Now, find an unused entry in the array. for (i=0; i<10; ++i) if (Opening_Doors[i].USED == 0) break; // If the array is full, we can't open it. Return adn make the user wait... if (i >= 10) return; // Get the address of the empty spot. Again, this saves time later on. Door = &Opening_Doors[i]; // Get the map texture number for the original door. TxtNum = *(world+y*64+x); // Now, we start to fill in the Door info. Door->USED = 1; // This one is now used. Door->x = x; // It is located at these map coords. Door->y = y; Door->Orig_Txt_Num = TxtNum-1; // And uses this texture. Door->New_Txt_Num = 40+i; // We're putting the animated texture here Door->Pcnt_opened = 0; // It's not open yet. Door->Num_Col_Opnd = 0; Door->Opening = 1; // But it's going to be. Door->delay = TextureFlags[TxtNum].delay; // Set the door's delay value. // OK. Now we make a new copy of the door. if (!(WallTextures[40+i] = (char far *)farmalloc(16384l))) { // If there was an error allocating, I'd like to know it. txtscreen(); printf("Error allocating memory for opening door. \n"); Opening_Doors[i].USED = 0; return; } // Copy the texture over. _fmemcpy(WallTextures[40+i],WallTextures[TxtNum-1],16384l); // Now, copy the textures parameters over... TextureFlags[i+41].IsTransparent = 1; // It's definately transparent now. TextureFlags[i+41].CanWalkThru = 0; // Can't go through it yet. TextureFlags[i+41].IsDoor = 1; // It's a door for sure. TextureFlags[i+41].DoorType = TextureFlags[TxtNum].DoorType; TextureFlags[i+41].IsTranslucent = TextureFlags[TxtNum].IsTranslucent; TextureFlags[i+41].IsSecretDoor = TextureFlags[TxtNum].IsSecretDoor; TextureFlags[i+41].IsRecessed = TextureFlags[TxtNum].IsRecessed; TextureFlags[i+41].bc = TextureFlags[TxtNum].bc; // These define the rectangle to animate. TextureFlags[i+41].br = TextureFlags[TxtNum].br; TextureFlags[i+41].ec = TextureFlags[TxtNum].ec; TextureFlags[i+41].er = TextureFlags[TxtNum].er; TextureFlags[i+41].speed = TextureFlags[TxtNum].speed; // This is the amt. to move the door each frame. // Now we place the new texture information into the map. *(world+y*64+x) = 41+i; } void open_doors() { // // This function loops through the Opening_Doors aray, and opens each active door a // little bit more, based on it's speed. When 90% opened, it sets the "CanWalkThru" // flag. When 100% opened, it clears the Opening flag. // int i, // Outer loop index variable. x, // Loop variable for the animation routine. y, bc, // These define the rectangle that is to be animated in the texture. br, ec, er, mc, spd; // This is the amount to move the animated part of the texture. char far *base; // This points to the door's texture. struct TextureFlagsTYPE *TextUsed; // Points to the door's texture info. // For each entry in the Opening_Doors array, for (i=0; i<10; ++i) // If it is active, and opening... if ((Opening_Doors[i].USED) && (Opening_Doors[i].Opening) && (Opening_Doors[i].Pcnt_opened < 100)) { // Get local copies of it's texture innfo. This is, again, faster. TextUsed = &(TextureFlags[Opening_Doors[i].New_Txt_Num+1]); bc = TextUsed->bc; br = TextUsed->br; ec = TextUsed->ec; er = TextUsed->er; mc = (ec-bc)/2 + bc; // Get a pointer to the bitmap. base = WallTextures[Opening_Doors[i].New_Txt_Num]; // and it's speed. spd = TextureFlags[Opening_Doors[i].New_Txt_Num+1].speed; // Now animate the door. switch ( TextUsed->DoorType ) // 1=normal, 2=elevator, 3=garage { case 1: // Normal door. // Move bitmap of door area over by 1 column . for (x=ec; x >= bc+spd; --x) _fmemcpy(base+(x*128l)+br,base+br+((x-spd)*128l),er-br+1); // Clear out the edge, so we can see through it. for ( ; x>= bc; --x) _fmemset(base+(x*128l)+br,0,er-br); // Update the percent opened. Opening_Doors[i].Pcnt_opened += ((100*spd)/(ec-bc)); break; case 2: // Elevator style door. Splits in the middle, scrolling to the sides. // Move bitmap towards left side from the middle... for (x=ec; x > mc+spd; --x) _fmemcpy(base+(x*128l)+br,base+br+((x-spd)*128l),er-br+1); // Clear out the middle, so we can see through it. for ( ; x > mc; --x) _fmemset(base+(x*128l)+br,0,er-br); // Move bitmap towards right side from the middle... for (x=bc; x <= mc-spd; ++x) _fmemcpy(base+(x*128l)+br,base+br+((x+spd)*128l),er-br+1); // Clear out the middle, so we can see through it. for ( ; x <= mc; ++x) _fmemset(base+(x*128l)+br,0,er-br); // Update the percent opened. (these open twice as fast) Opening_Doors[i].Pcnt_opened += ((200*spd)/(ec-bc)); break; case 3: // Garage style door. Slides up from the bottom. // Move bitmap towards top side from the bottom... for (x=bc ; x <= ec; ++x) { for (y=br; y <= er - spd; ++y ) *(base+y+(x*128l)) = *(base+(y+spd)+(x*128l)); for ( ; y <= er ; ++y) *(base+y+(x*128l)) = 0; } // Update the percent opened. Opening_Doors[i].Pcnt_opened += ((100*spd)/(er-br)); break; } // If it's 90% opened, let people walk through it. if (Opening_Doors[i].Pcnt_opened >= 90) TextUsed->CanWalkThru = 1; // If it's 100% open, mark it as closing. if (Opening_Doors[i].Pcnt_opened >= 100) Opening_Doors[i].Opening = 0; } } void close_doors() { // // This function closes all Active doors that are marked with "Opening=0". It copies // part of the original bitmap back into the animated texture, and updates it's state. // When the Percent_Opened is less than 90% it clears the CanWalkThru flag, which keeps // anyone from ealking through the texture anymore. When it reaches 0%, the original door // is put back into the map, and the animated texture space is deleted. // int i, // Outer loop index variable. x, // Bitmap copy loop index variable. y, bc, // These define the rectangle to be animated on the texture. br, ec, er, mc, spd; // This is how much to shift the door each time. char far *base, // Pointer to the animated texture. far *oldbase; // Pointer to the original texture. struct TextureFlagsTYPE *TextUsed; // Pointer to the texture info for the door. // For each entry in the Opening_Doors array... for (i=0; i<10; ++i) // If we are REALLY closing this door... if ((Opening_Doors[i].USED) && (!Opening_Doors[i].Opening) && (Opening_Doors[i].Pcnt_opened > 0)) { // We check the delay first. Decrement it until it reaches zero first. if (Opening_Doors[i].delay > 0) --Opening_Doors[i].delay; // Afterwards, start animating the texture. else { // We copy the texture's information into local variables to save time. TextUsed = &(TextureFlags[Opening_Doors[i].New_Txt_Num+1]); bc = TextUsed->bc; br = TextUsed->br; ec = TextUsed->ec; er = TextUsed->er; mc = (ec-bc)/2 + bc; spd = TextUsed->speed; base = WallTextures[Opening_Doors[i].New_Txt_Num]; oldbase = WallTextures[Opening_Doors[i].Orig_Txt_Num]; switch ( TextUsed->DoorType ) // 1=normal, 2=elevator, 3=garage { case 1: // Normal door. // We copy part of the bitmap of the old door to the new door. // The part is defined bu Num_Col_Opnd. for (x = Opening_Doors[i].Num_Col_Opnd; x >= 0; --x) memcpy(base+(ec-Opening_Doors[i].Num_Col_Opnd+x)*128l+br,oldbase+(x+bc)*128l+br,er-br); // We update the size of the "copy wndow". Opening_Doors[i].Num_Col_Opnd += spd; // and clip it to a maximum amount (the size of the rectangle) if (Opening_Doors[i].Num_Col_Opnd > (ec-bc)) Opening_Doors[i].Num_Col_Opnd = ec-bc; // And we update the percent opened. Opening_Doors[i].Pcnt_opened -= ((100 * spd)/(ec-bc)); break; case 2: // Elevator style doors. // We copy part of the bitmap of the old door to the new door. // The part is defined bu Num_Col_Opnd. for (x = 0 ; x <= Opening_Doors[i].Num_Col_Opnd; ++x) { memcpy(base+(ec-x)*128l+br,oldbase+(mc+Opening_Doors[i].Num_Col_Opnd-x)*128l+br,er-br); memcpy(base+(bc+x)*128l+br,oldbase+(mc-Opening_Doors[i].Num_Col_Opnd+x)*128l+br,er-br); } // We update the size of the "copy wndow". Opening_Doors[i].Num_Col_Opnd += spd; // and clip it to a maximum amount (the size of a side door.) if (Opening_Doors[i].Num_Col_Opnd > (ec-bc)/2 ) Opening_Doors[i].Num_Col_Opnd = (ec-bc)/2; // And we update the percent opened. Opening_Doors[i].Pcnt_opened -= ((200 * spd)/(ec-bc)); break; case 3: // Garage style doors. // We copy part of the bitmap of the old door to the new door. // The part is defined bu Num_Col_Opnd. for(x=bc; x<=ec ; ++x) memcpy(base+x*128l+br,oldbase+x*128l+er-Opening_Doors[i].Num_Col_Opnd,Opening_Doors[i].Num_Col_Opnd+1); // We update the size of the "copy wndow". Opening_Doors[i].Num_Col_Opnd += spd; // and clip it to a maximum amount (the size of a door.) if (Opening_Doors[i].Num_Col_Opnd > (er-br)) Opening_Doors[i].Num_Col_Opnd = (er-br); // And we update the percent opened. Opening_Doors[i].Pcnt_opened -= ((100 * spd)/(er-br)); break; } // If we've closed enough, no one can walk through... if (Opening_Doors[i].Pcnt_opened < 90) TextureFlags[Opening_Doors[i].New_Txt_Num+1].CanWalkThru = 0; // If we're completely closed, put the old door back. if (Opening_Doors[i].Pcnt_opened <= 0) { farfree(base); Opening_Doors[i].USED = 0; // Mark this array entry as available... *(world+Opening_Doors[i].y*64+Opening_Doors[i].x) = Opening_Doors[i].Orig_Txt_Num+1; } } } } void ProcessTrigger(long &x, long &y, long &view_angle, int TriggerNbr) { switch (Triggers[TriggerNbr].Trigger_Type) { case 1: // Open door at location. start_door(Triggers[TriggerNbr].MapX,Triggers[TriggerNbr].Mapy); break; case 2: // Lock door at location. break; case 3: // Close door at location. break; case 4: // Start animated texture. Animations[TextureFlags[Triggers[TriggerNbr].TxtNbr].AnimNbr].Flagged = 1; break; case 5: // Stop animated texture. Animations[TextureFlags[Triggers[TriggerNbr].TxtNbr].AnimNbr].Flagged = 0; break; case 6: // Set ambient light level. ambient_level = Triggers[TriggerNbr].NewLightLvl; break; case 7: // Set zone light level. ZoneAttr[Triggers[TriggerNbr].ZoneNbr].ambient = Triggers[TriggerNbr].NewLightLvl; break; case 8: // Load new map. Allocate_World(x,y,view_angle,Triggers[TriggerNbr].NewFileName); // Now how about a special effect... _dos_setvect(0x09,OldInt9); fadeout(10); copyscrn(BkgBuffer); Render_View(x,y,view_angle); swapdblbuf(); fadein(10); _dos_setvect(0x09,NewInt9); break; case 9: // Teleport player. x = Triggers[TriggerNbr].MapX; y = Triggers[TriggerNbr].Mapy; view_angle = Triggers[TriggerNbr].NewAngle; // Now how about a special effect... // End special effect. break; case 10: // Load new texture. break; case 11: // Change wall map at location. *(world + (Triggers[TriggerNbr].Mapy-1)*64l + Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr; break; case 12: // Change floor map at location. *(FloorMap + (Triggers[TriggerNbr].Mapy-1) * 64l + Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr; break; case 13: // Change ceiling map at location. *(CeilMap + (Triggers[TriggerNbr].MapX-1)*64l + Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr; break; case 14: // Change floor lighting map at location *(FloorLightMap+(Triggers[TriggerNbr].MapX-1)*64l+Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr; break; case 15: // Change ceiling lighting map at location. *(CeilLightMap+(Triggers[TriggerNbr].MapX-1)*64l+Triggers[TriggerNbr].MapX-1) = Triggers[TriggerNbr].TxtNbr; break; case 16: // Start animated light texture. Light_Animations[LightFlags[Triggers[TriggerNbr].TxtNbr].AnimNbr].Flagged = 1; break; case 17: // Stop animated light texture. Light_Animations[LightFlags[Triggers[TriggerNbr].TxtNbr].AnimNbr].Flagged = 0; break; } } void CheckTriggers(long &x, long &y, long &view_angle) { // This function checks to see if the player (or other creature later on) sets off a trigger. // If not, then nothing happens. If so, then watch out... int Trigger_Nbr; int indx; if ((Trigger_Nbr = TriggerMap[x>>7][y>>7]) != 0) { for ( indx = ((Trigger_Nbr < 100)?Trigger_Nbr:1); indx <= Num_Triggers; ++indx) { // Is the player inside the area of the current trigger? if (Triggers[indx].Trigger_Area_Type == 1) { if (((x >= Triggers[indx].X1) && (x <= Triggers[indx].X2)) && ((y >= Triggers[indx].Y1) && (y <= Triggers[indx].Y2))) ProcessTrigger(x,y,view_angle,indx); } else if (((x - Triggers[indx].X1) * (x - Triggers[indx].X1) + (y - Triggers[indx].Y1) * (y - Triggers[indx].Y1)) <= Triggers[indx].Radius * Triggers[indx].Radius ) ProcessTrigger(x,y,view_angle,indx); if (Trigger_Nbr < 100 ) break; } } } int Rotate_Animations(void) { // This function loops through all of the animations loaded into the map, // updates all of the timers, and if needed, cycles the animation to the next frame. // int index; // Generic loop index. // // Wall / Floor / Ceiling animations. // // Step 1. Loop through the animations array (for all defined animations) for (index = 0; index < Num_Animations; ++index) { // Step 1a. Is the current animation triggered? if ( Animations[index].Flagged == 1 ) { // Step 1b. Decrement the timer for the current animation. if ( --Animations[index].Timer == 0) { // Step 1c. If = 0 then increment the frame counter. Animations[index].Curr_Frame++; // Step 1d. Make sure to reset to the 1st frame if we're at the end. if ( Animations[index].Curr_Frame >= Animations[index].Nbr_Frames ) Animations[index].Curr_Frame = 0; // Step 1e. Set the new frame. WallTextures[Animations[index].Txt_Nbr] = Animations[index].Frames[Animations[index].Curr_Frame]; // Step 1f. Reset the delay. Animations[index].Timer = Animations[index].Delay; } } } // // Light Animations // for (index = 0; index < Num_LightAnimations; ++index) { // Step 1a. Is the current animation triggered? if ( Light_Animations[index].Flagged == 1 ) { // Step 1b. Decrement the timer for the current animation. if ( --Light_Animations[index].Timer == 0) { // Step 1c. If = 0 then increment the frame counter. Light_Animations[index].Curr_Frame++; // Step 1d. Make sure to reset to the 1st frame if we're at the end. if ( Light_Animations[index].Curr_Frame >= Light_Animations[index].Nbr_Frames ) Light_Animations[index].Curr_Frame = 0; // Step 1e. Set the new frame. LightMapTiles[Light_Animations[index].Txt_Nbr] = Light_Animations[index].Frames[Light_Animations[index].Curr_Frame]; // Step 1f. Reset the delay. Light_Animations[index].Timer = Light_Animations[index].Delay; } } } return 1; } void processblinklights(void) { // This function will rotate the blinking lights through thier patterns... // int i; for (i=1; i <= Num_Light_Tiles; ++i) { ++LightFlags[i].CurrPatternIndx; if (LightFlags[i].LightType == 1) { if ( LightFlags[i].CurrPatternIndx < LightFlags[i].PulseWidth ) // Light is ON LightMapTiles[i] = LightFlags[i].OriginalBitmap; else LightMapTiles[i] = LightMapTiles[0]; if ( LightFlags[i].CurrPatternIndx >= LightFlags[i].Period ) LightFlags[i].CurrPatternIndx = 0; } else if ( LightFlags[i].LightType == 2 ) { if ( LightFlags[i].PulsePattern[LightFlags[i].CurrPatternIndx] == NULL ) LightFlags[i].CurrPatternIndx = 0; if ( LightFlags[i].PulsePattern[LightFlags[i].CurrPatternIndx] == '0' ) LightMapTiles[i] = LightMapTiles[0]; else LightMapTiles[i] = LightFlags[i].OriginalBitmap; } } } void AnimateFireScreen (void) { unsigned int I; unsigned long x; unsigned long y; time_t start; int value; char far *Src, *MixBuf; randomize(); Src = (char far *)farmalloc((unsigned)64321); if (Src == NULL) { txtscreen(); printf("Unable to allocate the fire animation buffer."); free_tables(); getch(); exit(1); } MixBuf = (char far *)farmalloc((unsigned)64321); if (MixBuf == NULL) { txtscreen(); printf("Unable to allocate the fire animation buffer."); farfree (Src); free_tables(); getch(); exit(1); } _fmemset(Src,0,64320); _fmemset(MixBuf,0,64320); start= clock(); while ((KeyScan == 129) || (KeyScan == 57)) // Wait for the keys to clear. ; while ((clock() - start) < 10*CLK_TCK) // Start the delay, 10 seconds... { if ((KeyScan == 129) || (KeyScan == 57))// if space or ESC, break out. break; _fmemcpy(MixBuf,BkgBuffer,64000u); for (I=0; I < 319; ++I) // Place a random bottom on Src. if ((rand() % 0x06) > 1) *(Src+64000+I) = 112; else *(Src+64000+I) = 0; for (y=140; y<200; ++y) // Average pixels, moving up 1 row for (x=1; x<319; ++x) // adding the decay. { value = (*(Src+x-1+(y * 320ul))+*(Src+x+1+(y * 320ul)) +*(Src+x-1+((y+1) * 320ul))+*(Src+x+((y+1) * 320ul)) +*(Src+x+1+((y+1) * 320ul )))/5 - 1; if ((value > 0) && y < 196) *(MixBuf+x+((y+4) * 320ul)) = value; *(Src+x+((y-1) * 320ul)) = value; } while(inportb(0x3da) & 0x08) // Wait for any current VBI to end. {}; while(!(inportb(0x3da) & 0x08)) // Wait for the beginning of the next one. {}; _fmemcpy(scrnbuf,MixBuf,64000u); while(inportb(0x3da) & 0x08) // Wait for any current VBI to end. {}; while(!(inportb(0x3da) & 0x08)) // Wait for the beginning of the next one. {}; } farfree (Src); farfree (MixBuf); } void main(void) { // // THIS is the function that drives it all. It begins by setting up the tables, the screen // the new keybard handler, loading the map, displaying the opening screens, processing user // input, checking the doors, collisions, etc. When done, it displays the closing screen, and // reports on the frames per seecond. int done=0, // This flag, when set, causes the outer While loop to terminate. i; // Generic loop index. Used for clearing some tables. char AutomapFlag = 0, // Automap state flag. TorchFlag = 0, TorchLevel = 8, FogState = 0; long x, // The players X position, in fine coordinates. y, // The players Y position, in fine coordinates. view_angle, // The angle which the player is facing. view_angle_90, // The angle the player is facing + 90 degreess. (Used for collision & door processing) x_cell, // The map cell COL the player is in. (Used for collision & door processing) y_cell, // The map cell ROW the player is in. x_sub_cell, // The fine coordinate inside the cell (0-63) of the player. y_sub_cell, // The fine coordinate inside the cell (0-63) of the player. dx, // The amount to move the player in X this round. dy; // The amount to move the player in Y this round. float fps; // Calculated frames per second. time_t initl; // The time the game started. (Used in calculating FPS). unsigned long frames = 0, // The cumulative number of frames displayed this game. InitMem = 0, MemLeft = 0; AutoMapBkgd = Load_PCX("automap.pcx",70*70,0); // OK, First thing we're going to do is clear out the palette table. InitMem = farcoreleft(); // Now, initialize the screen, and set the proper graphics mode. initscreen(1); // Set up the new keyboard handler, saving the address of the old one for later. OldInt9 = _dos_getvect(0x09); _dos_setvect(0x09,NewInt9); // Fade the screen to black. fadeout(25); // And load in the first full screen graphic. load_bkgd("open0.PCX",0); // Copy it to the double buffer, copyscrn(BkgBuffer); // Move that to the screen, swapdblbuf(); // And fade it in. fadein(25); // Now we wait 2 seconds. Esc and Space will cancel the delay. delay2 (2); // Do the same with the second picture, load_bkgd("open1.PCX",0); dissolveto(BkgBuffer); delay2 (7); // And the third. load_bkgd("open2.PCX",0); dissolveto(BkgBuffer); delay2 (7); fadeout(25); // Now we show the main title. load_bkgd("TITLE.PCX",0); copyscrn(BkgBuffer); swapdblbuf(); fadein(25); AnimateFireScreen(); fadeout(25); // temporarilly restore the OLD keyboard handler. Why? because we can die in Build_Tables // and Allocate_World. _dos_setvect(0x09,OldInt9); // make all the trig and support tables. Build_Tables(); // Load in the map, textures, etc. and set the player's initital position. Allocate_World(x,y,view_angle,"raymap.dat"); // and calculate the cell and sub-cell values. x_cell = x>>7; y_cell = y>>7; x_sub_cell = x & 127; y_sub_cell = y & 127; // Load the play screen background, load_bkgd("BKGD.PCX",0); // Copy it to the double buffer, copyscrn(BkgBuffer); // Render the 1st view, on top of the background, Render_View(x,y,view_angle); // Dump it to the screen, swapdblbuf(); // And fade in.......... WE START.... fadein(25); // Give us back our good kwyboard handler, OldInt9 = _dos_getvect(0x09); _dos_setvect(0x09,NewInt9); // and start counting up the Frames per second... frames = 0; initl = clock(); // This is the MAIN GAME LOOP. EVERYTHING IN THE GAME HAPPENS HERE. while(!done) { // Step 1. Process the doors. open_doors(); // Open all opening doors a little more. close_doors(); // close all closing doors a little more. Rotate_Animations(); // Cycle the animations. processblinklights(); // Check the triggers... CheckTriggers(x,y,view_angle); // Step 2. Check the keyboard for an ESC key (Quit). if (KeyScan == 129) done = 1; // Step 3. Check to see if the player wants to open a door. If so, then process it. if (KeyState & 0x10) // Check for opening a door... { // If the player is facing to the WEST if ((view_angle > ANGLE_330) || (view_angle < ANGLE_30)) { // If it really is a door right next to him (1 cell away), if (TextureFlags[*(world+(y_cell<<6)+x_cell+1)].IsDoor) // Then start it opening. start_door(x_cell+1,y_cell); } // Else see if it's to to the north. else if ((view_angle > ANGLE_60) && (view_angle < ANGLE_120)) { if (TextureFlags[*(world+((y_cell+1)<<6)+x_cell)].IsDoor) start_door(x_cell,y_cell+1); } // Else, see if it's to the west. else if ((view_angle > ANGLE_150) && (view_angle < ANGLE_210)) { if (TextureFlags[*(world+(y_cell<<6)+(x_cell-1))].IsDoor) start_door(x_cell-1,y_cell); } // Else, see if it's to the south. else if ((view_angle > ANGLE_240) && (view_angle < ANGLE_300)) { if (TextureFlags[*(world+((y_cell-1)<<6)+x_cell)].IsDoor) start_door(x_cell,y_cell-1); } } // Now check to see if the player wants the automap. if ((KeyState & 0x20) && (AutomapFlag == 0)) { AutomapFlag = 1; } if (!(KeyState & 0x20) && (AutomapFlag == 1)) { AutomapFlag = 2; } if ((KeyState & 0x20) && (AutomapFlag == 2)) { AutomapFlag = 3; } if (!(KeyState & 0x20) && (AutomapFlag == 3)) { AutomapFlag = 0; } // Now check to see if the player wants to change the torch level. if ((KeyState & 0x40) && (TorchFlag == 0)) { TorchFlag = 1; --TorchLevel; if (TorchLevel == 0) TorchLevel = 8; CalcTorchLevel(TorchLevel); } if (!(KeyState & 0x40) && (TorchFlag == 1)) { TorchFlag = 0; } // Step 4. Check for movement. The lower nibble of KeyState is for movement ONLY. if (KeyState & 0x0f) { // Reset dx and dy. dx=dy=0; // If the player is pressing the Right arrow, if (KeyState & 4) { // Decrement the view angle by 6 degrees, wrap at zero. if ((view_angle-=ANGLE_6)<ANGLE_0) view_angle+=ANGLE_360; } // Else, if the player is pressing the Left arrow. if (KeyState & 8) { // Increment the view angle by 6 degrees, Wrap at 360. if ((view_angle+=ANGLE_6)>=ANGLE_360) view_angle-=ANGLE_360; } // Else, if the player wants to go FORWARD... if (KeyState & 2) { // Set view_angle_90, and wrap it around at 360 deg. view_angle_90 = view_angle + ANGLE_90; if (view_angle_90 > ANGLE_360) view_angle_90 = view_angle_90 - ANGLE_360; // Calculate the dx and dy to move forward, at the current angle. (Trig) dx= (long)((sin_table[view_angle_90]*20)>>16); dy= (long)((sin_table[view_angle]*20)>>16); } // Else, if the player wants to move BACKWARDS if (KeyState & 1) { // Set view_angle_90, and wrap it around at 360 deg. view_angle_90 = view_angle + ANGLE_90; if (view_angle_90 > ANGLE_360) view_angle_90 = view_angle_90 - ANGLE_360; // Calculate the dx and dy to move backward, at the current angle. (Trig) dx= -(long)((sin_table[view_angle_90]*20)>>16); dy= -(long)((sin_table[view_angle]*20)>>16); } // Now , add the deltas to the current location, x+=dx; y+=dy; // Calculate the x and y cell and subcell values, x_cell = x>>7; y_cell = y>>7; x_sub_cell = x & 127; y_sub_cell = y & 127; // Now, we do collision detection... (Bumping into walls..) // If we're moving WEST, if (dx>0) { if ((*(world+(y_cell<<6)+x_cell+1) != 0) && // If there'a wall there, (!TextureFlags[*(world+(y_cell<<6)+x_cell+1)].CanWalkThru) && // And we can't walk through it, (x_sub_cell > (CELL_X_SIZE-OVERBOARD))) // And we're too close to it... x-= (x_sub_cell-(CELL_X_SIZE-OVERBOARD )); // then MOVE US BACK A BIT. } // Same as above, but going EAST. else { if ((*(world+(y_cell<<6)+x_cell-1) != 0) && // If there'a wall there, (!TextureFlags[*(world+(y_cell<<6)+x_cell-1)].CanWalkThru) && // And we can't walk through it, (x_sub_cell < (OVERBOARD))) // And we're too close to it... x+= (OVERBOARD-x_sub_cell) ; // then MOVE US BACK A BIT. } // If we're going NORTH, if (dy>0 ) { if ((*(world+((y_cell+1)<<6)+x_cell) != 0) && // If there'a wall there, (!TextureFlags[*(world+((y_cell+1)<<6)+x_cell)].CanWalkThru) && // And we can't walk through it, (y_sub_cell > (CELL_Y_SIZE-OVERBOARD))) // And we're too close to it... y-= (y_sub_cell-(CELL_Y_SIZE-OVERBOARD )); // then MOVE US BACK A BIT. } // Else, if we're going SOUTH else { if ((*(world+((y_cell-1)<<6)+x_cell) != 0) && // If there'a wall there, (!TextureFlags[*(world+((y_cell-1)<<6)+x_cell)].CanWalkThru) && // And we can't walk through it, (y_sub_cell < (OVERBOARD))) // And we're too close to it y+= (OVERBOARD-y_sub_cell); // then MOVE US BACK A BIT. } } // Get ready to draw the screen again... // Copy the background to the double buffer. copyscrn(BkgBuffer); // Render the view plane Render_View(x,y,view_angle); // If the automap is on, then render it to the double buffer. if ((AutomapFlag == 1) || (AutomapFlag == 2)) DrawAutomap(x,y,view_angle); // Send it to the screen. swapdblbuf(); // Increment our frame count. ++frames; // Go back an ddo it all again.... } MemLeft = farcoreleft(); // Now, the game is over, // Calculate the frames per second. fps = (float)frames/((clock()-initl)/(float)CLK_TCK); // Fade the screen out. fadeout(25); // Load in the ending screen. load_bkgd("ending.PCX",0); // Copy it over to the double buffer. copyscrn(BkgBuffer); // Move it to the screen, swapdblbuf(); // Fade it in. fadein(25); // Wait for 8 seconds, or until ESC or SPACE is pressed. delay2(8); // Give us back the normal keyboard handler _dos_setvect(0x09,OldInt9); // Go back to 80 col. text mode. txtscreen(); // Print out the FPS message. fprintf(stderr,"Frames per second: %f\nStarting Memory: %ld\n Memory left: %ld\nTotal Used: %ld",fps,InitMem,MemLeft,(unsigned long)(InitMem-MemLeft)); } // End Ray.cpp