2023-04-13 17:30:38 +02:00

422 lines
17 KiB
Plaintext

#using scripts\shared\math_shared;
#using scripts\shared\util_shared;
#precache( "lui_menu", "TempDialog" );
#precache( "lui_menu_data", "dialogText" );
#precache( "lui_menu_data", "title" );
// face.gsc
// Supposed to handle all facial and dialogue animations from regular scripts.
//#using_animtree ("generic_human"); - This file doesn't call animations directly.
function SayGenericDialogue(typeString)
{
if (level.disableGenericDialog)
{
return;
}
switch (typeString)
{
case "attack":
importance = 0.5;
break;
case "swing":
importance = 0.5;
typeString = "attack";
break;
case "flashbang":
importance = 0.7;
break;
case "pain_small":
importance = 0.4;
break;
case "pain_bullet":
wait(.01); //make sure dialog system has had a chance to deal with death before we play another line.
importance = 0.4;
break;
default:
/#println ("Unexpected generic dialog string: "+typeString);#/
importance = 0.3;
break;
}
SayGenericDialogueWithImportance(typeString, importance);
}
// Makes a character say the specified line in his voice, if he's not already saying something more
// important.
function SayGenericDialogueWithImportance(typeString, importance)
{
soundAlias = "dds_";
if( isdefined( self.dds_characterID ) )
{
soundAlias += self.dds_characterID;
}
else
{
/#printLn( "this AI does not have a dds_characterID" );#/
return;
}
soundAlias += "_" + typeString;
if(SoundExists(soundAlias))
{
self thread PlayFaceThread (undefined, soundAlias, importance);
}
}
function SetIdleFaceDelayed(facialAnimationArray)
{
self.a.idleFace = facialAnimationArray;
}
// Sets the facial expression to return to when not saying dialogue.
// The array is animation1, weight1, animation2, weight2, etc. The animations will play in turn - each time
// one finishes a new one will be chosen randomly based on weight.
function SetIdleFace(facialAnimationArray)
{
if (!anim.useFacialAnims)
{
return;
}
self.a.idleFace = facialAnimationArray;
self PlayIdleFace();
}
// Makes the character play the specified sound and animation. The anim and the sound are optional - you
// can just defined one if you don't have both.
// Generally, importance should be in the range of 0.6-0.8 for scripted dialogue.
// Importance is a float, from 0 to 1.
// 0.0 - Idle expressions
// 0.1-0.5 - most generic dialogue
// 0.6-0.8 - most scripted dialogue
// 0.9 - pain
// 1.0 - death
// Importance can also be one of these strings: "any", "pain" or "death", which specfies what sounds can
// interrupt this one.
function SaySpecificDialogue(facialanim, soundAlias, importance, notifyString, waitOrNot, timeToWait, toplayer)
{
///("SaySpecificDialog, facial: ",facialanim,", sound: ",soundAlias,", importance: "+importance+", notify: ",notifyString, ", WaitOrNot: ", waitOrNot, ", timeToWait: ", timeToWait);#/
self thread PlayFaceThread(facialanim, soundAlias, importance, notifyString, waitOrNot, timeToWait, toplayer);
}
// DEAD CODE REMOVAL
// Takes an array with a set of "anim" entries and a corresponding set of "weight" entries.
//ChooseAnimFromSet( animSet )
//{
// return; // Facial animations are now part of full body aniamtions
/*
if (!anim.useFacialAnims)
return;
// First, normalize the weights.
totalWeight = 0;
numAnims = animSet["anim"].size;
for ( i=0 ; i<numAnims ; i++ )
{
totalWeight += animSet["weight"][i];
}
for ( i=0 ; i<numAnims ; i++ )
{
animSet["weight"][i] = animSet["weight"][i] / totalWeight;
}
// Now choose an animation.
rand = RandomFloat(1);
runningTotal = 0;
chosenAnim = undefined;
for ( i=0 ; i<numAnims ; i++ )
{
runningTotal += animSet["weight"][i];
if (runningTotal >= rand)
{
chosenAnim = i;
break;
}
}
assert(isdefined(chosenAnim), "Logic error in ChooseAnimFromSet. Rand is " + rand + ".");
return animSet["anim"][chosenAnim];
*/
//}
//-----------------------------------------------------
// Housekeeping functions - these are for internal use
//-----------------------------------------------------
// PlayIdleFace doesn't force an idle animation to play - it will interrupt a current idle animation, but it
// won't play over a more important animation, like dialogue.
function PlayIdleFace()
{
return; // Idle facial animations are now in the full-body animations.
}
// PlayFaceThread is the workhorse of the system - it checks the importance, and if it's high enough, it
// plays the animation and/or sound specified.
// The waitOrNot parameter specifies what to do if another animation/sound is already playing.
// Options: "wait" or undefined. TimeToWait is an optional timeout time for waiting.
// Waiting faces are queued.
function PlayFaceThread( facialanim, str_script_alias, importance, notifyString, waitOrNot, timeToWait, toplayer )
{
//NOTE most of the described functionality was removed in COD2 (!)
//Talk about misleading comments...
//This must be called in it's own thread.
//
//The behavior has been simplifed greatly
// lines never wait
// a line stops the current line if at equal or higher priority
// a line doesn't play if at lower priority
//SBM 8/3/08
self endon( "death" );
if ( !isdefined( str_script_alias ) )
{
wait 1;
self notify( notifyString );
return; //no sound, don't do anything facials are now included into the main anims
}
str_notify_alias = str_script_alias; // Save this off because the script alias gets changed for females
if(!isdefined(level.NumberOfImportantPeopleTalking))level.NumberOfImportantPeopleTalking=0;
if(!isdefined(level.TalkNotifySeed))level.TalkNotifySeed=0;
if(!isdefined(notifyString))notifyString="PlayFaceThread " + str_script_alias;
if(!isdefined(self.a))self.a=SpawnStruct();
if(!isdefined(self.a.facialSoundDone))self.a.facialSoundDone=true;
if(!isdefined(self.isTalking))self.isTalking=false;
if ( self.isTalking ) //currently talking
{
//TUEY added equal to, so that the system won't spam the same notifies
if ( IsDefined( self.a.currentDialogImportance ) )
{
if ( importance < self.a.currentDialogImportance )
{
wait 1;
self notify( notifyString );
return; //skip it cause its not important
}
else if ( importance == self.a.currentDialogImportance )
{
if ( self.a.facialSoundAlias == str_script_alias )
{
return; //playing same alias twice? probably a script error somewhere
}
/# println( "WARNING: delaying alias " + self.a.facialSoundAlias + " to play " + str_script_alias ); #/
while ( self.isTalking ) //while is to fix a race condition
{
self waittill( "done speaking" ); //wait currently playing thread to finish
}
}
}
else
{
/# PrintLn("WARNING: interrupting alias "+self.a.facialSoundAlias+" to play "+str_script_alias); #/
self StopSound( self.a.facialSoundAlias );
self notify("cancel speaking");
while ( self.isTalking ) //while is to fix a race condition
{
self waittill( "done speaking" ); //wait currently playing thread to finish
}
}
}
assert( self.a.facialSoundDone );
assert( self.a.facialSoundAlias == undefined );
assert( self.a.facialSoundNotify == undefined );
assert( self.a.currentDialogImportance == undefined );
assert( !self.isTalking );
self notify( "bc_interrupt" );
self.isTalking = true;
self.a.facialSoundDone = false;
self.a.facialSoundNotify = notifyString;
self.a.facialSoundAlias = str_script_alias;
self.a.currentDialogImportance = importance;
if ( importance == 1.0 )
{
level.NumberOfImportantPeopleTalking += 1;
}
/#
if ( level.NumberOfImportantPeopleTalking > 1 )
{
println( "WARNING: multiple high priority dialogs are happening at once " + str_script_alias );
}
#/
uniqueNotify = notifyString + " " + level.TalkNotifySeed;
level.TalkNotifySeed += 1;
if ( isdefined( level.scr_sound ) && isdefined( level.scr_sound[ "generic" ] ) )
{
str_vox_file = level.scr_sound[ "generic" ][ str_script_alias ];
}
if ( isdefined( str_vox_file ) )
{
if ( SoundExists( str_vox_file ) )
{
if ( IsPlayer( toplayer ) )
{
self thread _play_sound_to_player_with_notify( str_vox_file, toplayer, uniqueNotify );
}
else
{
if ( isdefined( self GetTagOrigin( "J_Head" ) ) )
{
self PlaySoundWithNotify( str_vox_file, uniqueNotify, "J_Head" );
}
else
{
self PlaySoundWithNotify( str_vox_file, uniqueNotify );
}
}
}
else
{
/#
PrintLn( "WARNING: DIALOG VO SOUND: '" + str_script_alias + "' does not exist." );
self thread _missing_dialog( str_script_alias, str_vox_file, uniqueNotify );
#/
}
}
else
{
self thread _temp_dialog( str_script_alias, uniqueNotify );
}
self util::waittill_any( "death", "cancel speaking", uniqueNotify );
if ( importance == 1.0 )
{
level.NumberOfImportantPeopleTalking -= 1;
level.ImportantPeopleTalkingTime = GetTime();
}
if ( isdefined( self ) )
{
self.isTalking = false;
self.a.facialSoundDone = true;
self.a.facialSoundNotify = undefined;
self.a.facialSoundAlias = undefined;
self.a.currentDialogImportance = undefined;
self.lastSayTime = GetTime();
}
self notify( "done speaking", str_notify_alias );
self notify( notifyString );
}
// HACK: there is no way to get a notify from a sound on a specific client.
// Even though SoundGetPlaybackTime isn't 100% and won't be accurate with locs,
// it's our best bet for now.
function _play_sound_to_player_with_notify( soundAlias, toplayer, uniqueNotify )
{
self endon( "death" );
toplayer endon( "death" );
self PlaySoundToPlayer( soundAlias, toplayer );
n_playbackTime = SoundGetPlaybackTime( soundAlias );
if ( n_playbackTime > 0 )
{
wait n_playbackTime * .001;
}
else
{
wait 1.0;
}
self notify( uniqueNotify );
}
function private _temp_dialog( str_line, uniqueNotify, b_missing_vo = false )
{
SetDvar( "bgcache_disablewarninghints", 1 );
if ( !b_missing_vo && isdefined( self.propername ) )
{
str_line = self.propername + ": " + str_line;
}
foreach ( player in level.players )
{
if ( !isdefined( player GetLUIMenu( "TempDialog" ) ) )
{
player OpenLuiMenu( "TempDialog" );
}
player SetLuiMenuData( player GetLUIMenu( "TempDialog" ), "dialogText", str_line );
if ( b_missing_vo )
{
player SetLuiMenuData( player GetLUIMenu( "TempDialog" ), "title", "MISSING VO SOUND" );
}
else
{
player SetLuiMenuData( player GetLUIMenu( "TempDialog" ), "title", "TEMP VO" );
}
}
// wait time based on average words spoken (2 per second) and capped at 5 seconds total, -1 for propername
n_wait_time = ( StrTok( str_line, " ").size - 1 ) / 2;
n_wait_time = math::clamp( n_wait_time, 2, 5);
util::waittill_any_timeout( n_wait_time, "death", "cancel speaking" );
foreach ( player in level.players )
{
if ( isdefined( player GetLUIMenu( "TempDialog" ) ) )
{
player CloseLUIMenu( player GetLUIMenu( "TempDialog" ) );
}
}
SetDvar( "bgcache_disablewarninghints", 0 );
self notify( uniqueNotify );
}
function private _missing_dialog( str_script_alias, str_vox_file, uniqueNotify )
{
_temp_dialog( "script id: " + str_script_alias + " sound alias: " + str_vox_file, uniqueNotify, true );
}
function PlayFace_WaitForNotify(waitForString, notifyString, killmeString)
{
self endon ("death");
self endon (killmeString);
self waittill (waitForString);
self.a.faceWaitForResult = "notify";
self notify (notifyString);
}
function PlayFace_WaitForTime(time, notifyString, killmeString)
{
self endon ("death");
self endon (killmeString);
wait (time);
self.a.faceWaitForResult = "time";
self notify (notifyString);
}