1319 lines
33 KiB
C++
1319 lines
33 KiB
C++
|
/*
|
||
|
** 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 ;
|
||
|
}
|