#using scripts\codescripts\struct; #using scripts\shared\math_shared; #using scripts\shared\statemachine_shared; #using scripts\shared\system_shared; #using scripts\shared\array_shared; #using scripts\shared\util_shared; #using scripts\shared\vehicle_shared; #using scripts\shared\vehicle_death_shared; #using scripts\shared\vehicle_ai_shared; #using scripts\shared\audio_shared; #using scripts\shared\clientfield_shared; // Distance at which raps repath range is doubled and they check for others claimed locations. Needs to match. #namespace raps; #using_animtree( "generic" ); function autoexec __init__sytem__() { system::register("raps",&__init__,undefined,undefined); } function __init__() { clientfield::register( "vehicle", "raps_side_deathfx", 1, 1, "int" ); vehicle::add_main_callback( "raps", &raps_initialize ); slow_triggers = GetEntArray( "raps_slow", "targetname" ); array::thread_all( slow_triggers, &slow_raps_trigger ); } function raps_initialize() { self.fovcosine = 0; self.fovcosinebusy = 0; self.delete_on_death = true; self.health = self.healthdefault; self.last_jump_chance_time = 0; self UseAnimTree( #animtree ); self vehicle::friendly_fire_shield(); assert( isdefined( self.scriptbundlesettings ) ); self.settings = struct::get_script_bundle( "vehiclecustomsettings", self.scriptbundlesettings ); if( self.settings.aim_assist ) { self EnableAimAssist(); } self SetNearGoalNotifyDist( self.settings.near_goal_notify_dist ); self.goalRadius = 999999; self.goalHeight = 999999; self SetGoal( self.origin, false, self.goalRadius, self.goalHeight ); self.overrideVehicleDamage = &raps_callback_damage; self.allowFriendlyFireDamageOverride = &raps_AllowFriendlyFireDamage; self.do_death_fx = &do_death_fx; self thread vehicle_ai::nudge_collision(); self thread sndFunctions(); // self thread play_rumble_on_raps(); //disable some cybercom abilities if( IsDefined( level.vehicle_initializer_cb ) ) { [[level.vehicle_initializer_cb]]( self ); } defaultRole(); } function defaultRole() { self vehicle_ai::init_state_machine_for_role( "default" ); self vehicle_ai::get_state_callbacks( "combat" ).update_func = &state_combat_update; self vehicle_ai::get_state_callbacks( "driving" ).update_func = &state_scripted_update; self vehicle_ai::get_state_callbacks( "death" ).update_func = &state_death_update; self vehicle_ai::get_state_callbacks( "emped" ).update_func = &state_emped_update; self vehicle_ai::call_custom_add_state_callbacks(); vehicle_ai::StartInitialState( "combat" ); } function state_scripted_update( params ) { self endon( "change_state" ); self endon("death"); driver = self GetSeatOccupant( 0 ); if( isPlayer( driver ) ) { driver endon( "disconnect" ); driver util::waittill_attack_button_pressed(); self Kill( self.origin, driver ); } } // ---------------------------------------------- // State: death // ---------------------------------------------- function state_death_update( params ) { self endon ( "death" ); attacker = params.inflictor; if( !isdefined( attacker ) ) { attacker = params.attacker; } if( attacker !== self && ( !isdefined( self.owner ) || ( self.owner !== attacker ) ) && ( IsAI( attacker) || IsPlayer( attacker ) ) ) { self.damage_on_death = false; {wait(.05);}; // need to retest for attacker validity because of the wait attacker = params.inflictor; if( !isdefined( attacker ) ) { attacker = params.attacker; } if ( isdefined( attacker ) && !isdefined( self.detonate_sides_disabled ) ) { self detonate_sides( attacker ); } else { self.damage_on_death = true; } } self vehicle_ai::defaultstate_death_update(); } // ---------------------------------------------- // State: emped // ---------------------------------------------- function state_emped_update( params ) { self endon ( "death" ); self endon ( "change_state" ); if ( self.serverShortout === true ) { forward = VectorNormalize( ( ( self GetVelocity() )[0], ( self GetVelocity() )[1], 0 ) ); side = VectorCross( forward, (0,0,1) ) * math::randomsign(); self SetVehGoalPos( self.origin + side * 500 + forward * randomFloat(400), false, false ); wait 0.6; self ClearVehGoalPos(); self util::waittill_any_timeout( 1.5, "veh_collision", "change_state", "death" ); self Kill( self.origin, self.abnormal_status.attacker, self.abnormal_status.inflictor, GetWeapon( "emp" ) ); } else { vehicle_ai::defaultstate_emped_update( params ); } } // ---------------------------------------------- // State: combat // ---------------------------------------------- function state_combat_update( params ) { self endon( "change_state" ); self endon( "death" ); pathfailcount = 0; foundpath = true; self thread prevent_stuck(); self thread detonation_monitor(); self thread nudge_collision(); for( ;; ) { if ( ( isdefined( self.inpain ) && self.inpain ) ) { wait 0.1; } else if ( !IsDefined( self.enemy ) || ( isdefined( self.raps_force_patrol_behavior ) && self.raps_force_patrol_behavior ) ) { if( isdefined( self.settings.all_knowing ) ) { self force_get_enemies(); } // go slower when you don't have an enemy, patrolling self SetSpeed( self.settings.defaultMoveSpeed * 0.35 ); PixBeginEvent( "_raps::state_combat_update 1" ); queryResult = PositionQuery_Source_Navigation( self.origin, 0, self.settings.max_move_dist * 3, self.settings.max_move_dist * 3, self.radius * 2, self, self.radius * 4 ); PixEndEvent(); PositionQuery_Filter_InClaimedLocation( queryResult, self ); PositionQuery_Filter_DistanceToGoal( queryResult, self ); vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult ); // This already adds score best_point = undefined; best_score = -999999; foreach ( point in queryResult.data ) { // distance from origin /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = MapFloat( 0, 200, 0, 100, point.distToOrigin2D ); #/ point.score += MapFloat( 0, 200, 0, 100, point.distToOrigin2D );; if( point.inClaimedLocation ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "inClaimedLocation" ] = -500; #/ point.score += -500;; } /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "random" ] = randomFloatRange( 0, 50 ); #/ point.score += randomFloatRange( 0, 50 );; // Wander in a somewhat continuous direction if( isdefined( self.prevMoveDir ) ) { moveDir = VectorNormalize( point.origin - self.origin ); if( VectorDot( moveDir, self.prevMoveDir ) > 0.64 ) // cos 50' { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "currentMoveDir" ] = randomFloatRange( 50, 150 ); #/ point.score += randomFloatRange( 50, 150 );; } } if ( point.score > best_score ) { best_score = point.score; best_point = point; } } self vehicle_ai::PositionQuery_DebugScores( queryResult ); foundpath = false; if( isdefined( best_point ) ) { foundpath = self SetVehGoalPos( best_point.origin, false, true ); } else { wait 1; } if ( foundpath ) { self.prevMoveDir = VectorNormalize( best_point.origin - self.origin ); self.current_pathto_pos = undefined; self thread path_update_interrupt(); pathfailcount = 0; self vehicle_ai::waittill_pathing_done(); } else { // avoid infinite loop when failinig to find path wait 1; } } else if ( !self.enemy.allowdeath && self GetPersonalThreatBias( self.enemy ) == 0 ) // avoid magic bullet shielded enemy { self SetPersonalThreatBias( self.enemy, -2000, 30.0 ); {wait(.05);}; } else { foundpath = false; targetPos = raps_get_target_position(); if ( isdefined( targetPos ) ) { // Prevent training by not sending every raps to the same location unless they are getting close if( DistanceSquared( self.origin, targetPos ) > ( (400) * (400) ) && self IsPosInClaimedLocation( targetPos ) ) { PixBeginEvent( "_raps::state_combat_update 2" ); queryResult = PositionQuery_Source_Navigation( targetPos, 0, self.settings.max_move_dist, self.settings.max_move_dist, self.radius, self ); PixEndEvent(); PositionQuery_Filter_InClaimedLocation( queryResult, self.enemy ); best_point = undefined; best_score = -999999; foreach ( point in queryResult.data ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "distToOrigin" ] = MapFloat( 0, 200, 0, -200, Distance( point.origin, queryResult.origin ) ); #/ point.score += MapFloat( 0, 200, 0, -200, Distance( point.origin, queryResult.origin ) );; /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "heightToOrigin" ] = MapFloat( 50, 200, 0, -200, Abs( point.origin[2] - queryResult.origin[2] ) ); #/ point.score += MapFloat( 50, 200, 0, -200, Abs( point.origin[2] - queryResult.origin[2] ) );; if( point.inClaimedLocation === true ) { /# if ( !isdefined( point._scoreDebug ) ) { point._scoreDebug = []; } point._scoreDebug[ "inClaimedLocation" ] = -500; #/ point.score += -500;; } if ( point.score > best_score ) { best_score = point.score; best_point = point; } } self vehicle_ai::PositionQuery_DebugScores( queryResult ); if( isdefined( best_point ) ) { targetPos = best_point.origin; } } foundpath = self SetVehGoalPos( targetPos, false, true ); if ( self.test_failed_path === true ) { foundpath = vehicle_ai::waittill_pathresult( 0.5 ); } if ( foundpath ) { self.current_pathto_pos = targetPos; self thread path_update_interrupt(); pathfailcount = 0; self vehicle_ai::waittill_pathing_done(); } else { {wait(.05);}; } } if ( !foundpath ) { pathfailcount++; if ( pathfailcount > 2 ) { /# // recordLine( self.origin, self.origin + (0,0,3000), (0.3,1,0) ); // RecordSphere( self.origin, 30, (1,1,0) ); #/ // Try to change enemies if( IsDefined( self.enemy ) ) { self SetPersonalThreatBias( self.enemy, -2000, 5.0 ); } if ( pathfailcount > self.settings.max_path_fail_count ) { detonate(); } } wait .2; // just try to path strait to a nearby position on the path PixBeginEvent( "_raps::state_combat_update 3" ); queryResult = PositionQuery_Source_Navigation( self.origin, 0, self.settings.max_move_dist, self.settings.max_move_dist, self.radius, self ); PixBeginEvent( "_raps::state_combat_update 3" ); if( queryResult.data.size ) { point = queryResult.data[ randomint( queryResult.data.size ) ]; self SetVehGoalPos( point.origin, false, false ); self.current_pathto_pos = undefined; self thread path_update_interrupt(); // keep trying to detonate wait 2; self notify( "near_goal" ); // kill the path_update_interrupt just in case } } wait 0.2; } } } function prevent_stuck() { self endon( "change_state" ); self endon( "death" ); self notify( "end_prevent_stuck" ); self endon( "end_prevent_stuck" ); wait 2; count = 0; previous_origin = undefined; // detonate if position hasn't change for N counts while( true ) { if ( isdefined( previous_origin ) && DistanceSquared( previous_origin, self.origin ) < ( (0.1) * (0.1) ) && !( isdefined( level.bzm_worldPaused ) && level.bzm_worldPaused ) ) { count++; } else { previous_origin = self.origin; count = 0; } if ( count > 10 ) { detonate(); } wait 1; } } function check_detonation_dist( origin, enemy ) { if ( isdefined( enemy ) && IsAlive( enemy ) ) { // try to detonate when more in front of the enemy, frustrating to get killed from behind enemy_look_dir_offst = AnglesToForward( enemy.angles ) * 30; targetPoint = enemy.origin + enemy_look_dir_offst; if( distance2DSquared( targetPoint, origin ) < ( (self.settings.detonation_distance) * (self.settings.detonation_distance) ) && ( Abs( targetPoint[2] - origin[2] ) < self.settings.detonation_distance || Abs( targetPoint[2] - 20 - origin[2] ) < self.settings.detonation_distance ) ) { return true; } } return false; } function jump_detonate() { if( isdefined( self.sndAlias["jump_up"] ) ) self PlaySound( self.sndAlias["jump_up"] ); self LaunchVehicle( (0,0,1) * self.jumpforce, (0,0,0), true ); self.is_jumping = true; // Spin the Raps too /* if( math::cointoss() ) { side_dir = (0,-1,0); } else { side_dir = (0,1,0); } self LaunchVehicle( side_dir * 5, (400,0,0), true, 1 ); */ wait 0.4; time_to_land = 0.6; while( time_to_land > 0 ) { if( check_detonation_dist( self.origin, self.enemy ) ) { self detonate(); } wait 0.05; time_to_land -= 0.05; } if( IsAlive( self ) ) { self.is_jumping = false; trace = PhysicsTrace( self.origin + ( 0, 0, self.radius * 2 ), self.origin - ( 0, 0, 1000 ), ( -10, -10, -10 ), ( 10, 10, 10 ), self, (1 << 1) ); willFall = true; if ( trace[ "fraction" ] < 1 ) { pos = trace[ "position" ]; pos_on_navmesh = GetClosestPointOnNavMesh( pos, 100, self.radius, 0x00ffffff & ~0x00000020 ); if ( isdefined( pos_on_navmesh ) ) { willFall = false; } } if ( willFall ) { self detonate(); } } // re-enable stabilizers //self LaunchVehicle( (0,0,1), (0,0,0), true, 2 ); } function detonate( attacker ) { if( !isdefined( attacker ) ) attacker = self; self stopsounds(); self DoDamage( self.health + 1000, self.origin, attacker, self, "none", "MOD_EXPLOSIVE", 0, self.turretWeapon ); } function detonate_damage_monitored( enemy, weapon ) { self.selfDestruct = true; self DoDamage( 1000, self.origin, enemy, self, "none", "MOD_EXPLOSIVE", 0, self.turretWeapon ); } function detonation_monitor() { self endon( "death" ); self endon( "change_state" ); lastEnemy = undefined; while( 1 ) { try_detonate(); wait 0.2; // targeting audio if( isdefined( self.enemy ) && IsPlayer( self.enemy ) ) { // enemy change if( lastEnemy !== self.enemy ) { lastDistToEnemySquared = 10000.0 * 10000.0; lastEnemy = self.enemy; } if( !IsDefined( self.looping_targeting_sound ) ) { if( isdefined( self.sndAlias["vehRapsAlarm"] ) ) { self.looping_targeting_sound = spawn( "script_origin", self.origin ); self.looping_targeting_sound linkto(self); //set the client mask on the looping targeting sound to play only on the enemy self.looping_targeting_sound SetInvisibleToAll(); self.looping_targeting_sound SetVisibleToPlayer( self.enemy ); self.looping_targeting_sound playloopsound (self.sndAlias["vehRapsAlarm"]); self.looping_targeting_sound thread raps_audio_cleanup( self ); } } distToEnemySquared = distanceSquared( self.origin, self.enemy.origin ); if( distToEnemySquared < ( (250) * (250) ) ) { if( lastDistToEnemySquared > ( (250) * (250) ) && !( isdefined( self.serverShortout ) && self.serverShortout ) && isdefined( self.sndAlias["vehRapsClose250"] ) ) self PlaySoundToPlayer( self.sndAlias["vehRapsClose250"], self.enemy ); } else if( distToEnemySquared < ( (750) * (750) ) ) { if( lastDistToEnemySquared > ( (750) * (750) ) && !( isdefined( self.serverShortout ) && self.serverShortout ) && isdefined( self.sndAlias["vehRapsTargeting"] ) ) self PlaySoundToPlayer( self.sndAlias["vehRapsTargeting"], self.enemy ); } else if( distToEnemySquared < ( (1500) * (1500) ) ) { if( lastDistToEnemySquared > ( (1500) * (1500) ) && !( isdefined( self.serverShortout ) && self.serverShortout ) && isdefined( self.sndAlias["vehRapsClose1500"] ) ) self PlaySoundToPlayer( self.sndAlias["vehRapsClose1500"], self.enemy ); } if( distToEnemySquared < lastDistToEnemySquared ) { lastDistToEnemySquared = distToEnemySquared; } // let it slowly grow so we can play the sounds again if the player gets away lastDistToEnemySquared += ( (50 * 0.2) * (50 * 0.2) ); } } } function raps_audio_cleanup( owner ) { owner waittill( "death" ); if ( isdefined( owner ) ) { owner stopsounds(); } if ( isdefined( self ) ) { self StopLoopSound(); self Delete(); } } function try_detonate() { if( ( isdefined( self.disableAutoDetonation ) && self.disableAutoDetonation ) ) return; jump_time = 0.5; cur_time = GetTime(); can_jump = (cur_time - self.last_jump_chance_time > 1500); if( can_jump ) { predicted_origin = self.origin + self GetVelocity() * jump_time; } if( isdefined( predicted_origin ) && check_detonation_dist( predicted_origin, self.enemy ) ) { trace = BulletTrace( predicted_origin + (0,0,self.radius), self.enemy.origin + (0,0,self.radius), true, self ); if ( trace["fraction"] === 1.0 || isdefined( trace["entity"] ) ) { self.last_jump_chance_time = cur_time; jump_chance = self.settings.jump_chance; if ( self.enemy.origin[2] - 20 > predicted_origin[2] ) { jump_chance = self.settings.jump_chance * 2; } if( RandomFloat( 1 ) < jump_chance ) { self jump_detonate(); } } } else if( check_detonation_dist( self.origin, self.enemy ) ) { trace = BulletTrace( self.origin + (0,0,self.radius), self.enemy.origin + (0,0,self.radius), true, self ); if ( trace["fraction"] === 1.0 || isdefined( trace["entity"] ) ) { self detonate(); } } // just blow up if close to other enemies if( isdefined( self.owner ) ) { foreach( player in level.players ) { if( self.owner util::IsEnemyPlayer( player ) && ( !isdefined( self.enemy ) || player != self.enemy ) ) { if( player IsNoTarget() || !IsAlive( player ) ) { continue; } if ( player.ignoreme === true ) { continue; } if( !SessionModeIsCampaignGame() && !SessionModeIsZombiesGame() && player hasPerk( "specialty_nottargetedbyraps" ) ) { continue; } if( distanceSquared( player.origin, self.origin ) < ( (self.settings.detonation_distance) * (self.settings.detonation_distance) ) ) { trace = BulletTrace( self.origin + (0,0,self.radius), player.origin + (0,0,self.radius), true, self ); if ( trace["fraction"] === 1.0 || isdefined( trace["entity"] ) ) { self raps::detonate(); } } } } } } function raps_get_target_position() { if( isdefined( self.settings.all_knowing ) ) { if( isdefined( self.enemy ) ) { target_pos = self.enemy.origin; } } else { target_pos = vehicle_ai::GetTargetPos( vehicle_ai::GetEnemyTarget() ); } enemy = self.enemy; if( isdefined( target_pos ) ) { target_pos_onnavmesh = GetClosestPointOnNavMesh( target_pos, self.settings.detonation_distance * 1.5, self.radius, 0x00ffffff & ~0x00000020 ); } // if we can't find a position on the navmesh then just keep going to the current position if( !isdefined( target_pos_onnavmesh ) ) { if( isdefined( self.enemy ) ) { self SetPersonalThreatBias( self.enemy, -2000, 5.0 ); } if ( isdefined( self.current_pathto_pos ) ) { target_pos_onnavmesh = GetClosestPointOnNavMesh( self.current_pathto_pos, self.settings.detonation_distance * 2, self.settings.detonation_distance * 1.5, 0x00ffffff & ~0x00000020 ); } if ( isdefined( target_pos_onnavmesh ) ) { return target_pos_onnavmesh; } else { return self.current_pathto_pos; } } else if ( isdefined( self.enemy ) ) { if ( DistanceSquared( target_pos, target_pos_onnavmesh ) > ( (self.settings.detonation_distance * 0.9) * (self.settings.detonation_distance * 0.9) ) ) { self SetPersonalThreatBias( self.enemy, -2000, 5.0 ); } } if( isdefined( enemy ) && IsPlayer( enemy ) ) { enemy_vel_offset = enemy GetVelocity() * 0.5; enemy_look_dir_offset = AnglesToForward( enemy.angles ); if( distance2dSquared( self.origin, enemy.origin ) > ( (500) * (500) ) ) { enemy_look_dir_offset *= 110; } else { enemy_look_dir_offset *= 35; } offset = enemy_vel_offset + enemy_look_dir_offset; offset = ( offset[0], offset[1], 0 ); // just 2d if( TracePassedOnNavMesh( target_pos_onnavmesh, target_pos + offset ) ) { target_pos += offset; } else { target_pos = target_pos_onnavmesh; } } else { target_pos = target_pos_onnavmesh; } return target_pos; } function path_update_interrupt() { self endon( "death" ); self endon( "change_state" ); self endon( "near_goal" ); self endon( "reached_end_node" ); //ensure only one path_update_interrupt is running self notify( "clear_interrupt_threads" ); self endon( "clear_interrupt_threads" ); wait .1; // sometimes endons may get fired off so wait a bit for the goal to get updated while( 1 ) { if( isdefined( self.current_pathto_pos ) ) { if( distance2dSquared( self.current_pathto_pos, self.goalpos ) > ( (self.goalRadius) * (self.goalRadius) ) ) { wait 0.5; self notify( "near_goal" ); } targetPos = raps_get_target_position(); if ( isdefined( targetPos ) ) { // optimization, don't keep repathing as often when far away if( DistanceSquared( self.origin, targetPos ) > ( (400) * (400) ) ) { repath_range = self.settings.repath_range * 2; wait 0.1; } else { repath_range = self.settings.repath_range; } if( distance2dSquared( self.current_pathto_pos, targetPos ) > ( (repath_range) * (repath_range) ) ) { if( isdefined( self.sndAlias ) && isdefined( self.sndAlias["direction"] ) ) { self playsound( self.sndAlias["direction"] ); } self notify( "near_goal" ); } } if( isdefined( self.enemy ) && IsPlayer( self.enemy ) && !isdefined(self.slow_trigger) ) { forward = AnglesToForward( self.enemy GetPlayerAngles() ); dir_to_raps = self.origin - self.enemy.origin; speedToUse = self.settings.defaultMoveSpeed; if( IsDefined( self._override_raps_combat_speed ) ) { speedToUse = self._override_raps_combat_speed; } if( VectorDot( forward, dir_to_raps ) > 0 ) { self SetSpeed( speedToUse ); } else { self SetSpeed( speedToUse * 0.75 ); } } else { speedToUse = self.settings.defaultMoveSpeed; if( IsDefined( self._override_raps_combat_speed ) ) { speedToUse = self._override_raps_combat_speed; } self SetSpeed( speedToUse ); } wait 0.2; } else { wait 0.4; } } } function collision_fx( normal ) { tilted = ( normal[2] < 0.6 ); fx_origin = self.origin - normal * ( tilted ? 28 : 10 ); if( isdefined( self.sndAlias["vehRapsCollision"] ) ) self PlaySound( self.sndAlias["vehRapsCollision"] ); //PlayFX( level._effect[ "drone_nudge" ], fx_origin, normal ); } function nudge_collision() { self endon( "death" ); self endon( "change_state" ); self notify( "end_nudge_collision" ); self endon( "end_nudge_collision" ); while ( 1 ) { self waittill( "veh_collision", velocity, normal ); ang_vel = self GetAngularVelocity() * 0.8; self SetAngularVelocity( ang_vel ); // bounce off walls if ( IsAlive( self ) && VectorDot( normal, (0,0,1) ) < 0.5 ) // angle is more than 60 degree away from up direction { self SetVehVelocity( self.velocity + normal * 400 ); self collision_fx( normal ); } } } function raps_AllowFriendlyFireDamage( eInflictor, eAttacker, sMeansOfDeath, weapon ) { if( isdefined( self.owner ) && ( eAttacker == self.owner ) && isdefined( self.settings.friendly_fire ) && int( self.settings.friendly_fire ) && !weapon.isEmp ) { return true; } if ( isdefined( eAttacker ) && isdefined( eAttacker.archetype ) && isdefined( sMeansOfDeath ) && eAttacker.archetype == "raps" && sMeansOfDeath == "MOD_EXPLOSIVE" ) { return true; } return false; } function detonate_sides(eInflictor) { forward_direction = AnglesToForward( self.angles ); up_direction = AnglesToUp( self.angles ); origin = self.origin + VectorScale(up_direction, 15); right_direction = VectorCross(forward_direction, up_direction); right_direction = VectorNormalize(right_direction); left_direction = VectorScale(right_direction, -1); eInflictor CylinderDamage(VectorScale(right_direction, 140), origin, 15, 50, self.radiusdamagemax, self.radiusdamagemax / 5, self, "MOD_EXPLOSIVE", self.turretWeapon); eInflictor CylinderDamage(VectorScale(left_direction, 140), origin, 15, 50, self.radiusdamagemax, self.radiusdamagemax / 5, self, "MOD_EXPLOSIVE", self.turretWeapon); self.bSideDetonation = true; } function raps_callback_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) { if ( self.drop_deploying === true && sMeansOfDeath == "MOD_TRIGGER_HURT" && ( !isdefined( self.hurt_trigger_immune_end_time ) || ( GetTime() < self.hurt_trigger_immune_end_time ) ) ) return 0; if ( isdefined( eAttacker ) && isdefined( eAttacker.archetype ) && isdefined( sMeansOfDeath ) && eAttacker.archetype == "raps" && sMeansOfDeath == "MOD_EXPLOSIVE" ) { if ( eAttacker != self && isdefined( vDir ) && lengthSquared( vDir ) > 0.1 && ( !isdefined( eAttacker ) || eAttacker.team === self.team ) && ( !isdefined( eInflictor ) || eInflictor.team === self.team ) ) { self SetVehVelocity( self.velocity + VectorNormalize( vDir ) * 300 ); return 1; } } if ( vehicle_ai::should_emp( self, weapon, sMeansOfDeath, eInflictor, eAttacker ) ) { minEmpDownTime = 0.8 * self.settings.empdowntime; maxEmpDownTime = 1.2 * self.settings.empdowntime; self notify ( "emped", RandomFloatRange( minEmpDownTime, maxEmpDownTime ), eAttacker, eInflictor ); } if ( vehicle_ai::should_burn( self, weapon, sMeansOfDeath, eInflictor, eAttacker ) ) { self thread vehicle_ai::burning_thread( eAttacker, eInflictor ); } return iDamage; } function slow_raps_trigger() { self endon( "death" ); while( 1 ) { self waittill( "trigger", other ); if( IsVehicle( other ) && IsDefined( other.archetype ) && other.archetype == "raps" ) { other thread slow_raps( self ); } wait(0.1); } } function slow_raps( trigger ) { self notify( "slow_raps" ); self endon( "slow_raps" ); self endon( "death" ); self.slow_trigger = true; if( IsDefined( trigger.script_int ) ) { if( IsDefined( self._override_raps_combat_speed ) && self._override_raps_combat_speed < trigger.script_int ) { self SetSpeedImmediate( self._override_raps_combat_speed ); } else { self SetSpeedImmediate( trigger.script_int, 200, 200 ); } } else { if( IsDefined( self._override_raps_combat_speed ) && self._override_raps_combat_speed < 0.5 * self.settings.defaultMoveSpeed ) { self SetSpeed( self._override_raps_combat_speed ); } else { self SetSpeed( 0.5 * self.settings.defaultMoveSpeed ); } } wait 1; self ResumeSpeed(); self.slow_trigger = undefined; } function force_get_enemies() { if( isdefined( level.raps_force_get_enemies ) ) { return self [[level.raps_force_get_enemies]](); } foreach( player in level.players ) { if( self util::IsEnemyPlayer( player ) && !player.ignoreme ) { self GetPerfectInfo( player ); return; } } } function sndFunctions() { self.sndAlias = []; self.sndAlias["inAir"] = "veh_raps_in_air"; self.sndAlias["land"] = "veh_raps_land"; self.sndAlias["spawn"] = "veh_raps_spawn"; self.sndAlias["direction"] = "veh_raps_direction"; self.sndAlias["jump_up"] = "veh_raps_jump_up"; self.sndAlias["vehRapsClose250"] = "veh_raps_close_250"; self.sndAlias["vehRapsClose1500"] = "veh_raps_close_1500"; self.sndAlias["vehRapsTargeting"] = "veh_raps_targeting"; self.sndAlias["vehRapsAlarm"] = "evt_raps_alarm"; self.sndAlias["vehRapsCollision"] = "veh_wasp_wall_imp"; if( isdefined( self.vehicletype ) && (self.vehicletype == "spawner_enemy_zombie_vehicle_raps_suicide" || self.vehicletype == "spawner_zombietron_veh_meatball" || self.vehicletype == "spawner_zombietron_veh_meatball_med" || self.vehicletype == "spawner_zombietron_veh_meatball_small") ) { self.sndAlias["inAir"] = "zmb_meatball_in_air"; self.sndAlias["land"] = "zmb_meatball_land"; self.sndAlias["spawn"] = undefined; self.sndAlias["direction"] = undefined; self.sndAlias["jump_up"] = "zmb_meatball_jump_up"; self.sndAlias["vehRapsClose250"] = "zmb_meatball_close_250"; self.sndAlias["vehRapsClose1500"] = undefined; self.sndAlias["vehRapsTargeting"] = "zmb_meatball_targeting"; self.sndAlias["vehRapsAlarm"] = undefined; self.sndAlias["vehRapsCollision"] = "zmb_meatball_collision"; } if( self isDrivablePlayerVehicle() ) { self thread drivableRapsInAir(); } else { self thread raps_in_air_audio(); if( SessionModeIsCampaignGame() || SessionModeIsZombiesGame() ) self thread raps_spawn_audio(); } } function drivableRapsInAir() { self endon ("death"); while(1) { self waittill ("veh_landed"); if( isdefined( self.sndAlias["land"] ) ) self playsound (self.sndAlias["land"]); } } function raps_in_air_audio() //need to move to client at some point { self endon ("death"); if( !SessionModeIsCampaignGame() && !SessionModeIsZombiesGame() ) self waittill( "veh_landed" ); while(1) { self waittill ("veh_inair"); //notify sent from the vehicle system when all four 'tires' are off the scripts\cp\cp_mi_zurich_newworld_underground if( isdefined( self.sndAlias["inAir"] ) ) self playsound (self.sndAlias["inAir"]); self waittill ("veh_landed"); if( isdefined( self.sndAlias["land"] ) ) self playsound (self.sndAlias["land"]); } } function raps_spawn_audio() //need to move to client at some point { self endon( "death" ); wait randomfloatrange (0.25, 1.5); if( isdefined( self.sndAlias["spawn"] ) ) self playsound (self.sndAlias["spawn"]); } function isDrivablePlayerVehicle() { str_vehicletype = self.vehicletype; if (isdefined( str_vehicletype ) && StrEndsWith( str_vehicletype, "_player" ) ) { return true; } return false; } function do_death_fx() { self vehicle::do_death_dynents(); if(isdefined(self.bSideDetonation )) self clientfield::set( "raps_side_deathfx", 1 ); else self clientfield::set( "deathfx", 1 ); } //function play_rumble_on_raps() //{ // self endon( "death" ); // // while( 1 ) // { // vr = Abs( self GetSpeed() / self GetMaxSpeed() ); // // if( vr >= 0.1 ) // { // self PlayRumbleOnEntity( "damage_heavy" ); // } // // Wait 0.1; // } // //}