/*
**	Command & Conquer Generals Zero Hour(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

////////////////////////////////////////////////////////////////////////////////
//																																						//
//  (c) 2001-2003 Electronic Arts Inc.																				//
//																																						//
////////////////////////////////////////////////////////////////////////////////

// FILE: ProjectileStreamUpdate.cpp //////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, May 2002
// Desc:   Tracks all projectiles fired so they can be drawn as a stream
///////////////////////////////////////////////////////////////////////////////////////////////////

// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"	// This must go first in EVERY cpp file int the GameEngine

#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/ProjectileStreamUpdate.h"
#include "WWMath/Vector3.h"

#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ProjectileStreamUpdate::ProjectileStreamUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
	ObjectID m_projectileIDs[MAX_PROJECTILE_STREAM];
	for( Int index = 0; index < MAX_PROJECTILE_STREAM; index++ )
	{
		m_projectileIDs[index] = INVALID_ID;
	}

	m_owningObject = INVALID_ID;
	m_nextFreeIndex = 0;
	m_firstValidIndex = 0;
	
	m_targetObject = INVALID_ID;
	m_targetPosition.zero();
} 

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ProjectileStreamUpdate::~ProjectileStreamUpdate( void )
{

}

//-------------------------------------------------------------------------------------------------
/** The update callback. */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime ProjectileStreamUpdate::update( void )
{
	cullFrontOfList();

	// Update the draw module about our points (arrange array so they can read?)

	if( considerDying() )
		TheGameLogic->destroyObject( getObject() );

	return UPDATE_SLEEP_NONE;
}

void ProjectileStreamUpdate::addProjectile( ObjectID sourceID, ObjectID newID, ObjectID victimID, const Coord3D *victimPos )
{
	DEBUG_ASSERTCRASH( m_owningObject == INVALID_ID  ||  m_owningObject == sourceID, ("Two objects are trying to use the same Projectile stream.") );//Don't cross the streams!
	if( m_owningObject == INVALID_ID )
		m_owningObject = sourceID;

	// Keep track of target, and insert a hole if the target has changed
	if( victimID != INVALID_ID )
	{
		// Object shot
		if( victimID != m_targetObject )
		{
			// Changed targets, insert a hole to break the stream
			m_projectileIDs[ m_nextFreeIndex ] = INVALID_ID;
			m_nextFreeIndex = (m_nextFreeIndex + 1) % MAX_PROJECTILE_STREAM;

			// And mark this as our new target
			m_targetObject = victimID;
		}

		// Clear position so we know we are an object shot
		m_targetPosition.zero();
	}
	else if( victimPos != NULL )
	{
		if( ! (m_targetPosition == (*victimPos)) )
		{
			// New position, so insert hole
			m_projectileIDs[ m_nextFreeIndex ] = INVALID_ID;
			m_nextFreeIndex = (m_nextFreeIndex + 1) % MAX_PROJECTILE_STREAM;

			// And mark this as our new target
			m_targetPosition = (*victimPos);
		}

		// Clear object so we know we are a position shot
		m_targetObject = INVALID_ID;
	}
	else
	{
		DEBUG_CRASH(("A projectile stream was fired at neither an object nor a position.  Probably bad."));
	}

	// Keep track of the id in a circular array
	m_projectileIDs[ m_nextFreeIndex ] = newID;
	m_nextFreeIndex = (m_nextFreeIndex + 1) % MAX_PROJECTILE_STREAM;
	DEBUG_ASSERTCRASH( m_nextFreeIndex != m_firstValidIndex, ("Need to increase the allowed number of simultaneous particles in ProjectileStreamUpdate.") );
}

void ProjectileStreamUpdate::cullFrontOfList()
{
	while( (m_firstValidIndex != m_nextFreeIndex)  &&  (TheGameLogic->findObjectByID( m_projectileIDs[m_firstValidIndex] ) == NULL) )
	{
		// Chew off the front if they are gone.  Don't chew on the middle, as bad ones there are just a break in the chain
		m_firstValidIndex = (m_firstValidIndex + 1) % MAX_PROJECTILE_STREAM;
	}
}

Bool ProjectileStreamUpdate::considerDying()
{
	if( m_firstValidIndex == m_nextFreeIndex  &&  m_owningObject != INVALID_ID )
	{
		//If I have no projectiles to watch, and my master is dead, then yes, I want to die
		if( TheGameLogic->findObjectByID(m_owningObject) == NULL )
			return TRUE;
	}

	return FALSE;
}

void ProjectileStreamUpdate::getAllPoints( Vector3 *points, Int *count )
{
	Int pointCount = 0;
	Int pointIndex = m_firstValidIndex;


	Object *obj = TheGameLogic->findObjectByID(m_owningObject);


	while( pointIndex != m_nextFreeIndex )
	{
		// Go through the array I think of as good.  Holes in the middle get 0,0,0.  I write
		// to pointCount because I am unrolling a circular array into the same sized flat one
		// since I am writing anyway.
		Object *projectile = TheGameLogic->findObjectByID( m_projectileIDs[pointIndex] );

		if( projectile )
		{
			Coord3D thisPoint = *projectile->getPosition();
			points[pointCount].X = thisPoint.x;
			points[pointCount].Y = thisPoint.y;
			points[pointCount].Z = thisPoint.z;
			

			if ( obj && obj->isKindOf( KINDOF_VEHICLE ) )				// this makes the stream skim along my roof, if I have a roof
			{
				const Coord3D *pos = obj->getPosition();
				Real myTop = obj->getGeometryInfo().getMaxHeightAbovePosition() + pos->z + 0.5f;
				Coord3D delta;
				delta.x = pos->x - points[pointCount].X;
				delta.y = pos->y - points[pointCount].Y;
				delta.z = 0.0f;
				if( delta.length() <= obj->getGeometryInfo().getMajorRadius() * 1.5f )
					points[pointCount].Z = MAX( points[pointCount].Z, myTop );
			}




		}
		else
		{
			points[pointCount].X = 0;
			points[pointCount].Y = 0;
			points[pointCount].Z = 0;
		}

		pointIndex = (pointIndex + 1) % MAX_PROJECTILE_STREAM;
		pointCount++;
	}

	*count = pointCount;
}

void ProjectileStreamUpdate::setPosition( const Coord3D *newPosition )
{
	Object *me = getObject();
	me->setPosition( newPosition );
}

// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ProjectileStreamUpdate::crc( Xfer *xfer )
{

	// extend base class
	UpdateModule::crc( xfer );

}  // end crc

// ------------------------------------------------------------------------------------------------
/** Xfer method
	* Version Info:
	* 1: Initial version 
	* 2: Target tracking for line breaking
*/
// ------------------------------------------------------------------------------------------------
void ProjectileStreamUpdate::xfer( Xfer *xfer )
{

	// version
	XferVersion currentVersion = 2;
	XferVersion version = currentVersion;
	xfer->xferVersion( &version, currentVersion );

	// extend base class
	UpdateModule::xfer( xfer );

	// projectile ids
	xfer->xferUser( m_projectileIDs, sizeof( ObjectID ) * MAX_PROJECTILE_STREAM );

	// next free index
	xfer->xferInt( &m_nextFreeIndex );

	// first valid index
	xfer->xferInt( &m_firstValidIndex );

	// owning object
	xfer->xferObjectID( &m_owningObject );

	if( version >= 2 )
	{
		xfer->xferObjectID( &m_targetObject );
		xfer->xferCoord3D( &m_targetPosition );
	}

}  // end xfer

// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ProjectileStreamUpdate::loadPostProcess( void )
{

	// extend base class
	UpdateModule::loadPostProcess();

}  // end loadPostProcess
