#using scripts\codescripts\struct; #using scripts\shared\_oob; #using scripts\shared\array_shared; #using scripts\shared\audio_shared; #using scripts\shared\challenges_shared; #using scripts\shared\clientfield_shared; #using scripts\shared\hostmigration_shared; #using scripts\shared\hud_shared; #using scripts\shared\killstreaks_shared; #using scripts\shared\popups_shared; #using scripts\shared\scoreevents_shared; #using scripts\shared\tweakables_shared; #using scripts\shared\util_shared; #using scripts\shared\weapons\_flashgrenades; #using scripts\shared\weapons\_weaponobjects; #using scripts\shared\vehicle_shared; #using scripts\shared\vehicle_death_shared; #using scripts\mp\_challenges; #using scripts\mp\_util; #using scripts\mp\gametypes\_globallogic_audio; #using scripts\mp\gametypes\_globallogic_utils; #using scripts\mp\gametypes\_hostmigration; #using scripts\mp\gametypes\_shellshock; #using scripts\mp\gametypes\_spawning; #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; #precache( "string", "KILLSTREAK_EARNED_RCBOMB" ); #precache( "string", "KILLSTREAK_RCBOMB_NOT_AVAILABLE" ); #precache( "string", "KILLSTREAK_RCBOMB_INBOUND" ); #precache( "string", "KILLSTREAK_RCBOMB_HACKED" ); #precache( "string", "KILLSTREAK_DESTROYED_RCBOMB" ); #precache( "string", "mpl_killstreak_rcbomb" ); #precache( "fx", "_t6/weapon/grenade/fx_spark_disabled_rc_car" ); #precache( "fx", "killstreaks/fx_rcxd_lights_grn" ); #precache( "fx", "killstreaks/fx_rcxd_lights_red" ); #precache( "fx", "killstreaks/fx_rcxd_exp" ); #namespace rcbomb; function init() { level._effect["rcbombexplosion"] = "killstreaks/fx_rcxd_exp"; killstreaks::register( "rcbomb", "rcbomb", "killstreak_rcbomb", "rcbomb_used",&ActivateRCBomb ); killstreaks::register_strings( "rcbomb", &"KILLSTREAK_EARNED_RCBOMB", &"KILLSTREAK_RCBOMB_NOT_AVAILABLE", &"KILLSTREAK_RCBOMB_INBOUND", undefined, &"KILLSTREAK_RCBOMB_HACKED", false ); killstreaks::register_dialog( "rcbomb", "mpl_killstreak_rcbomb", "rcBombDialogBundle", undefined, "friendlyRcBomb", "enemyRcBomb", "enemyRcBombMultiple", "friendlyRcBombHacked", "enemyRcBombHacked", "requestRcBomb" ); killstreaks::allow_assists( "rcbomb", true); killstreaks::register_alt_weapon( "rcbomb", "killstreak_remote" ); killstreaks::register_alt_weapon( "rcbomb", "rcbomb_turret" ); remote_weapons::RegisterRemoteWeapon( "rcbomb", &"", &StartRemoteControl, &EndRemoteControl, false ); vehicle::add_main_callback( "rc_car_mp", &InitRCBomb ); clientfield::register( "vehicle", "rcbomb_stunned", 1, 1, "int" ); } function InitRCBomb() { rcbomb = self; rcbomb clientfield::set( "enemyvehicle", 1 ); rcbomb.allowFriendlyFireDamageOverride = &RCCarAllowFriendlyFireDamage; rcbomb EnableAimAssist(); rcbomb SetDrawInfrared( true ); rcbomb.delete_on_death = true; rcbomb.death_enter_cb = &waitRemoteControl; rcbomb.disableRemoteWeaponSwitch = true; rcbomb.overrideVehicleDamage = &OnDamage; rcbomb.overrideVehicleDeath = &OnDeath; //rcbomb.remoteWeaponShutdownDelay = RCBOMB_SHUTDOWN_DELAY; rcbomb.watch_remote_weapon_death = true; rcbomb.watch_remote_weapon_death_duration = ( 0.3 ); if ( IsSentient( rcbomb ) == false ) rcbomb MakeSentient(); // so other sentients will consider this as a potential enemy } function waitRemoteControl() { remote_controlled = ( isdefined( self.control_initiated ) && self.control_initiated ) || ( isdefined( self.controlled ) && self.controlled ); if( remote_controlled ) { notifyString = self util::waittill_any_return( "remote_weapon_end", "rcbomb_shutdown" ); if( notifyString == "remote_weapon_end" ) self waittill( "rcbomb_shutdown" ); else self waittill( "remote_weapon_end" ); } else self waittill( "rcbomb_shutdown" ); } function toggleLightsOnAfterTime( time ) { self notify("toggleLightsOnAfterTime_singleton"); self endon ("toggleLightsOnAfterTime_singleton"); rcbomb = self; rcbomb endon( "death" ); wait( time ); rcbomb clientfield::set( "toggle_lights", 0 ); } function HackedPreFunction( hacker ) { rcbomb = self; rcbomb clientfield::set( "toggle_lights", 1 ); rcbomb.owner unlink(); rcbomb clientfield::set( "vehicletransition", 0 ); rcbomb.owner killstreaks::clear_using_remote(); rcbomb MakeVehicleUnusable(); } function HackedPostFunction( hacker ) { rcbomb = self; hacker remote_weapons::UseRemoteWeapon( rcbomb, "rcbomb", true, false ); rcbomb MakeVehicleUnusable(); hacker killstreaks::set_killstreak_delay_killcam( "rcbomb" ); hacker killstreak_hacking::set_vehicle_drivable_time_starting_now( rcbomb ); } function ConfigureTeamPost( owner, isHacked ) { rcbomb = self; rcbomb thread WatchOwnerGameEvents(); } function ActivateRCBomb( hardpointType ) { assert( IsPlayer( self ) ); player = self; if( !player killstreakrules::isKillstreakAllowed( hardpointType, player.team ) ) { return false; } if ( player UseButtonPressed() ) { return false; } placement = CalculateSpawnOrigin( self.origin, self.angles ); if( !isdefined( placement ) || !self IsOnGround() || self util::isUsingRemote() || killstreaks::is_interacting_with_object() || self oob::IsTouchingAnyOOBTrigger() || self killstreaks::is_killstreak_start_blocked() ) { self iPrintLnBold( &"KILLSTREAK_RCBOMB_NOT_PLACEABLE" ); return false; } killstreak_id = player killstreakrules::killstreakStart( "rcbomb", player.team, false, true ); if( killstreak_id == (-1) ) { return false; } rcbomb = SpawnVehicle( "rc_car_mp", placement.origin, placement.angles, "rcbomb" ); rcbomb killstreaks::configure_team( "rcbomb", killstreak_id, player, "small_vehicle", undefined, &ConfigureTeamPost ); rcbomb killstreak_hacking::enable_hacking( "rcbomb", &HackedPreFunction, &HackedPostFunction ); rcbomb.damageTaken = 0; rcbomb.abandoned = false; rcbomb.killstreak_id = killstreak_id; rcbomb.activatingKillstreak = true; rcbomb SetInvisibleToAll(); rcbomb thread WatchShutdown(); rcbomb.health = killstreak_bundles::get_max_health( hardpointType ); rcbomb.maxhealth = killstreak_bundles::get_max_health( hardpointType ); rcbomb.hackedhealth = killstreak_bundles::get_hacked_health( hardpointType ); rcbomb.hackedHealthUpdateCallback = &rcbomb_hacked_health_update; rcbomb.ignore_vehicle_underneath_splash_scalar = true; self thread killstreaks::play_killstreak_start_dialog( "rcbomb", self.team, killstreak_id ); self AddWeaponStat( GetWeapon( "rcbomb" ) , "used", 1 ); remote_weapons::UseRemoteWeapon( rcbomb, "rcbomb", true, false ); if ( !isdefined( player ) || !isAlive( player ) || ( isdefined( player.laststand ) && player.laststand ) || player IsEMPJammed() ) { if ( isdefined( rcbomb ) ) { rcbomb notify( "remote_weapon_shutdown" ); rcbomb notify( "rcbomb_shutdown" ); } return false; } rcbomb SetVisibleToAll(); rcbomb.activatingKillstreak = false; Target_Set( rcbomb ); rcbomb thread WatchGameEnded(); return true; } function rcbomb_hacked_health_update( hacker ) { rcbomb = self; if ( rcbomb.health > rcbomb.hackedhealth ) { rcbomb.health = rcbomb.hackedhealth; } } function StartRemoteControl( rcbomb ) { player = self; rcbomb UseVehicle( player, 0 ); rcbomb clientfield::set( "vehicletransition", 1 ); rcbomb thread audio::sndUpdateVehicleContext(true); rcbomb thread WatchTimeout(); rcbomb thread WatchDetonation(); rcbomb thread WatchHurtTriggers(); rcbomb thread WatchWater(); player vehicle::set_vehicle_drivable_time_starting_now( ( 40 * 1000 ) ); } function EndRemoteControl( rcbomb, exitRequestedByOwner ) { if ( exitrequestedbyowner == false ) { rcbomb notify( "rcbomb_shutdown" ); rcbomb thread audio::sndUpdateVehicleContext(false); } rcbomb clientfield::set( "vehicletransition", 0 ); } function WatchDetonation() { rcbomb = self; rcbomb endon( "rcbomb_shutdown" ); rcbomb endon( "death" ); while( !rcbomb.owner attackbuttonpressed() ) { {wait(.05);}; } rcbomb notify( "rcbomb_shutdown" ); } function WatchWater() { self endon( "rcbomb_shutdown" ); inWater = false; while( !inWater ) { wait ( 0.5 ); trace = physicstrace( self.origin + ( 0, 0, 10 ), self.origin + ( 0, 0, 6 ), ( -2, -2, -2 ), ( 2, 2, 2 ), self, ( (1 << 2) )); inWater = trace["fraction"] < 1.0; } self.abandoned = true; self notify( "rcbomb_shutdown" ); } function WatchOwnerGameEvents() { self notify("WatchOwnerGameEvents_singleton"); self endon ("WatchOwnerGameEvents_singleton"); rcbomb = self; rcbomb endon( "rcbomb_shutdown" ); rcbomb.owner util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); rcbomb.abandoned = true; rcbomb notify( "rcbomb_shutdown" ); } function WatchTimeout() { rcbomb = self; rcbomb thread killstreaks::WaitForTimeout( "rcbomb", ( 40 * 1000 ), &rc_shutdown, "rcbomb_shutdown" ); } function rc_shutdown() { rcbomb = self; rcbomb notify( "rcbomb_shutdown" ); } function WatchShutdown() { rcbomb = self; rcbomb endon( "death" ); rcbomb waittill( "rcbomb_shutdown" ); if ( isdefined( rcbomb.activatingKillstreak ) && rcbomb.activatingKillstreak ) { // we can delete since it should not have been made visible yet killstreakrules::killstreakStop( "rcbomb", rcbomb.originalteam, rcbomb.killstreak_id ); rcbomb notify( "rcbomb_shutdown" ); rcbomb delete(); // still need to delete here } else { attacker = ( isdefined( rcbomb.owner ) ? rcbomb.owner : undefined ); rcbomb DoDamage( rcbomb.health + 1, rcbomb.origin + (0, 0, 10), attacker, attacker, "none", "MOD_EXPLOSIVE", 0 ); } } function WatchHurtTriggers() { rcbomb = self; rcbomb endon( "rcbomb_shutdown" ); while( true ) { rcbomb waittill ( "touch", ent ); if( isdefined( ent.classname ) && ( ent.classname == "trigger_hurt" || ent.classname == "trigger_out_of_bounds" ) ) { rcbomb notify( "rcbomb_shutdown" ); } } } function OnDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal ) { if ( self.activatingKillstreak ) { return 0.0; } if ( !isdefined( eAttacker ) || eAttacker != self.owner ) { iDamage = killstreaks::OnDamagePerWeapon( "rcbomb", eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, self.maxhealth, undefined, self.maxhealth*0.4, undefined, 0, undefined, true, 1.0 ); } if( isdefined( eAttacker ) && isdefined( eAttacker.team ) && eAttacker.team != self.team ) { if( weapon.isEmp ) { self.damage_on_death = false; self.died_by_emp = true; iDamage = self.health + 1; // destroy if hit by emp //thread remote_weapons::do_static_fx(); } } // C4 destroys the HC-XD with any damage (TODO: consider creating a more robust solution if need be) if ( weapon.name == "satchel_charge" && sMeansOfDeath == "MOD_EXPLOSIVE" ) { iDamage = self.health + 1; } return iDamage; } function OnDeath( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime ) { rcbomb = self; player = rcbomb.owner; player endon( "disconnect" ); player endon( "joined_team" ); player endon( "joined_spectators" ); killstreakrules::killstreakStop( "rcbomb", rcbomb.originalTeam, rcbomb.killstreak_id ); rcbomb clientfield::set( "enemyvehicle", 0 ); //if( isdefined( player ) && ( !isdefined( eAttacker ) || ( eAttacker.team != rcbomb.team ) ) && !level.gameEnded ) //rcbomb remote_weapons::do_static_fx(); rcbomb Explode( eAttacker, weapon ); hide_after_wait_time = ( ( rcbomb.died_by_emp === true ) ? ( 0.2 ) : ( 0.1 ) ); if( isdefined( player ) ) { player util::freeze_player_controls( true ); rcbomb thread HideAfterWait( hide_after_wait_time ); //rcbomb util::DeleteAfterTime( RCBOMB_SHUTDOWN_DELAY ); wait( ( 0.2 ) ); player util::freeze_player_controls( false ); } else { rcbomb thread HideAfterWait( hide_after_wait_time ); //rcbomb util::DeleteAfterTime( RCBOMB_SHUTDOWN_DELAY_ABANDONED ); } if ( isdefined( rcbomb ) ) rcbomb notify( "rcbomb_shutdown" ); } function WatchGameEnded() { rcbomb = self; rcbomb endon( "death" ); level waittill("game_ended"); rcbomb.abandoned = true; rcbomb.selfDestruct = true; rcbomb notify( "rcbomb_shutdown" ); } function HideAfterWait( waitTime ) { self endon( "death" ); wait waitTime; self SetInvisibleToAll(); } function Explode( attacker, weapon ) { self endon ("death"); owner = self.owner; if ( !isdefined( attacker ) && isdefined( self.owner ) ) { attacker = self.owner; } self vehicle_death::death_fx(); self thread vehicle_death::death_radius_damage(); self thread vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay ); self vehicle::toggle_tread_fx( false ); self vehicle::toggle_exhaust_fx( false ); self vehicle::toggle_sounds( false ); self vehicle::lights_off(); self PlayRumbleOnEntity( "rcbomb_explosion" ); if ( !self.abandoned && attacker != self.owner && isPlayer( attacker ) ) { attacker challenges::destroyRCBomb( weapon ); if ( self.owner util::IsEnemyPlayer( attacker ) ) { scoreevents::processScoreEvent( "destroyed_hover_rcxd", attacker, self.owner, weapon ); LUINotifyEvent( &"player_callout", 2, &"KILLSTREAK_DESTROYED_RCBOMB", attacker.entnum ); if ( isdefined( weapon ) && weapon.isValid ) { weaponStatName = "destroyed"; level.globalKillstreaksDestroyed++; // increment the destroyed stat for this, we aren't using the weaponStatName variable from above because it could be "kills" and we don't want that weapon_rcbomb = GetWeapon( "rcbomb" ); attacker AddWeaponStat( weapon_rcbomb, "destroyed", 1 ); attacker AddWeaponStat( weapon, "destroyed_controlled_killstreak", 1 ); } self killstreaks::play_destroyed_dialog_on_owner( "rcbomb", self.killstreak_id ); } } } function RCCarAllowFriendlyFireDamage( eInflictor, eAttacker, sMeansOfDeath, weapon ) { if ( isdefined( eAttacker ) && eAttacker == self.owner ) return true; if ( isdefined( eInflictor ) && eInflictor islinkedto( self ) ) return true; return false; } function GetPlacementStartHeight() { startheight = ( 50 ); switch( self GetStance() ) { case "crouch": startheight = ( 30 ); break; case "prone": startheight = ( 15 ); break; } return startheight; } function CalculateSpawnOrigin( origin, angles ) { startheight = GetPlacementStartHeight(); mins = (-5,-5,0); // keep the min z 0 so the return point is the exact height of the collision maxs = (5,5,10); startPoints = []; startAngles = []; wheelCounts = []; testCheck = []; largestCount = 0; largestCountIndex = 0; testangles = []; testangles[0] = (0,0,0); testangles[1] = (0,20,0); testangles[2] = (0,-20,0); testangles[3] = (0,45,0); testangles[4] = (0,-45,0); heightoffset = 5; for (i = 0; i < testangles.size; i++ ) { testCheck[i] = false; startAngles[i] = ( 0, angles[1], 0 ); startPoint = origin + VectorScale( anglestoforward( startAngles[i] + testangles[i]), ( 70 ) ); endPoint = startPoint - (0,0,100); startPoint = startPoint + (0,0,startheight); // ignore water on this one mask = (1 << 0) | (1 << 1); // using physicstrace so we dont slip through small cracks trace = physicstrace( startPoint, endPoint, mins, maxs, self, mask); // if any player intersection then skip if ( isdefined(trace["entity"]) && IsPlayer(trace["entity"])) { wheelCounts[i] = 0; continue; } startPoints[i] = trace["position"] + (0,0,heightoffset); wheelCounts[i] = TestWheelLocations(startPoints[i],startAngles[i],heightoffset); if ( positionWouldTelefrag( startPoints[i] ) ) continue; if ( largestCount < wheelCounts[i] ) { largestCount = wheelCounts[i]; largestCountIndex = i; } // going to early out on the first I find with valid tire positions if ( wheelCounts[i] >= 3 ) { testCheck[i] = true; if ( TestSpawnOrigin( startPoints[i], startAngles[i] ) ) { placement = SpawnStruct(); placement.origin = startPoints[i]; placement.angles = startAngles[i]; return placement; } } } for (i = 0; i < testangles.size; i++ ) { if ( !testCheck[i] ) { if ( wheelCounts[i] >= 2 ) { if ( TestSpawnOrigin( startPoints[i], startAngles[i] ) ) { placement = SpawnStruct(); placement.origin = startPoints[i]; placement.angles = startAngles[i]; return placement; } } } } return undefined; } function TestWheelLocations( origin, angles, heightoffset ) { forward = 13; side = 10; wheels = []; wheels[0] = ( forward, side, 0 ); wheels[1] = ( forward, -1 * side, 0 ); wheels[2] = ( -1 * forward, -1 * side, 0 ); wheels[3] = ( -1 * forward, side, 0 ); height = 5; touchCount = 0; yawangles = (0,angles[1],0); for (i = 0; i < 4; i++ ) { wheel = RotatePoint( wheels[i], yawangles ); startPoint = origin + wheel; endPoint = startPoint + (0,0,(-1 * height) - heightoffset); startPoint = startPoint + (0,0,height - heightoffset) ; trace = bulletTrace( startPoint, endPoint, false, self ); if ( trace["fraction"] < 1 ) { touchCount++; } } return touchCount; } function TestSpawnOrigin( origin, angles ) { liftedorigin = origin + (0,0,5); size = 12; height = 15; mins = (-1 * size,-1 * size,0 ); maxs = ( size,size,height ); absmins = liftedorigin + mins; absmaxs = liftedorigin + maxs; if( BoundsWouldTelefrag( absmins, absmaxs ) ) { return false; } startheight = getPlacementStartHeight(); mask = (1 << 0) | (1 << 1) | (1 << 2); // test the volume where we are going to place the car // note that this physics trace is not an oriented box. trace = physicstrace( liftedorigin, (origin +(0,0,1)), mins, maxs, self, mask); if ( trace["fraction"] < 1 ) { return false; } // swept trace of a small bounding box from head height to where we are placing the car // to make sure there is no wall between us and the car size = 2.5; height = size * 2; mins = (-1 * size,-1 * size,0 ); maxs = ( size,size,height ); sweeptrace = physicstrace( (self.origin + (0,0,startheight)), liftedorigin, mins, maxs, self, mask); if ( sweeptrace["fraction"] < 1 ) { return false; } return true; }