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

682 lines
28 KiB
Plaintext

#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;
}