/******************* ( Animation Construction Kit 3D ) ***********************/
/*			 Point of View Routines				     */
/* CopyRight (c) 1993	   Author: Lary Myers				     */
/*****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#include <mem.h>
#include <alloc.h>
#include <io.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <sys\stat.h>
#include "ack3d.h"
#include "ackeng.h"
#include "ackext.h"

UINT xRay(int x,int y,int angle,ACKENG *ae);
UINT yRay(int x,int y,int angle,ACKENG *ae);
long long_sqrt(long v);

/****************************************************************************
** Normally an internal call made by AckMovePOV() to determine if a hit	   **
** has occurred with an object. The application can make this call if	   **
** desired at other times than moving the POV. If POV_OBJECT is returned   **
** then the app can call AckGetObjectHit() to retrieve the number of the   **
** object hit.								   **
**									   **
****************************************************************************/
int AckCheckObjPosn(ACKENG *ae,int xPlayer,int yPlayer,int PlayerAngle)
{
    int	    i,mPos,result;
    int	    j,count,SaveCenter;
    int	    ObjX,ObjY,ObjNum;
    int	    NewX,NewY,MapPosn;
    int	    MaxOpp,Column,ColBeg,ColEnd;
    long    deltax,deltay;
    long    xp,yp,MinDistance,distance;
    long    SinValue,CosValue;

result = POV_NOTHING;
MinDistance = 3000000L;
SinValue = SinTable[PlayerAngle];
CosValue = CosTable[PlayerAngle];
MapPosn = (yPlayer & 0xFFC0) + (xPlayer >> 6);

for (i = 0; i < MAX_OBJECTS; i++)
    {

    if (!ae->ObjList[i].Active || ae->ObjList[i].Flags & OF_PASSABLE)
	continue;

    mPos = (ae->ObjList[i].y & 0xFFC0) + (ae->ObjList[i].x >> 6);
    if (mPos == MapPosn)
	{
	ObjX = ae->ObjList[i].x;
	ObjY = ae->ObjList[i].y;
	NewX = ObjX - xPlayer;
	NewY = ObjY - yPlayer;

	if (PlayerAngle > INT_ANGLE_180 && (NewY-63) > 0)
	    continue;

	if (PlayerAngle < INT_ANGLE_180 && (NewY+63) < 0)
	    continue;

	if (PlayerAngle > INT_ANGLE_270 || PlayerAngle < INT_ANGLE_90)
	    {
	    if ((NewX+63) < 0)
		continue;
	    }

	if (PlayerAngle < INT_ANGLE_270 && PlayerAngle > INT_ANGLE_90)
	    {
	    if ((NewX-63) > 0)
		continue;
	    }


	if ((PlayerAngle == 0 || PlayerAngle == INT_ANGLE_180) && NewX == 0)
	    continue;

	if ((PlayerAngle == INT_ANGLE_90 || PlayerAngle == INT_ANGLE_270) &&
	    NewY == 0)
	    continue;


    /* Rotate coordinates to current player angle */
	deltax = ((NewX * CosValue) + (NewY * SinValue)) >> FP_SHIFT;
	deltay = ((NewY * CosValue) - (NewX * SinValue)) >> FP_SHIFT;

	MaxOpp = ((LongTanTable[INT_ANGLE_30] * (long)deltax) >> FP_SHIFT);

	if (NewY < ObjY)
	    {
	    MaxOpp = -MaxOpp;
	    deltay = -deltay;
	    }

	if ((deltay+32) < MaxOpp)
	    continue;

	if (NewX < 0) NewX = -NewX;
	if (NewY < 0) NewY = -NewY;
	distance = NewX + NewY;
	distance -= min(NewX,NewY) / 2;

	if (distance > MAX_DISTANCE)
	    continue;

	if (distance < MinDistance)
	    {
	    LastObjectHit = i;
	    MinDistance = distance;
	    result = POV_OBJECT;
	    }
	}
    }

return(result);
}



/****************************************************************************
** The application should make this call whenever the POV is moved. The	   **
** purpose of this function is to check if the move will strike a wall or  **
** object and if not, then to actually move the coordinates of the POV to  **
** the new location. When called, Angle must be in the range from 0 to	   **
** INT_ANGLE_360-1 and Amount should be a positive value (normally less	   **
** than 64 - values greater than around 44 could cause the POV to move	   **
** through walls!). The result code returned indicates whether the move	   **
** was successful (if zero) or what the POV actually hit (Xwall, Ywall,	   **
** or object).								   **
**									   **
****************************************************************************/
int AckMovePOV(ACKENG *ae,int Angle,int Amount)
{
    int	    x1,y1,HitResult,NewAngle;
    UCHAR   gCode;
    int	    MapPosn;

x1 = ae->xPlayer + (int)((CosTable[Angle] * Amount) >> FP_SHIFT);
y1 = ae->yPlayer + (int)((SinTable[Angle] * Amount) >> FP_SHIFT);

HitResult = AckCheckHit(ae->xPlayer,ae->yPlayer,Angle,ae);

if (!HitResult)
    {
    MapPosn = (y1 & 0xFFC0) + (x1 >> 6);

    if (AckCheckObjPosn(ae,x1,y1,Angle))
	return(POV_OBJECT);

    gCode = Grid[MapPosn] & 0xFF;
    if (gCode > 0 && gCode < DOOR_XCODE)
	{
	if (!(Grid[MapPosn] & DOOR_TYPE_SECRET))
	    return(POV_XWALL);
	}

    ae->xPlayer = x1;
    ae->yPlayer = y1;
    return(HitResult);
    }

if (HitResult == POV_OBJECT)
    return(HitResult);


if (HitResult == POV_XWALL)
    {
    x1 = ae->xPlayer;
    if (Angle < INT_ANGLE_180)
	NewAngle = INT_ANGLE_90;
    else
	NewAngle = INT_ANGLE_270;

    }
else
    {
    y1 = ae->yPlayer;
    if (Angle > INT_ANGLE_270 || Angle < INT_ANGLE_90)
	NewAngle = 0;
    else
	NewAngle = INT_ANGLE_180;
    }


if (!AckCheckHit(ae->xPlayer,ae->yPlayer,NewAngle,ae))
    {
    MapPosn = (y1 & 0xFFC0) + (x1 >> 6);

    if (AckCheckObjPosn(ae,x1,y1,Angle))
	return(POV_OBJECT);

    gCode = Grid[MapPosn] & 0xFF;
    if (gCode > 0 && gCode < DOOR_XCODE)
	if (!(Grid[MapPosn] & DOOR_TYPE_SECRET))
	    return(POV_XWALL);

    ae->xPlayer = x1;
    ae->yPlayer = y1;
    return(POV_NOTHING);
    }

return(HitResult);
}


/****************************************************************************
** Similiar to the AckMovePOV() above except ignores collision with the	   **
** same object being moved, and also checks for a collision with the	   **
** player. Angle should be the direction to move the object, Amount is the **
** map unit distance the object is to be moved.				   **
**									   **
****************************************************************************/
int AckMoveObjectPOV(ACKENG *ae,int ObjIndex,int Angle,int Amount)
{
    int	    x1,y1,HitResult,NewAngle,oNum;
    UCHAR   gCode;
    int	    MapPosn,PlayerPosn;

x1 = ae->ObjList[ObjIndex].x + (int)((CosTable[Angle] * Amount) >> FP_SHIFT);
y1 = ae->ObjList[ObjIndex].y + (int)((SinTable[Angle] * Amount) >> FP_SHIFT);

HitResult = AckCheckHit(ae->ObjList[ObjIndex].x,ae->ObjList[ObjIndex].y,Angle,ae);

if (!HitResult)
    {
    MapPosn = (y1 & 0xFFC0) + (x1 >> 6);

    oNum = AckCheckObjPosn(ae,x1,y1,Angle);
    if (oNum > 0 && LastObjectHit != ObjIndex)
	return(POV_OBJECT);

    gCode = Grid[MapPosn] & 0xFF;
    if (gCode > 0 && gCode < DOOR_XCODE)
	if (!(Grid[MapPosn] & DOOR_TYPE_SECRET))
	    return(POV_XWALL);

    ae->ObjList[ObjIndex].x = x1;
    ae->ObjList[ObjIndex].y = y1;

    PlayerPosn = (ae->yPlayer & 0xFFC0) + (ae->xPlayer >> 6);
    if (MapPosn == PlayerPosn)
	return(POV_PLAYER);

    }

return(HitResult);
}




/****************************************************************************
** Internal call used by AckMovePOV() and AckMoveObjectPOV() to determine  **
** if a wall was hit. This routine does NOT check for hits with objects.   **
** ViewAngle is the angle to check against (usually the angle the POV is   **
** facing, but could also be 180 degrees from the facing angle to see if   **
** the POV hits something while backing up).				   **
**									   **
****************************************************************************/
int AckCheckHit(int xPlayer,int yPlayer,int ViewAngle,ACKENG *ae)
{
    int	    BitmapColumn,BitmapNumber,yBitmap,distance;
    int	    i,WallCode;
    long    WallDistance,xd,yd,yDistance;
    long    CheckDist;

WallDistance = 3000000;	    /* Set to a ridiculous value */
WallCode     = POV_NOTHING;
CheckDist    = 48L;	    /* Initial minimum distance to look for */
BitmapNumber = 0;	    /* Initialize to no bitmap found */

/* Set number of objects seen on this pass */
TotalObjects = 0;

/* Don't allow one of these angles, causes either the X or Y ray to not be */
/* cast which gives a false reading about an obstacle.			   */
if (ViewAngle == INT_ANGLE_45 ||
    ViewAngle == INT_ANGLE_135 ||
    ViewAngle == INT_ANGLE_225 ||
    ViewAngle == INT_ANGLE_315)
    ViewAngle++;

/* Don't cast an X ray if no chance of striking a X wall */
if (ViewAngle != INT_ANGLE_90 && ViewAngle != INT_ANGLE_270)
    {
    BitmapNumber = xRay(xPlayer,yPlayer,ViewAngle,ae);

    if (BitmapNumber)
	{
	xd = iLastX - xPlayer;

	/* Use the delta X to determine the distance to the wall */
	WallDistance = (xd * InvCosTable[ViewAngle]) >> 14;

	if (WallDistance < 0)
	    WallDistance = 120000L;

	/* Set the wall struck code to an X wall */
	WallCode = POV_XWALL;
	LastMapPosn = xMapPosn;
	}
    }

/* Don't cast a Y ray if impossible to strike a Y wall */
if (ViewAngle != 0 && ViewAngle != INT_ANGLE_180)
    {
    yBitmap = yRay(xPlayer,yPlayer,ViewAngle,ae);

    if (yBitmap)
	{
	yd = iLastY - yPlayer;

	/* Use the delta Y to determine distance to the wall */
	yDistance = (yd * InvSinTable[ViewAngle]) >> 14;
	if (yDistance < 0)
	    yDistance = 120000L;

	/* If Y wall closer than X wall then use Y wall data */
	if (yDistance < WallDistance)
	    {
	    WallDistance = yDistance;

	    /* Indicate the wall struck was a Y wall */
	    WallCode = POV_YWALL;
	    BitmapNumber = yBitmap;
	    LastMapPosn = yMapPosn;
	    }

	}

    }

/* Since doors appear in the middle of the wall, adjust the minimum distance */
/* to it. This handles walking up close to a door.			     */
if (BitmapNumber >= DOOR_XCODE)
    CheckDist += 64L;

if (WallCode)
    {
    /* Adjust the distance based on the center of the screen */
    WallDistance *= ViewCosTable[160];

    /* Remove fixed point and round-up */
    xd = WallDistance >> 14;
    if (WallDistance - (xd << 14) >= 8096)
	xd++;

    /* Remove initial fixed point and round-up */
    WallDistance = xd >> 6;
    if (xd - (WallDistance << 6) >= 32)
	WallDistance++;

    /* If the wall or object is further than the minimum distance, we can */
    /* continue moving in this direction.				  */
    if (WallDistance > CheckDist)
	WallCode = POV_NOTHING;
    }

return(WallCode);
}


/****************************************************************************
** Obsolete routine, was used for internal movement by the ACK engine.	   **
**									   **
****************************************************************************/
void AckMoveObject(int Index,int dx,int dy,ACKENG *ae)
{
    int	    Pos,NewPos,x1,y1;

ae->ObjList[Index].y += dy;
ae->ObjList[Index].x += dx;
x1 = ae->ObjList[Index].x >> 6;
y1 = ae->ObjList[Index].y >> 6;
NewPos = (y1 * GRID_WIDTH) + x1;
ae->ObjList[Index].mPos = NewPos;

}

/****************************************************************************
** This routine can be called by the application to automatically update   **
** any objects that have multiple bitmaps to display. (Note: This is	   **
** different than objects that have multiple sides). Any objects that need **
** a new bitmap displayed will be updated by this routine.		   **
**									   **
****************************************************************************/
void AckCheckObjectMovement(ACKENG *ae)
{
	    int	    i,dx;

for (i = 1; i < ae->MaxObjects; i++)
    {
    if (!ae->ObjList[i].Active)
	continue;

    if (!ae->ObjList[i].Speed)
	continue;

    if (!ae->ObjList[i].Flags & OF_ANIMATE)
	continue;

    dx = ae->ObjList[i].CurNum + 1;
    if (dx > ae->ObjList[i].MaxNum)
	dx = 0;

    ae->ObjList[i].CurNum = dx;
    }

}


