#include maps\_utility; #include animscripts\Utility; #include animscripts\SetPoseMovement; #include animscripts\Combat_utility; #include animscripts\shared; #include common_scripts\Utility; #include maps\_spawner; #using_animtree( "fakeshooters" ); init() { setAnimArray(); level.drone_impact = loadfx( "impacts/fx_flesh_hit" ); level.drone_muzzleflash = loadfx( "weapon/muzzleflashes/fx_standard_flash" ); if( !IsDefined( level.traceHeight ) ) { level.traceHeight = 400; } if( !IsDefined( level.droneStepHeight ) ) { level.droneStepHeight = 100; } if( !IsDefined( level.max_drones ) ) { level.max_drones = []; } if( !IsDefined( level.max_drones["axis"] ) ) { level.max_drones["axis"] = 32; } if( !IsDefined( level.max_drones["allies"] ) ) { level.max_drones["allies"] = 32; } if ( isSplitScreen() ) { level.max_drones["axis"] = 8; level.max_drones["allies"] = 8; } if( !IsDefined( level.drones ) ) { level.drones = []; } if( !IsDefined( level.drones["axis"] ) ) { level.drones["axis"] = struct_arrayspawn(); } if( !IsDefined( level.drones["allies"] ) ) { level.drones["allies"] = struct_arrayspawn(); } array_thread( getentarray( "drone_axis", "targetname" ), ::drone_triggers_think ); array_thread( getentarray( "drone_allies", "targetname" ), ::drone_triggers_think ); flag_init("reached_drone_spawn_cap"); level.MAX_DRONES_PER_FRAME = 10; level.drone_spawned_this_frame = 0; level thread reset_drone_throttle(); } build_struct_targeted_origins() { if( !IsDefined( self.target ) ) { return; } self.targeted = getstructarray(self.target,"targetname"); } drone_triggers_think() { self endon( "death" ); if( self.targetname == "drone_allies" ) { team = "allies"; } else { team = "axis"; } self build_struct_targeted_origins(); qFakeDeath = true; if( ( IsDefined( self.script_allowdeath ) ) &&( self.script_allowdeath == 0 ) ) { qFakeDeath = false; } qSightTrace = false; if( ( IsDefined( self.script_trace ) ) &&( self.script_trace > 0 ) ) { qSightTrace = true; } assert( IsDefined( self.targeted ) ); assert( IsDefined( self.targeted[0] ) ); if( IsDefined( self.script_ender ) ) { level endon( self.script_ender ); } self waittill( "trigger" ); if( !IsDefined( self.script_repeat ) ) { repeat_times = 999999; } else { repeat_times = self.script_repeat; } if( ( ( IsDefined( self.script_noteworthy ) ) && ( self.script_noteworthy == "looping" ) ) || ( isdefined( self.script_noteworthy ) && ( IsSubStr(self.script_noteworthy, "group") ) ) || ( ( IsDefined( self.script_looping ) ) &&( self.script_looping > 0 ) ) ) { assert( IsDefined( self.script_delay ) ||( IsDefined( self.script_delay_min ) && IsDefined( self.script_delay_max ) ) ); self endon( "stop_drone_loop" ); for( i = 0; i < repeat_times; i++ ) { level notify( "new drone Spawn wave" ); spawnSize = undefined; if( IsDefined( self.script_drones_min ) ) { max = self.targeted.size; if( IsDefined( self.script_drones_max ) ) { max = self.script_drones_max; } if( self.script_drones_min == max ) { spawnSize = max; } else { spawnSize = ( self.script_drones_min + RandomInt( max - self.script_drones_min ) ); } } self thread drone_spawngroup( self.targeted, qFakeDeath, spawnSize, qSightTrace, team ); self drone_triggers_delay_first_spawn(); if( ( IsDefined( self.script_requires_player ) ) &&( self.script_requires_player > 0 ) ) { self waittill( "trigger" ); } if( !IsDefined( self.script_repeat ) ) { repeat_times = 999999; } } } else { spawnSize = undefined; if( IsDefined( self.script_drones_min ) ) { max = self.targeted.size; if( IsDefined( self.script_drones_max ) ) { max = self.script_drones_max; } if( self.script_drones_min == max ) { spawnSize = max; } else { spawnSize = ( self.script_drones_min + RandomInt( max - self.script_drones_min ) ); } } self drone_triggers_delay_first_spawn(); self thread drone_spawngroup( self.targeted, qFakeDeath, spawnSize, qSightTrace, team ); if( IsDefined( self.count ) && self.count > 1 ) { wait( 0.05 ); self.count--; self thread drone_triggers_think(); } } } drone_triggers_delay_first_spawn() { if( IsDefined( self.script_delay ) ) { if( self.script_delay > 0 ) { wait( self.script_delay ); } } else if( IsDefined( self.script_delay_min ) && IsDefined( self.script_delay_max ) ) { if( self.script_delay_max > self.script_delay_min ) { wait( RandomFloatRange( self.script_delay_min, self.script_delay_max ) ); } } } generate_offsets( spawncount ) { offsets = []; delta = 0.5 / spawncount; for( i = 0; i < spawncount; i++ ) { id = randomint( spawncount * 2 ); offsets[i] = id * delta; } return offsets; } drone_spawngroup( spawnpoint, qFakeDeath, spawnSize, qSightTrace, team ) { spawncount = spawnpoint.size; if( IsDefined( spawnSize ) ) { spawncount = spawnSize; spawnpoint = array_randomize( spawnpoint ); } if( ( spawncount > spawnpoint.size ) && ( spawnpoint.size > 1 ) ) { spawncount = spawnpoint.size; } offsets = []; if( isdefined( self.script_noteworthy ) && ( IsSubStr(self.script_noteworthy, "group") ) ) offsets = generate_offsets( spawncount ); for( i = 0; i < spawncount; i++ ) { if (IsDefined(self.script_int)) { wait RandomFloat(0.1, 1.0); } while(!self ok_to_trigger_spawn()) wait_network_frame(); if( i < spawnpoint.size ) { spawnpoint[i] thread drone_spawn( qFakeDeath, qSightTrace, team, offsets[i] ); } else { if( i > 0 && offsets[i-1] == offsets[i] ) wait( randomfloat( .8, 1.1 ) ); else wait( randomfloat( .5, .9 ) ); spawnpoint[spawnpoint.size - 1] thread drone_spawn( qFakeDeath, qSightTrace, team, offsets[i] ); } level._numTriggerSpawned ++; } } #using_animtree( "fakeshooters" ); drone_spawn( qFakeDeath, qSightTrace, team, offset,respawner ) { if(!isDefined(respawner)) { level endon( "new drone Spawn wave" ); } if(isDefined(self.script_ender)) { level endon(self.script_ender); } if( !IsDefined( qFakeDeath ) ) { qFakeDeath = false; } if( !IsDefined( qSightTrace ) ) { qSightTrace = false; } check_drone_throttle(); while( ( qSightTrace ) &&( self spawnpoint_playersView() ) ) { wait 0.2; } if( level.drones[team].lastindex > level.max_drones[team] ) { return; } if( isdefined( offset ) ) spawnoffset = offset * 2 - 1; else spawnoffset = 0; spawnpos = self.origin; if( isdefined( self.radius ) ) { angles = ( 0, 0, 0 ); if( isdefined( self.angles ) ) angles = self.angles; right = AnglesToRight( angles ); spawnpos += vector_scale( right, ( spawnoffset * self.radius ) ); } level.drone_spawned_this_frame++; guy = Spawn( "script_model", groundpos( spawnpos ) ); guy.droneRunOffset = spawnoffset; if( IsDefined( self.angles ) ) { guy.angles = self.angles; } else if( IsDefined( self.targeted ) ) { guy.angles = VectorToAngles( self.targeted[0].origin - guy.origin ); } assert( IsDefined( level.drone_spawnFunction[team] ) ); if(IsDefined(level.drone_spawnFunction_passNode)) { guy [[level.drone_spawnFunction[team]]]( self ); } else { guy [[level.drone_spawnFunction[team]]](); } if(isDefined(self.weaponinfo)) { guy.weapon = self.weaponinfo; weaponModel = GetWeaponModel( guy.weapon ); guy Attach( weaponModel, "tag_weapon_right" ); guy UseWeaponHideTags(guy.weapon); guy.bulletsInClip = WeaponClipSize( guy.weapon ); } else { guy drone_assign_weapon( team ); } guy.targetname = "drone"; guy.script_noteworthy = self.script_noteworthy; guy MakeFakeAI(); guy.team = team; guy.fakeDeath = qFakeDeath; guy drone_set_run_cycle(); if( IsDefined(level.drone_run_rate) ) { guy.droneRunRate = level.drone_run_rate; } else if( IsDefined(level.drone_run_rate_multiplier) ) { guy.droneRunRate = guy.droneRunRate * level.drone_run_rate_multiplier; } guy thread drone_think( self ); if(isDefined(self.script_string) && self.script_string == "respawn" ) { if(isDefined(self.script_ender) ) { level thread drone_respawn_after_death(guy,self,qFakeDeath, qSightTrace, team, offset,self.script_ender); } else { level thread drone_respawn_after_death(guy,self,qFakeDeath, qSightTrace, team, offset,undefined); } } } check_drone_throttle() { can_spawn = false; while(!can_spawn) { if(level.drone_spawned_this_frame > level.MAX_DRONES_PER_FRAME) { flag_set("reached_drone_spawn_cap"); } flag_waitopen("reached_drone_spawn_cap"); wait(0.05); if(level.drone_spawned_this_frame < level.MAX_DRONES_PER_FRAME) { can_spawn = true; } } } reset_drone_throttle() { while(true) { waittillframeend; flag_clear("reached_drone_spawn_cap"); level.drone_spawned_this_frame = 0; wait(0.05); } } drone_respawn_after_death(guy,start_struct,qFakeDeath, qSightTrace, team, offset,ender) { if(isDefined(ender)) { level endon(ender); } guy waittill("death"); wait(randomintrange(3,5)); if(isDefined(start_struct.script_string) && start_struct.script_string == "respawn") { start_struct thread drone_spawn(qFakeDeath, false, team, offset,1); } } spawnpoint_playersView() { if( !IsDefined( level.cos80 ) ) { level.cos80 = cos( 80 ); } players = get_players(); player_view_count = 0; success = false; for( i = 0; i < players.size; i++ ) { prof_begin( "drone_math" ); forwardvec = AnglesToForward( players[i].angles ); normalvec = VectorNormalize( self.origin - players[i] GetOrigin() ); vecdot = vectordot( forwardvec, normalvec ); prof_end( "drone_math" ); if( vecdot > level.cos80 ) { prof_begin( "drone_math" ); success = BulletTracePassed( players[i] GetEye(), self.origin +( 0, 0, 48 ), false, self ); prof_end( "drone_math" ); if( success ) { player_view_count++; } } } if( player_view_count != 0 ) { return true; } return false; } drone_assign_weapon( team ) { if( team == "allies" ) { if( IsDefined( level.drone_weaponlist_allies ) && level.drone_weaponlist_allies.size > 0 ) { if( level.drone_weaponlist_allies[0] == "unarmed" ) { self.weapon = undefined; return; } randWeapon = RandomInt( level.drone_weaponlist_allies.size ); self.weapon = level.drone_weaponlist_allies[randWeapon]; ASSERTEX( IsDefined( self.weapon ), "_drones::couldn't assign weapon from level.drone_weaponlist because the array value is undefined." ); } else { switch( level.campaign ) { case "american": self.weapon = drone_allies_assignWeapon_american(); break; case "british": self.weapon = drone_allies_assignWeapon_british(); break; case "russian": self.weapon = drone_allies_assignWeapon_russian(); break; } } } else { if( IsDefined( level.drone_weaponlist_axis ) && level.drone_weaponlist_axis.size > 0 ) { randWeapon = RandomInt( level.drone_weaponlist_axis.size ); self.weapon = level.drone_weaponlist_axis[randWeapon]; ASSERTEX( IsDefined( self.weapon ), "_drones::couldn't assign weapon from level.drone_weaponlist because the array value is undefined." ); } else { switch( level.campaign ) { case "american": self.weapon = drone_axis_assignWeapon_japanese(); break; case "british": self.weapon = drone_axis_assignWeapon_german(); break; case "russian": self.weapon = drone_axis_assignWeapon_german(); break; } } } weaponModel = GetWeaponModel( self.weapon ); self Attach( weaponModel, "tag_weapon_right" ); self UseWeaponHideTags(self.weapon); self.bulletsInClip = WeaponClipSize( self.weapon ); } drone_allies_assignWeapon_american() { array = []; array[array.size] = "m16_sp"; return array[RandomInt( array.size )]; } drone_allies_assignWeapon_british() { array = []; array[array.size] = "m16_sp"; return array[RandomInt( array.size )]; } drone_allies_assignWeapon_russian() { array = []; array[array.size] = "ak47_sp"; return array[RandomInt( array.size )]; } drone_axis_assignWeapon_german() { array = []; array[array.size] = "ak47_sp"; return array[RandomInt( array.size )]; } drone_axis_assignWeapon_japanese() { array = []; array[array.size] = "ak47_sp"; return array[RandomInt( array.size )]; } drone_setName() { wait( 0.25 ); if( !IsDefined( self ) ) { return; } if( self.team != "allies" ) { return; } if( !IsDefined( level.names ) ) { maps\_names::setup_names(); } if( IsDefined( self.script_friendname ) ) { self.name = self.script_friendname; } else { switch( level.campaign ) { case "american": self maps\_names::get_name_for_nationality( "american" ); break; case "russian": self maps\_names::get_name_for_nationality( "russian" ); break; case "british": self maps\_names::get_name_for_nationality( "british" ); break; } } assert( IsDefined( self.name ) ); subText = undefined; if( !IsDefined( self.weapon ) ) { subText = &""; } else { switch( self.weapon ) { case "commando_sp": subText = &""; break; case "m1garand": case "m1garand_wet": case "lee_enfield": case "m1carbine": case "SVT40": case "mosin_rifle": subText = ( &"WEAPON_RIFLEMAN" ); break; case "thompson": case "thompson_wet": subText = ( &"WEAPON_SUBMACHINEGUNNER" ); break; case "BAR": case "ppsh": default: subText = ( &"WEAPON_SUPPORTGUNNER" ); break; } } if( ( IsDefined( self.model ) ) &&( issubstr( self.model, "medic" ) ) ) { subText = ( &"WEAPON_MEDICPLACEHOLDER" ); } assert( IsDefined( subText ) ); self setlookattext( self.name, &""); } drone_think( firstNode ) { self endon( "death" ); self.health = 1000000; self thread drone_setName(); if(self.team == "allies") { level thread maps\_friendlyfire::friendly_fire_think( self ); } self thread drones_clear_variables(); structarray_add( level.drones[self.team], self ); level notify( "new_drone" ); if(IsDefined(level._drones_mg_target)) { self.turrettarget = Spawn( "script_origin", self.origin+( 0, 0, 50 ) ); self.turrettarget LinkTo( self ); } self endon( "drone_death" ); assert( IsDefined( firstNode ) ); if( IsDefined( level.drone_think_func ) ) { self thread [[level.drone_think_func]](); } if( ( IsDefined( self.fakeDeath ) ) &&( self.fakeDeath == true ) ) { self thread drone_fakeDeath(); } self endon( "drone_shooting" ); self.no_death_sink = false; if( IsDefined( firstNode.script_drone_no_sink ) && firstNode.script_drone_no_sink ) { self.no_death_sink = true; } self drone_runChain( firstNode ); wait( 0.05 ); level notify("drone_at_last_node", self); self.running = undefined; idle_org = self.origin; idle_ang = self.angles; self useAnimTree( #animtree ); idleAnim[0] = %stand_alert_1; idleAnim[1] = %stand_alert_2; idleAnim[2] = %stand_alert_3; while( IsDefined( self ) ) { self AnimScripted( "drone_idle_anim", idle_org, idle_ang, idleAnim[RandomInt( idleAnim.size )] ); self waittillmatch( "drone_idle_anim", "end" ); } } #using_animtree( "fakeshooters" ); drone_mortarDeath( direction ) { self useAnimTree( #animtree ); switch( direction ) { case "up": self thread drone_doDeath( %death_explosion_up10 ); break; case "forward": self thread drone_doDeath( %death_explosion_forward13 ); break; case "back": self thread drone_doDeath( %death_explosion_back13 ); break; case "left": self thread drone_doDeath( %death_explosion_left11 ); break; case "right": self thread drone_doDeath( %death_explosion_right13 ); break; } } #using_animtree( "fakeshooters" ); drone_flameDeath() { self useAnimTree( #animtree ); self thread drone_fakeDeath( true, true ); } #using_animtree( "fakeshooters" ); drone_fakeDeath( instant, flamedeath ) { if( !IsDefined( instant ) ) { instant = false; } self endon( "delete" ); self endon( "drone_death" ); explosivedeath = false; explosion_ori = ( 0, 0, 0 ); if(!IsDefined(flamedeath)) { flamedeath = false; } while( IsDefined( self ) ) { if( !instant ) { self SetCanDamage( true ); self waittill( "damage", amount, attacker, direction_vec, damage_ori, type ); if( type == "MOD_GRENADE" || type == "MOD_GRENADE_SPLASH" || type == "MOD_EXPLOSIVE" || type == "MOD_EXPLOSIVE_SPLASH" || type == "MOD_PROJECTILE" || type == "MOD_PROJECTILE_SPLASH" ) { self.damageweapon = "none"; explosivedeath = true; explosion_ori = damage_ori; } else if( type == "MOD_BURNED" ) { flamedeath = true; } self death_notify_wrapper( attacker, type ); if( self.team == "axis" && ( IsPlayer( attacker ) || attacker == level.playervehicle ) ) { level notify( "player killed drone" ); } } if( ( IsDefined( self.customFirstAnim ) ) &&( self.customFirstAnim == true ) ) { self waittill( "customFirstAnim done" ); } if( !IsDefined( self ) ) { return; } self notify( "Stop shooting" ); self.dontDelete = true; deathAnim = undefined; self useAnimTree( #animtree ); if( explosivedeath ) { direction = drone_get_explosion_death_dir( self.origin, self.angles, explosion_ori, 50 ); self thread drone_mortarDeath( direction ); return; } else if( flamedeath ) { deaths[0] = %ai_flame_death_a; deaths[1] = %ai_flame_death_b; deaths[2] = %ai_flame_death_c; deaths[3] = %ai_flame_death_d; } else if( IsDefined( self.running ) ) { deaths[0] = %death_run_stumble; deaths[1] = %death_run_onfront; deaths[2] = %death_run_onleft; deaths[3] = %death_run_forward_crumple; } else { deaths[0] = %death_stand_dropinplace; } self thread drone_doDeath( deaths[RandomInt( deaths.size )] ); return; } } #using_animtree( "fakeshooters" ); drone_delayed_bulletdeath( waitTime, deathRemoveNotify ) { self endon( "delete" ); self endon( "drone_death" ); self.dontDelete = true; if( !IsDefined( waitTime ) ) { waitTime = 0; } if( waitTime > 0 ) { wait( waitTime ); } self thread drone_fakeDeath( true ); } do_death_sound() { camp = level.campaign; team = self.team; alias = undefined; if(camp == "american" && team == "allies") alias = "dds_generic_death_american"; if(camp == "american" && team == "axis") alias = "dds_generic_death_japanese"; if(camp == "russian" && team == "allies") alias = "dds_generic_death_russian"; if(camp == "russian" && team == "axis") alias = "dds_generic_death_german"; if(camp == "vietnamese" && team == "axis") alias = "dds_generic_death_vietnamese "; if(IsDefined(alias) && SoundExists(alias) && !IsDefined(level._drones_sounds_disable)) { self thread play_sound_in_space( alias ); } } #using_animtree( "fakeshooters" ); drone_doDeath( deathAnim, deathRemoveNotify ) { self moveTo( self.origin, 0.05, 0, 0 ); traceDeath = false; if( ( IsDefined( self.running ) ) && self.running ) { traceDeath = true; } self.running = undefined; self notify( "drone_death" ); self notify( "Stop shooting" ); self Unlink(); self useAnimTree( #animtree ); self thread drone_doDeath_impacts(); do_death_sound(); prof_begin( "drone_math" ); cancelRunningDeath = false; if( traceDeath ) { offset = getcycleoriginoffset( self.angles, deathAnim ); endAnimationLocation = ( self.origin + offset ); endAnimationLocation = PhysicsTrace( ( endAnimationLocation +( 0, 0, 128 ) ), ( endAnimationLocation -( 0, 0, 128 ) ) ); d1 = abs( endAnimationLocation[2] - self.origin[2] ); if( d1 > 20 ) { cancelRunningDeath = true; } else { forwardVec = AnglesToForward( self.angles ); rightVec = AnglesToRight( self.angles ); upVec = anglestoup( self.angles ); relativeOffset = ( 50, 0, 0 ); secondPos = endAnimationLocation; secondPos += vector_scale( forwardVec, relativeOffset[0] ); secondPos += vector_scale( rightVec, relativeOffset[1] ); secondPos += vector_scale( upVec, relativeOffset[2] ); secondPos = PhysicsTrace( ( secondPos +( 0, 0, 128 ) ), ( secondPos -( 0, 0, 128 ) ) ); d2 = abs( secondPos[2] - self.origin[2] ); if( d2 > 20 ) { cancelRunningDeath = true; } } } prof_end( "drone_math" ); if( cancelRunningDeath ) { deathAnim = %death_stand_dropinplace; } self animscripted( "drone_death_anim", self.origin, self.angles, deathAnim, "deathplant" ); self thread drone_ragdoll( deathAnim ); self waittillmatch( "drone_death_anim", "end" ); if( !IsDefined( self ) ) { return; } self setcontents( 0 ); if( IsDefined( deathRemoveNotify ) ) { level waittill( deathRemoveNotify ); } else { wait 3; } if( !IsDefined( self ) ) { return; } if( !IsDefined(self.no_death_sink) || (IsDefined(self.no_death_sink) && !self.no_death_sink )) { self MoveTo( self.origin - ( 0, 0, 100 ), 7 ); wait( 3 ); } if( !IsDefined( self ) ) { return; } self.dontDelete = undefined; self thread drone_delete(); } drone_ragdoll( deathAnim ) { time = self GetAnimTime( deathAnim ); wait( time * 0.55 ); if( IsDefined( self.weapon ) ) { weaponModel = GetWeaponModel( self.weapon ); if( IsDefined( weaponModel ) ) { self detach( weaponModel, "tag_weapon_right" ); } } if( isdefined( level.no_drone_ragdoll ) && level.no_drone_ragdoll == true ) { } else { self StartRagDoll(); } } drone_doDeath_impacts() { bone[0] = "J_Knee_LE"; bone[1] = "J_Ankle_LE"; bone[2] = "J_Clavicle_LE"; bone[3] = "J_Shoulder_LE"; bone[4] = "J_Elbow_LE"; impacts = ( 1 + RandomInt( 2 ) ); for( i = 0; i < impacts; i++ ) { playfxontag( level.drone_impact, self, bone[RandomInt( bone.size )] ); if( !IsDefined(level._drones_sounds_disable)) { self PlaySound( "prj_bullet_impact_small_flesh" ); } wait( 0.05 ); } } drone_runChain( point_start ) { self endon( "drone_death" ); self endon( "drone_shooting" ); runPos = undefined; while( IsDefined( self ) ) { if( IsDefined( point_start.script_death ) ) { self.dontDelete = true; self thread drone_delayed_bulletdeath( 0 ); } else if( ( IsDefined( point_start.script_death_min ) ) &&( IsDefined( point_start.script_death_max ) ) ) { self.dontDelete = true; self thread drone_delayed_bulletdeath( point_start.script_death_min + RandomFloat( point_start.script_death_max - point_start.script_death_min ) ); } if( ( IsDefined( point_start.script_delete ) ) &&( point_start.script_delete >= 0 ) ) { self.dontDelete = undefined; self thread drone_delete( point_start.script_delete ); } if( !IsDefined( point_start.targeted ) ) { break; } point_end = point_start.targeted; if( ( !IsDefined( point_end ) ) ||( !IsDefined( point_end[0] ) ) ) { break; } index = RandomInt( point_end.size ); runPos = groundpos( point_end[index].origin ); if( IsDefined( point_end[index].radius ) ) { assert( point_end[index].radius > 0 ); if( !IsDefined( self.droneRunOffset ) ) { self.droneRunOffset = ( 0 - 1 +( RandomFloat( 2 ) ) ); } if( !IsDefined( point_end[index].angles ) ) { point_end[index].angles = ( 0, 0, 0 ); } prof_begin( "drone_math" ); forwardVec = AnglesToForward( point_end[index].angles ); rightVec = AnglesToRight( point_end[index].angles ); upVec = anglestoup( point_end[index].angles ); relativeOffset = ( 0, ( self.droneRunOffset * point_end[index].radius ) , 0 ); runPos += vector_scale( forwardVec, relativeOffset[0] ); runPos += vector_scale( rightVec, relativeOffset[1] ); runPos += vector_scale( upVec, relativeOffset[2] ); prof_end( "drone_math" ); } script_noteworthy = point_start.script_noteworthy; script_string = point_start.script_string; script_int = point_start.script_int; self ShooterRun( runPos, script_noteworthy, script_string, script_int ); point_start = point_end[index]; } if( IsDefined( runPos ) ) { if( IsDefined( point_start.script_noteworthy ) ) { self ShooterRun( runPos, point_start.script_noteworthy ); } else { self ShooterRun( runPos ); } } if( ( IsDefined( point_start.script_delete ) ) &&( point_start.script_delete >= 0 ) ) { self thread drone_delete( point_start.script_delete ); } } drones_clear_variables() { if( IsDefined( self.voice ) ) { self.voice = undefined; } } drone_delete( delayTime ) { if( ( IsDefined( delayTime ) ) &&( delayTime > 0 ) ) { wait( delayTime ); } if( !IsDefined( self ) ) { return; } self notify( "drone_death" ); self notify( "drone_idle_anim" ); if( !( is_in_array( level.drones[self.team].array, self ) ) ) { self Delete(); return; } structarray_remove( level.drones[self.team], self ); if( !IsDefined( self.dontDelete ) ) { if( IsDefined( self.turrettarget ) ) { self.turrettarget delete(); } if( IsDefined( self.shootTarget ) && !self.shootTarget is_vehicle()) { self.shootTarget delete(); } self detachall(); self delete(); } } #using_animtree( "fakeShooters" ); ShooterRun( destinationPoint, event, target_targetname, script_int ) { if( !IsDefined( self ) ) { return; } self endon("death"); self notify( "Stop shooting" ); self UseAnimTree( #animtree ); prof_begin( "drone_math" ); d = distance( self.origin, destinationPoint ); if( !IsDefined( self.droneRunRate ) ) { self.droneRunRate = 200; } speed = ( d / self.droneRunRate ); self.lowheight = false; self turnToFacePoint( destinationPoint, speed ); customFirstAnim = undefined; if( IsDefined( event ) ) { switch( event ) { case "idle_then_alert": AssertEX( IsDefined(level.droneidleanims) && level.droneidleanims.size > 0, "No drone idle anims setup for the level" ); random_index = RandomInt(level.droneidleanims.size); self ClearAnim( self.drone_run_cycle, 0 ); self SetAnim(level.droneidleanims[random_index], 1, 0.05); level waittill("alert_all_drones"); self ClearAnim( level.droneidleanims[random_index], 0); wait(RandomFloatRange(0.1, 0.5)); self.idletextprint = "idle guy got to his next node"; self.drone_run_cycle = drone_pick_run_anim(); self.running = undefined; break; case "cheer_then_run": AssertEx( IsDefined(level.droneidleanims) && level.droneidleanims.size > 0, "No drone idle anims setup for the level" ); random_index = RandomInt(level.droneidleanims.size); self ClearAnim( self.drone_run_cycle, 0); self SetAnim(level.droneidleanims[random_index], 1, 0.05); level waittill("alert_all_drones"); wait(RandomFloatRange(0.1, 10.0)); self ClearAnim( level.droneidleanims[random_index], 0); break; case "jump": customFirstAnim = %jump_across_100; break; case "jumpdown": customFirstAnim = %jump_down_56; break; case "wall_hop": customFirstAnim = %traverse_wallhop; break; case "step_up": customFirstAnim = %step_up_low_wall; break; case "trench_jump_out": customFirstAnim = %ai_mantle_on_48; break; case "low_height": self.lowheight = true; break; case "mortardeath_up": self thread drone_mortarDeath( "up" ); return; case "mortardeath_forward": self thread drone_mortarDeath( "forward" ); return; case "mortardeath_back": self thread drone_mortarDeath( "back" ); return; case "mortardeath_left": self thread drone_mortarDeath( "left" ); return; case "mortardeath_right": self thread drone_mortarDeath( "right" ); return; case "shoot": forwardVec = AnglesToForward( self.angles ); rightVec = AnglesToRight( self.angles ); upVec = anglestoup( self.angles ); relativeOffset = ( 300, 0, 64 ); shootPos = self.origin; shootPos += vector_scale( forwardVec, relativeOffset[0] ); shootPos += vector_scale( rightVec, relativeOffset[1] ); shootPos += vector_scale( upVec, relativeOffset[2] ); self.shootTarget = Spawn( "script_origin", shootPos ); self thread ShooterShoot( self.shootTarget ); return; case "shoot_then_run_after_notify": forwardVec = AnglesToForward( self.angles ); rightVec = AnglesToRight( self.angles ); upVec = anglestoup( self.angles ); relativeOffset = ( 300, 0, 64 ); shootPos = self.origin; shootPos += vector_scale( forwardVec, relativeOffset[0] ); shootPos += vector_scale( rightVec, relativeOffset[1] ); shootPos += vector_scale( upVec, relativeOffset[2] ); self.shootTarget = Spawn( "script_origin", shootPos ); self thread ShooterShoot( self.shootTarget ); self waittill ("Stop shooting"); self ClearAnim(%combat_directions, 0); self ClearAnim(%exposed_reload, 0); break; case "shoot_at_vehicle_then_move": AssertEx(IsDefined(target_targetname), "Drone shooting at vehicle, but doesn't know which one, set .script_string on node"); vehicleTarget = GetEnt(target_targetname, "targetname"); self.shootTarget = vehicleTarget; self.num_shots = script_int; self thread ShooterShoot( self.shootTarget, true ); self waittill("Stop shooting"); self ClearAnim(%combat_directions, 0); self ClearAnim(%exposed_reload, 0); break; case "cover_stand": self thread drone_cover( event ); self waittill( "drone out of cover" ); self SetFlaggedAnimKnob( "cover_exit", %coverstand_trans_OUT_M, 1, .1, 1 ); self waittillmatch( "cover_exit", "end" ); break; case "cover_crouch": self thread drone_cover( event ); self waittill( "drone out of cover" ); self SetFlaggedAnimKnob( "cover_exit", %covercrouch_run_out_M, 1, .1, 1 ); self waittillmatch( "cover_exit", "end" ); break; case "cover_crouch_fire": self thread drone_cover_fire( event ); self waittill( "drone out of cover" ); self SetFlaggedAnimKnob( "cover_exit", %covercrouch_run_out_M, 1, 0.5, 1 ); self waittillmatch( "cover_exit", "end" ); break; case "flamedeath": self thread drone_flameDeath(); break; case "run_flame": self SetCanDamage(false); self drone_set_run_cycle( %ai_flame_death_run ); self.droneRunRate = 100; self.running = false; self thread ShooterRun_doRunAnim(); randomAnimRate = undefined; d = distance( self.origin, destinationPoint ); speed = ( d / self.droneRunRate ); break; case "run_fast": self drone_set_run_cycle(); self.running = false; self thread ShooterRun_doRunAnim(); randomAnimRate = undefined; d = distance( self.origin, destinationPoint ); speed = ( d / self.droneRunRate ); break; } } minRate = 0.5; maxRate = 1.5; randomAnimRate = minRate + RandomFloat( maxRate - minRate ); if( IsDefined( customFirstAnim ) ) { self.customFirstAnim = true; self.running = undefined; randomAnimRate = undefined; angles = VectorToAngles( destinationPoint - self.origin ); offset = getcycleoriginoffset( angles, customFirstAnim ); endPos = self.origin + offset; endPos = PhysicsTrace( ( endPos +( 0, 0, 64 ) ), ( endPos -( 0, 0, level.traceHeight ) ) ); t = getanimlength( customFirstAnim ); assert( t > 0 ); self ClearAnim( self.drone_run_cycle, 0 ); self notify( "stop_run_anim" ); self moveto( endPos, t, 0, 0 ); self SetFlaggedAnimKnobRestart( "drone_custom_anim" , customFirstAnim ); self waittillmatch( "drone_custom_anim", "end" ); self.origin = endPos; self notify( "customFirstAnim done" ); d = distance( self.origin, destinationPoint ); speed = ( d / self.droneRunRate ); } self.customFirstAnim = undefined; self thread ShooterRun_doRunAnim( randomAnimRate ); self drone_runto( destinationPoint, speed ); prof_end( "drone_math" ); } drone_runto( destinationPoint, totalMoveTime ) { if( totalMoveTime < 0.1 ) { return; } percentIncrement = 0.1; percentage = 0.0; incements = ( 1 / percentIncrement ); dividedMoveTime = ( totalMoveTime * percentIncrement ); startingPos = self.origin; oldZ = startingPos[2]; for( i = 0; i < incements; i++ ) { prof_begin( "drone_math" ); percentage += percentIncrement; x = ( destinationPoint[0] - startingPos[0] ) * percentage + startingPos[0]; y = ( destinationPoint[1] - startingPos[1] ) * percentage + startingPos[1]; if( self.lowheight == true ) { percentageMark = PhysicsTrace( ( x, y, destinationPoint[2] + 64 ), ( x, y, destinationPoint[2] - level.traceHeight ) ); } else { percentageMark = PhysicsTrace( ( x, y, destinationPoint[2] + level.traceHeight ), ( x, y, destinationPoint[2] - level.traceHeight ) ); } if( ( percentageMark[2] - oldZ ) > level.droneStepHeight ) { percentageMark = ( percentageMark[0], percentageMark[1], oldZ ); } oldZ = percentageMark[2]; prof_end( "drone_math" ); self moveTo( percentageMark, dividedMoveTime, 0, 0 ); wait( dividedMoveTime ); } } ShooterShoot( target, rpg, one_clip ) { if( isDefined(rpg) && rpg == true ) { self thread ShooterShootThreadRPG( target ); } else { self thread ShooterShootThread( target, one_clip ); } } #using_animtree( "fakeShooters" ); ShooterShootThreadRPG( target ) { self endon( "death" ); self UseAnimTree( #animtree ); self.running = undefined; self thread aimAtTargetThread( target, "Stop shooting" ); shootAnimLength = 0; tag_flash = self.origin + (0,0,50); if(!IsDefined(self.num_shots)) { self.num_shots = 1; } for( i = 0; i < self.num_shots; i++ ) { if( i > 0 ) { self SetFlaggedAnimKnobAllRestart( "reloadanim", %exposed_reload, %root, 1, 0.4 ); wait( 1 + RandomFloat( 2 ) ); } self Set3FlaggedAnimKnobs( "no flag", "aim", "stand", 1, 0.3, 1 ); wait( 1 + RandomFloat( 2) ); AssertEX(IsDefined(level.drone_rpg), "No level.drone_rpg specified for drones to shoot with" ); MagicBullet( level.drone_rpg, tag_flash, target.origin, self ); wait( 1 + RandomFloat( 2) ); } self notify("Stop shooting"); } ShooterShootThread( target, one_clip ) { self notify( "Stop shooting" ); if( !isdefined(one_clip) ) { one_clip = false; } if (!IsDefined(self.script_noteworthy)) { self notify( "drone_shooting" ); } else if (IsDefined(self.script_noteworthy) && self.script_noteworthy != "run_n_gun_drones") { self notify( "drone_shooting" ); } self endon( "Stop shooting" ); self UseAnimTree( #animtree ); self.running = undefined; self thread aimAtTargetThread( target, "Stop shooting" ); shootAnimLength = 0; while( IsDefined( self ) ) { if( self.bulletsInClip <= 0 ) { weaponModel = getWeaponModel( self.weapon ); if( IsDefined( self.weaponModel ) ) { weaponModel = self.weaponModel; } numAttached = self getattachsize(); attachName = []; for( i = 0; i < numAttached; i++ ) { attachName[i] = self getattachmodelname( i ); } self SetFlaggedAnimKnobAllRestart( "reloadanim", %exposed_reload, %root, 1, 0.4 ); self.bulletsInClip = randomintrange (4, 8); self waittillmatch( "reloadanim", "end" ); if( one_clip ) { self notify( "Stop shooting" ); } } self Set3FlaggedAnimKnobs( "no flag", "aim", "stand", 1, 0.3, 1 ); wait( 1 + RandomFloat( 2 ) ); if( !IsDefined( self ) ) { return; } numShots = RandomInt( 4 )+1; if( numShots > self.bulletsInClip ) { numShots = self.bulletsInClip; } for( i = 0; i < numShots; i++ ) { if( !IsDefined( self ) ) { return; } self Set3FlaggedAnimKnobsRestart( "shootinganim", "shoot", "stand", 1, 0.05, 1 ); playfxontag( level.drone_muzzleflash, self, "tag_flash" ); if(!IsDefined(level._drones_sounds_disable)) { if( self.team == "axis" ) { switch( level.campaign ) { case "american": break; case "russian": self PlaySound( "wpn_mosin_fire" ); break; case "british": self PlaySound( "wpn_mosin_fire" ); break; } } else { switch( level.campaign ) { case "american": self PlaySound( "wpn_mosin_fire" ); break; case "russian": self PlaySound( "wpn_mosin_fire" ); break; case "british": self PlaySound( "wpn_mosin_fire" ); break; } } } self.bulletsInClip--; if( shootAnimLength == 0 ) { shootAnimLength = GetTime(); self waittillmatch( "shootinganim", "end" ); shootAnimLength = ( GetTime() - shootAnimLength ) / 1000; } else { wait( shootAnimLength - 0.1 + RandomFloat( 0.3 ) ); if( !IsDefined( self ) ) { return; } } } } } ShooterRun_doRunAnim( animRateMod ) { if( IsDefined( self.running ) && self.running ) { return; } self notify( "stop_shooterrun" ); self endon( "stop_shooterrun" ); self.running = true; if( !IsDefined( animRateMod ) ) { animRateMod = 1.0; } self endon( "stop_run_anim" ); adjustAnimRate = false; while( ( IsDefined( self.running ) ) &&( self.running == true ) ) { animRate = ( self.droneRunRate / self.drone_run_cycle_speed ); if( adjustAnimRate ) { animRate = ( animRate * animRateMod ); adjustAnimRate = false; } self SetFlaggedAnimKnobRestart( "drone_run_anim" , self.drone_run_cycle, 1, .2, animRate ); self waittillmatch( "drone_run_anim", "end" ); if( !IsDefined( self ) ) { return; } } } drone_debugLine( fromPoint, toPoint, color, durationFrames ) { } turnToFacePoint( point, speed ) { desiredAngles = VectorToAngles( point - self.origin ); if( !IsDefined( speed ) ) { speed = 0.5; } else if( speed > 0.5 ) { speed = 0.5; } if( speed < 0.1 ) { return; } self rotateTo( ( 0, desiredAngles[1], 0 ), speed, 0, 0 ); } Set3FlaggedAnimKnobs( animFlag, animArray, pose, weight, blendTime, rate ) { if( !IsDefined( self ) ) { return; } self setAnimKnob( %combat_directions, weight, blendTime, rate ); self SetFlaggedAnimKnob( animFlag, level.drone_animArray[animArray][pose]["up"], 1, blendTime, 1 ); self SetAnimKnob( level.drone_animArray[animArray][pose]["straight"], 1, blendTime, 1 ); self SetAnimKnob( level.drone_animArray[animArray][pose]["down"], 1, blendTime, 1 ); } Set3FlaggedAnimKnobsRestart( animFlag, animArray, pose, weight, blendTime, rate ) { if( !IsDefined( self ) ) { return; } self setAnimKnobRestart( %combat_directions, weight, blendTime, rate ); self SetFlaggedAnimKnobRestart( animFlag, level.drone_animArray[animArray][pose]["up"], 1, blendTime, 1 ); self SetAnimKnobRestart( level.drone_animArray[animArray][pose]["straight"], 1, blendTime, 1 ); self SetAnimKnobRestart( level.drone_animArray[animArray][pose]["down"], 1, blendTime, 1 ); } applyBlend( offset ) { if( offset < 0 ) { unstraightAnim = %combat_down; self SetAnim( %combat_up, 0.01, 0, 1 ); offset *= -1; } else { unstraightAnim = %combat_up; self SetAnim( %combat_down, 0.01, 0, 1 ); } if( offset > 1 ) { offset = 1; } unstraight = offset; if( unstraight >= 1.0 ) { unstraight = 0.99; } if( unstraight <= 0 ) { unstraight = 0.01; } straight = 1 - unstraight; self SetAnim( unstraightAnim, unstraight, 0, 1 ); self SetAnim( %combat_straight, straight, 0, 1 ); } aimAtTargetThread( target, stopString ) { self endon( stopString ); while( IsDefined( self ) ) { targetPos = target.origin; turnToFacePoint( targetPos ); offset = getTargetUpDownOffset( targetPos ); applyBlend( offset ); wait( 0.05 ); } } getTargetUpDownOffset( target ) { pos = self.origin; dir = ( target[0] - pos[0], target[1] - pos[1], target[2] - pos[2] ); dir = VectorNormalize( dir ); return dir[2]; } setAnimArray() { level.drone_animArray["aim"] ["stand"]["down"] = %stand_aim_down; level.drone_animArray["aim"] ["stand"]["straight"] = %stand_aim_straight; level.drone_animArray["aim"] ["stand"]["up"] = %stand_aim_up; level.drone_animArray["aim"] ["crouch"]["down"] = %crouch_aim_down; level.drone_animArray["aim"] ["crouch"]["straight"] = %crouch_aim_straight; level.drone_animArray["aim"] ["crouch"]["up"] = %crouch_aim_up; level.drone_animArray["auto"] ["stand"]["down"] = %stand_shoot_auto_down; level.drone_animArray["auto"] ["stand"]["straight"] = %stand_shoot_auto_straight; level.drone_animArray["auto"] ["stand"]["up"] = %stand_shoot_auto_up; level.drone_animArray["auto"] ["crouch"]["down"] = %crouch_shoot_auto_down; level.drone_animArray["auto"] ["crouch"]["straight"] = %crouch_shoot_auto_straight; level.drone_animArray["auto"] ["crouch"]["up"] = %crouch_shoot_auto_up; level.drone_animArray["shoot"] ["stand"]["down"] = %stand_shoot_down; level.drone_animArray["shoot"] ["stand"]["straight"] = %stand_shoot_straight; level.drone_animArray["shoot"] ["stand"]["up"] = %stand_shoot_up; level.drone_animArray["shoot"] ["crouch"]["down"] = %crouch_shoot_down; level.drone_animArray["shoot"] ["crouch"]["straight"] = %crouch_shoot_straight; level.drone_animArray["shoot"] ["crouch"]["up"] = %crouch_shoot_up; } drone_cover_fire( type ) { self endon( "drone_stop_cover" ); self endon( "death" ); while( true ) { drone_cover( type ); self SetAnimKnob( %stand_aim_straight, 1, 0.3, 1 ); wait(0.3); forwardVec = AnglesToForward( self.angles ); rightVec = AnglesToRight( self.angles ); upVec = anglestoup( self.angles ); relativeOffset = ( 300, 0, 0 ); shootPos = self.origin; shootPos += vector_scale( forwardVec, relativeOffset[0] ); shootPos += vector_scale( rightVec, relativeOffset[1] ); shootPos += vector_scale( upVec, relativeOffset[2] ); if(isDefined(self.shootTarget)) { self.shootTarget Delete(); } self.shootTarget = Spawn( "script_origin", shootPos ); self.bulletsInClip = randomint(4) + 3; self thread ShooterShoot( self.shootTarget, false, true ); self waittill( "Stop shooting" ); } } drone_cover( type ) { self endon( "drone_stop_cover" ); if( !IsDefined( self.a ) ) { self.a = SpawnStruct(); } self.running = undefined; self.a.array = []; if( type == "cover_stand" ) { self.a.array["hide_idle"] = %coverstand_hide_idle; self.a.array["hide_idle_twitch"] = array( %coverstand_hide_idle_twitch01, %coverstand_hide_idle_twitch02, %coverstand_hide_idle_twitch03, %coverstand_hide_idle_twitch04, %coverstand_hide_idle_twitch05 ); self.a.array["hide_idle_flinch"] = array( %coverstand_react01, %coverstand_react02, %coverstand_react03, %coverstand_react04 ); self SetFlaggedAnimKnobRestart( "cover_approach", %coverstand_trans_IN_M, 1, .3, 1 ); self waittillmatch( "cover_approach", "end" ); self thread drone_cover_think(); } else if( type == "cover_crouch" ) { self.a.array["hide_idle"] = %covercrouch_hide_idle; self.a.array["hide_idle_twitch"] = array( %covercrouch_twitch_1, %covercrouch_twitch_2, %covercrouch_twitch_3, %covercrouch_twitch_4 ); self SetFlaggedAnimKnobRestart( "cover_approach", %covercrouch_run_in_M, 1, .3, 1 ); self waittillmatch( "cover_approach", "end" ); self thread drone_cover_think(); } else if( type == "cover_crouch_fire" ) { self.a.array["hide_idle"] = %covercrouch_hide_idle; self.a.array["hide_idle_twitch"] = array( %covercrouch_twitch_1, %covercrouch_twitch_2, %covercrouch_twitch_3, %covercrouch_twitch_4 ); self SetAnimKnob( %covercrouch_hide_idle, 1, 0.4, 1 ); wait(0.4); self drone_cover_think( 1 + randomint(3) ); } } drone_cover_think( max_loops ) { self endon( "drone_stop_cover" ); if( !isdefined(max_loops) ) { max_loops = -1; } loops = 0; while( loops < max_loops || max_loops == -1 ) { useTwitch = ( RandomInt( 2 ) == 0 ); if( useTwitch ) { idleanim = animArrayPickRandom( "hide_idle_twitch" ); } else { idleanim = animarray( "hide_idle" ); } self drone_playIdleAnimation( idleAnim, useTwitch ); loops++; } } drone_playIdleAnimation( idleAnim, needsRestart ) { self endon( "drone_stop_cover" ); if( needsRestart ) { self SetFlaggedAnimKnobRestart( "idle", idleAnim, 1, .1, 1 ); } else { self SetFlaggedAnimKnob ( "idle", idleAnim, 1, .1, 1 ); } self.a.coverMode = "Hide"; self waittillmatch( "idle", "end" ); } drone_get_explosion_death_dir( self_pos, self_angle, explosion_pos, up_distance ) { if( Distance2D( self_pos, explosion_pos ) < up_distance ) { return "up"; } p1 = self_pos - VectorNormalize( AnglesToForward( self_angle ) ) * 10000; p2 = self_pos + VectorNormalize( AnglesToForward( self_angle ) ) * 10000; p_intersect = PointOnSegmentNearestToPoint( p1, p2, explosion_pos ); side_away_dist = Distance2D( p_intersect, explosion_pos ); side_close_dist = Distance2D( p_intersect, self_pos ); if( side_close_dist != 0 ) { angle = ATan( side_away_dist / side_close_dist ); dot_product = vectordot( AnglesToForward( self_angle ), VectorNormalize( explosion_pos - self_pos ) ); if( dot_product < 0 ) { angle = 180 - angle; } if( angle < 45 ) { return "back"; } else if( angle > 135 ) { return "forward"; } } self_right_angle = VectorNormalize( AnglesToRight( self_angle ) ); right_point = self_pos + self_right_angle *( up_distance * 0.5 ); if( Distance2D( right_point, explosion_pos ) < Distance2D( self_pos, explosion_pos ) ) { return "left"; } else { return "right"; } } animArray( animname ) { assert( IsDefined(self.a.array) ); return self.a.array[animname]; } animArrayAnyExist( animname ) { assert( IsDefined( self.a.array ) ); return self.a.array[animname].size > 0; } animArrayPickRandom( animname ) { assert( IsDefined( self.a.array ) ); assert( self.a.array[animname].size > 0 ); if ( self.a.array[animname].size > 1 ) { index = RandomInt( self.a.array[animname].size ); } else { index = 0; } return self.a.array[animname][index]; } #using_animtree( "fakeshooters" ); drone_pick_run_anim() { if(IsDefined(level.drone_run_cycle_override)) { if(IsArray(level.drone_run_cycle_override)) { return level.drone_run_cycle_override[RandomInt(level.drone_run_cycle_override.size)]; } else { return level.drone_run_cycle_override; } } droneRunAnims = array( %combat_run_fast_3, %run_n_gun_F, %ai_viet_run_n_gun_F, %ai_viet_run_lowready_f, %ch_khe_E1B_troopssprint_1, %ch_khe_E1B_troopssprint_2, %ch_khe_E1B_troopssprint_3, %ch_khe_E1B_troopssprint_4, %ch_khe_E1B_troopssprint_5, %ch_khe_E1B_troopssprint_6, %ch_khe_E1B_troopssprint_7 ); index = RandomInt( droneRunAnims.size ); return droneRunAnims[index]; } drone_set_run_cycle( runAnim ) { if( !IsDefined(runAnim) ) { runAnim = drone_pick_run_anim(); } self.drone_run_cycle = runAnim; self.drone_run_cycle_speed = drone_run_anim_speed( runAnim ); self.droneRunRate = self.drone_run_cycle_speed; } drone_run_anim_speed( runAnim ) { run_cycle_delta = GetMoveDelta( runAnim, 0, 1 ); run_cycle_dist = Length( run_cycle_delta ); run_cycle_length = GetAnimLength( runAnim ); run_cycle_speed = run_cycle_dist / run_cycle_length; return run_cycle_speed; }