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

802 lines
31 KiB
Plaintext

#using scripts\shared\ai_shared;
#using scripts\shared\animation_debug_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\flag_shared;
#using scripts\shared\flagsys_shared;
#using scripts\shared\math_shared;
#using scripts\shared\string_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#namespace animation;
function autoexec __init__sytem__() { system::register("animation",&__init__,undefined,undefined); }
function __init__()
{
if ( GetDvarString( "debug_anim_shared", "" ) == "" )
{
SetDvar( "debug_anim_shared", "" );
}
setup_notetracks();
}
/@
"Name: first_frame( <animation>, [v_origin_or_ent], [v_angles_or_tag] )"
"Summary: Puts the animating models or AI or vehicles into the first frame of the animation"
"Module: Animation"
"CallOn: The entity to animate."
"MandatoryArg: <animation> The animation to first-frame."
"OptionalArg: [v_origin_or_ent] The origin or entity to align the animation to."
"OptionalArg: [v_angles_or_tag] Option tag to align the animation to."
"Example: ai animation::first_frame( "rappel_animation" );"
@/
function first_frame( animation, v_origin_or_ent, v_angles_or_tag )
{
self thread play( animation, v_origin_or_ent, v_angles_or_tag, 0 );
}
/@
"Name: last_frame( <animation>, [v_origin_or_ent], [v_angles_or_tag] )"
"Summary: Puts the animating models or AI or vehicles into the last frame of the animation"
"Module: Animation"
"CallOn: The entity to animate."
"MandatoryArg: <animation> The animation to last-frame."
"OptionalArg: [v_origin_or_ent] The origin or entity to align the animation to."
"OptionalArg: [v_angles_or_tag] Option tag to align the animation to."
"Example: ai animation::last_frame( "rappel_animation" );"
@/
function last_frame( animation, v_origin_or_ent, v_angles_or_tag )
{
self thread play( animation, v_origin_or_ent, v_angles_or_tag, 0, 0, 0, 0, 1 );
}
/@
"Name: play( <animation>, [v_origin_or_ent], [v_angles_or_tag], [n_rate], [n_blend_in], [n_blend_out], [n_lerp] )"
"Summary: Plays an animation"
"Module: Animation"
"CallOn: The entity to animate."
"MandatoryArg: <animation> The animation to play."
"OptionalArg: [v_origin_or_ent] The origin or entity to align the animation to."
"OptionalArg: [v_angles_or_tag] Option tag to align the animation to."
"OptionalArg: [n_rate] Rate scalar for animation speed (.5 for half speed, 4 for super speed)."
"OptionalArg: [n_blend_in] Blend In Time."
"OptionalArg: [n_blend_out] Blend Out Time."
"OptionalArg: [n_lerp] Lerp position over time to smooth out animation."
"OptionalArg: [n_start_time] Time to start the animation at [0-1]."
"OptionalArg: [b_show_player_firstperson_weapon] Only applies to players. Determines if the player weapon should be visible in first person cinematic"
"OptionalArg: [b_unlink_after_completed] Unlink the entity from the linked entity when the animatio is done (default: true)"
"Example: ai animation::play( "rappel_animation" );"
"SPMP: singleplayer"
@/
function play( animation, v_origin_or_ent, v_angles_or_tag, n_rate = 1, n_blend_in = .2, n_blend_out = .2, n_lerp = 0, n_start_time = 0, b_show_player_firstperson_weapon = false, b_unlink_after_completed = true )
{
if ( SessionModeIsZombiesGame() && self IsRagdoll() )
{
return;
}
self endon( "death" );
self thread _play( animation, v_origin_or_ent, v_angles_or_tag, n_rate, n_blend_in, n_blend_out, n_lerp, n_start_time, b_show_player_firstperson_weapon, b_unlink_after_completed );
self waittill( "scriptedanim" );
}
/@
"Name: stop( [n_blend_out] )"
"Summary: stops an animation"
"Module: Animation"
"CallOn: The entity to animate."
"OptionalArg: [n_blend_out] Blend Out Time."
"Example: ai animation::stop();"
@/
function stop( n_blend = 0.2 )
{
flagsys::clear( "scriptedanim" );
self StopAnimScripted( n_blend );
}
/#
function debug_print( str_animation, str_msg )
{
str_dvar = GetDvarString( "debug_anim_shared", "" );
if ( str_dvar != "" )
{
b_print = false;
if ( StrIsNumber( str_dvar ) )
{
if ( Int( str_dvar ) > 0 )
{
b_print = true;
}
}
else if ( IsSubStr( str_animation, str_dvar )
|| ( isdefined( self.animname ) && IsSubStr( self.animname, str_dvar ) ) )
{
b_print = true;
}
if ( b_print )
{
PrintTopRightln( str_animation + " - " + string::rfill( str_msg, 10 ) + " - " + string::rfill( "" + self GetEntityNumber(), 4 ) + " [" + string::rfill( "" + GetTime(), 6 ) + "]", ( 1, 1, 0 ), -1 );
}
}
}
#/
function _play( animation, v_origin_or_ent, v_angles_or_tag, n_rate, n_blend_in, n_blend_out, n_lerp, n_start_time, b_show_player_firstperson_weapon, b_unlink_after_completed )
{
self endon( "death" );
self notify( "new_scripted_anim" );
self endon( "new_scripted_anim" );
/#
debug_print( animation, "Started" );
#/
flagsys::set_val( "firstframe", n_rate == 0 );
flagsys::set( "scripted_anim_this_frame" );
flagsys::set( "scriptedanim" );
if(!isdefined(v_origin_or_ent))v_origin_or_ent=self;
b_link = false;
// Check to see if the entity has an anim rate override set
if ( isdefined( self.n_script_anim_rate ) )
{
n_rate = self.n_script_anim_rate;
}
if ( IsVec( v_origin_or_ent ) && IsVec( v_angles_or_tag ) )
{
self AnimScripted( animation, v_origin_or_ent, v_angles_or_tag, animation, "normal", undefined, n_rate, n_blend_in, n_lerp, n_start_time, true, b_show_player_firstperson_weapon );
}
else
{
if ( IsString( v_angles_or_tag ) )
{
Assert( isdefined( v_origin_or_ent.model ), "Cannot align animation '" + animation + "' to tag '" + v_angles_or_tag + "' because the animation is not aligned to a model." );
// LinkTo was not working correctly with animation and always positioning the object as if it was linked to the tag_origin
// Moving the object to the tag position fixes this.
//self teleport( animation, v_origin_or_ent, v_angles_or_tag );
v_pos = v_origin_or_ent GetTagOrigin( v_angles_or_tag );
v_ang = v_origin_or_ent GetTagAngles( v_angles_or_tag );
if ( n_lerp > 0 )
{
prevOrigin = self.origin;
prevAngles = self.angles;
}
if ( !isdefined(v_pos) )
{
v_pos = v_origin_or_ent.origin;
v_ang = v_origin_or_ent.angles;
}
if ( IsActor( self ) )
{
self ForceTeleport( v_pos, v_ang );
}
else
{
self.origin = v_pos;
self.angles = v_ang;
}
b_link = true;
self LinkTo( v_origin_or_ent, v_angles_or_tag, ( 0, 0, 0 ), ( 0, 0, 0 ) );
if ( n_lerp > 0 )
{
if ( IsActor( self ) )
{
self ForceTeleport( prevOrigin, prevAngles );
}
else
{
self.origin = prevOrigin;
self.angles = prevAngles;
}
}
self AnimScripted( animation, v_pos, v_ang, animation, "normal", undefined, n_rate, n_blend_in, n_lerp, n_start_time, true, b_show_player_firstperson_weapon );
}
else
{
v_angles = ( isdefined( v_origin_or_ent.angles ) ? v_origin_or_ent.angles : ( 0, 0, 0 ) );
self AnimScripted( animation, v_origin_or_ent.origin, v_angles, animation, "normal", undefined, n_rate, n_blend_in, n_lerp, n_start_time, true, b_show_player_firstperson_weapon );
}
}
if ( IsPlayer( self ) )
{
set_player_clamps();
}
/#
self thread anim_info_render_thread( animation, v_origin_or_ent, v_angles_or_tag, n_rate, n_blend_in, n_blend_out, n_lerp );
#/
if ( !IsAnimLooping( animation ) && ( n_blend_out > 0 ) && ( n_rate > 0 ) && ( n_start_time < 1 ) )
{
if( !AnimHasNotetrack( animation, "start_ragdoll" ) )
{
self thread _blend_out( animation, n_blend_out, n_rate, n_start_time );
}
}
self thread handle_notetracks( animation );
if ( GetAnimFrameCount( animation ) > 1 || IsAnimLooping( animation ) )
{
self waittillmatch( animation, "end" );
}
else
{
{wait(.05);};
}
if ( b_link && ( isdefined( b_unlink_after_completed ) && b_unlink_after_completed ) )
{
self Unlink();
}
flagsys::clear( "scriptedanim" );
flagsys::clear( "firstframe" );
/#
debug_print( animation, "Ended" );
#/
waittillframeend;
flagsys::clear( "scripted_anim_this_frame" );
}
function _blend_out( animation, n_blend, n_rate, n_start_time )
{
self endon( "death" );
self endon( "end" );
self endon( "scriptedanim" );
self endon( "new_scripted_anim" );
n_server_length = Floor( GetAnimLength( animation ) / .05 ) * .05; // clamp to full server frames.
while ( true )
{
n_current_time = self GetAnimTime( animation ) * n_server_length;
n_time_left = n_server_length - n_current_time;
if ( n_time_left <= n_blend )
{
self StopAnimScripted( n_blend );
break;
}
{wait(.05);};
}
}
function _get_align_ent( e_align )
{
e = self;
if ( isdefined( e_align ) )
{
e = e_align;
}
if(!isdefined(e.angles))e.angles=( 0, 0, 0 );
return e;
}
function _get_align_pos( v_origin_or_ent, v_angles_or_tag )
{
if(!isdefined(v_origin_or_ent))v_origin_or_ent=self.origin;
if(!isdefined(v_angles_or_tag))v_angles_or_tag=(isdefined(self.angles)?self.angles:( 0, 0, 0 ));
s = SpawnStruct();
if ( IsVec( v_origin_or_ent ) )
{
Assert( IsVec( v_angles_or_tag ), "Angles must be a vector if origin is." );
s.origin = v_origin_or_ent;
s.angles = v_angles_or_tag;
}
else
{
e_align = _get_align_ent( v_origin_or_ent );
if ( IsString( v_angles_or_tag ) )
{
s.origin = e_align GetTagOrigin( v_angles_or_tag );
s.angles = e_align GetTagAngles( v_angles_or_tag );
}
else
{
s.origin = e_align.origin;
s.angles = e_align.angles;
}
}
if(!isdefined(s.angles))s.angles=( 0, 0, 0 );
return s;
}
/@
"Name: teleport( <ents>, <scene>, <str_tag>, <animname_override> )"
"Summary: Makes an AI move to the position to start an animation."
"Module: Animation"
"CallOn: The root entity that is the point of relativity for the scene, be it node, ai, vehicle, etc."
"MandatoryArg: <ents> entities that will move."
"MandatoryArg: <scene> The animation scene name."
"OptionalArg: <str_tag> The str_tag to animate relative to (must exist in the entity this function is called on)."
"OptionalArg: <animname_override> Animname to use instead of ent.animname"
"Example: node teleport( boat, "float_by_the_dock" );"
"SPMP: singleplayer"
@/
function teleport( animation, v_origin_or_ent, v_angles_or_tag, time )
{
if ( !IsDefined( time ) )
{
time = 0.0;
}
s = _get_align_pos( v_origin_or_ent, v_angles_or_tag );
v_pos = GetStartOrigin( s.origin, s.angles, animation, time );
v_ang = GetStartAngles( s.origin, s.angles, animation, time );
if ( IsActor( self ) )
{
self ForceTeleport( v_pos, v_ang );
}
else
{
self.origin = v_pos;
self.angles = v_ang;
}
}
function reach( animation, v_origin_or_ent, v_angles_or_tag, b_disable_arrivals = false )
{
self endon( "death" );
s_tracker = SpawnStruct();
self thread _reach( s_tracker, animation, v_origin_or_ent, v_angles_or_tag, b_disable_arrivals );
s_tracker waittill( "done" );
}
function _reach( s_tracker, animation, v_origin_or_ent, v_angles_or_tag, b_disable_arrivals = false )
{
self endon( "death" );
self notify( "stop_going_to_node" );
self notify( "new_anim_reach" );
flagsys::wait_till_clear( "anim_reach" ); // let any previous force goal thread cleanup first
flagsys::set( "anim_reach" );
s = _get_align_pos( v_origin_or_ent, v_angles_or_tag );
goal = GetStartOrigin( s.origin, s.angles, animation );
n_delta = DistanceSquared( goal, self.origin );
if ( n_delta > 4 * 4 )
{
self StopAnimScripted( .2 ); // Allways stop current animation to do a new reach
if ( b_disable_arrivals )
{
if ( ai::has_behavior_attribute( "disablearrivals" ) )
{
ai::set_behavior_attribute( "disablearrivals", true );
}
// SUMEET TODO - investigate deceleration
self.stopanimdistsq = 0.0001; // turns off deceleration
}
// Rogue controlled robots don't respect force_goal, handle through the rogue_control_force_goal attribute.
if ( (isdefined(self.archetype) && ( self.archetype == "robot" )) )
{
ai::set_behavior_attribute( "rogue_control_force_goal", goal );
}
else if ( ai::has_behavior_attribute( "vignette_mode" ) && !( isdefined( self.ignoreVignetteModeForAnimReach ) && self.ignoreVignetteModeForAnimReach ) )
{
ai::set_behavior_attribute( "vignette_mode", "fast" );
}
self thread ai::force_goal( goal, 15, true, undefined, false, true );
/#
self thread debug_anim_reach();
#/
self util::waittill_any( "goal", "new_anim_reach", "new_scripted_anim", "stop_scripted_anim" );
if ( ai::has_behavior_attribute( "disablearrivals" ) )
{
ai::set_behavior_attribute( "disablearrivals", false );
self.stopanimdistsq = 0; // reset to zero (turns on deceleration)
}
}
else
{
waittillframeend; // if we skip the reach, we need to wait here to let other script wait for notifies
}
if ( !(isdefined(self.archetype) && ( self.archetype == "robot" )) && ai::has_behavior_attribute( "vignette_mode" ) )
{
ai::set_behavior_attribute( "vignette_mode", "off" );
}
flagsys::clear( "anim_reach" );
s_tracker notify( "done" );
self notify( "reach_done" );
}
/#
function debug_anim_reach()
{
self endon( "death" );
self endon( "goal" );
self endon( "new_anim_reach" );
self endon( "new_scripted_anim" );
self endon( "stop_scripted_anim" );
while ( true )
{
level flagsys::wait_till( "anim_debug" );
Print3D( self.origin, "ANIM REACH", ( 1, 0, 0 ), 1, 1, 1 );
{wait(.05);};
}
}
#/
/@
"Name: set_death_anim( <animation>, [v_origin_or_ent], [v_angles_or_tag], [n_rate], [n_blend_in], [n_blend_out], [n_lerp] )"
"Summary: Plays an animation on death. Only use on AI."
"Module: Animation"
"CallOn: The entity to animate."
"MandatoryArg: <animation> The animation to play."
"OptionalArg: [v_origin_or_ent] The origin or entity to align the animation to."
"OptionalArg: [v_angles_or_tag] Option tag to align the animation to."
"OptionalArg: [n_rate] Rate scalar for animation speed (.5 for half speed, 4 for super speed)."
"OptionalArg: [n_blend_in] Blend In Time."
"OptionalArg: [n_blend_out] Blend Out Time."
"OptionalArg: [n_lerp] Lerp position over time to smooth out animation."
"Example:ai animation::set_death_anim( "death_animation" );"
"SPMP: singleplayer"
@/
function set_death_anim( animation, v_origin_or_ent, v_angles_or_tag, n_rate, n_blend_in, n_blend_out, n_lerp )
{
self notify( "new_death_anim" );
if ( isdefined( animation ) )
{
self.skipdeath = true;
self thread _do_death_anim( animation, v_origin_or_ent, v_angles_or_tag, n_rate, n_blend_in, n_blend_out, n_lerp );
}
else
{
self.skipdeath = false;
}
}
function _do_death_anim( animation, v_origin_or_ent, v_angles_or_tag, n_rate, n_blend_in, n_blend_out, n_lerp )
{
self endon( "new_death_anim" );
self waittill( "death" );
if ( isdefined( self ) && !self IsRagdoll() )
{
self play( animation, v_origin_or_ent, v_angles_or_tag, n_rate, n_blend_in, n_blend_out, n_lerp );
}
}
function set_player_clamps()
{
if ( ( isdefined( self.player_anim_look_enabled ) && self.player_anim_look_enabled ) )
{
self SetViewClamp( self.player_anim_clamp_right, self.player_anim_clamp_left, self.player_anim_clamp_top, self.player_anim_clamp_bottom );
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Notetrack Handling
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function add_notetrack_func( funcname, func )
{
if(!isdefined(level._animnotifyfuncs))level._animnotifyfuncs=[];
Assert( !isdefined( level._animnotifyfuncs[ funcname ] ), "Notetrack function already exists." );
level._animnotifyfuncs[ funcname ] = func;
}
function add_global_notetrack_handler( str_note, func, pass_notify_params, ... )
{
if(!isdefined(level._animnotetrackhandlers))level._animnotetrackhandlers=[];
if(!isdefined(level._animnotetrackhandlers[ str_note ]))level._animnotetrackhandlers[ str_note ]=[];
if ( !isdefined( level._animnotetrackhandlers[ str_note ] ) ) level._animnotetrackhandlers[ str_note ] = []; else if ( !IsArray( level._animnotetrackhandlers[ str_note ] ) ) level._animnotetrackhandlers[ str_note ] = array( level._animnotetrackhandlers[ str_note ] ); level._animnotetrackhandlers[ str_note ][level._animnotetrackhandlers[ str_note ].size]=array( func, pass_notify_params, vararg );;
}
function call_notetrack_handler( str_note, param1, param2 )
{
if ( isdefined( level._animnotetrackhandlers[ str_note ] ) )
{
foreach ( handler in level._animnotetrackhandlers[ str_note ] )
{
func = handler[0];
passNotifyParams = handler[1];
args = handler[2];
if( passNotifyParams )
{
self [[ func ]]( param1, param2 );
}
else
{
switch ( args.size )
{
case 6:
self [[ func ]]( args[0], args[1], args[2], args[3], args[4], args[5] );
break;
case 5:
self [[ func ]]( args[0], args[1], args[2], args[3], args[4] );
break;
case 4:
self [[ func ]]( args[0], args[1], args[2], args[3] );
break;
case 3:
self [[ func ]]( args[0], args[1], args[2] );
break;
case 2:
self [[ func ]]( args[0], args[1] );
break;
case 1:
self [[ func ]]( args[0] );
break;
case 0:
self [[ func ]]();
break;
default: AssertMsg( "Too many args passed to notetrack handler." );
}
}
}
}
}
function setup_notetracks()
{
add_notetrack_func( "flag::set", &flag::set );
add_notetrack_func( "flag::clear", &flag::clear );
add_notetrack_func( "util::break_glass", &util::break_glass );
clientfield::register( "scriptmover", "cracks_on", 1, GetMinBitCountForNum( 4 ), "int" );
clientfield::register( "scriptmover", "cracks_off", 1, GetMinBitCountForNum( 4 ), "int" );
add_global_notetrack_handler( "red_cracks_on", &cracks_on, false, "red" );
add_global_notetrack_handler( "green_cracks_on", &cracks_on, false, "green" );
add_global_notetrack_handler( "blue_cracks_on", &cracks_on, false, "blue" );
add_global_notetrack_handler( "all_cracks_on", &cracks_on, false, "all" );
add_global_notetrack_handler( "red_cracks_off", &cracks_off, false, "red" );
add_global_notetrack_handler( "green_cracks_off", &cracks_off, false, "green" );
add_global_notetrack_handler( "blue_cracks_off", &cracks_off, false, "blue" );
add_global_notetrack_handler( "all_cracks_off", &cracks_off, false, "all" );
add_global_notetrack_handler( "headlook_on", &enable_headlook, false, true );
add_global_notetrack_handler( "headlook_off", &enable_headlook, false, false );
add_global_notetrack_handler( "headlook_notorso_on", &enable_headlook_notorso, false, true );
add_global_notetrack_handler( "headlook_notorso_off", &enable_headlook_notorso, false, false );
add_global_notetrack_handler( "attach weapon", &attach_weapon, true );
add_global_notetrack_handler( "detach weapon", &detach_weapon, true );
add_global_notetrack_handler( "fire", &fire_weapon, false );
}
function handle_notetracks( animation )
{
self endon( "death" );
self endon( "new_scripted_anim" );
while ( true )
{
self waittill( animation, str_note, param1, param2 );
if ( isdefined( str_note ) )
{
if ( str_note != "end" && str_note != "loop_end" )
{
self thread call_notetrack_handler( str_note, param1, param2 );
}
else
{
return;
}
}
}
}
function cracks_on( str_type )
{
switch ( str_type )
{
case "red":
clientfield::set( "cracks_on", 1 );
break;
case "green":
clientfield::set( "cracks_on", 3 );
break;
case "blue":
clientfield::set( "cracks_on", 2 );
break;
case "all":
clientfield::set( "cracks_on", 4 );
break;
}
}
function cracks_off( str_type )
{
switch ( str_type )
{
case "red":
clientfield::set( "cracks_off", 1 );
break;
case "green":
clientfield::set( "cracks_off", 3 );
break;
case "blue":
clientfield::set( "cracks_off", 2 );
break;
case "all":
clientfield::set( "cracks_off", 4 );
break;
}
}
function enable_headlook( b_on = true )
{
if ( IsActor( self ) )
{
if ( b_on )
{
self LookAtEntity( level.players[0] );
}
else
{
self LookAtEntity();
}
}
}
function enable_headlook_notorso( b_on = true )
{
if ( IsActor( self ) )
{
if ( b_on )
{
self LookAtEntity( level.players[0], 1 );
}
else
{
self LookAtEntity();
}
}
}
function is_valid_weapon( weaponObject )
{
return ( isdefined( weaponObject ) && ( weaponObject != level.weaponNone ) );
}
function attach_weapon( weaponObject, tag = "tag_weapon_right" )
{
if ( IsActor( self ) )
{
if ( is_valid_weapon( weaponObject ) )
{
// tag is un-used for actors, may be we can find a use for it in future
// we assume that he will always use the right hand while animating with a weapon
ai::gun_switchto( weaponObject.name, "right" );
}
else
{
ai::gun_recall();
}
}
else
{
if ( !is_valid_weapon( weaponObject ) )
{
weaponObject = self.last_item;
}
if ( is_valid_weapon( weaponObject ) )
{
if ( self.item != level.weaponNone )
{
detach_weapon();
}
assert( isdefined( weaponObject.worldModel ) );
self Attach( weaponObject.worldModel, tag );
self SetEntityWeapon( weaponObject );
self.gun_removed = undefined;
self.last_item = weaponObject;
}
}
}
function detach_weapon( weaponObject, tag = "tag_weapon_right" )
{
if ( IsActor( self ) )
{
// tag is un-used, may be we can find a use for it in future
ai::gun_remove();
}
else
{
if ( !is_valid_weapon( weaponObject ) )
{
weaponObject = self.item;
}
if ( is_valid_weapon( weaponObject ) )
{
self Detach( weaponObject.worldModel, tag );
self SetEntityWeapon( level.weaponNone );
}
self.gun_removed = true;
}
}
function fire_weapon()
{
if( !IsAI( self ) )
{
if( self.item != level.weaponNone )
{
startPos = self GetTagOrigin( "tag_flash" );
endPos = startPos + VectorScale( AnglesToForward( self GetTagAngles( "tag_flash" ) ), 100 );
// The reason we use .item here instead of .weapon is because, weapon is not part of the
// entity fields but rather entity specific fields such as actor fields and such.
MagicBullet( self.item, startPos, endPos, self );
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// !Notetrack Handling
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////