2023-04-13 17:30:38 +02:00

1486 lines
46 KiB
Plaintext

#using scripts\codescripts\struct;
#using scripts\shared\_oob;
#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\challenges_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\hud_shared;
#using scripts\shared\killstreaks_shared;
#using scripts\shared\scoreevents_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\weapons\_weapons;
#using scripts\shared\vehicle_ai_shared;
#using scripts\shared\vehicle_shared;
#using scripts\shared\vehicles\_amws;
#using scripts\mp\_challenges;
#using scripts\mp\_util;
#using scripts\mp\gametypes\_dev;
#using scripts\mp\gametypes\_spawning;
#using scripts\mp\gametypes\_spawnlogic;
#using scripts\mp\killstreaks\_dogs;
#using scripts\mp\killstreaks\_emp;
#using scripts\mp\killstreaks\_killstreak_bundles;
#using scripts\mp\killstreaks\_killstreak_detect;
#using scripts\mp\killstreaks\_killstreak_hacking;
#using scripts\mp\killstreaks\_killstreakrules;
#using scripts\mp\killstreaks\_killstreaks;
#using scripts\mp\killstreaks\_remote_weapons;
#using scripts\mp\killstreaks\_satellite;
#using scripts\mp\killstreaks\_supplydrop;
#using scripts\mp\killstreaks\_uav;
#using scripts\shared\visionset_mgr_shared;
#precache( "string", "KILLSTREAK_EARNED_AI_TANK_DROP" );
#precache( "string", "KILLSTREAK_AI_TANK_NOT_AVAILABLE" );
#precache( "string", "KILLSTREAK_AI_TANK_INBOUND" );
#precache( "string", "KILLSTREAK_AI_TANK_HACKED" );
#precache( "string", "KILLSTREAK_DESTROYED_AI_TANK" );
#precache( "string", "mpl_killstreak_ai_tank" );
#precache( "triggerstring", "MP_REMOTE_USE_TANK" );
#precache( "fx", "killstreaks/fx_agr_emp_stun" );
#precache( "fx", "killstreaks/fx_agr_rocket_flash_1p" );
#precache( "fx", "killstreaks/fx_agr_rocket_flash_3p" );
#precache( "fx", "killstreaks/fx_agr_damage_state" );
#precache( "fx", "killstreaks/fx_agr_explosion" );
#precache( "fx", "killstreaks/fx_agr_drop_box" );
#using_animtree ( "mp_vehicles" );
#namespace ai_tank;
function init()
{
bundle = struct::get_script_bundle( "killstreak", "killstreak_" + "ai_tank_drop" );
level.ai_tank_minigun_flash_3p = "killstreaks/fx_agr_rocket_flash_3p";
killstreaks::register( "ai_tank_drop", "ai_tank_marker", "killstreak_ai_tank_drop", "ai_tank_drop_used",&useKillstreakAITankDrop );
killstreaks::register_alt_weapon( "ai_tank_drop", "amws_gun_turret" );
killstreaks::register_alt_weapon( "ai_tank_drop", "amws_launcher_turret" );
killstreaks::register_alt_weapon( "ai_tank_drop", "amws_gun_turret_mp_player" );
killstreaks::register_alt_weapon( "ai_tank_drop", "amws_launcher_turret_mp_player" );
killstreaks::register_remote_override_weapon( "ai_tank_drop", "killstreak_ai_tank" );
killstreaks::register_strings( "ai_tank_drop", &"KILLSTREAK_EARNED_AI_TANK_DROP", &"KILLSTREAK_AI_TANK_NOT_AVAILABLE", &"KILLSTREAK_AI_TANK_INBOUND", undefined, &"KILLSTREAK_AI_TANK_HACKED" );
killstreaks::register_dialog( "ai_tank_drop", "mpl_killstreak_ai_tank", "aiTankDialogBundle", "aiTankPilotDialogBundle", "friendlyAiTank", "enemyAiTank", "enemyAiTankMultiple", "friendlyAiTankHacked", "enemyAiTankHacked", "requestAiTank", "threatAiTank" );
killstreaks::devgui_scorestreak_command( "ai_tank_drop", "Debug Routes", "set devgui_tank routes");
// TODO: Move to killstreak data
level.killstreaks["ai_tank_drop"].threatOnKill = true;
remote_weapons::RegisterRemoteWeapon( "killstreak_ai_tank", &"MP_REMOTE_USE_TANK", &startTankRemoteControl, &endTankRemoteControl, false );
level.ai_tank_fov = Cos( 160 );
level.ai_tank_turret_weapon = GetWeapon( "ai_tank_drone_gun" );
level.ai_tank_turret_fire_rate = level.ai_tank_turret_weapon.fireTime;
level.ai_tank_remote_weapon = GetWeapon( "killstreak_ai_tank" );
spawns = spawnlogic::get_spawnpoint_array( "mp_tdm_spawn" );
level.ai_tank_damage_fx = "killstreaks/fx_agr_damage_state";
level.ai_tank_explode_fx = "killstreaks/fx_agr_explosion";
level.ai_tank_crate_explode_fx = "killstreaks/fx_agr_drop_box";
anims = [];
anims[ anims.size ] = %o_drone_tank_missile1_fire;
anims[ anims.size ] = %o_drone_tank_missile2_fire;
anims[ anims.size ] = %o_drone_tank_missile3_fire;
anims[ anims.size ] = %o_drone_tank_missile_full_reload;
if(!isdefined(bundle.ksMainTurretRecoilForceZOffset))bundle.ksMainTurretRecoilForceZOffset=0;
if(!isdefined(bundle.ksWeaponReloadTime))bundle.ksWeaponReloadTime=0.5;
visionset_mgr::register_info( "visionset", "agr_visionset", 1, 80, 16, true, &visionset_mgr::ramp_in_out_thread_per_player_death_shutdown, false );
/#
level thread tank_devgui_think();
#/
thread register();
}
function register()
{
clientfield::register( "vehicle", "ai_tank_death", 1, 1, "int" );
clientfield::register( "vehicle", "ai_tank_missile_fire", 1, 2, "int" );
clientfield::register( "vehicle", "ai_tank_stun", 1, 1, "int" );
clientfield::register( "toplayer", "ai_tank_update_hud", 1, 1, "counter" );
}
function useKillstreakAITankDrop(hardpointType)
{
team = self.team;
if( !self supplydrop::isSupplyDropGrenadeAllowed( hardpointType ) )
{
return false;
}
killstreak_id = self killstreakrules::killstreakStart( hardpointType, team, false, false );
if ( killstreak_id == -1 )
{
return false;
}
context = SpawnStruct();
if ( !isdefined( context ) )
{
killstreak_stop_and_assert( hardpointType, team, killstreak_id, "Failed to spawn struct for ai tank." );
return false;
}
context.radius = level.killstreakCoreBundle.ksAirdropAITankRadius;
context.dist_from_boundary = 16;
context.max_dist_from_location = 4;
context.perform_physics_trace = true;
context.check_same_floor = true;
context.isLocationGood = &is_location_good;
context.objective = &"airdrop_aitank";
context.killstreakRef = hardpointType;
context.validLocationSound = level.killstreakCoreBundle.ksValidAITankLocationSound;
context.tracemask = (1 << 0) | (1 << 2);
context.dropTag = "tag_attach";
context.dropTagOffset = ( -35, 0, 10 );
result = self supplydrop::useSupplyDropMarker( killstreak_id, context );
// the marker is out but the chopper is yet to come
self notify( "supply_drop_marker_done" );
if ( !isdefined(result) || !result )
{
//if( !self.supplyGrenadeDeathDrop )
killstreakrules::killstreakStop( hardpointType, team, killstreak_id );
return false;
}
self killstreaks::play_killstreak_start_dialog( "ai_tank_drop", self.team, killstreak_id );
self killstreakrules::displayKillstreakStartTeamMessageToAll( "ai_tank_drop" );
self AddWeaponStat( GetWeapon( "ai_tank_marker" ), "used", 1 );
return result;
}
function crateLand( crate, category, owner, team, context )
{
// note: original context is being changed here
context.perform_physics_trace = false;
context.dist_from_boundary = 24;
context.max_dist_from_location = 96;
if ( !crate is_location_good( crate.origin, context ) || !isdefined( owner ) || team != owner.team || ( owner EMP::EnemyEMPActive() && !owner hasperk("specialty_immuneemp") ) )
{
killstreakrules::killstreakStop( category, team, crate.package_contents_id );
wait( 10 );
if ( isdefined( crate ) )
crate delete();
return;
}
origin = crate.origin;
crateBottom = BulletTrace( origin, origin + (0, 0, -50), false, crate );
if ( isdefined( crateBottom ) )
{
origin = crateBottom["position"] + (0,0,1);
}
PlayFX( level.ai_tank_crate_explode_fx, origin, (1, 0, 0), (0, 0, 1) );
PlaySoundAtPosition( "veh_talon_crate_exp", crate.origin );
level thread ai_tank_killstreak_start( owner, origin, crate.package_contents_id, category );
crate delete();
}
function is_location_good( location, context )
{
return supplydrop::IsLocationGood( location, context ) && valid_location( location );
}
function valid_location( location )
{
if ( !isdefined( location ) )
location = self.origin;
// only do this check if we are not the player, intended for deploy box only
if ( !isPlayer( self ) )
{
start = self GetCentroid();
end = location + ( 0, 0, 16 );
trace = PhysicsTrace( start, end, ( 0, 0, 0 ), ( 0, 0, 0 ), self, (1 << 4) );
if ( trace["fraction"] < 1 )
return false;
}
if( self oob::IsTouchingAnyOOBTrigger() )
{
return false;
}
return true;
}
function HackedCallbackPre( hacker )
{
drone = self;
drone clientfield::set( "enemyvehicle", 2 );
drone.owner stop_remote();
drone.owner clientfield::set_to_player( "static_postfx", 0 );
if( drone.controlled === true )
visionset_mgr::deactivate( "visionset", "agr_visionset", drone.owner );
drone.owner remote_weapons::RemoveAndAssignNewRemoteControlTrigger( drone.useTrigger );
drone remote_weapons::EndRemoteControlWeaponUse( true );
drone.owner unlink();
drone clientfield::set( "vehicletransition", 0 );
}
function HackedCallbackPost( hacker )
{
drone = self;
hacker remote_weapons::UseRemoteWeapon( drone, "killstreak_ai_tank", false );
drone notify("WatchRemoteControlDeactivate_remoteWeapons");
drone.killstreak_end_time = hacker killstreak_hacking::set_vehicle_drivable_time_starting_now( drone );
}
function ConfigureTeamPost( owner, isHacked )
{
drone = self;
drone thread tank_watch_owner_events();
}
function ai_tank_killstreak_start( owner, origin, killstreak_id, category )
{
team = owner.team;
waittillframeend;
if ( level.gameEnded )
return;
drone = SpawnVehicle( "spawner_bo3_ai_tank_mp", origin, (0, 0, 0), "talon" );
if ( !isdefined( drone ) )
{
killstreak_stop_and_assert( category, team, killstreak_id, "Failed to spawn ai tank vehicle." );
return;
}
drone.settings = struct::get_script_bundle( "vehiclecustomsettings", drone.scriptbundlesettings );
drone.customDamageMonitor = true; // Disable the default monitor_damage_as_occupant thread
drone.avoid_shooting_owner = true;
drone.avoid_shooting_owner_ref_tag = "tag_flash_gunner1";
drone killstreaks::configure_team( "ai_tank_drop", killstreak_id, owner, "small_vehicle", undefined, &ConfigureTeamPost );
drone killstreak_hacking::enable_hacking( "ai_tank_drop", &HackedCallbackPre, &HackedCallbackPost );
drone killstreaks::setup_health( "ai_tank_drop", 1500, 0 );
drone.original_vehicle_type = drone.vehicletype;
drone clientfield::set( "enemyvehicle", 1 );
drone SetVehicleAvoidance( true );
drone clientfield::set( "ai_tank_missile_fire", ( 3 ) );
drone.killstreak_id = killstreak_id;
drone.type = "tank_drone";
drone.dontDisconnectPaths = 1;
drone.isStunned = false;
drone.soundmod = "drone_land";
drone.ignore_vehicle_underneath_splash_scalar = true;
drone.treat_owner_damage_as_friendly_fire = true;
drone.ignore_team_kills = true;
drone.controlled = false;
drone MakeVehicleUnusable();
drone.numberRockets = ( 3 );
drone.warningShots = 3;
drone SetDrawInfrared( true );
//set up number for this drone
if (!isdefined(drone.owner.numTankDrones))
drone.owner.numTankDrones=1;
else
drone.owner.numTankDrones++;
drone.ownerNumber = drone.owner.numTankDrones;
// make the drone targetable
Target_Set( drone, (0,0,20) );
Target_SetTurretAquire( drone, false );
// setup target group for missile lock on monitoring
drone vehicle::init_target_group();
drone vehicle::add_to_target_group( drone );
drone setup_gameplay_think( category );
drone.killstreak_end_time = GetTime() + ( 120 * 1000 );
owner remote_weapons::UseRemoteWeapon( drone, "killstreak_ai_tank", false );
drone thread kill_monitor();
drone thread deleteOnKillbrush( drone.owner );
drone thread tank_rocket_watch_ai();
level thread tank_game_end_think(drone);
/#
drone thread tank_think_debug();
#/
/#
//drone thread tank_debug_health();
#/
}
function get_vehicle_name( vehicle_version )
{
switch( vehicle_version )
{
case 2:
default:
return "spawner_bo3_ai_tank_mp";
break;
case 1:
return "ai_tank_drone_mp";
break;
}
}
function setup_gameplay_think( category )
{
drone = self;
drone thread tank_abort_think();
drone thread tank_team_kill();
drone thread tank_too_far_from_nav_mesh_abort_think();
drone thread tank_death_think( category );
drone thread tank_damage_think();
drone thread WatchWater();
}
function tank_think_debug() // self == drone
{
self endon ( "death" );
server_frames_to_persist = 1;
text_scale = 0.5;
text_alpha = 1.0;
text_color = ( 1, 1, 1 );
while ( 1 )
{
if ( GetDvarInt( "scr_ai_tank_think_debug" ) == 0 )
{
wait 5;
continue;
}
target_name = "unknown";
target_entity = undefined;
tank_is_idle = !isdefined( self.enemy ); // NOTE: enemy is set by code-side ai system
target_entity = self.enemy;
if ( isdefined( target_entity ) && !tank_is_idle )
{
if ( isdefined ( target_entity.name ) )
{
target_name = target_entity.name;
}
else if ( isdefined ( target_entity.remotename ) )
{
target_name = target_entity.remotename;
}
}
target_text = ( ( tank_is_idle ) ? "Target: none" : "Target: " + target_name );
/# Print3d( self.origin, target_text, text_color, text_alpha, text_scale, server_frames_to_persist ); #/
duration_text = "Duration: " + ( (self.killstreak_end_time - GetTime()) * 0.001 );
/# Print3d( self.origin + ( 0, 0, 12 ), duration_text, text_color, text_alpha, text_scale, server_frames_to_persist ); #/
can_see_text = "Can see: ";
if ( tank_is_idle )
{
can_see_text += "---";
}
else
{
can_see_text += ( ( self VehCanSee( target_entity ) ) ? "yes" : "no" );
}
/# Print3d( self.origin + ( 0, 0, -12 ), can_see_text, text_color, text_alpha, text_scale, server_frames_to_persist ); #/
movement_type_text = "Movement: ";
if ( isdefined( self.debug_ai_movement_type ) )
{
movement_type_text += self.debug_ai_movement_type;
}
else
{
movement_type_text += "---";
}
/# Print3d( self.origin + ( 0, 0, -24 ), movement_type_text, text_color, text_alpha, text_scale, server_frames_to_persist ); #/
if ( isdefined( self.debug_ai_move_to_point ) )
{
/# util::debug_sphere( self.debug_ai_move_to_point + ( 0, 0, 16 ), 10, ( 0.1, 0.95, 0.1 ), 0.9, server_frames_to_persist ); #/
if ( isdefined( self.debug_ai_move_to_points_considered ) )
{
foreach( point in self.debug_ai_move_to_points_considered )
{
point_color = ( 0.65, 0.65, 0.65 ); // grey-ish
if ( isdefined( point.score ) )
{
if ( point.score != 0 )
{
if ( point.score < 0 )
{
point_color = ( 0.65, 0.1, 0.1 ); //dark red-ish
}
else if ( point.score > 50 )
{
point_color = ( 0.1, 0.65, 0.1 ); // dark green-ish
}
else
{
point_color = ( 0.95, 0.95, 0.1 ); // yellow-ish
}
score_text_scale = text_scale;
score_text_color = text_color;
if ( point.origin != self.debug_ai_move_to_point )
{
score_text_scale *= 0.67;
}
else
{
score_text_scale *= 1.5;
score_text_color = ( 0.05, 0.98, 0.05 ); // green-ish
}
/# Print3d( point.origin + ( 0, 0, 16 ), point.score, score_text_color, text_alpha, score_text_scale, server_frames_to_persist ); #/
}
}
if ( point.origin != self.debug_ai_move_to_point )
{
/# util::debug_sphere( point.origin + ( 0, 0, 16 ), 3, point_color, 0.5, server_frames_to_persist ); #/
}
}
}
}
{wait(.05);};
}
}
function tank_team_kill()
{
self endon( "death" );
self.owner waittill( "teamKillKicked" );
self notify ( "death" );
}
function kill_monitor()
{
self endon( "death" );
last_kill_vo = 0;
kill_vo_spacing = 4000;
while(1)
{
self waittill( "killed", victim );
if ( !isdefined( self.owner ) || !isdefined( victim ) )
continue;
if ( self.owner == victim )
continue;
if ( level.teamBased && self.owner.team == victim.team )
continue;
if ( !self.controlled && last_kill_vo + kill_vo_spacing < GetTime() )
{
self killstreaks::play_pilot_dialog_on_owner( "kill", "ai_tank_drop", self.killstreak_id );
last_kill_vo = GetTime();
}
}
}
function tank_abort_think()
{
tank = self;
tank thread killstreaks::WaitForTimeout( "ai_tank_drop", ( 120 * 1000 ), &tank_timeout_callback, "death", "emp_jammed" );
}
function tank_timeout_callback()
{
self killstreaks::play_pilot_dialog_on_owner( "timeout", "ai_tank_drop" );
self.timed_out = true;
self notify( "death" );
}
function tank_watch_owner_events()
{
self notify( "tank_watch_owner_events_singleton" );
self endon ( "tank_watch_owner_events_singleton" );
self endon( "death" );
self.owner util::waittill_any( "joined_team", "disconnect", "joined_spectators" );
self MakeVehicleUsable();
self.controlled = false;
if ( isdefined( self.owner ) )
{
self.owner unlink();
self clientfield::set( "vehicletransition", 0 );
}
self MakeVehicleUnusable();
if ( isdefined( self.owner ) && ( self.controlled === true ) )
{
visionset_mgr::deactivate( "visionset", "agr_visionset", self.owner );
self.owner stop_remote();
}
self.abandoned = true;
self notify( "death" );
}
function tank_game_end_think(drone)
{
drone endon( "death" );
level waittill("game_ended");
drone notify( "death" );
}
function stop_remote() // dead
{
if ( !isdefined( self ) )
return;
self killstreaks::clear_using_remote();
self remote_weapons::destroyRemoteHUD();
self util::clientNotify( "nofutz" );
}
function tank_hacked_health_update( hacker )
{
tank = self;
hackedDamageTaken = tank.defaultMaxHealth - tank.hackedHealth;
assert ( hackedDamageTaken > 0 );
if ( hackedDamageTaken > tank.damageTaken )
{
tank.damageTaken = hackedDamageTaken;
}
}
function tank_damage_think()
{
self endon( "death" );
assert( isdefined( self.maxhealth ) );
self.defaultMaxHealth = self.maxhealth;
maxhealth = self.maxhealth; // actual max heath should be set now.
self.maxhealth = 999999;
self.health = self.maxhealth;
self.isStunned = false;
self.hackedHealthUpdateCallback = &tank_hacked_health_update;
self.hackedHealth = killstreak_bundles::get_hacked_health( "ai_tank_drop" );
low_health = false;
self.damageTaken = 0;
for ( ;; )
{
self waittill( "damage", damage, attacker, dir, point, mod, model, tag, part, weapon, flags, inflictor, chargeLevel );
self.maxhealth = 999999;
self.health = self.maxhealth;
/#
self.damage_debug = ( damage + " (" + weapon.name + ")" );
#/
if ( weapon.isEmp && (mod == "MOD_GRENADE_SPLASH"))
{
emp_damage_to_apply = killstreak_bundles::get_emp_grenade_damage( "ai_tank_drop", maxhealth );
if ( !isdefined( emp_damage_to_apply ) )
emp_damage_to_apply = ( maxhealth / 2 );
self.damageTaken += emp_damage_to_apply;
damage = 0;
if ( !self.isStunned && emp_damage_to_apply > 0 )
{
self.isStunned = true;
challenges::stunnedTankWithEMPGrenade( attacker );
self thread tank_stun( 4 );
}
}
if ( !self.isStunned )
{
if ( weapon.doStun && (mod == "MOD_GRENADE_SPLASH" || mod == "MOD_GAS") )
{
self.isStunned = true;
self thread tank_stun( 1.5 );
}
}
weapon_damage = killstreak_bundles::get_weapon_damage( "ai_tank_drop", maxhealth, attacker, weapon, mod, damage, flags, chargeLevel );
if ( !isdefined( weapon_damage ) )
{
if ( mod == "MOD_RIFLE_BULLET" || mod == "MOD_PISTOL_BULLET" || weapon.name == "hatchet" || (mod == "MOD_PROJECTILE_SPLASH" && weapon.bulletImpactExplode) )
{
if ( isPlayer( attacker ) )
if ( attacker HasPerk( "specialty_armorpiercing" ) )
damage += int( damage * level.cac_armorpiercing_data );
if ( weapon.weapClass == "spread")
damage = damage * 1.5;
weapon_damage = damage * .8;
}
if ( ( mod == "MOD_PROJECTILE" || mod == "MOD_GRENADE_SPLASH" || mod == "MOD_PROJECTILE_SPLASH" ) && damage != 0 && !weapon.isEmp && !weapon.bulletImpactExplode)
{
weapon_damage = damage * 1;
}
if ( !isdefined( weapon_damage ) )
{
weapon_damage = damage;
}
}
self.damageTaken += weapon_damage;
if ( self.controlled )
{
self.owner SendKillstreakDamageEvent( int( weapon_damage ) );
self.owner vehicle::update_damage_as_occupant( self.damageTaken, maxhealth );
}
if ( self.damageTaken >= maxhealth )
{
if( isdefined( self.owner ) )
self.owner.dofutz = true;
self.health = 0;
self notify( "death", attacker, mod, weapon );
return;
}
if ( !low_health && self.damageTaken > maxhealth / 1.8 )
{
self killstreaks::play_pilot_dialog_on_owner( "damaged", "ai_tank_drop", self.killstreak_id );
self thread tank_low_health_fx();
low_health = true;
}
}
}
function tank_low_health_fx()
{
self endon( "death" );
self.damage_fx = spawn( "script_model", self GetTagOrigin("tag_origin") + (0,0,-14) );
if ( !isdefined( self.damage_fx ) )
{
// intentionally not adding an AssertMsg() here
return;
}
self.damage_fx SetModel( "tag_origin" );
self.damage_fx LinkTo(self, "tag_turret", (0,0,-14), (0,0,0) );
wait ( 0.1 );
PlayFXOnTag( level.ai_tank_damage_fx, self.damage_fx, "tag_origin" );
}
function deleteOnKillbrush(player)
{
player endon("disconnect");
self endon("death");
killbrushes = GetEntArray( "trigger_hurt","classname" );
while(1)
{
for (i = 0; i < killbrushes.size; i++)
{
if (self istouching(killbrushes[i]) )
{
if ( isdefined(self) )
{
self notify( "death", self.owner );
}
return;
}
}
wait( 0.1 );
}
}
function tank_stun( duration )
{
self endon( "death" );
self notify( "stunned" );
self ClearVehGoalPos();
forward = AnglesToForward( self.angles );
forward = self.origin + forward * 128;
forward = forward - ( 0, 0, 64 );
self SetTurretTargetVec( forward );
self DisableGunnerFiring( 0, true );
self LaserOff();
if (self.controlled)
{
self.owner FreezeControls( true );
self.owner SendKillstreakDamageEvent( 400 );
}
if (isdefined(self.owner.fullscreen_static))
{
self.owner thread remote_weapons::stunStaticFX( duration );
}
self clientfield::set( "ai_tank_stun", 1 );
if( self.controlled )
self.owner clientfield::set_to_player( "static_postfx", 1 );
wait ( duration );
self clientfield::set( "ai_tank_stun", 0 );
if( self.controlled )
self.owner clientfield::set_to_player( "static_postfx", 0 );
if (self.controlled)
{
self.owner FreezeControls( false );
}
self DisableGunnerFiring( 0, false );
self.isStunned = false;
}
function emp_crazy_death()
{
self clientfield::set( "ai_tank_stun", 1 );
self notify ("death");
time = 0;
randomAngle = RandomInt(360);
while (time < 1.45)
{
self SetTurretTargetVec(self.origin + AnglesToForward((RandomIntRange(305, 315), int((randomAngle + time * 180)), 0)) * 100);
if (time > 0.2)
{
self FireWeapon( 1 );
if ( RandomInt(100) > 85)
{
rocket = self FireWeapon( 0 );
if ( isdefined( rocket ) )
{
rocket.from_ai = true;
}
}
}
time += 0.05;
{wait(.05);};
}
self clientfield::set( "ai_tank_death", 1 );
PlayFX( level.ai_tank_explode_fx, self.origin, (0, 0, 1) );
PlaySoundAtPosition( "wpn_agr_explode", self.origin );
{wait(.05);};
self hide();
}
function tank_death_think( hardpointName )
{
team = self.team;
killstreak_id = self.killstreak_id;
self waittill( "death", attacker, damageFromUnderneath, weapon );
// self waittill( "death", attacker, damageFromUnderneath, weapon, point, dir, modType );
if ( !isdefined( self ) )
{
killstreak_stop_and_assert( hardpointName, team, killstreak_id, "Failed to handle death. A." );
return;
}
self.dead = true;
self LaserOff();
self ClearVehGoalPos();
not_abandoned = ( !isdefined( self.abandoned ) || !self.abandoned );
if ( self.controlled == true )
{
self.owner SendKillstreakDamageEvent( 600 );
self.owner remote_weapons::destroyRemoteHUD();
}
self clientfield::set( "ai_tank_death", 1 );
stunned = false;
settings = self.settings;
if ( isdefined( settings ) && ( self.timed_out === true || self.abandoned === true ) )
{
fx_origin = self GetTagOrigin( (isdefined(settings.timed_out_death_tag_1)?settings.timed_out_death_tag_1:"tag_origin") );
PlayFx( (isdefined(settings.timed_out_death_fx_1)?settings.timed_out_death_fx_1:level.ai_tank_explode_fx), (isdefined(fx_origin)?fx_origin:self.origin), ( 0, 0, 1 ) );
PlaySoundAtPosition( (isdefined(settings.timed_out_death_sound_1)?settings.timed_out_death_sound_1:"wpn_agr_explode"), self.origin );
}
else
{
PlayFX( level.ai_tank_explode_fx, self.origin, ( 0, 0, 1 ) );
PlaySoundAtPosition( "wpn_agr_explode", self.origin );
}
if ( not_abandoned )
{
util::wait_network_frame();
if ( !isdefined( self ) )
{
killstreak_stop_and_assert( hardpointName, team, killstreak_id, "Failed to handle death. B." );
return;
}
}
if ( self.controlled )
{
self Ghost(); // keep the view for player with the dead by using ghost, otherwise, will end up at feet of player
}
else
{
self Hide();
}
if (isdefined(self.damage_fx))
{
self.damage_fx delete();
}
attacker = self [[ level.figure_out_attacker ]]( attacker );
if ( isdefined( attacker ) && IsPlayer( attacker ) && isdefined( self.owner ) && attacker != self.owner )
{
if ( self.owner util::IsEnemyPlayer( attacker ) )
{
scoreevents::processScoreEvent( "destroyed_aitank", attacker, self.owner, weapon );
LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_AI_TANK", attacker.entnum );
attacker AddWeaponStat( weapon, "destroyed_aitank", 1 );
controlled = false;
if ( isdefined( self.wasControlledNowDead ) && self.wasControlledNowDead )
{
attacker AddWeaponStat( weapon, "destroyed_controlled_killstreak", 1 );
controlled = true;
}
attacker challenges::destroyScoreStreak( weapon, controlled, true );
attacker challenges::destroyNonAirScoreStreak_PostStatsLock( weapon );
attacker AddWeaponStat( weapon, "destroy_aitank_or_setinel", 1 );
self killstreaks::play_destroyed_dialog_on_owner( "ai_tank_drop", self.killstreak_id );
}
else
{
//Destroyed Friendly Killstreak
}
}
if ( not_abandoned )
{
self util::waittill_any_timeout( 2.0, "remote_weapon_end" );
if ( !isdefined( self ) )
{
killstreak_stop_and_assert( hardpointName, team, killstreak_id, "Failed to handle death. C." );
return;
}
}
killstreakrules::killstreakStop( hardpointName, team, self.killstreak_id );
if ( isdefined( self.aim_entity ) )
self.aim_entity delete();
self delete();
}
function killstreak_stop_and_assert( hardpoint_name, team, killstreak_id, assert_msg )
{
killstreakrules::killstreakStop( hardpoint_name, team, killstreak_id );
AssertMsg( assert_msg );
}
function tank_too_far_from_nav_mesh_abort_think()
{
self endon( "death" );
not_on_nav_mesh_count = 0;
for ( ;; )
{
wait( 1 );
not_on_nav_mesh_count = ( isdefined( GetClosestPointOnNavMesh( self.origin, ( 40 * 12 ) ) ) ? 0 : not_on_nav_mesh_count + 1 );
if ( not_on_nav_mesh_count >= 4 )
{
self notify( "death" );
}
}
}
function tank_has_radar()
{
if ( level.teambased )
{
return ( uav::HasUAV( self.team ) || satellite::HasSatellite( self.team ) );
}
return ( uav::HasUAV( self.entnum ) || satellite::HasSatellite( self.entnum ) );
}
function tank_get_player_enemies( on_radar )
{
enemies = [];
if ( !isdefined( on_radar ) )
{
on_radar = false;
}
if ( on_radar )
{
time = GetTime();
}
foreach( teamKey, team in level.alivePlayers )
{
if ( level.teambased && teamKey == self.team )
{
continue;
}
foreach( player in team )
{
if ( !valid_target( player, self.team, self.owner ) )
{
continue;
}
if ( on_radar )
{
if ( time - player.lastFireTime > 3000 && !tank_has_radar() )
{
continue;
}
}
enemies[ enemies.size ] = player;
}
}
return enemies;
}
function tank_compute_enemy_position()
{
enemies = tank_get_player_enemies( false );
position = undefined;
if ( enemies.size )
{
x = 0;
y = 0;
z = 0;
foreach( enemy in enemies )
{
x += enemy.origin[0];
y += enemy.origin[1];
z += enemy.origin[2];
}
x /= enemies.size;
y /= enemies.size;
z /= enemies.size;
position = ( x, y, z );
}
return position;
}
function valid_target( target, team, owner )
{
if ( !isdefined( target ) )
{
return false;
}
if ( !IsAlive( target ) )
{
return false;
}
if ( target == owner )
{
return false;
}
if ( IsPlayer( target ) )
{
if ( target.sessionstate != "playing" )
{
return false;
}
if ( isdefined( target.lastspawntime ) && GetTime() - target.lastspawntime < 3000 )
{
return false;
}
if ( target hasPerk( "specialty_nottargetedbyaitank" ) )
{
return false;
}
/#
if ( target IsInMoveMode( "ufo", "noclip" ) )
{
return false;
}
#/
}
if ( level.teambased )
{
if ( isdefined( target.team ) && team == target.team )
{
return false;
}
}
if ( isdefined( target.owner ) && target.owner == owner )
{
return false;
}
if ( isdefined( target.script_owner ) && target.script_owner == owner )
{
return false;
}
if ( ( isdefined( target.dead ) && target.dead ) )
{
return false;
}
if ( isdefined( target.targetname ) && target.targetname == "riotshield_mp" )
{
if ( isdefined( target.damageTaken ) && target.damageTaken >= GetDvarInt( "riotshield_deployed_health" ) )
{
return false;
}
}
return true;
}
function startTankRemoteControl( drone ) // self == player
{
drone MakeVehicleUsable();
drone ClearVehGoalPos();
drone ClearTurretTarget();
drone LaserOff();
drone.treat_owner_damage_as_friendly_fire = false;
drone.ignore_team_kills = false;
if ( isdefined( drone.PlayerDrivenVersion ) )
drone SetVehicleType( drone.PlayerDrivenVersion );
drone usevehicle( self, 0 );
drone clientfield::set( "vehicletransition", 1 );
drone MakeVehicleUnusable();
drone SetBrake( false );
drone thread tank_rocket_watch( self );
drone thread vehicle::monitor_missiles_locked_on_to_me( self );
self vehicle::set_vehicle_drivable_time( ( 120 * 1000 ), drone.killstreak_end_time );
self vehicle::update_damage_as_occupant( (isdefined(drone.damageTaken)?drone.damageTaken:0), (isdefined(drone.defaultmaxhealth)?drone.defaultmaxhealth:100) );
drone update_client_ammo( drone.numberRockets, true );
visionset_mgr::activate( "visionset", "agr_visionset", self, 1, 90000, 1 );
}
function endTankRemoteControl( drone, exitRequestedByOwner )
{
not_dead = !( isdefined( drone.dead ) && drone.dead );
if ( isdefined( drone.owner ) )
{
drone.owner remote_weapons::destroyRemoteHUD();
}
drone.treat_owner_damage_as_friendly_fire = true;
drone.ignore_team_kills = true;
if( drone.classname == "script_vehicle")
drone MakeVehicleUnusable();
if ( isdefined( drone.original_vehicle_type ) && not_dead )
drone SetVehicleType( drone.original_vehicle_type );
if ( isdefined( drone.owner ) )
drone.owner vehicle::stop_monitor_missiles_locked_on_to_me();
if( exitRequestedByOwner && not_dead )
{
drone vehicle_ai::set_state( "combat" );
}
if ( drone.cobra === true && not_dead )
drone thread amws::cobra_retract();
if ( isdefined( drone.owner ) && ( drone.controlled === true ) )
visionset_mgr::deactivate( "visionset", "agr_visionset", drone.owner );
drone clientfield::set( "vehicletransition", 0 );
}
function perform_recoil_missile_turret( player ) // self == drone
{
bundle = level.killstreakBundle["ai_tank_drop"];
Earthquake( 0.4, 0.5, self.origin, 200 );
self perform_recoil( "tag_barrel", ( ( ( isdefined( self.controlled ) && self.controlled ) ? bundle.ksMainTurretRecoilForceControlled : bundle.ksMainTurretRecoilForce ) ), bundle.ksMainTurretRecoilForceZOffset );
if ( self.controlled && isdefined( player ) )
{
player PlayRumbleOnEntity( "sniper_fire" );
}
}
function perform_recoil( recoil_tag, force_scale_factor, force_z_offset ) // self == drone
{
angles = self GetTagAngles( recoil_tag );
dir = AnglesToForward( angles );
self LaunchVehicle( dir * force_scale_factor, self.origin + ( 0, 0, force_z_offset ), false );
}
function update_client_ammo( ammo_count, driver_only_update = false ) // self == vehicle
{
if ( !driver_only_update )
{
self clientfield::set( "ai_tank_missile_fire", ammo_count );
}
if ( self.controlled )
{
self.owner clientfield::increment_to_player( "ai_tank_update_hud", 1 );
}
}
function tank_rocket_watch( player )
{
self endon( "death" );
player endon( "stopped_using_remote");
if ( self.numberRockets <= 0 )
{
self reload_rockets( player );
}
if ( !self.isStunned )
{
self DisableDriverFiring( false );
}
while( true )
{
player waittill( "missile_fire", missile );
missile.ignore_team_kills = self.ignore_team_kills;
self.numberRockets--;
self update_client_ammo( self.numberRockets );
self perform_recoil_missile_turret( player );
if ( self.numberRockets <= 0 )
{
self reload_rockets( player );
}
}
}
function tank_rocket_watch_ai()
{
self endon( "death" );
while( true )
{
self waittill( "missile_fire", missile );
missile.ignore_team_kills = self.ignore_team_kills;
missile.killCamEnt = self;
}
}
function reload_rockets( player )
{
bundle = level.killstreakBundle["ai_tank_drop"];
self DisableDriverFiring( true );
// setup the "reload" time for the player's vehicle HUD
weapon_wait_duration_ms = Int( bundle.ksWeaponReloadTime * 1000 );
player SetVehicleWeaponWaitDuration( weapon_wait_duration_ms );
player SetVehicleWeaponWaitEndTime( GetTime() + weapon_wait_duration_ms );
wait ( bundle.ksWeaponReloadTime );
self.numberRockets = ( 3 );
self update_client_ammo( self.numberRockets );
wait (0.4);
if ( !self.isStunned )
{
self DisableDriverFiring( false );
}
}
function WatchWater()
{
self endon( "death" );
inWater = false;
while( !inWater )
{
wait ( 0.3 );
trace = physicstrace( self.origin + ( 0, 0, ( 42 ) ), self.origin + ( 0, 0, ( 12 ) ), ( -2, -2, -2 ), ( 2, 2, 2 ), self, ( (1 << 2) ));
inWater = ( trace["fraction"] < ( (( 42 ) - ( 36 )) / ( ( 42 ) - ( 12 ) ) ) && trace["fraction"] != 1.0 );
waterTraceDistanceFromEnd = ( ( 42 ) - ( 12 ) ) - ( trace["fraction"] * ( ( 42 ) - ( 12 ) ) );
static_alpha = min( 1.0, waterTraceDistanceFromEnd / ( ( 36 ) - ( 12 ) ) );
// design does not want beeping audio for when water is an issue, maybe a different kind of audio?
if ( isdefined( self.owner ) && self.controlled )
self.owner clientfield::set_to_player( "static_postfx", ( ( static_alpha > 0.0 ) ? 1 : 0 ) );
}
if( isdefined( self.owner ) )
self.owner.dofutz = true;
self notify( "death" );
}
/#
function tank_devgui_think()
{
SetDvar( "devgui_tank", "" );
for ( ;; )
{
wait( 0.25 );
level.ai_tank_turret_fire_rate = level.ai_tank_turret_weapon.fireTime;
if ( GetDvarString( "devgui_tank" ) == "routes" )
{
devgui_debug_route();
SetDvar( "devgui_tank", "" );
}
}
}
function tank_debug_patrol( node1, node2 )
{
self endon( "death" );
self endon( "debug_patrol" );
for( ;; )
{
self SetVehGoalPos( node1.origin, true );
self waittill( "reached_end_node" );
wait( 1 );
self SetVehGoalPos( node2.origin, true );
self waittill( "reached_end_node" );
wait( 1 );
}
}
function devgui_debug_route()
{
iprintln( "Choose nodes with 'A' or press 'B' to cancel" );
nodes = dev::dev_get_node_pair();
if ( !isdefined( nodes ) )
{
iprintln( "Route Debug Cancelled" );
return;
}
iprintln( "Sending talons to chosen nodes" );
tanks = GetEntArray( "talon", "targetname" );
foreach( tank in tanks )
{
tank notify( "debug_patrol" );
tank thread tank_debug_patrol( nodes[0], nodes[1] );
}
}
function tank_debug_hud_init()
{
host = util::getHostPlayer();
while ( !isdefined( host ) )
{
wait( 0.25 );
host = util::getHostPlayer();
}
x = 80;
y = 40;
level.ai_tank_bar = NewClientHudElem( host );
level.ai_tank_bar.x = x + 80;
level.ai_tank_bar.y = y + 2;
level.ai_tank_bar.alignX = "left";
level.ai_tank_bar.alignY = "top";
level.ai_tank_bar.horzAlign = "fullscreen";
level.ai_tank_bar.vertAlign = "fullscreen";
level.ai_tank_bar.alpha = 0;
level.ai_tank_bar.foreground = 0;
level.ai_tank_bar setshader( "black", 1, 8 );
level.ai_tank_text = NewClientHudElem( host );
level.ai_tank_text.x = x + 80;
level.ai_tank_text.y = y;
level.ai_tank_text.alignX = "left";
level.ai_tank_text.alignY = "top";
level.ai_tank_text.horzAlign = "fullscreen";
level.ai_tank_text.vertAlign = "fullscreen";
level.ai_tank_text.alpha = 0;
level.ai_tank_text.fontScale = 1;
level.ai_tank_text.foreground = 1;
}
function tank_debug_health()
{
self.damage_debug = "";
level.ai_tank_bar.alpha = 1;
level.ai_tank_text.alpha = 1;
for ( ;; )
{
{wait(.05);};
if ( !isdefined( self ) || !IsAlive( self ) )
{
level.ai_tank_bar.alpha = 0;
level.ai_tank_text.alpha = 0;
return;
}
width = self.health / self.maxhealth * 300;
width = int( max( width, 1 ) );
level.ai_tank_bar setShader( "black", width, 8 );
str = ( self.health + " Last Damage: " + self.damage_debug );
level.ai_tank_text SetText( str );
}
}
#/