boiii-scripts/shared/clientfaceanim_shared.csc
2023-04-13 17:30:38 +02:00

283 lines
13 KiB
Plaintext

#using scripts\codescripts\struct;
#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
// SUMEET TODO
// A very temp facial animation system for players for April 13th Demo.
// After the demo, I will be looking at adding mode support and make it data driven.
#namespace clientfaceanim;
function autoexec __init__sytem__() { system::register("clientfaceanim_shared",undefined,&main,undefined); }
function main()
{
callback::on_spawned( &on_player_spawned );
level._clientFaceAnimOnPlayerSpawned = &on_player_spawned;
}
function private on_player_spawned( localClientNum )
{
FacialAnimationsInit( localClientNum );
self callback::on_shutdown( &on_player_shutdown );
// local player needs to handle the death as they dont get shutdown on death
self thread on_player_death( localClientNum );
}
function private on_player_shutdown( localClientNum )
{
if( self IsPlayer() )
{
self notify("stopFacialThread");
corpse = self GetPlayerCorpse();
if ( !isDefined( corpse ) )
return;
if ( ( isdefined( corpse.facialDeathAnimStarted ) && corpse.facialDeathAnimStarted ) )
return;
corpse util::waittill_dobj( localClientNum );
if ( isDefined( corpse ) )
{
corpse ApplyDeathAnim( localClientNum );
corpse.facialDeathAnimStarted = true;
}
}
}
function private on_player_death( localClientNum )
{
self endon("entityshutdown");
self waittill("death");
if( self IsPlayer() )
{
self notify("stopFacialThread");
corpse = self GetPlayerCorpse();
if ( ( isdefined( corpse.facialDeathAnimStarted ) && corpse.facialDeathAnimStarted ) )
return;
corpse util::waittill_dobj( localClientNum );
if ( isDefined( corpse ) )
{
corpse ApplyDeathAnim( localClientNum );
corpse.facialDeathAnimStarted = true;
}
}
}
function private FacialAnimationsInit( localClientNum )
{
BuildAndValidateFacialAnimationList( localClientNum );
if( self IsPlayer() )
{
self thread FacialAnimationThink( localClientNum );
}
}
function BuildAndValidateFacialAnimationList( localClientNum )
{
if( !IsDefined( level.__clientFacialAnimationsList ) )
{
level.__clientFacialAnimationsList = [];
level.__clientFacialAnimationsList["combat"] = array( "ai_face_male_generic_idle_1","ai_face_male_generic_idle_2","ai_face_male_generic_idle_3" );
level.__clientFacialAnimationsList["combat_shoot"] = array( "ai_face_male_aim_fire_1","ai_face_male_aim_fire_2","ai_face_male_aim_fire_3" );
level.__clientFacialAnimationsList["death"] = array( "ai_face_male_death_1","ai_face_male_death_2","ai_face_male_death_3" );
level.__clientFacialAnimationsList["melee"] = array( "ai_face_male_melee_1" );
level.__clientFacialAnimationsList["pain"] = array( "ai_face_male_pain_1" );
level.__clientFacialAnimationsList["swimming"] = array( "mp_face_male_swim_idle_1" );
level.__clientFacialAnimationsList["jumping"] = array( "mp_face_male_jump_idle_1" );
level.__clientFacialAnimationsList["sliding"] = array( "mp_face_male_slides_1" );
level.__clientFacialAnimationsList["sprinting"] = array( "mp_face_male_sprint_1" );
level.__clientFacialAnimationsList["wallrunning"]= array( "mp_face_male_wall_run_1" );
// validate death animations against looping flag
deathAnims = level.__clientFacialAnimationsList["death"];
foreach( deathAnim in deathAnims )
{
assert( !IsAnimLooping( localClientNum, deathAnim ), "FACIAL ANIM - Death facial animation " + deathAnim + " is set to looping in the GDT. It needs to be non-looping." );
}
}
}
function private FacialAnimationThink_getWaitTime( localClientNum )
{
if ( !isdefined( localClientNum ) )
return 1.0;
// make defines
min_wait = 0.1;
max_wait = 1.0;
min_wait_distance_sq = 50 * 50;
max_wait_distance_sq = 800 * 800;
local_player = GetLocalPlayer( localClientNum );
if ( !isdefined( local_player ) )
return max_wait;
if ( local_player == self && !IsThirdPerson( localClientNum ) )
return max_wait;
distanceSq = DistanceSquared( local_player.origin, self.origin );
if ( (distanceSq > max_wait_distance_sq) )
distance_factor = 1;
else if ( (distanceSq < min_wait_distance_sq) )
distance_factor = 0;
else
distance_factor = (distanceSq - min_wait_distance_sq) / (max_wait_distance_sq - min_wait_distance_sq);
return ((max_wait - min_wait) * distance_factor) + min_wait;
}
function private FacialAnimationThink( localClientNum )
{
self endon ("entityshutdown");
self notify("stopFacialThread");
self endon("stopFacialThread");
// Facial animation only required to be initialized for one local client.
if( IsDefined( self.__clientFacialAnimationsThinkStarted ) )
return;
self.__clientFacialAnimationsThinkStarted = true;
assert( self IsPlayer() );
self util::waittill_dobj( localClientNum );
while( isdefined( self ) )
{
UpdateFacialAnimForPlayer( localClientNum, self );
wait_time = self FacialAnimationThink_getWaitTime(localClientNum);
if(!isdefined(wait_time))wait_time=1.0;
wait wait_time;
}
}
function private UpdateFacialAnimForPlayer( localClientNum, player )
{
if ( !isdefined( player ) )
return;
if ( !isdefined( localClientNum ) )
return;
if( !IsDefined( player._currentFaceState ) )
player._currentFaceState = "inactive";
currFaceState = player._currentFaceState;
nextFaceState = player._currentFaceState;
if( player IsInScritpedAnim() )
{
ClearAllFacialAnims(localClientNum);
player._currentFaceState = "inactive";
return;
}
if( player IsPlayerDead() )
{
nextFaceState = "death";
}
else if( player IsPlayerFiring() )
{
nextFaceState = "combat_shoot";
}
else if( player IsPlayerSliding() )
{
nextFaceState = "sliding";
}
else if( player IsPlayerWallRunning() )
{
nextFaceState = "wallrunning";
}
// sprint needs to be above the jumping because jumping stays on for a while
else if( player IsPlayerSprinting() )
{
nextFaceState = "sprinting";
}
else if( player IsPlayerJumping() || player IsPlayerDoubleJumping() )
{
nextFaceState = "jumping";
}
else if( player IsPlayerSwimming() )
{
nextFaceState = "swimming";
}
else
{
nextFaceState = "combat";
}
if( player._currentFaceState == "inactive" || currFaceState != nextFaceState )
{
Assert( IsDefined( level.__clientFacialAnimationsList[nextFaceState] ) );
ApplyNewFaceAnim( localClientNum, array::random( level.__clientFacialAnimationsList[nextFaceState] ) );
player._currentFaceState = nextFaceState;
}
}
function private ApplyNewFaceAnim( localClientNum, animation )
{
ClearAllFacialAnims(localClientNum);
if( IsDefined( animation ) )
{
self._currentFaceAnim = animation;
self SetFlaggedAnimKnob( "ai_secondary_facial_anim", animation, 1.0, 0.1, 1.0 );
}
}
function private ApplyDeathAnim( localClientNum )
{
if( IsDefined( self._currentFaceState ) && self._currentFaceState == "death" )
return;
if( IsDefined( self ) && IsDefined( level.__clientFacialAnimationsList ) && IsDefined( level.__clientFacialAnimationsList["death"] ) )
{
self._currentFaceState = "death";
ApplyNewFaceAnim( localClientNum, array::random( level.__clientFacialAnimationsList["death"] ) );
}
}
function private ClearAllFacialAnims(localClientNum)
{
if( IsDefined( self._currentFaceAnim ) && self hasdobj(localClientNum) )
{
self ClearAnim( self._currentFaceAnim, 0.2 );
}
self._currentFaceAnim = undefined;
}