/****************************************************************************
  C.CPP

  Features
  ~~~~~~~~
  o Video Buffer kan elke afmetingen hebben, slechts BufferW,H en S aanpassen
    is voldoende om naar een andere resolutie over te schakelen...

****************************************************************************/


#include <stdio.h>
#include <dos.h>
#include <conio.h>
#include <math.h>
#include <ERRNO.H>
#include <STDDEF.H>
#include <STDLIB.H>
#include <MEM.H>
#include <sys\stat.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>


/****************************************************************************
 Types
****************************************************************************/

typedef unsigned char 		UBYTE;
typedef unsigned int		UWORD;
typedef unsigned long int 	ULONG;

typedef char			SBYTE;
typedef int			SWORD;
typedef long int		SLONG;

struct TWallLut
{
  ULONG	S;			// Width and height of destination image
  ULONG	X;			// }_ TopLeft-coordinates of
  ULONG Y;            		// }  destination image
};

struct TWallType
{
  UBYTE *Bitmap;		// Pointer to bitmap image
  UWORD HoleX1;			// }_ TopLeft-coordinates of hole
  UWORD HoleY1;                 // }
  UWORD HoleX2;			// }_ BottomRight-coordinates of hole
  UWORD HoleY2;                 // }
};

struct TWall
{
  ULONG Z;   			// Z-position of wall
  ULONG Type;			// Type of wall
};

struct TLevel
{
  UWORD NumWall;              	// Total number of walls
};

struct TPlayer
{
  ULONG	Z;    			// Z-position
  SWORD ZSpd;			// Tunnel scroll speed (in texels...)
  UWORD Scroll;			// Tunnel scroll position (in texels...)

  UWORD	X; 			// }_ Center-coordinates of player ship
  UWORD	Y;                      // }
  SWORD XSpd;			// }_ Speed
  SWORD YSpd;                   // }

  UWORD Level;			// Heheh
};

struct TGame
{
  ULONG Flags;			// Game-loop status flags
  UWORD	FirstWall;		// Number of first visible wall
  UWORD LastWall;		// Number of last visible wall
};

/****************************************************************************
 Constants
****************************************************************************/

#define TxtW		256		// }_ Texture dimensions
#define TxtH		256             // }
#define WallW		128             // }_ Wall dimensions
#define WallH		128             // }

#define	BufferW		160  		// Video buffer width (bytes per line)
#define BufferH		128  		// Video buffer height
#define BufferS		20480 		// Video buffer width * height

#define ClipL         	0               // Clipping borders
#define ClipR         	(BufferW-1)     //
#define ClipT       	0               //
#define ClipB         	(BufferH-1)     //

#define CenterX		(BufferW/2)	// }_ Center of video buffer
#define CenterY		(BufferH/2)     // }

#define Pi		3.14159265
#define Rad2Texel	(TxtW/(2*Pi))	// Convert radians to texels

#define TunnelD		TxtH*32		// Depth of tunnel (in texels)
#define TunnelD2	TunnelD*2

#define ViewD		(TunnelD2/8)	// Maximum visible depth
#define RealZ		200		// Depth where player is behind wall

#define MinSpd		256             // Minimum speed
#define MaxSpd		25600		// Maximum speed

#define XAccel		1		// Acceleration
#define YAccel		1               //
#define ZAccel		256             //

#define RMax		((BufferH/2)-8)	// Player movement border
#define RMax2		(RMax*RMax)     //

#define MaxWallType	10		// Maximum number of wall types
#define MaxWall		100		// Maximum number of walls
#define	MaxLevel	10		// Maximum number of levels


/****************************************************************************
 Variables
****************************************************************************/

SWORD oldmode=*(int *)MK_FP(0x40,0x49);      	// Old videomode
UBYTE far *Screen=(char far *)MK_FP(0xa000,0);  // Pointer to screen
UBYTE huge VBuffer[BufferS];			// Offscreen video buffer

extern UBYTE huge txt[65536];           // Texture (256x256)
extern UBYTE huge wall[16384];          // Wall Texture (128x128)

UBYTE huge wall1[16384];
UBYTE huge wall2[16384];

UWORD huge TxtLut[BufferS];		// Texture LookUpTable
TWallLut huge WallLut[ViewD];		// Wall LookUpTable

TWallType WallType[MaxWallType];        // Wall types
TWall 	Wall[MaxWall];   		// Walls MOET OOK _LEVEL_ DIMENSIE BIJ!!!
TLevel 	Level[MaxLevel];		// Levels
TPlayer Player;             		// Player
TGame	Game;				// Game information


/****************************************************************************
 Prototypes
****************************************************************************/

extern "C"
{
	void SetGfxMode(int mode);
	void SetPalette(char far *regs, int first, int num);
	void VRetrace(void);
}

void CalcTunnel();
void DrawTunnel();
void DoWalls();

void Scale
(
  UBYTE *SourcePtr,
  ULONG SourceW,
  ULONG SourceH,
  UBYTE *BufferPtr,
  SLONG BufferX,
  SLONG BufferY,
  SLONG DestW,
  SLONG DestH
);

void DoPlayer();
void DrawPlayer();

ULONG InHole(UWORD Type);

void DisplayBuffer();

void SaveCalc();



  ULONG R2=0;



/****************************************************************************
 Main
****************************************************************************/

int main(void)
{

  SetGfxMode(0x13);                               // Set new videomode

  CalcTunnel();
  SaveCalc();

  // Copieer wall texture
  for (ULONG t=0; t<16384; t++) wall1[t]=wall[t];
  for (t=0; t<16384; t++) wall2[t]=wall[t];

  // Maak holes in wall textures
  for (int i=0; i<64; i++) for (int j=0; j<64; j++) wall[i*128+j]=0;
  for (i=0; i<128; i++) for (j=64; j<128; j++) wall1[i*128+j]=0;
  for (i=0; i<128; i++) for (j=0; j<64; j++) wall2[i*128+j]=0;

  // Init walltypes
  WallType[0].Bitmap=wall;
  WallType[0].HoleX1=0;
  WallType[0].HoleY1=0;
  WallType[0].HoleX2=79;
  WallType[0].HoleY2=63;

  WallType[1].Bitmap=wall1;
  WallType[1].HoleX1=80;
  WallType[1].HoleY1=0;
  WallType[1].HoleX2=159;
  WallType[1].HoleY2=127;

  WallType[2].Bitmap=wall2;
  WallType[2].HoleX1=0;
  WallType[2].HoleY1=0;
  WallType[2].HoleX2=79;
  WallType[2].HoleY2=127;


  // Init other stuff
  Player.X=CenterX;
  Player.Y=CenterY;
  Player.XSpd=0;
  Player.YSpd=0;

  Player.Z=0;
  Player.ZSpd=0;
  Player.Scroll=0;

  Game.FirstWall=0;
  Game.LastWall=0;

  Level[0].NumWall=5;

  Wall[0].Z=256; Wall[0].Type = 0;
  Wall[1].Z=1024; Wall[1].Type = 1;
  Wall[2].Z=2100; Wall[2].Type = 2;
  Wall[3].Z=2400; Wall[3].Type = 0;
  Wall[4].Z=3000; Wall[4].Type = 1;

  Game.Flags=0;

  while(!Game.Flags)
  {
    VRetrace();

    DrawTunnel();
    DoWalls();

    DoPlayer();

    DisplayBuffer();
    printf("R2:%ld \r",R2);

  };

//  SetGfxMode(oldmode);				// Restore videomode

  switch(Game.Flags)
  {
    case 1: printf("\nUser break\n"); break;
    case 2: printf("\nLevel finished\n"); break;
    case 3: printf("\nCollision!\n"); break;
  };
  char C=0;
  while(kbhit()) C=getch();

  return(0);
}

/****************************************************************************
 CalcTunnel
 ----------------------------------------------------------------------------
 Inputs     : -
 Output     : -
 Description: Calculates texture and wall look-up-tables.
 History    : -
****************************************************************************/

void CalcTunnel()
{
  // Variables --------------------------------------------------------------

  double dX,dY,R;
  UWORD U,V;

  // Calculate Texture LookUpTable ------------------------------------------

  for (ULONG i=0; i < BufferS; i++){

    dX=(SWORD) ((i % BufferW) - CenterX);  		// X distance
    dY=(SWORD) ((i / BufferW) - CenterY);   		// Y distance

    R=sqrt(dX*dX+dY*dY);          			// Distance to center

    if((SWORD) R!=0)
    {
      U=(UWORD) (atan2(dY,dX) * Rad2Texel) % TxtW; 	// U
      V=(UWORD) (TunnelD / R)              % TxtH;	// V
    }
    else
    {
      U=0;              	  			// Center Pixel
      V=TunnelD;   					//
    };

    TxtLut[i]=U+V*TxtW;					// Offset in texture
  };


  // Calculate Wall LookUpTable ---------------------------------------------

  for (i=1; i < ViewD; i++){
    WallLut[i].S = TunnelD2 / i;			// Project 3d -> 2d
    WallLut[i].X = CenterX - (WallLut[i].S/2);
    WallLut[i].Y = CenterY - (WallLut[i].S/2);
  };
};


/****************************************************************************
 DrawTunnel
 ----------------------------------------------------------------------------
 Inputs     : -
 Outputs    : -
 Description: Draws the tunnel into the Video Buffer.
 History    : -
****************************************************************************/


void DrawTunnel()
{
  for (unsigned int i=0; i < BufferS; i++)
    VBuffer[i]=txt[ (TxtLut[i]+Player.Scroll) &0xFFFF];
};


/****************************************************************************
 Scale
 ----------------------------------------------------------------------------
 Inputs     : *SourcePtr      Address of image buffer
	      SourceW         }_ Size of source image
	      SourceH         }
	      *BufferPtr      Address of destination buffer
	      BufferX         }_ Topleft-coords of image in dest. buffer
	      BufferY         }
	      DestW           }_ Size of destination image
	      DestH           }
 Output     : -
 Description: Scales a (byte-per-pixel) chunky image.
	      o Performs clipping.
	      o Does not perform mirroring.
	      o Destination coordinates are allowed to be negative.
	      o Color 0 is transparent.
 History    : 05 Dec 1996, First version.
	      17 Dec 1996, Transparency added.
	      18 Dec 1996, Fixed clipping bug.
****************************************************************************/

void Scale
(
  UBYTE *SourcePtr,
  ULONG SourceW,
  ULONG SourceH,
  UBYTE *BufferPtr,
  SLONG BufferX,
  SLONG BufferY,
  SLONG DestW,
  SLONG DestH
)
{
  // Variables --------------------------------------------------------------

  ULONG XStep, YStep;           // Steps for source image
  ULONG SX, SY;                 // Source image interpolators
  ULONG StartSX;                // Initial value for SX

  SLONG ClipW, ClipH;           // Clipped width and height of dest. image
  ULONG BufferStep;             // Step to get to the next dest. row

  UBYTE *SourceLoop;            // Source image pointer for the scaling-loops

  UBYTE Pix;			// Temp. variable to hold source pixel

  // Clipping ---------------------------------------------------------------

  if (DestW < 2) return;
  if (DestH < 2) return;

  if (BufferX > ClipR) return;
  if (BufferY > ClipB) return;
  if ((BufferX + DestW) < ClipL) return;
  if ((BufferY + DestH) < ClipT) return;

  XStep   = (SourceW << 16) / DestW;
  YStep   = (SourceH << 16) / DestH;
  ClipW   = DestW;
  ClipH   = DestH;
  StartSX = 0;
  SY      = 0;

  if (BufferY < ClipT)                          // Clip to Top
  {
    ClipH     -= (ClipT - BufferY);
    SY         = (ClipT - BufferY) * YStep;
    BufferY    = ClipT;
  };

  if ((BufferY + ClipH) > ClipB)            	// Clip to Bottom
    ClipH = ClipB - BufferY + 1;

  if (BufferX < ClipL)                          // Clip to Left
  {
    ClipW     -= (ClipL - BufferX);
    StartSX    = (ClipL - BufferX) * XStep;
    BufferX    = ClipL;
  };

  if ((BufferX + ClipW) > ClipR)            	// Clip to Right
    ClipW = ClipR - BufferX + 1;

  // Scaling ----------------------------------------------------------------

  SourceLoop = SourcePtr;
  BufferPtr += (BufferY * BufferW) + BufferX;
  BufferStep = BufferW - ClipW;

  for(int i=0; i < ClipH; i++)                  // Row loop
  {
    SourceLoop = SourcePtr + ((SY >> 16) * SourceW);
    SX         = StartSX;

    for(int j=0; j < ClipW; j++)                // Column loop
    {
      Pix = *(SourceLoop + (SX >> 16));		// Read source pixel
      if (Pix!=0) *BufferPtr = Pix;   		// Not transparent ?

      BufferPtr += 1;				// Step destination
      SX += XStep;                              // Step source
    };

    BufferPtr += BufferStep;                    // Step destination
    SY        += YStep;                         // Step source
  };
};


/****************************************************************************
 DisplayBuffer
 ----------------------------------------------------------------------------
 Inputs     : -
 Outputs    : -
 Description: Copies the Video Buffer to the screen (mode 13h).
 History    : -
****************************************************************************/

void DisplayBuffer()
{
  UWORD sy = 0, vy = 0;
  for (UWORD y=0; y<BufferH; y++)
  {
    for (UWORD x=0; x<BufferW; x++) Screen[x+sy]=VBuffer[x+vy];
    sy+=320;
    vy+=BufferW;
  };
};


/****************************************************************************
 DoWalls
 ----------------------------------------------------------------------------
 Inputs     : -
 Outputs    : -
 Description: Draws all visible walls.
 History    : -
****************************************************************************/

void DoWalls()
{
  ULONG RelZ;
  SLONG t;

  // Check if FirstWall is still visible ------------------------------------

  if (Wall[Game.FirstWall].Z <= Player.Z)
  {
    Game.FirstWall++;
    if (Game.FirstWall > (Level[0].NumWall-1) )  	  // Level finished ?
      Game.Flags=2;
  };

  // Check if a new wall becomes visible ------------------------------------

  if (Game.LastWall >= (Level[0].NumWall-1))               // No more walls ?
    Game.LastWall = Level[0].NumWall-1;
  else
    if (Wall[Game.LastWall+1].Z < (Player.Z + ViewD))   	// New wall ?
      Game.LastWall++;

  // Draw the visible walls back to front -----------------------------------

  for (t=Game.LastWall; t >= (Game.FirstWall-1); t--)
  {
    RelZ = Wall[t].Z - Player.Z;      			        // Relative Z
    Scale(WallType[Wall[t].Type].Bitmap,
	  WallW, WallH, VBuffer,
	  WallLut[RelZ].X, WallLut[RelZ].Y,
	  WallLut[RelZ].S, WallLut[RelZ].S);
  };


  // Draw the first visible wall and player ---------------------------------

  RelZ = Wall[Game.FirstWall].Z - Player.Z;			// Relative Z

  if ((ULONG) RelZ < ViewD)                       // Is wall really visible ?
  {
    if (RelZ < RealZ)	                 	 // Player obscured by wall ?
    {
      DrawPlayer();
      Scale(WallType[Wall[Game.FirstWall].Type].Bitmap,
	    WallW, WallH, VBuffer,
	    WallLut[RelZ].X, WallLut[RelZ].Y,
	    WallLut[RelZ].S, WallLut[RelZ].S);
    }
    else      			        	// Player is in front of wall
    {
      Scale(WallType[Wall[Game.FirstWall].Type].Bitmap,
	    WallW, WallH, VBuffer,
	    WallLut[RelZ].X, WallLut[RelZ].Y,
	    WallLut[RelZ].S, WallLut[RelZ].S);

      DrawPlayer();
    };
  }
  else ;// DrawPlayer();


  // Test for collision -----------------------------------------------------

  if ( (RelZ <= RealZ) &&			 // Right position for test ?
       (RelZ >= (RealZ-(Player.ZSpd/256))) )
    if (InHole(Wall[Game.FirstWall].Type)==0)            // Not inside hole ?
    {
      Game.Flags=3;
    };
};


/****************************************************************************
 DoPlayer
 ----------------------------------------------------------------------------
 Inputs     : -
 Outputs    : -
 Description: Does player things.
 History    : -
****************************************************************************/

void DoPlayer()
{
  char C=0;

  // Get input --------------------------------------------------------------

  while(kbhit()) C=getch();

  switch(C)
  {
    case '+':
      Player.ZSpd += ZAccel;
      if (Player.ZSpd > MaxSpd) Player.ZSpd=MaxSpd;
      break;

    case '4': Player.XSpd -= XAccel; break;
    case '6': Player.XSpd += XAccel; break;
    case '2': Player.YSpd += YAccel; break;
    case '8': Player.YSpd -= YAccel; break;

    case '7': Player.XSpd -= XAccel; Player.YSpd -= YAccel; break;
    case '1': Player.XSpd -= XAccel; Player.YSpd += YAccel; break;
    case '9': Player.YSpd -= YAccel; Player.XSpd += XAccel; break;
    case '3': Player.YSpd += YAccel; Player.XSpd += XAccel; break;


    case 0:
      Player.ZSpd -= ZAccel;
      if (Player.ZSpd < MinSpd) Player.ZSpd=MinSpd;

      if (Player.XSpd < 0){
	Player.XSpd += XAccel;
	if (Player.XSpd > 0) Player.XSpd=0;};

      if (Player.XSpd > 0){
	Player.XSpd -= XAccel;
	if (Player.XSpd < 0) Player.XSpd=0;};

      if (Player.YSpd < 0){
	Player.YSpd += YAccel;
	if (Player.YSpd > 0) Player.YSpd=0;};

      if (Player.YSpd > 0){
	Player.YSpd -= YAccel;
	if (Player.YSpd < 0) Player.YSpd=0;};

      break;

    case 27 :
      Game.Flags=1;
      break;
  };

  // Move player ------------------------------------------------------------

  Player.Z     += (Player.ZSpd / TxtH);
  Player.Scroll = (Player.Scroll+Player.ZSpd) & 0xFFFF;

/* Dit werkt niet. Je checked namelijk al of X, Y R overschrijdt, terwijl
   ze dat nog niet doen. Dan zet je de spd op 0. Dan gaat speler weer in
   dezelfde richting verder, en dat kan want we zijn nog steeds niet
   groter dan R. Dan op een bep. moment weer wel en dan begint het verhaal
   opnieuw.

   Beter is checken of het fout gaat en dan de _positie_ aanpassen. Maar
   dat moet weer met COS,SIN en TAN waarschijnlijk. Maar snelheid is hier
   niet zo belangrijk anyway.....
*/
  SWORD dX = Player.X + Player.XSpd - CenterX;
  SWORD dY = Player.Y + Player.YSpd - CenterY;

  R2 = dX*dX + dY*dY;

  if (R2 > RMax2)		                        // Outside boundary ?
  {
    Player.XSpd=0;
    Player.YSpd=0;
  }
  else
  {
    Player.X += Player.XSpd;
    Player.Y += Player.YSpd;

  };


};


/****************************************************************************
 DrawPlayer
 ----------------------------------------------------------------------------
 Inputs      : -
 Output      : -
 Description : Draws the player ship and its shadow.
 History     : -
****************************************************************************/

void DrawPlayer()
{
  ULONG i,j;

  for (i=BufferH-12; i<(BufferH-8); i++)		            // Shadow
    for (j=Player.X-8; j<(Player.X+8); j++)
      VBuffer[i*BufferW+j]=2;

  for (i=Player.Y-8; i<(Player.Y+8); i++)                           // Player
    for (j=Player.X-8; j<(Player.X+8); j++)
      VBuffer[i*BufferW+j]=1;
};


/****************************************************************************
 InHole
 ----------------------------------------------------------------------------
 Inputs     : Type = Type of wall
 Outputs    : 0    = Outside hole
	      1    = Inside hole
 Description: Determines if ship is _completely_ inside the hole of a wall.
 History    : 23 Dec 1996.
****************************************************************************/

ULONG InHole(UWORD Type)
{
  if ((Player.X-8) < WallType[Type].HoleX1) return(0);	         // Outside ?
  if ((Player.Y-8) < WallType[Type].HoleY1) return(0);
  if ((Player.X+7) > WallType[Type].HoleX2) return(0);
  if ((Player.Y+7) > WallType[Type].HoleY2) return(0);

  return(1);                    	                     // Inside hole !
};







/***************************************************************************/

/* TunnelD = 127*256 is maximaal dat naar je toe komt,
		128*256 is maximaal dat van je af gaat,
		1*256 is minimaal dat naar je toe komt,
		256*256 is minimaal dat van je af gaat...
		tadaa...effuh uitzoekuh!
*/


   ULONG S[ViewD];  // buiten SaveCalc() alloceren, anders krijgen we een
   ULONG X[ViewD];  // stack-fuckup en klopt de halve S-array niet meer !!!
   ULONG Y[ViewD];  // oh ja, zonder compiler error natuurlijk ...

void SaveCalc()
{
   int handle;

   /* change the default file mode from text to binary */
   _fmode = O_BINARY;

   /* create a binary file for reading and writing */
   handle = creat("txt.lut", S_IREAD |S_IWRITE);

   write(handle, TxtLut, (160*128 * 2));

   /* close the file */
   close(handle);

   /* Split up WallLut into 3 arrays */

   for (ULONG i=0; i<ViewD; i++)
   {
     S[i]=WallLut[i].S;
     X[i]=WallLut[i].X;
     Y[i]=WallLut[i].Y;
   };

   handle = creat("wall_s.lut", S_IREAD | S_IWRITE);
   write(handle, S, (ViewD * 4));
   close(handle);

   handle = creat("wall_x.lut", S_IREAD | S_IWRITE);
   write(handle, X, (ViewD * 4));
   close(handle);

   handle = creat("wall_y.lut", S_IREAD | S_IWRITE);
   write(handle, Y, (ViewD * 4));
   close(handle);

}

