/*
**	Command & Conquer Renegade(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/>.
*/

/*********************************************************************************************** 
 ***                            Confidential - Westwood Studios                              *** 
 *********************************************************************************************** 
 *                                                                                             * 
 *                 Project Name : Commando                                                     * 
 *                                                                                             * 
 *                     $Archive:: /Commando/Code/Combat/transition.cpp                        $* 
 *                                                                                             * 
 *                      $Author:: Byon_g                                                      $* 
 *                                                                                             * 
 *                     $Modtime:: 1/09/02 3:53p                                               $* 
 *                                                                                             * 
 *                    $Revision:: 75                                                          $* 
 *                                                                                             * 
 *---------------------------------------------------------------------------------------------* 
 * Functions:                                                                                  * 
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/*
**	Includes
*/
#include "transition.h"
#include "soldier.h"
#include "debug.h"
#include "assets.h"
#include "combat.h"
#include "ccamera.h"
#include "gameobjmanager.h"		// hack???
#include "scripts.h"
#include "hanim.h"
#include "slist.h"
#include "vehicle.h"
#include "phys3.h"
#include "wwprofile.h"
#include "diaglog.h"
#include <string.h>
#include <stdlib.h>
#include "pathaction.h"


/*
**
*/
const char * TransitionTypeNames[] = {
		"LADDER_EXIT_TOP",
		"LADDER_EXIT_BOTTOM",
		"LADDER_ENTER_TOP",
		"LADDER_ENTER_BOTTOM",
		"LEGACY_VEHICLE_ENTER_0",
		"LEGACY_VEHICLE_ENTER_1",
		"LEGACY_VEHICLE_EXIT_0",
		"LEGACY_VEHICLE_EXIT_1",
		"VEHICLE_ENTER",
		"VEHICLE_EXIT",
};


/*
**
*/
bool	Is_Point_Inside( const Vector3 & pos, const Vector3 & min, const Vector3 & max )
{
	return ( ( pos.X >= min.X ) && ( pos.X <= max.X ) && 
				( pos.Y >= min.Y ) && ( pos.Y <= max.Y ) && 
				( pos.Z >= min.Z ) && ( pos.Z <= max.Z ) );
}

/*
** Save and Load for TransitionDataClass
*/
namespace TRANSITION_DATA_CLASS_SAVELOAD
{
	enum	{
		CHUNKID_VARIABLES						=  0x11051106,

		MICROCHUNKID_TYPE						=	1,
		MICROCHUNKID_ZONE,
		MICROCHUNKID_ANIMATION_NAME,
		MICROCHUNKID_ENDING_TM,
		MICROCHUNKID_LADDER_INDEX
	};
}

/*
**
*/
TransitionDataClass::TransitionDataClass( )
	:	Type( LADDER_EXIT_TOP )
{

}

/*
**
*/
const char * TransitionDataClass::Get_Type_Name( StyleType type )
{
	WWASSERT( ((int)type) < Get_Num_Types() );
	return TransitionTypeNames[((int)type)];
}

/*
**
*/
bool TransitionDataClass::Save( ChunkSaveClass & csave )
{
	using namespace TRANSITION_DATA_CLASS_SAVELOAD;
	csave.Begin_Chunk( CHUNKID_VARIABLES );

		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TYPE, Type );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ZONE, Zone );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ENDING_TM, EndingTM );
		WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_ANIMATION_NAME, AnimationName );
		
	csave.End_Chunk();
	return true;
}

/*
**
*/
bool TransitionDataClass::Load( ChunkLoadClass & cload )
{
	using namespace TRANSITION_DATA_CLASS_SAVELOAD;
	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_VARIABLES:
			{
				while (cload.Open_Micro_Chunk()) {
					switch(cload.Cur_Micro_Chunk_ID()) {

						READ_MICRO_CHUNK( cload, MICROCHUNKID_TYPE, Type );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ZONE, Zone );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ENDING_TM, EndingTM );
						READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_ANIMATION_NAME, AnimationName );

						default:
							Debug_Say(( "Unrecognized Transition Variable chunkID\n" ));
							break;
					}
					cload.Close_Micro_Chunk();
				}
				break;
			}	

			default:
				Debug_Say(( "Unrecognized Transition chunkID\n" ));
				break;

		}
		cload.Close_Chunk();
	}

	switch ( Type ) { 
		case LEGACY_VEHICLE_ENTER_0:
		case LEGACY_VEHICLE_ENTER_1:
			Type = VEHICLE_ENTER;
			break;
		case LEGACY_VEHICLE_EXIT_0:
		case LEGACY_VEHICLE_EXIT_1:
			Type = VEHICLE_EXIT;
			break;
	}

	return true;
}

/*
** TransitionCompletionDataStruct
*/
namespace TRANSITION_COMPLETION_SAVE_LOAD
{

enum	{
	CHUNKID_VARIABLES								= 1105991816,
	CHUNKID_VEHICLE,

	MICROCHUNKID_TYPE								=	1,
};

}

bool	TransitionCompletionDataStruct::Save( ChunkSaveClass & csave )
{
	using namespace TRANSITION_COMPLETION_SAVE_LOAD;

	csave.Begin_Chunk( CHUNKID_VARIABLES );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TYPE, Type );
	csave.End_Chunk();

	csave.Begin_Chunk( CHUNKID_VEHICLE );
		Vehicle.Save( csave );
	csave.End_Chunk();

	return true;
}

bool	TransitionCompletionDataStruct::Load( ChunkLoadClass & cload )
{
	using namespace TRANSITION_COMPLETION_SAVE_LOAD;

	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_VARIABLES:
				while (cload.Open_Micro_Chunk()) {
					switch(cload.Cur_Micro_Chunk_ID()) {
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TYPE, Type );

						default:
							Debug_Say(( "Unrecognized TransitionCompletion Variable chunkID\n" ));
							break;

					}
					cload.Close_Micro_Chunk();
				}
				break;

			case CHUNKID_VEHICLE:
				Vehicle.Load( cload );
				break;

			default:
				Debug_Say(( "Unrecognized TransitionCompletion chunkID\n" ));
				break;

		}
		cload.Close_Chunk();
	}

	return true;
}


/*
** TransitionInstanceClass
*/
TransitionInstanceClass::TransitionInstanceClass( const TransitionDataClass & data ) :
	Data( data ),
	EndingTM( 1 ),
	LadderIndex( -1 ),
	Zone( Vector3( 0,0,0 ), Vector3( 0,0,0 ) )
{
}

TransitionInstanceClass::~TransitionInstanceClass( void )
{
}

bool	TransitionInstanceClass::Check( SoldierGameObj *obj, bool action_trigger )
{
	Vector3	pos;
	obj->Get_Position( &pos );

	// make sure we are in the zone
	if ( CollisionMath::Overlap_Test( Zone, pos ) == CollisionMath::OUTSIDE ) {
		return false;		// not inside
	}

	// ignore triggers when transitioning
	if ( obj->Is_State_Locked() ) {
		return false;		
	}

	// Bail if not manually triggered and not ladder exit and
	if ( !action_trigger && 
		  ( Get_Type() != TransitionDataClass::LADDER_EXIT_TOP ) && 
		  ( Get_Type() != TransitionDataClass::LADDER_EXIT_BOTTOM ) ) {
#if 0
		// no auto transitions or not human controlled
		if ( !CombatManager::Are_Transitions_Automatic() ||
			  !obj->Is_Human_Controlled() ) 
#endif
		{
			return false;
		}
	}

	bool condition = action_trigger;

	if ( action_trigger == false ) {
		const float	MIN_VELOCITY = 0.1f;
		// Check for auto trigger
		Vector3 vel;
		obj->Get_Velocity( vel );
		if ( vel.Length() > MIN_VELOCITY ) {

			Vector3 end_direction;

			// make sure we meet the conditions
			switch ( Get_Type() ) {

				case TransitionDataClass::LADDER_ENTER_BOTTOM:
					end_direction = EndingTM.Get_X_Vector();
					end_direction.Normalize();
					break;

				case TransitionDataClass::LADDER_ENTER_TOP:
					end_direction = -EndingTM.Get_X_Vector();
					end_direction.Normalize();
					break;

				case TransitionDataClass::LADDER_EXIT_BOTTOM:
					end_direction = Vector3( 0, 0, -1 );
					break;

				case TransitionDataClass::LADDER_EXIT_TOP:
					end_direction = Vector3( 0, 0, 1 );
					break;

				case TransitionDataClass::VEHICLE_ENTER:
				case TransitionDataClass::VEHICLE_EXIT:
					vel.Z = 0; // don't consider vertical
					end_direction = EndingTM.Get_Translation() - Zone.Center; 
					end_direction.Normalize();
					break;

				default:
					Debug_Say(( "Unrecognized Transition Type\n" ));
					condition = false;
					break;
			}


			// If still has length
			if ( vel.Length() > MIN_VELOCITY ) {
				vel.Normalize();
				float	dotp = Vector3::Dot_Product( vel, end_direction );
				if ( dotp > 0.5f ) {
					condition = true;
				}
			}
		}
	}

	// make sure we meet the conditions
	switch ( Get_Type() ) {

		case TransitionDataClass::LADDER_EXIT_TOP:
		case TransitionDataClass::LADDER_EXIT_BOTTOM:
			if ( !obj->Is_On_Ladder() ) {
				condition = false;
			}
			break;

		case TransitionDataClass::LADDER_ENTER_TOP:
		case TransitionDataClass::LADDER_ENTER_BOTTOM:
			if ( !obj->Is_Upright() ) {
				condition = false;
			}
			break;

		case TransitionDataClass::VEHICLE_ENTER:
			if ( !obj->Is_Upright()  && !obj->Is_Wounded() ) {
				// Added wounded because we couldn't enter vehicles in a tib field
				condition = false;
			}

			VehicleGameObj * p_vehicle;
			p_vehicle = (VehicleGameObj *) Vehicle.Get_Ptr();
			WWASSERT(p_vehicle != NULL);

			if (!obj->Is_Permitted_To_Enter_Vehicle() ||
				 !p_vehicle->Is_Entry_Permitted(obj)) {
				condition = false;
			}

			// When entering a vehicle, ensure that there are no walls between us
			// Do this by casting a ray from our position to the vehicle position; only
			// checking against static geometry.
			{
				Vector3 p0,p1;
				p_vehicle->Get_Position(&p0);
				obj->Get_Position(&p1);

				CastResultStruct result;
				LineSegClass ray(p0,p1);
				int colgroup = obj->Peek_Physical_Object()->Get_Collision_Group();
				PhysRayCollisionTestClass raytest(ray,&result,colgroup,COLLISION_TYPE_PHYSICAL);
				raytest.CheckDynamicObjs = false;

				PhysicsSceneClass::Get_Instance()->Cast_Ray(raytest);

				if (result.Fraction < 1.0f) {
					condition = false;
				}
			}

			break;

		case TransitionDataClass::VEHICLE_EXIT:
			if ( !obj->Is_In_Vehicle() ) {
				condition = false;
			}
			break;


		default:
			Debug_Say(( "Unrecognized Transition Type\n" ));
			condition = false;
			break;
	}

	if ( !condition ) {
		return false;
	}

	//
	//	Check to ensure the object can teleport to the ending location
	//
	Matrix3D dummy_tm;
	MoveablePhysClass *move_phys = obj->Peek_Physical_Object()->As_MoveablePhysClass();
	
	switch ( Get_Type() ) {

		case TransitionDataClass::LADDER_ENTER_TOP:
		case TransitionDataClass::LADDER_ENTER_BOTTOM:
		{			
			//
			//	Its not legal to get on a ladder when someone else is on the ladder.
			// Try to detect this case.
			//
			if( obj == COMBAT_STAR && LadderIndex >= 0 ) {
				ScriptableGameObj	*occupant = PathActionClass::Get_Ladder_Occupant( LadderIndex );
				if( occupant != NULL && occupant != obj ) {
					condition = false;
				}
			}
			break;
		}

		case TransitionDataClass::LADDER_EXIT_TOP:
		case TransitionDataClass::LADDER_EXIT_BOTTOM:
			
			//
			// When exiting a Ladder, we just have to check for a dynamic object blocking our
			// exit point.  (the third parameter to Can_Teleport_And_Stand is true...)
			//
			/*if (	move_phys != NULL &&	
				   move_phys->Can_Teleport(EndingTM, true) == false)
			{
				condition = false;
			}*/
		break;

		case TransitionDataClass::VEHICLE_EXIT:
			
			//
			// When exiting a vehicle, we perform two steps:
			// - ignore our vehicle and sweep the character to the exit transform
			// - un-ignore the vehicle and do a teleport test.
			//
			if (move_phys != NULL) {
				
				bool can_move_to;
				
				VehicleGameObj * vgobj = Get_Vehicle();
				PhysClass * pobj = vgobj->Peek_Physical_Object();
				
				pobj->Inc_Ignore_Counter();
				can_move_to = move_phys->Can_Move_To(EndingTM);
				pobj->Dec_Ignore_Counter();

				if (	(can_move_to == false) || 
						(move_phys->Can_Teleport_And_Stand(EndingTM,&dummy_tm) == false) ) 
				{
					condition = false;
				}
			}
	
		break;

	}

	if ( !condition ) {
		return false;
	}

	Start( obj );

	return true;
}

void	TransitionInstanceClass::Start( SoldierGameObj *obj )
{
//	Debug_Say(( "Starting Animation\n" ));

	// if this is the camera's target...
	if ( COMBAT_STAR == obj ) {
		// Get duration
#if 0
		HAnimClass * animation = WW3DAssetManager::Get_Instance()->Get_HAnim( Data.Get_Animation_Name() );
		if ( animation ) {
			float anim_duration = animation->Get_Num_Frames() / animation->Get_Frame_Rate();
			animation->Release_Ref();
//			COMBAT_CAMERA->Set_Lerp_Time( anim_duration * 1.3f );
			COMBAT_CAMERA->Set_Lerp_Time( anim_duration );
		}
#else
		COMBAT_CAMERA->Set_Lerp_Time( 1.0f );

#endif
	}

	// set the object ending position & orientation
#ifdef WWDEBUG
	Vector3 pos = EndingTM.Get_Translation();
	if (!pos.Is_Valid()) {
		WWDEBUG_SAY(("Transition EndingTM has invalid position: %f, %f, %f\r\n",pos.X,pos.Y,pos.Z));
	}
#endif

	obj->Set_Transform( EndingTM );

	if ( obj->Peek_Physical_Object() != NULL && obj->Peek_Physical_Object()->As_Phys3Class() != NULL ) {
		obj->Peek_Physical_Object()->As_Phys3Class()->Set_Velocity( Vector3(0,0,0) );
	}

	// make the completion data
	TransitionCompletionDataStruct * completion_data = new TransitionCompletionDataStruct;
	completion_data->Type = Data.Get_Type();
	completion_data->Vehicle = Vehicle;

	//
	//	Mark ladders as being used/free as necessary
	//
	switch ( Get_Type() ) {

		case TransitionDataClass::LADDER_ENTER_TOP:
		case TransitionDataClass::LADDER_ENTER_BOTTOM:
		{			
			//
			//	Mark this ladder as being "in use" so no one else tries to get on it
			//
			if( obj == COMBAT_STAR && LadderIndex >= 0 ) {
				PathActionClass::Set_Ladder_Occupant( LadderIndex, obj );
			}
			break;
		}

		case TransitionDataClass::LADDER_EXIT_TOP:
		case TransitionDataClass::LADDER_EXIT_BOTTOM:
		{
			//
			//	Free this ladder so other's can use it
			//
			if( obj == COMBAT_STAR && LadderIndex >= 0 ) {
				PathActionClass::Set_Ladder_Occupant( LadderIndex, NULL );
			}

			break;
		}
	}

#if 0
	obj->Start_Transition_Animation( Data.Get_Animation_Name(), completion_data );

	VehicleGameObj * vehicle = Get_Vehicle();

	// If vehicle entry/exit, allow it to animate as well
	switch ( Get_Type() ) {

		case TransitionDataClass::VEHICLE_ENTER:
		{
			if ( vehicle ) {
				vehicle->Passenger_Entering();
			}
			break;
		}

		case TransitionDataClass::VEHICLE_EXIT:
		{
			WWASSERT( vehicle );
//			obj->Set_Vehicle_State( SoldierGameObj::EXITING_VEHICLE, vehicle );
			vehicle->Passenger_Exiting();
			break;
		}

		default:
			break;
	}
#else
	End( obj, completion_data );
#endif

}

void	TransitionInstanceClass::End( SoldierGameObj *obj, 
				TransitionCompletionDataStruct * completion_data )
{
	VehicleGameObj * vehicle = (VehicleGameObj *)completion_data->Vehicle.Get_Ptr();

	switch ( completion_data->Type ) {

		case TransitionDataClass::LADDER_EXIT_TOP:
		case TransitionDataClass::LADDER_EXIT_BOTTOM:
			obj->Exit_Ladder();

			if ( obj == COMBAT_STAR ) {
				Vector3 pos;
				obj->Get_Position( &pos );
				DIAG_LOG(( "LAEX", "%1.2f; %1.2f; %1.2f", pos.X, pos.Y, pos.Z ));
			}
			break;

		case TransitionDataClass::LADDER_ENTER_TOP:
		case TransitionDataClass::LADDER_ENTER_BOTTOM:
			obj->Enter_Ladder( completion_data->Type == TransitionDataClass::LADDER_ENTER_TOP );

			if ( obj == COMBAT_STAR ) {
				Vector3 pos;
				obj->Get_Position( &pos );
				DIAG_LOG(( "LAEN", "%1.2f; %1.2f; %1.2f", pos.X, pos.Y, pos.Z ));
			}
			break;

		case TransitionDataClass::VEHICLE_ENTER:
		{
			if ( vehicle != NULL ) {
				vehicle->Add_Occupant( obj );
			}
			break;
		}

		case TransitionDataClass::VEHICLE_EXIT:
		{
			if ( vehicle != NULL ) {
				vehicle->Remove_Occupant( obj );
			} else {
				obj->Exit_Vehicle();
			}
			break;
		}

		default:
			Debug_Say(( "Unrecognized Transition Type\n" ));
			break;
	}

	// Delete the data (soldier better not use it)
	delete completion_data;
}

void	TransitionInstanceClass::Set_Parent_Transform( const Matrix3D & tm )
{
	Matrix3D::Multiply( tm, Data.Get_Ending_TM(), &EndingTM );
	OBBoxClass::Transform( tm, Data.Get_Zone(), &Zone );
}


/*
** TransitionManager
*/
SList<TransitionInstanceClass>		Transitions;
SList<TransitionInstanceClass>		TransitionsDestroy;

void	TransitionManager::Add( TransitionInstanceClass * transition )
{
	Transitions.Add_Tail( transition );
}

void	TransitionManager::Destroy( TransitionInstanceClass * transition )
{
	TransitionsDestroy.Add_Tail( transition );
}

void	TransitionManager::Reset( void )
{
	Destroy_Pending();

	while ( Transitions.Get_Count() ) {
		delete Transitions.Remove_Head();
	}
}

void	TransitionManager::Destroy_Pending( void )
{
	while ( TransitionsDestroy.Get_Count() ) {
		TransitionInstanceClass * trans = TransitionsDestroy.Remove_Head(); 
		Transitions.Remove( trans );
		delete trans;
	}
}

bool	TransitionManager::Check( SoldierGameObj *obj, bool action_trigger )
{
	WWPROFILE( "Transition Check" );
	SLNode<TransitionInstanceClass> *ti_node;
	for (	ti_node = Transitions.Head(); ti_node; ti_node = ti_node->Next()) {
		if ( ti_node->Data()->Check( obj, action_trigger ) ) {
			Destroy_Pending();
			return true;
		}
	}
	return false;
}


void	TransitionManager::Build_Ladder_List (DynamicVectorClass<TransitionInstanceClass *> &list)
{
	SLNode<TransitionInstanceClass> *ti_node = NULL;
	for (	ti_node = Transitions.Head(); ti_node; ti_node = ti_node->Next()) {
		
		//
		//	Is this a ladder transition?
		//
		TransitionDataClass::StyleType type = ti_node->Data()->Get_Type ();	
		if (	type == TransitionDataClass::LADDER_EXIT_TOP || type == TransitionDataClass::LADDER_EXIT_BOTTOM ||
				type == TransitionDataClass::LADDER_ENTER_TOP || type == TransitionDataClass::LADDER_ENTER_BOTTOM)
		{
			list.Add( ti_node->Data () );
		}
	}

	return ;
}