//-----------------------------------------------------------------------------
//
//  $Logfile:: /EF2/Code/DLLs/game/gravpath.cpp                              $
// $Revision:: 8                                                              $
//   $Author:: Singlis                                                        $
//     $Date:: 9/26/03 2:36p                                                  $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
//
// DESCRIPTION:
// Gravity path - Used for underwater currents and wells.

#include "_pch_cpp.h"
#include "entity.h"
#include "gravpath.h"
#include "container.h"
#include "navigate.h"
#include "misc.h"
#include "player.h"

GravPathManager gravPathManager;

CLASS_DECLARATION( Class, GravPathManager, NULL )
{
	{ NULL,NULL }
};

GravPathManager::~GravPathManager()
{
	Reset();
}

void GravPathManager::Reset( void )
{
	while( pathList.NumObjects() > 0 )
	{
		delete ( GravPath * )pathList.ObjectAt( 1 );
	}
	
	pathList.FreeObjectList();
}

void GravPathManager::AddPath(GravPath *p)
{
	pathList.AddObject( p );
}

void GravPathManager::RemovePath(GravPath *p)
{
	pathList.RemoveObject( p );
}

void GravPathManager::DrawGravPaths( void )
{
	int i;
	int num = pathList.NumObjects();
	
	for( i = 1; i <= num; i++ )
	{
		GravPath *p = ( GravPath * )pathList.ObjectAt( i );
		
		p->DrawPath( 1.0f, 0.0f, 0.0f );
	}
}

Vector GravPathManager::CalculateGravityPull(Entity &ent, const Vector &pos, qboolean *force, float *max_speed)
{
	int            i,num;
	GravPath       *p;
	GravPathNode   *node;
	Vector         point;
	Vector         newpoint;
	Vector         dir;
	float          bestdist = 99999;
	float          dist;
	float          speed;
	float          radius;
	Vector         velocity;
	int            bestpath = 0;
	int            entity_contents, grav_contents;
	
	
	*force = false;
	
	num = pathList.NumObjects();
	if ( !num )
	{
		return vec_zero;
	}
	
	entity_contents = gi.pointcontents( ent.origin, 0 );
	
	for( i = 1; i <= num; i++ )
	{
		p = ( GravPath * )pathList.ObjectAt( i );
		
		if ( !p )
			continue;
		
		// Check to see if path is active
		node = p->GetNode( 1 );
		if ( !node || !node->active )
			continue;
		
		// Check to see if the contents are the same
		grav_contents = gi.pointcontents( node->origin, 0 );
		
		// If grav node is in water, make sure ent is too.
		if ( ( grav_contents & CONTENTS_WATER ) && !( entity_contents & CONTENTS_WATER ) )
			continue;
		
		// Test to see if we are in this path's bounding box
		if ( (pos.x < p->maxs.x) && (pos.y < p->maxs.y) && (pos.z < p->maxs.z) &&
			(pos.x > p->mins.x) && (pos.y > p->mins.y) && (pos.z > p->mins.z) )
		{
			point = p->ClosestPointOnPath(pos, ent, &dist, &speed, &radius);
			
			// If the closest distance on the path is greater than the radius, then
			// do not consider this path.
			
			if (dist > radius)
            {
				continue;
            }
			else if (dist < bestdist)
            {
				bestpath = i;
				bestdist  = dist;
            }
		}
	}
	
	if (!bestpath)
	{
		return vec_zero;
	}
	
	p = ( GravPath * )pathList.ObjectAt( bestpath );
	if ( !p )
		return velocity;
	*force    = p->force;
	dist     = p->DistanceAlongPath(pos, &speed);
	newpoint = p->PointAtDistance( pos, dist + speed, ent.isSubclassOf( Player ), max_speed );
	dir      = newpoint-pos;
	dir.normalize();
	velocity = dir * speed;
	
	//velocity *= .75;
	return velocity;
}

/*****************************************************************************/
/*QUAKED info_grav_pathnode (0 0 .5) (-16 -16 0) (16 16 32) HEADNODE FORCE PULL_UPWARDS
 "radius" Radius of the effect of the pull (Default is 256)
 "speed"  Speed of the pull (Use negative for a repulsion) (Default is 100)

  Set HEADNODE to signify the head of the path.
  Set FORCE if you want un-fightable gravity ( i.e. can't go backwards )
  Set PULL_UPWARDS if you want the gravnodes to pull you upwards also
******************************************************************************/

#define PULL_UPWARDS ( 1 << 2 )

Event EV_GravPath_Create
(
	"gravpath_create",
	EV_SCRIPTONLY,
	NULL,
	NULL,
	"Create the grav path."
);
Event EV_GravPath_Activate
(
	"activate",
	EV_SCRIPTONLY,
	NULL,
	NULL,
	"Activate the grav path."
);
Event EV_GravPath_Deactivate
(
	"deactivate",
	EV_SCRIPTONLY,
	NULL,
	NULL,
	"Deactivate the grav path."
);
Event EV_GravPath_SetSpeed
(
	"speed",
	EV_DEFAULT,
	"f",
	"speed",
	"Set the speed of the grav path."
);
Event EV_GravPath_SetMaxSpeed
(
	"maxspeed",
	EV_SCRIPTONLY,
	"f",
	"maxspeed",
	"Set the max speed of the grav path."
);
Event EV_GravPath_SetRadius
(
	"radius",
	EV_SCRIPTONLY,
	"f",
	"radius",
	"Set the radius of the grav path."
);

CLASS_DECLARATION( Entity, GravPathNode, "info_grav_pathnode" )
{
	{ &EV_GravPath_Create,							&GravPathNode::CreatePath },
	{ &EV_GravPath_Activate,						&GravPathNode::Activate },
	{ &EV_GravPath_Deactivate,						&GravPathNode::Deactivate },
	{ &EV_GravPath_SetSpeed,						&GravPathNode::SetSpeed },
	{ &EV_GravPath_SetMaxSpeed,						&GravPathNode::SetMaxSpeed },
	{ &EV_GravPath_SetRadius,						&GravPathNode::SetRadius },

	{ NULL, NULL }
};

GravPathNode::GravPathNode()
{
	if ( LoadingSavegame )
	{
		// all data will be setup by the archive function
		return;
	}
	
	setMoveType( MOVETYPE_NONE );
	setSolidType( SOLID_NOT );
	hideModel();
	
	speed     = 100.0f;
	max_speed = 200.0f;
	radius    = 256.0f;
	headnode  = spawnflags & 1;
	active    = true;
	
	// This is the head of a new path, post an event to create the path
	if ( headnode )
	{
		PostEvent( EV_GravPath_Create, 0.0f );
	}
}

void GravPathNode::SetSpeed( Event *ev )
{
	speed = ev->GetFloat( 1 );
}

void GravPathNode::SetMaxSpeed( Event *ev )
{
	max_speed = ev->GetFloat( 1 );
}

void GravPathNode::SetRadius( Event *ev )
{
	radius = ev->GetFloat( 1 );
}

float GravPathNode::Speed( void )
{
	if ( active )
		return speed;
	else
		return 0;
}

float GravPathNode::MaxSpeed( void )
{
	return max_speed;
}

void GravPathNode::Activate( Event *ev )
{
	GravPathNode   *node;
	Entity         *ent;
	const char     *target;
	
	active = true;
	node = this;
	// Go through the entire path and activate it
	target = node->Target();
	while (target[0])
	{
		ent = G_FindTarget( NULL, target );
		if ( ent )
		{
			node = (GravPathNode *)ent;
			assert( node );
			node->active = true;
		}
		else
		{
			gi.Error( ERR_DROP, "GravPathNode::CreatePath: target %s not found\n",target);
		}
		target = node->Target();
	}
}

void GravPathNode::Deactivate(Event *ev)
{
	GravPathNode   *node;
	Entity         *ent;
	const char     *target;
	
	active = false;
	node = this;
	// Go through the entire path and activate it
	target = node->Target();
	while (target[0])
	{
		ent = G_FindTarget( NULL, target);
		if ( ent )
		{
			node = (GravPathNode *)ent;
			assert( node );
			node->active = false;
		}
		else
		{
			gi.Error( ERR_DROP, "GravPathNode::CreatePath: target %s not found\n",target);
		}
		target = node->Target();
	}
}

void GravPathNode::CreatePath(Event *ev)
{
	const char     *target;
	GravPath       *path = new GravPath;
	GravPathNode   *node;
	Entity         *ent;
	
	ClearBounds( path->mins, path->maxs );
	
	// This node is the head of a path, create a new path in the path manager.
	// and add it in, then add all of it's children in the path.
	node = this;
	path->AddNode(node);
	path->force = spawnflags & 2;
	
	// Make the path from the targetlist.
	target = node->Target();
	while (target[0])
	{
		ent = G_FindTarget( NULL, target );
		if ( ent )
		{
			node = (GravPathNode *)ent;
			assert( node );
			path->AddNode(node);
		}
		else
		{
			gi.Error( ERR_DROP, "GravPathNode::CreatePath: target %s not found\n",target);
		}
		target = node->Target();
	}
	
	// Set the origin.
	path->origin = path->mins + path->maxs;
	path->origin *= 0.5f;
}

CLASS_DECLARATION( Listener, GravPath, NULL )
{
	{ NULL, NULL }
};

GravPath::GravPath()
{
	pathlength  = 0;
	from        = NULL;
	to          = NULL;
	nextnode    = 1;
	force			= 0;
	
	if ( !LoadingSavegame )
	{
		gravPathManager.AddPath(this);
	}
}

GravPath::~GravPath()
{
	pathlength  = 0;
	from        = NULL;
	to          = NULL;
	nextnode    = 1;
	gravPathManager.RemovePath(this);
}

void GravPath::Clear( void )
{
	nextnode = 1;
	pathlength = 0;
	from = NULL;
	to = NULL;
	pathlist.FreeObjectList();
}

void GravPath::Reset( void )
{
	nextnode = 1;
}

GravPathNode *GravPath::Start( void )
{
	return from;
}

GravPathNode *GravPath::End( void )
{
	return to;
}

void GravPath::AddNode( GravPathNode *node )
{
	int num;
	Vector r,addp;
	
	if ( !from )
	{
		from = node;
	}
	
	to = node;
	pathlist.AddObject( GravPathNodePtr( node ) );
	
	num = NumNodes();
	if ( num > 1 )
	{
		pathlength += ( node->origin - GetNode( num )->origin ).length();
	}
	
	r.setXYZ(node->Radius(),node->Radius(),node->Radius());
	addp = node->origin + r;
	AddPointToBounds(addp,mins,maxs);
	addp = node->origin - r;
	AddPointToBounds(addp,mins,maxs);
}

GravPathNode *GravPath::GetNode( int num )
{
	return pathlist.ObjectAt( num );
}

GravPathNode *GravPath::NextNode( void )
{
	if ( nextnode <= NumNodes() )
	{
		return pathlist.ObjectAt( nextnode++ );
	}
	return NULL;
}

Vector GravPath::ClosestPointOnPath( const Vector &pos, Entity &ent, float *ret_dist, float *speed, float *radius )
{
	GravPathNode	*s;
	GravPathNode	*e;
	int	      	num;
	int		      i;
	float		      bestdist;
	Vector	      bestpoint;
	float		      dist;
	float		      segmentlength;
	Vector	      delta;
	Vector	      p1;
	Vector      	p2;
	Vector	      p3;
	float		      t;
	//trace_t	      trace;
	
	num = NumNodes();
	s = GetNode( 1 );
	//trace = G_Trace( pos, ent.mins, ent.maxs, s->origin, &ent, MASK_PLAYERSOLID, false, "GravPath::ClosestPointOnPath 1" );
	bestpoint = s->origin;
	delta = bestpoint - pos;
	bestdist = delta.length();
	*speed = s->Speed();
	*radius = s->Radius();
	
	for( i = 2; i <= num; i++ )
	{
		e = GetNode( i );
		
		// check if we're closest to the endpoint
		delta = e->origin - pos;
		dist = delta.length();
		
		if ( dist < bestdist )
		{
			//trace = G_Trace( pos, ent.mins, ent.maxs, e->origin, &ent, MASK_PLAYERSOLID, false, "GravPath::ClosestPointOnPath 2" );
			bestdist = dist;
			bestpoint = e->origin;
			*speed = e->Speed();
			*radius = e->Radius();
		}
		
		// check if we're closest to the segment
		p1 = e->origin - s->origin;
		segmentlength = p1.length();
		p1	*= 1.0f / segmentlength;
		p2 = pos - s->origin;
		
		t = p1 * p2;
		if ( ( t > 0.0f ) && ( t < segmentlength ) )
		{
			p3 = ( p1 * t ) + s->origin;
			
			delta = p3 - pos;
			dist = delta.length();
			if ( dist < bestdist )
			{
				//trace = G_Trace( pos, ent.mins, ent.maxs, p3, &ent, MASK_PLAYERSOLID, false, "GravPath::ClosestPointOnPath 3" );
				bestdist = dist;
				bestpoint = p3;
				*speed  = (e->Speed() * t) + (s->Speed() * (1.0f - t));
				*radius = (e->Radius() * t) + (s->Radius() * (1.0f - t));
			}
		}
		
		s = e;
	}
	*ret_dist = bestdist;
	return bestpoint;
}

float GravPath::DistanceAlongPath( const Vector &pos, float *speed )
{
	GravPathNode	*s;
	GravPathNode	*e;
	int	      	num;
	int		      i;
	float		      bestdist;
	float		      dist;
	float		      segmentlength;
	Vector	      delta;
	Vector	      segment;
	Vector      	p1;
	Vector	      p2;
	Vector	      p3;
	float		      t;
	float		      pathdist;
	float		      bestdistalongpath;
	float          oosl;
	pathdist = 0;
	
	num = NumNodes();
	s = GetNode( 1 );
	delta = s->origin - pos;
	bestdist = delta.length();
	bestdistalongpath = 0;
	*speed = s->Speed();
	
	for( i = 2; i <= num; i++ )
	{
		e = GetNode( i );
		
		segment = e->origin - s->origin;
		segmentlength = segment.length();
		
		// check if we're closest to the endpoint
		delta = e->origin - pos;
		dist = delta.length();
		
		if ( dist < bestdist )
		{
			bestdist = dist;
			bestdistalongpath = pathdist + segmentlength;
			*speed = e->Speed();
		}
		
		// check if we're closest to the segment
		oosl = ( 1.0f / segmentlength );
		p1 = segment * oosl;
		p1.normalize();
		p2 = pos - s->origin;
		
		t = p1 * p2;
		if ( ( t > 0.0f ) && ( t < segmentlength ) )
		{
			p3 = ( p1 * t ) + s->origin;
			
			delta = p3 - pos;
			dist = delta.length();
			if ( dist < bestdist )
			{
				bestdist = dist;
				bestdistalongpath = pathdist + t;
				
				t *= oosl;
				*speed = (e->Speed() * t) + (s->Speed() * (1.0f - t));
            }
		}
		
		s = e;
		pathdist += segmentlength;
	}
	
	return bestdistalongpath;
}

Vector GravPath::PointAtDistance( const Vector &pos, float dist, qboolean is_player, float *max_speed )
{
	GravPathNode	*s;
	GravPathNode	*e;
	int      		num;
	int		      i;
	Vector	      delta;
	Vector	      p1;
	float	      	t;
	float		      pathdist;
	float		      segmentlength;
	
	num = NumNodes();
	s = GetNode( 1 );
	pathdist = 0;
	
	for( i = 2; i <= num; i++ )
	{
		e = GetNode( i );
		
		delta = e->origin - s->origin;
		segmentlength = delta.length();
		
		if ( ( pathdist + segmentlength ) > dist )
		{
			t = dist - pathdist;
			
			p1 = delta * ( t / segmentlength );
			//         return p1 + s->origin;
			
			if ( e->spawnflags & PULL_UPWARDS && is_player )
				p1.z = p1.length() / 2.0f;
			
			*max_speed = e->MaxSpeed();
			
			return p1 + pos;
		}
		
		s = e;
		pathdist += segmentlength;
	}
	
	*max_speed = s->MaxSpeed();
	
	// cap it off at start or end of path
	return s->origin;
}

void GravPath::DrawPath( float r, float g, float b )
{
	Vector	      s;
	Vector	      e;
	Vector	      offset;
	GravPathNode	*node;
	int		      num;
	int		      i;
	
	num = NumNodes();
	node = GetNode( 1 );
	s = node->origin;
	G_DebugBBox( s, Vector(8.0f, 8.0f, 8.0f), Vector(-8.0f, -8.0f, -8.0f), 0.0f, 1.0f, 0.0f, 1.0f );
	offset = ( Vector( r, g, b ) * 4.0f ) + Vector( 0.0f, 0.0f, 0.0f );
	offset = Vector( 0.0f, 0.0f, 0.0f );
	
	for( i = 2; i <= num; i++ )
	{
		node = GetNode( i );
		e = node->origin;
		
		G_DebugLine( s + offset, e + offset, r, g, b, 1 );
		G_DebugBBox( e, Vector( 8.0f, 8.0f, 8.0f ), Vector( -8.0f, -8.0f, -8.0f ), 0.0f, 1.0f, 0.0f, 1.0f );
		s = e;
	}
	
	G_DebugBBox( origin,mins-origin,maxs-origin, 1.0f, 0.0f, 0.0f, 1.0f );
}

int GravPath::NumNodes( void )
{
	return pathlist.NumObjects();
}

float GravPath::Length( void )
{
	return pathlength;
}
