/*
**	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/scripts.cpp                           $* 
 *                                                                                             * 
 *                      $Author:: Greg_h                                                      $* 
 *                                                                                             * 
 *                     $Modtime:: 7/09/02 9:19a                                               $* 
 *                                                                                             * 
 *                    $Revision:: 53                                                          $* 
 *                                                                                             * 
 *---------------------------------------------------------------------------------------------* 
 * Functions:                                                                                  * 
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "scripts.h"
#include "debug.h"
#include "scriptcommands.h"
#include "physicalgameobj.h"
#include "wwstring.h"
#include "combat.h"
#include "wwprofile.h"
#include "ffactorylist.h"
#include "rawfile.h"
#include "gametype.h"
#include <stdio.h>
#include <win.h>

ScriptCommands* EngineCommands = NULL;

#if 1
#define	SCRIPT_PROFILE_START( x )	WWProfileManager::Profile_Start( "Scripts" );
#define	SCRIPT_PROFILE_STOP( x )	WWProfileManager::Profile_Stop( ); 
#else
#define	SCRIPT_PROFILE_START( x )
#define	SCRIPT_PROFILE_STOP( x ) 
#endif

/*
**
*/
HINSTANCE hDLL = NULL;
LPFN_CREATE_SCRIPT ScriptManager::ScriptCreateFunct = NULL;
LPFN_DESTROY_SCRIPT ScriptManager::ScriptDestroyFunct = NULL;
SimpleDynVecClass<ScriptClass *> ScriptManager::ActiveScriptList;
SimpleDynVecClass<ScriptClass *> ScriptManager::PendingDestroyList;
bool	ScriptManager::EnableScriptCreation = true;



/*
**
*/
void ScriptManager::Init(void)
{
	hDLL = NULL;
	EngineCommands = Get_Script_Commands();

#ifdef	PARAM_EDITING_ON	// Editor build
	Load_Scripts("SCRIPTS.DLL");
#else
	#ifdef	WWDEBUG		// DEBUG and PROFILE
		if ( DebugManager::Load_Debug_Scripts() ) {
			Load_Scripts("SCRIPTSD.DLL");		// DEBUG
		} else {
	#ifdef	NDEBUG		// PROFILE
			Load_Scripts("SCRIPTSP.DLL");		// PROFILE
	#else
			Load_Scripts("SCRIPTSD.DLL");		// DEBUG
	#endif
		}
	#else
		Load_Scripts("SCRIPTS.DLL");		// RELEASE
	#endif
#endif
}


/*
**
*/
void ScriptManager::Shutdown(void)
{
	// Release scripts
	while (ActiveScriptList.Count()) {
		ScriptClass* script = ActiveScriptList[0];
		assert(script != NULL);

		assert(ScriptDestroyFunct != NULL);
		ScriptDestroyFunct(script);
		
		ActiveScriptList.Delete(0);
	}

	if (hDLL != NULL) {
		FreeLibrary(hDLL);
		hDLL = NULL;
	}
}


void ScriptManager::Destroy_Pending(void)
{
	// Destroy all the scripts in the pending destroy list.
	while (PendingDestroyList.Count()) {
		ScriptClass* script = PendingDestroyList[0];
		assert(script != NULL);

		// If the script has an owner then it must be detached before it
		// can be destroyed.
		ScriptableGameObj* object = script->Owner();

		if (object != NULL) {
			object->Remove_Observer(script);
		}

		// Destroy the script
		assert(ScriptDestroyFunct != NULL);
		ScriptDestroyFunct(script);
		PendingDestroyList.Delete(0);
	}
}


/*
**
*/
void ScriptManager::Load_Scripts(const char* dll_filename)
{
	Debug_Say(("Script Manager Loading Script File %s\n", dll_filename));


	// If we're in multiplay and not the server, just bail
   if (!IS_SOLOPLAY && CombatManager::I_Am_Only_Client())
	{
		return;
	}

#ifndef	PARAM_EDITING_ON	// Only do this in the *game*

	// Check if we have a mod, if so, un-pack the scripts from the PKG (if present)
	FileFactoryClass * mod_pkg = FileFactoryListClass::Get_Instance()->Peek_Temp_FileFactory();
	if (mod_pkg != NULL) {
		FileClass * scripts_dll = mod_pkg->Get_File( dll_filename );
		if ((scripts_dll != NULL) && (scripts_dll->Is_Available())) {

			const char * _TMP_SCRIPTS_DLL_FILENAME = "_MOD_SCRIPTS.DLL";

			scripts_dll->Open(FileClass::READ);
			RawFileClass unpacked_scripts(_TMP_SCRIPTS_DLL_FILENAME);

			if (unpacked_scripts.Create()) {
		
				unpacked_scripts.Open(FileClass::WRITE);

				// Copy the dll from the PKG (mix) file into our temporary _scripts directory
				static char buffer[16000];
				int scripts_size = scripts_dll->Size();
				int cur_pos = 0;
				while (cur_pos < scripts_size) {
					int read_count = WWMath::Min(scripts_size - cur_pos,sizeof(buffer));
					scripts_dll->Read(buffer,read_count);
					unpacked_scripts.Write(buffer,read_count);
					cur_pos += read_count;
				}


				// change 'dll_filename' so that we load the newly created dll
				if (cur_pos == scripts_size) {
					dll_filename = _TMP_SCRIPTS_DLL_FILENAME;
				}

				unpacked_scripts.Close();
			}
			scripts_dll->Close();
			mod_pkg->Return_File(scripts_dll);
		}
	}
#endif

	hDLL = LoadLibrary(dll_filename);

	if (hDLL == NULL) {
		Debug_Say(("Cound not load DLL file %s\n", dll_filename));
		return;
	}

	// Get create script function
	ScriptCreateFunct = (LPFN_CREATE_SCRIPT)GetProcAddress(hDLL, LPSTR_CREATE_SCRIPT);
	assert(ScriptCreateFunct != NULL);

	if (!ScriptCreateFunct) {
		Debug_Say(("Cound not find Create_Script\n"));
	}

	// Get destroy script function
	ScriptDestroyFunct = (LPFN_DESTROY_SCRIPT)GetProcAddress(hDLL, LPSTR_DESTROY_SCRIPT);
	assert(ScriptDestroyFunct != NULL);

	if (!ScriptDestroyFunct) {
		Debug_Say(("Cound not find Destroy_Script\n"));
	}

	// Initialize request script destroy function
	LPFN_SET_REQUEST_DESTROY_FUNC set_request_destroy_func = 
		(LPFN_SET_REQUEST_DESTROY_FUNC)GetProcAddress(hDLL, LPSTR_SET_REQUEST_DESTROY_FUNC);
	assert(set_request_destroy_func != NULL);

	if (set_request_destroy_func != NULL) {
		set_request_destroy_func(Request_Destroy_Script);
	} else {
		Debug_Say(("Cound not find Set_Request_Destroy_Func\n"));
	}

	// Initialize script commands if not being run from the editor
	if (CombatManager::Are_Observers_Active()) {
		LPFN_SET_SCRIPT_COMMANDS set_commands_func =
			(LPFN_SET_SCRIPT_COMMANDS)GetProcAddress(hDLL, LPSTR_SET_SCRIPT_COMMANDS);
		assert(set_commands_func != NULL);

		if (set_commands_func != NULL) {
			ScriptCommandsClass commands;
			commands.Commands = EngineCommands;

			bool success = set_commands_func(&commands);

			if (!success) {
				Debug_Say(("Failed to set script commands!\n"));

				// This should keep us from going to scripts!
				ScriptCreateFunct = NULL;
			}
		} else {
			Debug_Say(("Cound not find Set_Script_Commands\n"));
		}
	}
}


/*
**
*/
ScriptClass* ScriptManager::Create_Script(const char* script_name)
{
	ScriptClass* script = NULL;

	if (EnableScriptCreation && ScriptCreateFunct != NULL) {
		script = ScriptCreateFunct(script_name);

		if (script != NULL) {
			script->Set_ID( GameObjObserverManager::Get_Next_Observer_ID() );
			ActiveScriptList.Add(script);
		}
	}

	return script;
}


/*
**
*/
void ScriptManager::Request_Destroy_Script(ScriptClass* script)
{
	ActiveScriptList.Delete(script);

	// Do not add the script to the destroy list if it is already there.
	for (int index = 0; index < PendingDestroyList.Count(); index++) {
		if (PendingDestroyList[index] == script) {
			return;
		}
	}

	PendingDestroyList.Add(script);
}


/*
** Script Manager Save and Load
*/
enum	{
	CHUNKID_SCRIPT_ENTRY					=	131001134,
	CHUNKID_SCRIPT_HEADER,
	CHUNKID_SCRIPT_DATA,

	MICROCHUNKID_NAME						= 1,

// Denzil 3/31/00 - This information is now saved by the script.
#if(0)
	MICROCHUNKID_PARAM_COUNT,
#endif
	MICROCHUNKID_PARAM,
	MICROCHUNKID_GAME_OBJ_OBSERVER_PTR,
	MICROCHUNKID_OWNER_PTR,
	MICROCHUNKID_ID,
};


/*
**
*/
bool ScriptManager::Save(ChunkSaveClass& csave)
{
	for (int index = 0; index < ActiveScriptList.Count(); index++) {
		ScriptClass* script = ActiveScriptList[ index ];

		csave.Begin_Chunk( CHUNKID_SCRIPT_ENTRY );
		csave.Begin_Chunk( CHUNKID_SCRIPT_HEADER );

		StringClass name = script->Get_Name();
//		Debug_Say(("Saving script '%s'\n", name));
		WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_NAME, name );

		char paramString[256];
		script->Get_Parameters_String(paramString, sizeof(paramString));
//		Debug_Say(("\tParameters: '%s'\n", paramString));
		WRITE_MICRO_CHUNK_STRING(csave, MICROCHUNKID_PARAM, paramString);

		GameObjObserverClass* game_obj_observer_ptr = (GameObjObserverClass*)script;
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_GAME_OBJ_OBSERVER_PTR, game_obj_observer_ptr );

		ScriptableGameObj* owner_ptr = *(script->Get_Owner_Ptr());
//		Debug_Say(("\tObjectPtr: '%p'\n", *owner_ptr));
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_OWNER_PTR, owner_ptr );

		int id = script->Get_ID();
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ID, id );
//		Debug_Say(( "Saved Script ID %d\n", id ));

		csave.End_Chunk();

		// If data is not saved, script will be re-created
		if (CombatManager::Are_Observers_Active()) {
			csave.Begin_Chunk(CHUNKID_SCRIPT_DATA);
			ScriptSaver	saver(csave);
			script->Save(saver);
			csave.End_Chunk();
		}

		csave.End_Chunk();
	}
	return true;
}


bool	ScriptManager::Load( ChunkLoadClass & cload )
{
	WWASSERT( ActiveScriptList.Count() == 0 );

	while (cload.Open_Chunk()) {

		GameObjObserverClass * game_obj_observer_ptr = NULL;
		PhysicalGameObj * owner_ptr = NULL;

		WWASSERT( cload.Cur_Chunk_ID() == CHUNKID_SCRIPT_ENTRY );

		ScriptClass *script = NULL;

		// Load header
		cload.Open_Chunk();
		WWASSERT( cload.Cur_Chunk_ID() == CHUNKID_SCRIPT_HEADER );

		int obs_id = -1;

//		int param_index = 0;
		while (cload.Open_Micro_Chunk()) {
			int id = cload.Cur_Micro_Chunk_ID();
			switch( id ) {
				case MICROCHUNKID_NAME:
				{
					StringClass	name;
					LOAD_MICRO_CHUNK_WWSTRING( cload, name );
					WWASSERT( script == NULL );
					script = Create_Script( name );
					if ( script == NULL ) {
						Debug_Say(( "Script %s not found \n", name ));
					}

					// A Missing script is not fatal
//					WWASSERT( script != NULL );
					break;
				}

				case MICROCHUNKID_PARAM:
				{
					if ( script != NULL ) {
						StringClass	param;
						LOAD_MICRO_CHUNK_WWSTRING( cload, param );
						script->Set_Parameters_String(param);
					}
					break;
				}

				READ_MICRO_CHUNK( cload, MICROCHUNKID_GAME_OBJ_OBSERVER_PTR, game_obj_observer_ptr );
				READ_MICRO_CHUNK( cload, MICROCHUNKID_OWNER_PTR, owner_ptr );

				READ_MICRO_CHUNK( cload, MICROCHUNKID_ID, obs_id );

				default:
					Debug_Say(( "Unrecognized ScriptCollection Header chunkID\n" ));
					break;
			}
			cload.Close_Micro_Chunk();
		}
		cload.Close_Chunk();

		if ( script != NULL ) {

			if ( obs_id != -1 ) {
				script->Set_ID( obs_id );
//				Debug_Say(( "Loaded Script ID %d\n", obs_id ));
			}

			// If there is data, load
			if ( cload.Open_Chunk() ) {
				WWASSERT( cload.Cur_Chunk_ID() == CHUNKID_SCRIPT_DATA );
				ScriptLoader loader( cload );
				script->Load( loader );
				cload.Close_Chunk();
			}

			WWASSERT( game_obj_observer_ptr != NULL );
			if ( game_obj_observer_ptr != NULL ) {
				SaveLoadSystemClass::Register_Pointer(game_obj_observer_ptr, (GameObjObserverClass *)script);
			}

			// set the owner, and request remap
			*(script->Get_Owner_Ptr()) = owner_ptr;
			REQUEST_POINTER_REMAP( (void **)script->Get_Owner_Ptr() );
		} else {
			SaveLoadSystemClass::Register_Pointer(game_obj_observer_ptr, (GameObjObserverClass *)NULL);
		}


		cload.Close_Chunk();
	}
	return true;
}


 