/*
**	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/scriptablegameobj.cpp                 $*
 *                                                                                             *
 *                      $Author:: Byon_g                                                      $*
 *                                                                                             *
 *                     $Modtime:: 11/12/01 4:20p                                              $*
 *                                                                                             *
 *                    $Revision:: 37                                                          $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "scriptablegameobj.h"
#include "damage.h"
#include "scripts.h"
#include "debug.h"
#include "explosion.h"
#include "assets.h"
#include "combatsound.h"
#include "matrix3d.h"
#include "phys.h"
#include "smartgameobj.h"
#include "soldier.h"
#include "animcontrol.h"
#include "chunkio.h"
#include "saveload.h"
#include "combat.h"
#include "persistfactory.h"
#include "combatchunkid.h"
#include "parameter.h"
#include "radar.h"
#include "playertype.h"
#include "matinfo.h"
#include "gameobjmanager.h"
#include "pscene.h"
#include "soundsceneobj.h"
#include "wwprofile.h"

/*
** ScriptableGameObjDef - Defintion class for a ScriptableGameObj
*/
ScriptableGameObjDef::ScriptableGameObjDef( void )
{
	SCRIPTLIST_PARAM (ScriptableGameObjDef, "Scripts", ScriptNameList, ScriptParameterList);
}

enum	{
	CHUNKID_DEF_PARENT							=	627001056,
	CHUNKID_DEF_VARIABLES,

	XXX_MICROCHUNKID_DEF_TYPE						=	1,
	MICROCHUNKID_DEF_SCRIPT_NAME,
	MICROCHUNKID_DEF_SCRIPT_PARAMETERS,
};

bool	ScriptableGameObjDef::Save( ChunkSaveClass & csave )
{
	csave.Begin_Chunk( CHUNKID_DEF_PARENT );
		BaseGameObjDef::Save( csave );
	csave.End_Chunk();

	csave.Begin_Chunk( CHUNKID_DEF_VARIABLES );
		WWASSERT( ScriptNameList.Count() == ScriptParameterList.Count() );
		for ( int i = 0; i < ScriptNameList.Count(); i++ ) {
			WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_DEF_SCRIPT_NAME, ScriptNameList[i] );
			WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_DEF_SCRIPT_PARAMETERS, ScriptParameterList[i] );
		}
	csave.End_Chunk();

	return true;
}

bool	ScriptableGameObjDef::Load( ChunkLoadClass &cload )
{
	WWASSERT( ScriptNameList.Count() == ScriptParameterList.Count() );
	StringClass str;
	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_DEF_PARENT:
				BaseGameObjDef::Load( cload );
				break;

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

						case MICROCHUNKID_DEF_SCRIPT_NAME:
							LOAD_MICRO_CHUNK_WWSTRING( cload, str );
							ScriptNameList.Add( str );
							break;

						case MICROCHUNKID_DEF_SCRIPT_PARAMETERS:
							LOAD_MICRO_CHUNK_WWSTRING( cload, str );
							ScriptParameterList.Add( str );
							break;

						default:
							Debug_Say(( "Unhandled Variable Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
							break;
					}
					cload.Close_Micro_Chunk();
				}
				break;

			default:
				Debug_Say(( "Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
				break;

		}
		cload.Close_Chunk();
	}
	WWASSERT( ScriptNameList.Count() == ScriptParameterList.Count() );
	return true;
}


/*
** Game Object Observer Timer (used in Scripts)
*/
class	GameObjObserverTimerClass {
public:
	GameObjObserverTimerClass( int observer_id = 0, float time = 0, int timer_id = 0 )
		{	ObserverID = observer_id;  RemainingTime = time; TimerID = timer_id; }

	bool	Save( ChunkSaveClass & csave );
	bool	Load( ChunkLoadClass & cload );

	bool	Update( void )		{ RemainingTime -= TimeManager::Get_Frame_Seconds(); return RemainingTime <= 0; }
	bool	Expired( void )	{ return RemainingTime <= 0; }

	int					ObserverID;
	float					RemainingTime;
	int					TimerID;
};

enum	{
	CHUNKID_TIMER_VARIABLES				=	922991755,
	CHUNKID_TIMER_SENDER,

	MICROCHUNKID_REMAINING_TIME		=	1,
	MICROCHUNKID_TIMER_ID,
	MICROCHUNKID_OBSERVER_ID,
	MICROCHUNKID_TYPE,
	MICROCHUNKID_PARAM,
};

bool	GameObjObserverTimerClass::Save( ChunkSaveClass & csave )
{
	csave.Begin_Chunk( CHUNKID_TIMER_VARIABLES );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_REMAINING_TIME, RemainingTime );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TIMER_ID, TimerID );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_OBSERVER_ID, ObserverID );
	csave.End_Chunk();

	return true;
}

bool	GameObjObserverTimerClass::Load( ChunkLoadClass & cload )
{
	cload.Open_Chunk();
	WWASSERT( cload.Cur_Chunk_ID() == CHUNKID_TIMER_VARIABLES );

	while (cload.Open_Micro_Chunk()) {
		switch(cload.Cur_Micro_Chunk_ID()) {
			READ_MICRO_CHUNK( cload, MICROCHUNKID_REMAINING_TIME, RemainingTime );
			READ_MICRO_CHUNK( cload, MICROCHUNKID_TIMER_ID, TimerID );
			READ_MICRO_CHUNK( cload, MICROCHUNKID_OBSERVER_ID, ObserverID );

			default:
				Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
				break;
		}
		cload.Close_Micro_Chunk();
	}
	cload.Close_Chunk();
	return true;
}


/*
** Game Object Custom Timer (used in Scripts)
*/
class	GameObjCustomTimerClass {
public:

	GameObjCustomTimerClass( ScriptableGameObj *sender = NULL, float time = 0, int type = 0, int param = 0 ) :
		RemainingTime( time ), Type( type ), Param( param)		{ if ( sender != NULL ) Sender = sender; }

	bool	Save( ChunkSaveClass & csave );
	bool	Load( ChunkLoadClass & cload );

	bool	Update( void )		{ RemainingTime -= TimeManager::Get_Frame_Seconds(); return RemainingTime <= 0; }
	bool	Expired( void )	{ return RemainingTime <= 0; }

	float					RemainingTime;
	GameObjReference	Sender;
	int					Type;
	int					Param;
};

bool	GameObjCustomTimerClass::Save( ChunkSaveClass & csave )
{
	csave.Begin_Chunk( CHUNKID_TIMER_VARIABLES );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_REMAINING_TIME, RemainingTime );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TYPE, Type );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_PARAM, Param );
	csave.End_Chunk();

	if ( Sender != NULL ) {
		csave.Begin_Chunk( CHUNKID_TIMER_SENDER );
		Sender.Save( csave );
		csave.End_Chunk();
	}

	return true;
}

bool	GameObjCustomTimerClass::Load( ChunkLoadClass & cload )
{
	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_TIMER_VARIABLES:
				while (cload.Open_Micro_Chunk()) {
					switch(cload.Cur_Micro_Chunk_ID()) {
						READ_MICRO_CHUNK( cload, MICROCHUNKID_REMAINING_TIME, RemainingTime );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TYPE, Type );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_PARAM, Param );

						default:
							Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
							break;
					}
					cload.Close_Micro_Chunk();
				}
				break;

			case CHUNKID_TIMER_SENDER:
				Sender.Load( cload );
				break;

			default:
				Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
				break;

		}
		cload.Close_Chunk();
	}

	return true;
}


/*
** ScriptableGameObj
*/
ScriptableGameObj::ScriptableGameObj( void ) :
	ReferenceableGameObj( this ),
	ObserverCreatedPending( false )
{
}

ScriptableGameObj::~ScriptableGameObj( void )
{
	Remove_All_Observers();

	/*
	** Delete the ObserverTimerList. ST - 6/11/2001 9:20PM
	*/
	while (ObserverTimerList.Count()) {
		delete ObserverTimerList[0];
		ObserverTimerList.Delete(0);
	}

	/*
	** Delete the CustomTimerList. ST - 6/11/2001 9:20PM
	*/
	while (CustomTimerList.Count()) {
		delete CustomTimerList[0];
		CustomTimerList.Delete(0);
	}
}


/*
**
*/
void	ScriptableGameObj::Init( const ScriptableGameObjDef & definition )
{
	BaseGameObj::Init( definition );
	Copy_Settings( definition );
	return ;
}

/*
**
*/
void	ScriptableGameObj::Copy_Settings( const ScriptableGameObjDef & definition )
{
	//
	//	Only assign scripts on the server
	//
	if (CombatManager::I_Am_Server()) {

		//
		//	Attach the scripts
		//
		WWASSERT( definition.ScriptNameList.Count() == definition.ScriptParameterList.Count() );
		for ( int i = 0; i < definition.ScriptNameList.Count(); i++ ) {
			ScriptClass* script = ScriptManager::Create_Script( definition.ScriptNameList[i] );
			if (script) {
				script->Set_Parameters_String( definition.ScriptParameterList[i] );
				// Don't call Add observer, because we don't want Created called yet.
				// Start_Observers should be called later, which will call Created
				Insert_Observer(script);
			}
		}
	}

	return ;
}


/*
**
*/
void	ScriptableGameObj::Re_Init( const ScriptableGameObjDef & definition )
{
	//
	//	Remove all currently running scripts
	//
	Remove_All_Observers();

	//
	//	Copy any internal settings from the definition
	//
	Copy_Settings( definition );

	//
	//	Reset our definition pointer
	//
	BaseGameObj::Init( definition );
	return ;
}


/*
**
*/
void	ScriptableGameObj::Post_Re_Init( void )
{
	//
	//	Start the new scripts executing
	//
	Start_Observers();
	return ;
}


const ScriptableGameObjDef & ScriptableGameObj::Get_Definition( void ) const
{
	return (const ScriptableGameObjDef &)BaseGameObj::Get_Definition();
}

void	ScriptableGameObj::Set_Delete_Pending( void )
{
	if ( !Is_Delete_Pending() ) {
		if ( CombatManager::Are_Observers_Active() ) {
			const GameObjObserverList & observer_list = Get_Observers();
			for( int index = 0; index < observer_list.Count(); index++ ) {
				observer_list[ index ]->Destroyed( this );
			}
		}

		if ( this == COMBAT_STAR ) {
			CombatManager::Star_Killed();
		}

		BaseGameObj::Set_Delete_Pending ();
	}
}


/*
** ScriptableGameObj Save and Load
*/
enum	{
	CHUNKID_PARENT							=	627001122,
	CHUNKID_VARIABLES,
	CHUNKID_REFERENCEABLE,
	CHUNKID_CUSTOM_TIMER,
	CHUNKID_OBSERVER_TIMER,

	MICROCHUNKID_REFERENCEABLE_PTR	= 1,
	MICROCHUNKID_GAME_OBJ_OBSERVER_PTR,
	MICROCHUNKID_OBSERVER_CREATED_PENDING,
};

bool	ScriptableGameObj::Save( ChunkSaveClass & csave )
{
	csave.Begin_Chunk( CHUNKID_PARENT );
		BaseGameObj::Save( csave );
	csave.End_Chunk();

	csave.Begin_Chunk( CHUNKID_REFERENCEABLE );
		ReferenceableGameObj::Save( csave );
	csave.End_Chunk();

	csave.Begin_Chunk( CHUNKID_VARIABLES );
		ReferenceableGameObj * referenceable_ptr = (ReferenceableGameObj *)this;
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_REFERENCEABLE_PTR, referenceable_ptr );

		const GameObjObserverList & observer_list = Get_Observers();
		for( int index = 0; index < observer_list.Count(); index++ ) {
			void	* game_obj_observer_ptr = observer_list[ index ];
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_GAME_OBJ_OBSERVER_PTR, game_obj_observer_ptr );
		}

		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_OBSERVER_CREATED_PENDING, ObserverCreatedPending );
	csave.End_Chunk();

	int i;
	for ( i = 0; i < ObserverTimerList.Count(); i++ ) {
		csave.Begin_Chunk( CHUNKID_OBSERVER_TIMER );
			ObserverTimerList[i]->Save( csave );
		csave.End_Chunk();
	}

	for ( i = 0; i < CustomTimerList.Count(); i++ ) {
		csave.Begin_Chunk( CHUNKID_CUSTOM_TIMER );
			CustomTimerList[i]->Save( csave );
		csave.End_Chunk();
	}

	return true;
}

bool	ScriptableGameObj::Load( ChunkLoadClass &cload )
{
	ReferenceableGameObj * referenceable_ptr = NULL;

	WWASSERT( Observers.Count() == 0 );

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

			case CHUNKID_PARENT:
				BaseGameObj::Load( cload );
				break;

			case CHUNKID_REFERENCEABLE:
				ReferenceableGameObj::Load( cload );
				break;

			case CHUNKID_VARIABLES:
				while (cload.Open_Micro_Chunk()) {
					switch(cload.Cur_Micro_Chunk_ID()) {
						READ_MICRO_CHUNK( cload, MICROCHUNKID_REFERENCEABLE_PTR, referenceable_ptr );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_OBSERVER_CREATED_PENDING, ObserverCreatedPending );

						case MICROCHUNKID_GAME_OBJ_OBSERVER_PTR:
							GameObjObserverClass * ptr;
							cload.Read(&ptr,sizeof(ptr));
							Observers.Add( ptr );
							break;

						default:
							Debug_Say(( "Unhandled Variable Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
							break;
					}
					cload.Close_Micro_Chunk();
				}
				break;

			case CHUNKID_OBSERVER_TIMER:
				GameObjObserverTimerClass * otimer;
				otimer = new GameObjObserverTimerClass();
				otimer->Load( cload );
				ObserverTimerList.Add( otimer );
				break;

			case CHUNKID_CUSTOM_TIMER:
				GameObjCustomTimerClass * ctimer;
				ctimer = new GameObjCustomTimerClass();
				ctimer->Load( cload );
				CustomTimerList.Add( ctimer );
				break;

			default:
				Debug_Say(( "Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
				break;

		}
		cload.Close_Chunk();
	}

	// Request a remap on all the observers
	for ( int ob_idx = 0; ob_idx < Observers.Count(); ob_idx++ ) {
		REQUEST_POINTER_REMAP( (void **)&(Observers[ ob_idx ]) );
	}

	WWASSERT(referenceable_ptr != NULL);
	if (referenceable_ptr != NULL) {
		SaveLoadSystemClass::Register_Pointer(referenceable_ptr , (ReferenceableGameObj *)this);
	}

	SaveLoadSystemClass::Register_Post_Load_Callback(this);

	return true;
}

void ScriptableGameObj::On_Post_Load( void )
{
	BaseGameObj::On_Post_Load();

	// Delete any NULL pointers
	GameObjObserverList & observer_list = (GameObjObserverList &)Get_Observers();
	for( int index = 0; index < observer_list.Count(); index++ ) {
		if ( observer_list[ index ] == NULL ) {
			observer_list.Delete( index );
			index--;
		}
	}

	if ( CombatManager::Is_First_Load() ) {
		ObserverCreatedPending = true;
	}
}

/*
**
*/
void ScriptableGameObj::Start_Observers( void )
{
	// If we just came from the editor, call created on all out observers
	const GameObjObserverList & observer_list = Get_Observers();
	for( int index = 0; index < observer_list.Count(); index++ ) {
		observer_list[ index ]->Created( this );
	}
}

void ScriptableGameObj::Add_Observer( GameObjObserverClass * observer )
{
	WWASSERT(observer != NULL);

	Insert_Observer( observer );

	// Don't call created if in the editor
	if ( CombatManager::Are_Observers_Active() ) {
		observer->Created( this );
	}
}

void ScriptableGameObj::Insert_Observer( GameObjObserverClass * observer )
{
	WWASSERT(observer != NULL);

	observer->Attach( this );
	Observers.Add( observer );
}

void ScriptableGameObj::Remove_Observer( GameObjObserverClass * observer )
{
	Observers.Delete( observer );
	observer->Detach( this );
	// if observer is a script, if will be deleted soon after this
}

void ScriptableGameObj::Remove_All_Observers(void)
{
	while(Observers.Count() != 0) {
		Remove_Observer(Observers[0]);
	}
}

void	ScriptableGameObj::Start_Observer_Timer( int observer_id, float duration, int timer_id )
{
	ObserverTimerList.Add( new GameObjObserverTimerClass( observer_id, duration, timer_id ) );
}

void	ScriptableGameObj::Start_Custom_Timer( ScriptableGameObj * from, float delay, int type, int param )
{
	CustomTimerList.Add( new GameObjCustomTimerClass( from, delay, type, param ) );
}

void	ScriptableGameObj::Think( void )
{
	if (Is_Always_Dirty()) {
#pragma message ("Forcing game objects to be network dirty for updates.\n")
		Set_Object_Dirty_Bit (NetworkObjectClass::BIT_FREQUENT, true);
	}

	if ( ObserverCreatedPending ) {
		Start_Observers();
		ObserverCreatedPending = false;
	}

	BaseGameObj::Think();
}

void	ScriptableGameObj::Post_Think( void )
{
	BaseGameObj::Post_Think();

	WWPROFILE( "Scriptable PostThink" );

	// Note: The cinematic script comes later in the obj list then the things it creates.
	// This means that objects the script creates when it thinks (via timers) dont think
	// (bump animation forward) until the next frame.  Be wary of changing this order.

	// Check Timers
	int i;
	for ( i = ObserverTimerList.Count() - 1; i >= 0; i-- ) {
		if ( ObserverTimerList[i]->Update() ) {

//			Debug_Say(( "Timer Expired for %d\n", ObserverTimerList[i]->ObserverID ));

			bool found = false;

			WWASSERT( ObserverTimerList[i]->ObserverID != 0 );
			const GameObjObserverList & observer_list = Get_Observers();
			for( int index = 0; index < observer_list.Count(); index++ ) {
				if ( observer_list[ index ]->Get_ID() == ObserverTimerList[i]->ObserverID ) {
					observer_list[ index ]->Timer_Expired( this, ObserverTimerList[i]->TimerID );
					found = true;
				}
			}

			if ( !found ) {
				Debug_Say(( "Failed to find observer id %d for timer expired....\n", ObserverTimerList[i]->ObserverID ));

				const GameObjObserverList & observer_list = Get_Observers();
				for( int index = 0; index < observer_list.Count(); index++ ) {
					Debug_Say(( "have %d\n", observer_list[ index ]->Get_ID() ));
				}
			}

			delete ObserverTimerList[i];
			ObserverTimerList.Delete( i );
		}
	}

	for ( i = CustomTimerList.Count() - 1; i >= 0; i-- ) {
		if ( CustomTimerList[i]->Update() ) {

			ScriptableGameObj *sender = CustomTimerList[i]->Sender;

			const GameObjObserverList & observer_list = Get_Observers();
			for( int index = 0; index < observer_list.Count(); index++ ) {
				observer_list[ index ]->Custom( this, CustomTimerList[i]->Type, CustomTimerList[i]->Param, sender );
			}

			delete CustomTimerList[i];
			CustomTimerList.Delete( i );
		}
	}
}

//------------------------------------------------------------------------------------
void	ScriptableGameObj::Get_Information( StringClass & string )
{
	// If we just came from the editor, call created on all out observers
	const GameObjObserverList & observer_list = Get_Observers();
	for( int index = 0; index < observer_list.Count(); index++ ) {
		StringClass temp;
		temp.Format( "%d:%s\n", observer_list[ index ]->Get_ID(), observer_list[ index ]->Get_Name() );
		string += temp;
	}
}

//------------------------------------------------------------------------------------
void	ScriptableGameObj::On_Sound_Ended( SoundSceneObjClass *sound_obj )
{
	if ( sound_obj == NULL ) {
		return ;
	}

	int sound_id = sound_obj->Get_ID();

	//
	// Notify all observers
	//
	const GameObjObserverList & observer_list = Get_Observers();
	for( int index = 0; index < observer_list.Count(); index++ ) {

		//
		//	Send a custom event to this observer notifying it that this sound has ended
		//
		observer_list[ index ]->Custom( this, CUSTOM_EVENT_SOUND_ENDED, sound_id, NULL );
	}

	return ;
}


/*
**
*/
void	ScriptableGameObj::Export_Creation( BitStreamClass &packet )
{
	BaseGameObj::Export_Creation( packet );
	return ;
}


/*
**
*/
void	ScriptableGameObj::Import_Creation( BitStreamClass &packet )
{
	BaseGameObj::Import_Creation( packet );

	//
	//	Ensure we don't have any scripts running
	//
	Remove_All_Observers();
	return ;
}