/*
**	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/>.
*/

/***********************************************************************************************
 ***              C O N F I D E N T I A L  ---  W E S T W O O D  S T U D I O S               ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Combat																		  *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Combat/activeconversation.cpp      $*
 *                                                                                             *
 *                       Author:: Patrick Smith                                                *
 *                                                                                             *
 *                     $Modtime:: 1/16/02 6:01p                                               $*
 *                                                                                             *
 *                    $Revision:: 31                                                          $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */


#include "activeconversation.h"
#include "soldier.h"
#include "chunkio.h"
#include "actionparams.h"
#include "timemgr.h"
#include "orator.h"
#include "pathfind.h"
#include "globalsettings.h"
#include "soldierobserver.h"
#include "gameobjmanager.h"
#include "conversationmgr.h"
#include "debug.h"
#include "wwaudio.h"
#include "audiblesound.h"
#include "translatedb.h"
#include "translateobj.h"
#include "combat.h"


////////////////////////////////////////////////////////////////
//	Constants
////////////////////////////////////////////////////////////////
enum
{
	CHUNKID_VARIABLES				= 0x08090727,
	CHUNKID_ORATOR,
	CHUNKID_MONITOR
};

enum
{
	XXXXX_CONVERSATION_PTR		= 0,
	VARID_CURRENT_REMARK,
	XXXXX_NEXT_REMARK_TIME,
	VARID_STATE,
	VARID_OLD_PTR,
	VARID_ID,
	VARID_INITIALIZING_TIMER,
	VARID_ACTION_ID,
	VARID_SPOKEN_BITMASK,
	VARID_CENTRAL_POS,
	VARID_PRIORITY,
	VARID_MAXDIST,
	VARID_IS_INTERRUPTABLE,
	VARID_CONVERSATION_ID,
	VARID_NEXT_REMARK_TIMER,
};

static const float RAND_STAND_DIST		= 2.0F;
static const float MIN_AUDIENCE_DIST	= 4.0F;


////////////////////////////////////////////////////////////////
//
//	ActiveConversationClass
//
////////////////////////////////////////////////////////////////
ActiveConversationClass::ActiveConversationClass (void)	:
	ID (0),
	Conversation (NULL),
	CurrentRemark (-1),
	NextRemarkTimer (0),
	State (STATE_INITIALIZING),
	InitializingTimeLeft (60000),
	ActionID (0),
	OratorSpokenBitmask (0),
	CentralPos (0, 0, 0),
	Priority (30),
	MaxDist (0),
	IsInterruptable (true),
	CurrentSound (NULL)
{
	return ;
}


////////////////////////////////////////////////////////////////
//
//	~ActiveConversationClass
//
////////////////////////////////////////////////////////////////
ActiveConversationClass::~ActiveConversationClass (void)
{
	Stop_Current_Sound ();
	Free_Orator_List ();
	REF_PTR_RELEASE (Conversation);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Free_Orator_List
//
////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Free_Orator_List (void)
{
	//
	//	Free each orator in our list
	//
	for (int index = 0; index < OratorList.Count (); index ++) {
		OratorClass *orator = OratorList[index];
		if (orator != NULL) {

			PhysicalGameObj *game_obj = orator->Get_Game_Obj ();
			if (game_obj != NULL) {

				//
				//	Was this a soldier we were talking to?
				//
				SoldierGameObj *soldier = game_obj->As_SoldierGameObj ();
				if (soldier != NULL) {

					//
					//	Turn idle animations back on for soldiers
					//
					soldier->As_SoldierGameObj ()->Set_Loiters_Allowed (true);
				
					//
					//	Make sure the soldier doesn't ramble on after the
					// conversation has ended.
					//
					soldier->Stop_Current_Speech ();

					//
					//	Reset the possibility that this soldier will have a conversation
					//
					SoldierObserverClass	*innate_ai = soldier->Get_Innate_Controller ();
					if (innate_ai != NULL) {
						innate_ai->Reset_Conversation_Timer ();
					}
					
					//
					//	If we were controlling the soldier's head, return
					// it to its default position
					//
					if (orator->Get_Flag (OratorClass::FLAG_DONT_TURN_HEAD) == false) {
						soldier->Cancel_Look_At ();
					}
				}

				//
				//	Let the game obj know its no longer in a conversation
				//
				game_obj->Set_Conversation (NULL);
			}

			delete orator;
			orator = NULL;
		}
	}

	OratorList.Delete_All ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Set_Conversation
//
////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Set_Conversation (ConversationClass *conversation)
{
	REF_PTR_SET (Conversation, conversation);
	CurrentRemark			= -1;
	NextRemarkTimer		= 0;
	OratorSpokenBitmask	= 0;
	Priority					= conversation->Get_Priority ();
	MaxDist					= conversation->Get_Max_Dist ();
	IsInterruptable		= conversation->Is_Interruptable ();
	Free_Orator_List ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Add_Orator
//
////////////////////////////////////////////////////////////////
OratorClass *
ActiveConversationClass::Add_Orator (PhysicalGameObj *game_obj)
{
	//
	//	Let the game obj know he's part of a conversation
	//
	if (game_obj != NULL) {
		game_obj->Set_Conversation (this);
	}

	//
	//	Allocate and initialize a new orator
	//
	OratorClass *orator = new OratorClass;
	orator->Initialize (game_obj);
	orator->Set_ID (OratorList.Count ());	

	//
	//	Add the new orator to our list
	//
	OratorList.Add (orator);

	//
	//	If orators are still being added to the conversation, the
	// the object is considered initializing.
	//
	State = STATE_INITIALIZING;
	return orator;
}


////////////////////////////////////////////////////////////////
//
//	Start_Conversation
//
////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Start_Conversation (void)
{
	WWASSERT (OratorList.Count () > 0);
	OratorSpokenBitmask = 0;

	//
	//	Find the first "visible" orator in our list
	//
	PhysicalGameObj *main_speaker = NULL;
	for (	int orator_index = 0;
			main_speaker == NULL && orator_index < OratorList.Count ();
			orator_index ++)
	{
		main_speaker = OratorList[orator_index]->Get_Game_Obj ();
	}

	//
	//	Get the location of the first orator
	//
	if (main_speaker != NULL) {
		
		//
		//	Pick a center position somewhere around the first speaker
		//
		main_speaker->Get_Position (&CentralPos);

		//
		//	Find a safe central position for the conversation
		//
		PathfindClass *pathfind = PathfindClass::Get_Instance();
		if (pathfind->Find_Random_Spot( CentralPos, 2, &CentralPos)) {

			for (int index = 0; index < OratorList.Count (); index ++) {

				//
				//	Don't allow this unit to face the speaker by default.  (This
				// flag will be cleared by the ActionCodeClass if its priority is high enough)
				//
				OratorList[index]->Set_Flag (OratorClass::FLAG_TEMP_DONT_FACE, true);

				//
				//	If this soldier is allowed to move, then calculate a new position for them
				//
				if (OratorList[index]->Get_Flag (OratorClass::FLAG_DONT_MOVE) == false) {

					//
					//	Lookup a safe random position to stand
					//
					Vector3 new_location;
					if (pathfind->Find_Random_Spot (CentralPos, 2, &new_location)) {

						//
						//	Store this position in our list so the orator can query
						// for his position later.
						//
						OratorList[index]->Set_Position (new_location);
					}
				}
			}
		}

		//
		//	Now, let each orator know its part of a conversation
		//
		for (int index = 0; index < OratorList.Count (); index ++) {			
			PhysicalGameObj *game_obj = OratorList[index]->Get_Game_Obj ();
			if (game_obj != NULL) {

				//
				//	Turn off idle animations for soldiers when they are
				// having a conversation
				//
				if (game_obj->As_SoldierGameObj () != NULL) {
					game_obj->As_SoldierGameObj ()->Set_Loiters_Allowed (false);
				}

				//
				//	Make sure we don't take control of a human player
				//
				if (	game_obj->As_SmartGameObj () == NULL ||
						game_obj->As_SmartGameObj ()->Is_Human_Controlled () ||
						OratorList.Count () <= 2)
				{
					OratorList[index]->Set_Flag (OratorClass::FLAG_DONT_MOVE,		true);
					OratorList[index]->Set_Flag (OratorClass::FLAG_TEMP_DONT_FACE, false);
					
					//
					//	Don't force facing if its a human player or inanimate object
					//
					if (	game_obj->As_SmartGameObj () == NULL ||
							game_obj->As_SmartGameObj ()->Is_Human_Controlled ())
					{
						OratorList[index]->Set_Flag (OratorClass::FLAG_DONT_FACE, true);
					}
					
					//
					//	Since this object doesn't have to move -- reset its position
					//
					Vector3 position;
					game_obj->Get_Position (&position);
					OratorList[index]->Set_Position (position);

				} else {
					ActionParamsStruct parameters;
					parameters.Priority		= Priority;
					parameters.ActionID		= ActionID;
					parameters.ObserverID	= 0;
					parameters.Join_Conversation (ID);			
					game_obj->As_SmartGameObj ()->Get_Action ()->Have_Conversation (parameters);
				}
			}
		}
	}

	//
	//	Begin waiting for the participants to move to their location
	//
	State = STATE_WAITING_FOR_AUDIENCE;

	//
	//	Don't allow a key conversation to be interrupted
	//
	if (Conversation != NULL && Conversation->Is_Key ()) {
		Set_Is_Interruptable (false);

		//
		//	Clear all other conversations from the manager
		//
		//ConversationMgrClass::Reset_All_Other_Conversations (this);
	} else {

		//
		//	Stop this conversation before it can start if there is
		// a key conversation playing
		//
		if (ConversationMgrClass::Is_Key_Conversation_Playing ()) {
			Stop_Conversation (ACTION_COMPLETE_CONVERSATION_INTERRUPTED);
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Think
//
////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Think (void)
{
	//
	// Make each orator do something meaningful
	//
	for (int index = 0; index < OratorList.Count (); index ++) {
		PhysicalGameObj *game_obj = OratorList[index]->Get_Game_Obj ();
		if (game_obj != NULL && game_obj->As_SoldierGameObj () != NULL) {
			SoldierGameObj *soldier = game_obj->As_SoldierGameObj ();
			
			//
			//	Stop the conversation if the orator is dead, otherwise
			// take control of some of his movements
			//
			if (soldier->Is_Dead () || soldier->Is_Destroyed ()) {
				Stop_Conversation (ACTION_COMPLETE_CONVERSATION_INTERRUPTED);
			} else {
				Control_Orator (soldier);
			}
		}
	}

	if (State == STATE_WAITING_FOR_AUDIENCE) {
		Check_For_Audience ();
	} else if (State == STATE_TALKING) {
		
		//
		//	Should we advance to the next remark?
		//
		NextRemarkTimer -= TimeManager::Get_Frame_Seconds ();
		if (NextRemarkTimer <= 0) {
			
			//
			//	If everyone is listening, then move on to the next remark,
			// otherwise kick out of the conversation
			//
			if (Is_Audience_In_Place () == false) {
				Stop_Conversation (ACTION_COMPLETE_CONVERSATION_INTERRUPTED);
			} else {
				Say_Next_Remark ();
			}
		}

	} else if (State == STATE_INITIALIZING) {
		
		//
		//	Scrap the conversation if too much time was spent initializing (probably an error).
		//
		InitializingTimeLeft -= TimeManager::Get_Frame_Seconds ();
		if (InitializingTimeLeft <= 0) {
			Stop_Conversation (ACTION_COMPLETE_CONVERSATION_UNABLE_TO_INIT);
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Say_Next_Remark
//
////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Say_Next_Remark (void)
{
	//
	//	Move onto the next remark if possible
	//
	CurrentRemark ++;
	if (CurrentRemark < Conversation->Get_Remark_Count ()) {

		//
		//	Notify the monitors of the event
		//
		if (CurrentRemark > 0) {			
			Notify_Monitors (CUSTOM_EVENT_CONVERSATION_REMARK_ENDED, CurrentRemark - 1);
		}
		Notify_Monitors (CUSTOM_EVENT_CONVERSATION_REMARK_STARTED, CurrentRemark);


		//
		//	Lookup who says the next line
		//
		ConversationRemarkClass remark;
		Conversation->Get_Remark_Info (CurrentRemark, remark);
		int orator_id	= remark.Get_Orator_ID ();
		int text_id		= remark.Get_Text_ID ();

		//
		//	Make sure the data is valid
		//

		WWASSERT_PRINT (orator_id >= 0 && orator_id < OratorList.Count (), Conversation->Get_Name ());
		if (orator_id >= 0 && orator_id < OratorList.Count ()) {			
			PhysicalGameObj *orator = OratorList[orator_id]->Get_Game_Obj ();

			//
			//	Set a bit which lets us know this orator has spoken at least once...
			//
			OratorSpokenBitmask |= (1 << orator_id);

			//
			//	Have the orator start his remark
			//
			SoldierGameObj *soldier = NULL;
			if (orator != NULL) {
				soldier = orator->As_SoldierGameObj ();
			}
						
			float duration = 0;
			
			if (soldier != NULL) {
				duration = SoldierGameObj::Say_Dynamic_Dialogue (text_id, soldier);
			} else {
				REF_PTR_RELEASE (CurrentSound);
				duration = SoldierGameObj::Say_Dynamic_Dialogue (text_id, NULL, &CurrentSound);
			}

			//
			//	Play an animation on the orator
			//
			if (orator != NULL && remark.Get_Animation_Name ().Get_Length () > 0) {
				orator->Set_Animation (remark.Get_Animation_Name (), false);
			}

			//
			//	Determine when we should switch to the next remark
			//
			NextRemarkTimer = duration;
		}

	} else {
		Stop_Conversation (ACTION_COMPLETE_CONVERSATION_ENDED);
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Is_Audience_In_Place
//
////////////////////////////////////////////////////////////////
bool
ActiveConversationClass::Is_Audience_In_Place (void)
{
	bool retval = true;

	//
	//	Determine how far away each participant can be before
	// we terminate the conversation
	//
	float max_dist = MaxDist;
	if (max_dist <= 0) {
		if (Conversation->Get_AI_State () == AI_STATE_COMBAT) {
			max_dist = GlobalSettingsDef::Get_Global_Settings ()->Get_Combat_Conversation_Dist ();
		} else {
			max_dist = GlobalSettingsDef::Get_Global_Settings ()->Get_Conversation_Dist ();
		}
	}
	
	//
	//	Square the distance for faster comparisons
	//
	float max_dist2 = max_dist * max_dist;

	//
	//	Check to see if everyone is close enough to have this conversation
	//
	for (int index = 0; index < OratorList.Count (); index ++) {
		PhysicalGameObj *game_obj = OratorList[index]->Get_Game_Obj ();
		if (game_obj != NULL) {
			
			//
			//	Get this soldier's position
			//
			Vector3 position;
			game_obj->Get_Position (&position);

			//
			//	Calculate how far from the conversation's center this
			// soldier is.
			//
			Vector3 delta = position - CentralPos;
			delta.Z = delta.Z * 0.5F;

			//
			//	If this distance is greater then the maximum allowed, then
			// the audience is NOT is place
			//
			float distance2 = delta.Length2 ();
			if (distance2 > max_dist2) {
				retval = false;
				break;
			}
		}
	}

	return retval;
}


////////////////////////////////////////////////////////////////
//
//	Check_For_Audience
//
////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Check_For_Audience (void)
{
	if (State == STATE_WAITING_FOR_AUDIENCE) {

		//
		//	If everyone is close enough then start the conversation
		//
		if (Is_Audience_In_Place ()) {
			State = STATE_TALKING;
			Notify_Monitors (CUSTOM_EVENT_CONVERSATION_BEGAN, Conversation->Get_ID ());
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Save
//
////////////////////////////////////////////////////////////////
bool
ActiveConversationClass::Save (ChunkSaveClass &csave)
{
	csave.Begin_Chunk (CHUNKID_VARIABLES);

		//
		//	Save the ID of the conversation (if necessary)
		//
		if (Conversation != NULL) {
			int conversation_id = Conversation->Get_ID ();
			WRITE_MICRO_CHUNK (csave, VARID_CONVERSATION_ID,	conversation_id);
		}

		WRITE_MICRO_CHUNK (csave, VARID_CURRENT_REMARK,			CurrentRemark);
		WRITE_MICRO_CHUNK (csave, VARID_NEXT_REMARK_TIMER,		NextRemarkTimer);
		WRITE_MICRO_CHUNK (csave, VARID_STATE,						State);
		WRITE_MICRO_CHUNK (csave, VARID_ID,							ID);
		WRITE_MICRO_CHUNK (csave, VARID_INITIALIZING_TIMER,	InitializingTimeLeft);
		WRITE_MICRO_CHUNK (csave, VARID_ACTION_ID,				ActionID);
		WRITE_MICRO_CHUNK (csave, VARID_SPOKEN_BITMASK,			OratorSpokenBitmask);
		WRITE_MICRO_CHUNK (csave, VARID_CENTRAL_POS,				CentralPos);
		WRITE_MICRO_CHUNK (csave, VARID_PRIORITY,					Priority);
		WRITE_MICRO_CHUNK (csave, VARID_MAXDIST,					MaxDist);
		WRITE_MICRO_CHUNK (csave, VARID_IS_INTERRUPTABLE,		IsInterruptable);
						
		//
		//	Save our current pointer so we can remap it on load
		//
		ActiveConversationClass *old_ptr = this;
		WRITE_MICRO_CHUNK (csave, VARID_OLD_PTR, old_ptr);
	
	csave.End_Chunk ();

	//
	//	Save each of the monitors
	//
	for (int index = 0; index < MAX_MONITORS; index ++) {		
		if (MonitorArray[index].Get_Ptr () != NULL) {
			csave.Begin_Chunk (CHUNKID_MONITOR);
				MonitorArray[index].Save (csave);
			csave.End_Chunk ();
		}		
	}

	//
	//	Save each of the orators
	//
	for (index = 0; index < OratorList.Count (); index ++) {
		csave.Begin_Chunk (CHUNKID_ORATOR);
			OratorList[index]->Save (csave);
		csave.End_Chunk ();
	}

	return true;
}


////////////////////////////////////////////////////////////////
//
//	Load
//
////////////////////////////////////////////////////////////////
bool
ActiveConversationClass::Load (ChunkLoadClass &cload)
{
	Free_Orator_List ();
	int monitor_index = 0;

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

			case CHUNKID_VARIABLES:
				Load_Variables (cload);
				break;

			case CHUNKID_ORATOR:
			{
				//
				//	Allocate and load a new orator object
				//
				OratorClass *orator = new OratorClass;
				orator->Load (cload);

				//
				//	Add this new object to our list
				//
				OratorList.Add (orator);
			}
			break;

			case CHUNKID_MONITOR:
			{				
				//
				//	Load the monitor's reference from the chunk
				//
				MonitorArray[monitor_index++].Load (cload);
			}
			break;
		}

		cload.Close_Chunk ();
	}

	return true;
}


///////////////////////////////////////////////////////////////////////
//
//	Load_Variables
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Load_Variables (ChunkLoadClass &cload)
{
	ActiveConversationClass *old_ptr = NULL;
	int conversation_id = 0;

	//
	//	Loop through all the microchunks that define the variables
	//
	while (cload.Open_Micro_Chunk ()) {
		switch (cload.Cur_Micro_Chunk_ID ()) {

			READ_MICRO_CHUNK (cload, VARID_CONVERSATION_ID,		conversation_id);
			READ_MICRO_CHUNK (cload, VARID_CURRENT_REMARK,		CurrentRemark);
			READ_MICRO_CHUNK (cload, VARID_NEXT_REMARK_TIMER,	NextRemarkTimer);
			READ_MICRO_CHUNK (cload, VARID_STATE,					State);
			READ_MICRO_CHUNK (cload, VARID_ID,						ID);
			READ_MICRO_CHUNK (cload, VARID_OLD_PTR,				old_ptr);
			READ_MICRO_CHUNK (cload, VARID_INITIALIZING_TIMER,	InitializingTimeLeft);
			READ_MICRO_CHUNK (cload, VARID_ACTION_ID,				ActionID);
			READ_MICRO_CHUNK (cload, VARID_SPOKEN_BITMASK,		OratorSpokenBitmask);		
			READ_MICRO_CHUNK (cload, VARID_CENTRAL_POS,			CentralPos);
			READ_MICRO_CHUNK (cload, VARID_PRIORITY,				Priority);
			READ_MICRO_CHUNK (cload, VARID_MAXDIST,				MaxDist);
			READ_MICRO_CHUNK (cload, VARID_IS_INTERRUPTABLE,	IsInterruptable);
		}

		cload.Close_Micro_Chunk ();
	}

	//
	//	Lookup the conversation pointer
	//
	if (conversation_id != 0) {
		Conversation = ConversationMgrClass::Find_Conversation (conversation_id);
	}

	//
	//	Register our old pointer so other objects can safely remap to it
	//
	WWASSERT (old_ptr != NULL);
	SaveLoadSystemClass::Register_Pointer (old_ptr, this);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Set_Orator_Arrived
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Set_Orator_Arrived (PhysicalGameObj *orator, bool has_arrived)
{
	WWASSERT (orator != NULL);
	
	for (int index = 0; index < OratorList.Count (); index ++) {
		PhysicalGameObj *curr_orator = OratorList[index]->Get_Game_Obj ();
	
		//
		//	If this is the orator we were looking for, then set
		// its arrived state
		//
		if (curr_orator != NULL && curr_orator == orator) {
			OratorList[index]->Set_Has_Arrived (has_arrived);
			break;
		}
	}		

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Get_Orator_Location
//
///////////////////////////////////////////////////////////////////////
bool
ActiveConversationClass::Get_Orator_Location (PhysicalGameObj *orator, Vector3 *position)
{
	WWASSERT (orator != NULL);
	WWASSERT (position != NULL);
	bool retval = false;			
	
	for (int index = 0; index < OratorList.Count (); index ++) {
		PhysicalGameObj *curr_orator = OratorList[index]->Get_Game_Obj ();
	
		//
		//	If this is the orator we were looking for, then pass the
		// expected position back to the caller
		//
		if (curr_orator != NULL && curr_orator == orator) {
			(*position) = OratorList[index]->Get_Position ();
			break;
		}
	}		

	return retval;
}


///////////////////////////////////////////////////////////////////////
//
//	Get_Current_Orator_Location
//
///////////////////////////////////////////////////////////////////////
bool
ActiveConversationClass::Get_Current_Orator_Location (Vector3 *position)
{
	WWASSERT (position != NULL);
	WWASSERT (Conversation != NULL);
	bool retval = false;

	//
	//	Lookup who is saying the current line
	//		
	int orator_id = 0;
	if (CurrentRemark >= 0 && CurrentRemark < Conversation->Get_Remark_Count ()) {
		ConversationRemarkClass remark;
		Conversation->Get_Remark_Info (CurrentRemark, remark);
		orator_id = remark.Get_Orator_ID ();
	}

	//
	//	Make sure the data is valid
	//
	WWASSERT (orator_id >= 0 && orator_id < OratorList.Count ());
	if (orator_id >= 0 && orator_id < OratorList.Count ()) {			
		
		//
		//	Now, return this orator's current position
		//
		PhysicalGameObj *orator = OratorList[orator_id]->Get_Game_Obj ();
		if (orator != NULL) {
			orator->Get_Position (position);
		}
		retval = true;
	}

	return retval;	
}


///////////////////////////////////////////////////////////////////////
//
//	Get_Current_Orator
//
///////////////////////////////////////////////////////////////////////
PhysicalGameObj *
ActiveConversationClass::Get_Current_Orator (void)
{
	PhysicalGameObj *current_orator = NULL;

	//
	//	Lookup who is saying the current line
	//			
	if (CurrentRemark >= 0 && CurrentRemark < Conversation->Get_Remark_Count ()) {
		
		ConversationRemarkClass remark;
		Conversation->Get_Remark_Info (CurrentRemark, remark);
		int orator_id	= remark.Get_Orator_ID ();

		if (orator_id >= 0 && orator_id < OratorList.Count ()) {
			current_orator = OratorList[orator_id]->Get_Game_Obj ();
		}
	}	

	return current_orator;
}


///////////////////////////////////////////////////////////////////////
//
//	Get_Conversation_Center
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Get_Conversation_Center (Vector3 *position)
{
	WWASSERT (position != NULL);
	(*position) = CentralPos;
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Get_Orator_Information
//
///////////////////////////////////////////////////////////////////////
OratorClass *
ActiveConversationClass::Get_Orator_Information (PhysicalGameObj *soldier)
{
	WWASSERT (soldier != NULL);
	OratorClass *orator = NULL;
	
	for (int index = 0; index < OratorList.Count (); index ++) {
		PhysicalGameObj *curr_soldier = OratorList[index]->Get_Game_Obj ();
	
		//
		//	If this is the orator we were looking for, then pass the
		// expected information back to the caller
		//
		if (curr_soldier != NULL && curr_soldier == soldier) {
			orator = OratorList[index];
			break;
		}
	}		

	return orator;
}


///////////////////////////////////////////////////////////////////////
//
//	Register_Monitor
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Register_Monitor (ScriptableGameObj *game_obj)
{
	bool found			= false;
	int empty_index	= -1;

	//
	//	Check to ensure this game object isn't already registered as a monitor
	//
	for (int index = 0; index < MAX_MONITORS; index ++) {
		ScriptableGameObj *curr_game_obj = MonitorArray[index];
		if (curr_game_obj == game_obj) {
			found = true;
			break;
		} else if (curr_game_obj == NULL) {
			empty_index = index;
		}
	}
	
	if (found == false) {

		//
		//	Insert the reference inside our array
		//
		if (empty_index != -1) {
			MonitorArray[empty_index] = game_obj;
		} else {
			Debug_Say (("Exceeded max monitors for an active conversation.\n"));
		}
	}

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Unregister_Monitor
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Unregister_Monitor (ScriptableGameObj	*game_obj)
{
	//
	//	Remove the monitor from our list
	//
	for (int index = 0; index < MAX_MONITORS; index ++) {
		ScriptableGameObj *curr_game_obj = MonitorArray[index];
		if (curr_game_obj == game_obj) {
			MonitorArray[index] = NULL;
			break;
		}
	}

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Notify_Monitors_On_End
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Notify_Monitors_On_End (ActionCompleteReason reason)
{
	//
	//	Notify all the game objects
	//
	for (int index = 0; index < MAX_MONITORS; index ++) {
		ScriptableGameObj *game_obj = MonitorArray[index];

		//
		//	Notify all the observers of this game object
		//
		if (game_obj != NULL) {
			const GameObjObserverList &observer_list = game_obj->Get_Observers ();
			for (int observer_index = 0; observer_index < observer_list.Count (); observer_index ++) {
				observer_list[observer_index]->Action_Complete (game_obj, ActionID, reason);
			}
		}
	}
		
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Notify_Monitors
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Notify_Monitors (int custom_event_id, int param)
{
	//
	//	Notify all the game objects
	//
	for (int index = 0; index < MAX_MONITORS; index ++) {
		ScriptableGameObj *game_obj = MonitorArray[index];

		//
		//	Notify all the observers of this game object
		//
		if (game_obj != NULL) {
			const GameObjObserverList &observer_list = game_obj->Get_Observers ();
			for (int observer_index = 0; observer_index < observer_list.Count (); observer_index ++) {
				observer_list[observer_index]->Custom (game_obj, custom_event_id, param, NULL);
			}
		}
	}
		
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Control_Orator
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Control_Orator (SoldierGameObj *soldier)
{
	OratorClass *orator_info = Get_Orator_Information (soldier);
	if (orator_info == NULL || CurrentRemark < 0) {
		return ;
	}

	const float HEAD_HEIGHT	= 1.7F;
	
	//
	//	Lookup information about the current remark
	//				
	ConversationRemarkClass remark;	
	Conversation->Get_Remark_Info (CurrentRemark, remark);

	if (orator_info->Get_Flag (OratorClass::FLAG_DONT_TURN_HEAD) == false) {

		//
		//	Now determine where this participant should be looking
		//
		PhysicalGameObj *orator = Get_Current_Orator ();		
		if (orator != NULL) {

			Vector3 look_pos (0, 0, 0);
			bool is_something_to_look_at = false;
			bool force_facing = false;

			//
			//	Lookup the object we are trying to look at...
			//
			int look_at_id						= orator_info->Get_Look_At_Obj ();
			PhysicalGameObj *look_at_obj	= NULL;
			if (look_at_id > 0) {
				look_at_obj = GameObjManager::Find_PhysicalGameObj (look_at_id);					
			} else if (look_at_id == -1) {
				look_at_obj = COMBAT_STAR;
			}
			
			//
			//	Now, either look at a specific object, look at the speaker, or
			// find a random person to look at...
			//
			if (look_at_obj != NULL) {
				
				look_at_obj->Get_Position (&look_pos);
				look_pos.Z += HEAD_HEIGHT;
				soldier->Look_At (look_pos, 100.0F);
				is_something_to_look_at	= true;
				force_facing				= true;
				
			} else if (orator != soldier) {

				Get_Current_Orator_Location (&look_pos);
				look_pos.Z += HEAD_HEIGHT;
				soldier->Look_At (look_pos, 100.0F);
				is_something_to_look_at = true;

			} else if (OratorList.Count () > 1) {

				//
				//	Choose a different participant to look at
				//				
				int index = remark.Get_Orator_ID () + 1;
				if (index >= OratorList.Count ()) {
					index = 0;
				}

				//
				//	Look at someone else
				//
				PhysicalGameObj *someone_to_lookat = OratorList[index]->Get_Game_Obj ();
				if (someone_to_lookat != NULL) {
					someone_to_lookat->Get_Position (&look_pos);
					look_pos.Z += HEAD_HEIGHT;
					soldier->Look_At (look_pos, 100.0F);
					is_something_to_look_at = true;
				}		
			}

			//
			// If the head look would turn the soldier to far, then turn the body as well
			//
			if (	is_something_to_look_at &&
					(force_facing ||
					 ((orator_info->Get_Flag (OratorClass::FLAG_DONT_FACE) == false) &&
					 (orator_info->Get_Flag (OratorClass::FLAG_TEMP_DONT_FACE) == false)))) 
			{
				//
				//	Get the target relative to the head
				//
				Vector3 relative_look_pos;
				Matrix3D::Inverse_Transform_Vector( soldier->Get_Transform (), look_pos, &relative_look_pos );

				//Vector3 delta = look_pos - soldier->Get_Transform ().Get_Translation ();

				//float curr_facing			= soldier->Get_Facing ();
				//float head_turn_angle	= WWMath::Atan2 (delta.Y, delta.X);
				//float angle					= head_turn_angle - curr_facing;
				float angle = WWMath::Atan2 (relative_look_pos.Y, relative_look_pos.X);

				if (force_facing || WWMath::Fabs (angle) > DEG_TO_RADF (15)) {
					soldier->Set_Targeting (look_pos, false);
				}
			}
		}
	}
	
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Stop_Conversation
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Stop_Conversation (ActionCompleteReason reason)
{
	if (Is_Finished ()) {
		return ;
	}

	State = STATE_FINISHED;

	Stop_Current_Sound ();

	if (CurrentRemark >= 0 && CurrentRemark < Conversation->Get_Remark_Count ()) {
		
		//
		//	Get the current speaker
		//
		ConversationRemarkClass remark;
		Conversation->Get_Remark_Info (CurrentRemark, remark);
		int orator_id	= remark.Get_Orator_ID ();

		//
		//	Sanity check
		//
		if (orator_id >= 0 && orator_id < OratorList.Count ()) {			
			PhysicalGameObj *orator = OratorList[orator_id]->Get_Game_Obj ();

			//
			//	Have the current speaker stop speaking
			//
			if (orator != NULL && orator->As_SoldierGameObj () != NULL) {
				orator->As_SoldierGameObj ()->Stop_Current_Speech ();
			}
		}
	}
	
	Free_Orator_List ();

	Notify_Monitors_On_End (reason);

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Get_Conversation_Time
//
///////////////////////////////////////////////////////////////////////
float
ActiveConversationClass::Get_Conversation_Time (void)
{
	float retval = 0.0F;

	for (int index = 0; index < Conversation->Get_Remark_Count (); index ++) {

		//
		//	Lookup up the line of text that will be spoken
		//
		ConversationRemarkClass remark;
		Conversation->Get_Remark_Info (index, remark);
		int text_id	= remark.Get_Text_ID ();

		//
		//	Lookup the translation object from the strings database
		//
		TDBObjClass *translate_obj = TranslateDBClass::Find_Object (text_id);
		if (translate_obj != NULL) {
			

			//
			//	Create the sound object
			//
			uint32 sound_def_id = translate_obj->Get_Sound_ID ();
			if (sound_def_id != 0) {
				
				//
				//	Add the duration of the sound object to the total
				//
				AudibleSoundClass *speech = WWAudioClass::Get_Instance ()->Create_Sound (sound_def_id);
				if (speech != NULL) {
					retval += (speech->Get_Duration() / 1000.0F);
					REF_PTR_RELEASE (speech);
				}
			}
		}		
	}

	return (retval > 0.0F) ? retval : 2.0F;
}


///////////////////////////////////////////////////////////////////////
//
//	Stop_Current_Sound
//
///////////////////////////////////////////////////////////////////////
void
ActiveConversationClass::Stop_Current_Sound (void)
{
	//
	//	Kill the current sound we are making (speech, scream, grunt, etc)
	//
	if (CurrentSound != NULL) {
		CurrentSound->Stop ();
		CurrentSound->Release_Ref ();
		CurrentSound = NULL;
	}

	return ;
}
