#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( , [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: 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( , [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: 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( , [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: 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( , , , )" "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: entities that will move." "MandatoryArg: The animation scene name." "OptionalArg: The str_tag to animate relative to (must exist in the entity this function is called on)." "OptionalArg: 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( , [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: 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 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////