iw6-scripts-dev/maps/mp/killstreaks/_deployablebox.gsc
2024-12-11 11:28:08 +01:00

846 lines
20 KiB
Plaintext

#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;
/*
Deployable box killstreaks: the player will be able to place a box in the world and teammates can grab items from it
this will be used on multiple killstreaks where you can place a box in the world with something in it
*/
BOX_TIMEOUT_UPDATE_INTERVAL = 1.0;
DEFAULT_USE_TIME = 3000;
BOX_DEFAULT_HEALTH = 999999; // so that boxes aren't killed in code
init()
{
if ( !IsDefined( level.boxSettings ) )
{
level.boxSettings = [];
}
}
///////////////////////////////////////////////////
// MARKER FUNCTIONS
// 2012-06-21 wallace
// Stole an updated version from _uplink.gsc. Should probably unify all these funcs eventually
//////////////////////////////////////////////////
beginDeployableViaMarker( lifeId, boxType )
{
self thread watchDeployableMarkerCancel( boxType );
self thread watchDeployableMarkerPlacement( boxType, lifeId );
while ( true )
{
result = self waittill_any_return( "deployable_canceled", "deployable_deployed", "death", "disconnect" );
return ( result == "deployable_deployed" );
}
}
tryUseDeployable( lifeId, boxType ) // self == player
{
self thread watchDeployableMarkerCancel( boxType );
self thread watchDeployableMarkerPlacement( boxType, lifeId );
while ( true )
{
result = self waittill_any_return( "deployable_canceled", "deployable_deployed", "death", "disconnect" );
return ( result == "deployable_deployed" );
}
}
watchDeployableMarkerCancel( boxType )
{
self endon( "death" );
self endon( "disconnect" );
self endon( "deployable_deployed" );
boxConfig = level.boxSettings[ boxType ];
currentWeapon = self getCurrentWeapon();
while( currentWeapon == boxConfig.weaponInfo )
{
self waittill( "weapon_change", currentWeapon );
}
self notify( "deployable_canceled" );
}
watchDeployableMarkerPlacement( boxType, lifeId )
{
self endon( "spawned_player" ); // you shouldn't do endon( "death" ) here because this thread needs to run
self endon( "disconnect" );
self endon( "deployable_canceled" );
while( true )
{
self waittill( "grenade_fire", marker, weaponName );
if( isReallyAlive(self) )
{
break;
}
else
{
marker Delete();
}
}
marker MakeCollideWithItemClip( true );
self notify( "deployable_deployed" );
marker.owner = self;
marker.weaponName = weaponName;
self.marker = marker;
marker PlaySoundToPlayer( level.boxSettings[ boxType ].deployedSfx, self );
marker thread markerActivate( lifeId, boxType, ::box_setActive );
}
override_box_moving_platform_death( data )
{
self notify( "death" ); // we're doing this here instead of letting the mover code just delete us so that we can run our necessary clean-up functionality (like removal of the objective marker from the minimap)
}
markerActivate( lifeId, boxType, usedCallback ) // self == marker
{
self notify( "markerActivate" );
self endon( "markerActivate" );
//self waittill( "explode", position );
self waittill( "missile_stuck" );
owner = self.owner;
position = self.origin;
if ( !isDefined( owner ) )
return;
box = createBoxForPlayer( boxType, position, owner );
// For moving platforms.
data = SpawnStruct();
data.linkParent = self GetLinkedParent();
//fixes wall hack exploit with linked items
if ( isDefined( data.linkParent ) && isDefined( data.linkParent.model ) && DeployableExclusion( data.linkParent.model ) )
{
box.origin = data.linkParent.origin;
grandParent = data.linkParent GetLinkedParent();
if ( isDefined( grandParent ) )
data.linkParent = grandParent;
else
data.linkParent = undefined;
}
data.deathOverrideCallback = ::override_box_moving_platform_death;
box thread maps\mp\_movers::handle_moving_platforms( data );
box.moving_platform = data.linkParent;
box SetOtherEnt(owner);
// ES - 2/24/14 - This waitframe is causing an issue where, when deployed on a moving platform, the "death" notification is sent instantly, but is never caught.
wait 0.05;
//self playSound( "sentry_gun_beep" );
box thread [[ usedCallback ]]();
self delete();
if( IsDefined(box) && (box touchingBadTrigger()) )
{
box notify( "death" );
}
}
DeployableExclusion( parentModel )
{
if ( parentModel == "mp_satcom" )
return true;
else if ( IsSubStr( parentModel, "paris_catacombs_iron" ) )
return true;
else if ( IsSubStr( parentModel, "mp_warhawk_iron_gate" ) )
return true;
return false;
}
isHoldingDeployableBox()
{
curWeap = self GetCurrentWeapon();
if ( IsDefined( curWeap ) )
{
foreach( deplBoxWeap in level.boxSettings )
{
if ( curWeap == deplBoxWeap.weaponInfo )
return true;
}
}
return false;
}
///////////////////////////////////////////////////
// END MARKER FUNCTIONS
//////////////////////////////////////////////////
///////////////////////////////////////////////////
// BOX HANDLER FUNCTIONS
//////////////////////////////////////////////////
createBoxForPlayer( boxType, position, owner )
{
assertEx( isDefined( owner ), "createBoxForPlayer() called without owner specified" );
boxConfig = level.boxSettings[ boxType ];
box = Spawn( "script_model", position - (0,0,1) );
box setModel( boxConfig.modelBase );
box.health = BOX_DEFAULT_HEALTH;
box.maxHealth = boxConfig.maxHealth;
box.angles = owner.angles;
box.boxType = boxType;
box.owner = owner;
box.team = owner.team;
box.id = boxConfig.id;
if ( IsDefined( boxConfig.dpadName ) )
{
box.dpadName = boxConfig.dpadName;
}
if ( IsDefined( boxConfig.maxUses ) )
{
box.usesRemaining = boxConfig.maxUses;
}
box box_setInactive();
box thread box_handleOwnerDisconnect();
box addBoxToLevelArray();
return box;
}
box_setActive( skipOwnerUse ) // self == box
{
self setCursorHint( "HINT_NOICON" );
boxConfig = level.boxSettings[ self.boxType ];
self setHintString( boxConfig.hintString );
self.inUse = false;
curObjID = maps\mp\gametypes\_gameobjects::getNextObjID();
Objective_Add( curObjID, "invisible", (0,0,0) );
if ( !IsDefined( self GetLinkedParent() ) )
Objective_Position( curObjID, self.origin );
else
Objective_OnEntity( curObjID, self );
Objective_State( curObjID, "active" );
Objective_Icon( curObjID, boxConfig.shaderName );
self.objIdFriendly = curObjID;
if ( level.teamBased )
{
Objective_Team( curObjID, self.team );
foreach ( player in level.players )
{
if ( self.team == player.team
&& (!IsDefined(boxConfig.canUseCallback) || player [[ boxConfig.canUseCallback ]](self) )
)
{
self box_SetIcon( player, boxConfig.streakName, boxConfig.headIconOffset );
}
}
}
else
{
Objective_Player( curObjID, self.owner GetEntityNumber() );
if( !IsDefined(boxConfig.canUseCallback) || self.owner [[ boxConfig.canUseCallback ]](self) )
{
self box_SetIcon( self.owner, boxConfig.streakName, boxConfig.headIconOffset );
}
}
self MakeUsable();
self.isUsable = true;
self SetCanDamage( true );
self thread box_handleDamage();
self thread box_handleDeath();
self thread box_timeOut();
self thread disableWhenJuggernaut();
self make_entity_sentient_mp( self.team, true );
if ( IsSentient( self ) )
{
self SetThreatBiasGroup( "DogsDontAttack" );
}
if ( IsDefined( self.owner ) )
self.owner notify( "new_deployable_box", self );
if (level.teamBased)
{
foreach ( player in level.participants )
{
_box_setActiveHelper( player, self.team == player.team, boxConfig.canUseCallback );
// handle team switches for human players
if ( !IsAI( player ) )
{
self thread box_playerJoinedTeam( player );
}
}
}
else
{
foreach ( player in level.participants )
{
_box_setActiveHelper( player, IsDefined( self.owner ) && self.owner == player, boxConfig.canUseCallback );
}
}
level thread teamPlayerCardSplash( boxConfig.splashName, self.owner, self.team );
self thread box_playerConnected();
self thread box_agentConnected();
if ( IsDefined( boxConfig.onDeployCallback ) )
{
self [[ boxConfig.onDeployCallback ]]( boxConfig );
}
self thread createBombSquadModel( self.boxType );
}
_box_setActiveHelper( player, bActivate, canUseFunc )
{
if ( bActivate )
{
if ( !IsDefined( canUseFunc ) || player [[ canUseFunc ]](self) )
{
self box_enablePlayerUse( player );
}
else
{
self box_disablePlayerUse( player );
// if this player is already a juggernaut then when they die, let them use the box
self thread doubleDip( player );
}
self thread boxThink( player );
}
else
{
self box_disablePlayerUse( player );
}
}
box_playerConnected() // self == box
{
self endon( "death" );
// when new players connect they need a boxthink thread run on them
while( true )
{
level waittill( "connected", player );
self childthread box_waittill_player_spawn_and_add_box( player );
}
}
box_agentConnected() // self == box
{
self endon( "death" );
// when new agents connect they need a boxthink thread run on them
while( true )
{
level waittill( "spawned_agent_player", agent );
self box_addBoxForPlayer( agent );
}
}
box_waittill_player_spawn_and_add_box( player ) // self == box
{
player waittill( "spawned_player" );
if ( level.teamBased )
{
self box_addBoxForPlayer( player );
// handle team switches for late joins
self thread box_playerJoinedTeam( player );
}
}
box_playerJoinedTeam( player ) // self == box
{
self endon( "death" );
player endon( "disconnect" );
// when new players connect they need a boxthink thread run on them
while( true )
{
player waittill( "joined_team" );
if ( level.teamBased )
{
self box_addBoxForPlayer( player );
}
}
}
box_addBoxForPlayer( player ) // self == box
{
if ( self.team == player.team )
{
self box_enablePlayerUse( player );
self thread boxThink( player );
self box_SetIcon( player, level.boxSettings[ self.boxType ].streakName, level.boxSettings[ self.boxType ].headIconOffset );
}
else
{
self box_disablePlayerUse( player );
self maps\mp\_entityheadIcons::setHeadIcon( player, "", (0,0,0) );
}
}
box_SetIcon( player, streakName, vOffset )
{
self maps\mp\_entityheadIcons::setHeadIcon( player, getKillstreakOverheadIcon( streakName ), (0, 0, vOffset), 14, 14, undefined, undefined, undefined, undefined, undefined, false );
}
box_enablePlayerUse( player ) // self == box
{
if ( IsPlayer(player) )
self EnablePlayerUse( player );
self.disabled_use_for[player GetEntityNumber()] = false;
}
box_disablePlayerUse( player ) // self == box
{
if ( IsPlayer(player) )
self DisablePlayerUse( player );
self.disabled_use_for[player GetEntityNumber()] = true;
}
box_setInactive()
{
self makeUnusable();
self.isUsable = false;
self maps\mp\_entityheadIcons::setHeadIcon( "none", "", (0,0,0) );
if ( isDefined( self.objIdFriendly ) )
_objective_delete( self.objIdFriendly );
}
box_handleDamage() // self == box
{
boxConfig = level.boxSettings[ self.boxType ];
self maps\mp\gametypes\_damage::monitorDamage(
boxConfig.maxHealth,
boxConfig.damageFeedback,
::box_handleDeathDamage,
::box_ModifyDamage,
true // isKillstreak
);
}
box_ModifyDamage( attacker, weapon, type, damage )
{
modifiedDamage = damage;
boxConfig = level.boxSettings[ self.boxType ];
if ( boxConfig.allowMeleeDamage )
{
modifiedDamage = self maps\mp\gametypes\_damage::handleMeleeDamage( weapon, type, modifiedDamage );
}
modifiedDamage = self maps\mp\gametypes\_damage::handleMissileDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleGrenadeDamage( weapon, type, modifiedDamage );
modifiedDamage = self maps\mp\gametypes\_damage::handleAPDamage( weapon, type, modifiedDamage, attacker );
return modifiedDamage;
}
box_handleDeathDamage( attacker, weapon, type, damage )
{
boxConfig = level.boxSettings[ self.boxType ];
notifyAttacker = self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, type, damage, boxconfig.xpPopup, boxConfig.voDestroyed );
if ( notifyAttacker )
{
attacker notify( "destroyed_equipment" );
}
}
box_handleDeath()
{
self waittill ( "death" );
// this handles cases of deletion
if ( !isDefined( self ) )
return;
self box_setInactive();
self removeBoxFromLevelArray();
boxConfig = level.boxSettings[ self.boxType ];
PlayFX( boxConfig.deathVfx, self.origin );
self PlaySound( "mp_killstreak_disappear" );
// 2013-03-08 wsh: whould probably validate all the used fields...
if ( IsDefined( boxConfig.deathDamageMax ) )
{
owner = undefined;
if ( IsDefined(self.owner) )
owner = self.owner;
// somewhat hacky:
// shift the origin of the damage because it'll collide with the box otherwise
// we could also apply the damage after we delete the item?
RadiusDamage( self.origin + (0, 0, boxConfig.headIconOffset),
boxConfig.deathDamageRadius,
boxConfig.deathDamageMax,
boxConfig.deathDamageMin,
owner,
"MOD_EXPLOSIVE",
boxConfig.deathWeaponInfo
);
}
self notify( "deleting" );
self delete();
}
box_handleOwnerDisconnect() // self == box
{
self endon ( "death" );
level endon ( "game_ended" );
self notify ( "box_handleOwner" );
self endon ( "box_handleOwner" );
self.owner waittill( "killstreak_disowned" );
self notify( "death" );
}
boxThink( player )
{
self endon ( "death" );
self thread boxCaptureThink( player );
if ( !IsDefined(player.boxes) )
{
player.boxes = [];
}
player.boxes[player.boxes.size] = self;
boxConfig = level.boxSettings[ self.boxType ];
for ( ;; )
{
self waittill ( "captured", capturer );
if (capturer == player)
{
player PlayLocalSound( boxConfig.onUseSfx );
if ( IsDefined( boxConfig.onuseCallback ) )
{
player [[ boxConfig.onUseCallback ]]( self );
}
// if this is not the owner then give the owner some xp
if( IsDefined( self.owner ) && player != self.owner )
{
self.owner thread maps\mp\gametypes\_rank::xpEventPopup( boxConfig.event );
self.owner thread maps\mp\gametypes\_rank::giveRankXP( "support", boxConfig.useXP );
}
if ( IsDefined( self.usesRemaining ) )
{
self.usesRemaining--;
if ( self.usesRemaining == 0)
{
self box_leave();
break;
}
}
if ( IsDefined( boxConfig.canUseOtherBoxes ) && boxConfig.canUseOtherBoxes )
{
// don't let the player use any other boxes
foreach( box in level.deployable_box[ boxConfig.streakName ] )
{
box maps\mp\killstreaks\_deployablebox::box_disablePlayerUse( self );
box maps\mp\_entityheadIcons::setHeadIcon( self, "", (0,0,0) );
box thread maps\mp\killstreaks\_deployablebox::doubleDip( self );
}
}
else
{
self maps\mp\_entityheadIcons::setHeadIcon( player, "", (0,0,0) );
self box_disablePlayerUse( player );
self thread doubleDip( player );
}
}
}
}
doubleDip( player ) // self == box
{
self endon( "death" );
player endon( "disconnect" );
// once they die, let them take from the box again
player waittill( "death" );
if( level.teamBased )
{
if( self.team == player.team )
{
self box_SetIcon( player, level.boxSettings[ self.boxType ].streakName, level.boxSettings[ self.boxType ].headIconOffset );
self box_enablePlayerUse( player );
}
}
else
{
if( IsDefined( self.owner ) && self.owner == player )
{
self box_SetIcon( player, level.boxSettings[ self.boxType ].streakName, level.boxSettings[ self.boxType ].headIconOffset );
self box_enablePlayerUse( player );
}
}
}
boxCaptureThink( player ) // self == box
{
level endon( "game_ended" );
while( isDefined( self ) )
{
self waittill( "trigger", tiggerer );
if (
IsDefined( level.boxSettings[ self.boxType ].noUseKillstreak )
&& level.boxSettings[ self.boxType ].noUseKillstreak
&& isKillstreakWeapon( player GetCurrentWeapon() )
)
{
continue;
}
if ( tiggerer == player && self useHoldThink( player, level.boxSettings[ self.boxType ].useTime ) )
{
self notify( "captured", player );
}
}
}
isFriendlyToBox( box )
{
return ( level.teamBased
&& self.team == box.team );
}
box_timeOut() // self == box
{
self endon( "death" );
level endon ( "game_ended" );
boxConfig = level.boxSettings[ self.boxType ];
lifeSpan = boxConfig.lifeSpan;
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( lifeSpan );
if ( IsDefined( boxConfig.voGone ) )
{
self.owner thread leaderDialogOnPlayer( boxConfig.voGone );
}
self box_leave();
}
box_leave()
{
// TODO: get sound for this
//if ( isDefined( self.owner ) )
// self.owner thread leaderDialogOnPlayer( "sentry_gone" );
wait( 0.05 );
self notify( "death" );
}
deleteOnOwnerDeath( owner ) // self == box.friendlyModel or box.enemyModel, owner == box
{
wait ( 0.25 );
self linkTo( owner, "tag_origin", (0,0,0), (0,0,0) );
owner waittill ( "death" );
box_leave();
}
box_ModelTeamUpdater( showForTeam ) // self == box model (enemy or friendly)
{
self endon ( "death" );
self hide();
foreach ( player in level.players )
{
if ( player.team == showForTeam )
self showToPlayer( player );
}
for ( ;; )
{
level waittill ( "joined_team" );
self hide();
foreach ( player in level.players )
{
if ( player.team == showForTeam )
self showToPlayer( player );
}
}
}
useHoldThink( player, useTime )
{
self maps\mp\_movers::script_mover_link_to_use_object( player );
player _disableWeapon();
player.boxParams = SpawnStruct();
player.boxParams.curProgress = 0;
player.boxParams.inUse = true;
player.boxParams.useRate = 0;
player.boxParams.id = self.id;
if ( isDefined( useTime ) )
{
player.boxParams.useTime = useTime;
}
else
{
player.boxParams.useTime = DEFAULT_USE_TIME;
}
result = useHoldThinkLoop( player );
Assert( IsDefined( result ) );
if ( isAlive( player ) )
{
player _enableWeapon();
maps\mp\_movers::script_mover_unlink_from_use_object( player );
}
if ( !isDefined( self ) )
return false;
player.boxParams.inUse = false;
player.boxParams.curProgress = 0;
return ( result );
}
useHoldThinkLoop( player )
{
config = player.boxParams;
while( player isPlayerUsingBox( config ) )
{
if ( !player maps\mp\_movers::script_mover_use_can_link( self ) )
{
player maps\mp\gametypes\_gameobjects::updateUIProgress( config, false );
return false;
}
config.curProgress += (50 * config.useRate);
if ( isDefined( player.objectiveScaler ) )
config.useRate = 1 * player.objectiveScaler;
else
config.useRate = 1;
player maps\mp\gametypes\_gameobjects::updateUIProgress( config, true );
if ( config.curProgress >= config.useTime )
{
player maps\mp\gametypes\_gameobjects::updateUIProgress( config, false );
return ( isReallyAlive( player ) );
}
wait 0.05;
}
player maps\mp\gametypes\_gameobjects::updateUIProgress( config, false );
return false;
}
disableWhenJuggernaut() // self == box
{
level endon( "game_ended" );
self endon( "death" );
while( true )
{
level waittill( "juggernaut_equipped", player );
self maps\mp\_entityheadIcons::setHeadIcon( player, "", (0,0,0) );
self box_disablePlayerUse( player );
self thread doubleDip( player );
}
}
addBoxToLevelArray() // self == box
{
// put the newly created box in the level array for the box type
level.deployable_box[ self.boxType ][ self GetEntityNumber() ] = self;
}
removeBoxFromLevelArray() // self == box
{
level.deployable_box[ self.boxType ][ self GetEntityNumber() ] = undefined;
}
createBombSquadModel( streakName ) // self == box
{
config = level.boxSettings[ streakName ];
if ( IsDefined( config.modelBombSquad ) )
{
bombSquadModel = Spawn( "script_model", self.origin );
bombSquadModel.angles = self.angles;
bombSquadModel Hide();
bombSquadModel thread maps\mp\gametypes\_weapons::bombSquadVisibilityUpdater( self.owner );
bombSquadModel SetModel( config.modelBombSquad );
bombSquadModel LinkTo( self );
bombSquadModel SetContents( 0 );
self.bombSquadModel = bombSquadModel;
self waittill ( "death" );
// Could have been deleted when the player was carrying the ims and died
if ( IsDefined( bombSquadModel ) )
{
bombSquadModel delete();
self.bombSquadModel = undefined;
}
}
}
isPlayerUsingBox( box ) // self == player
{
return ( !level.gameEnded
&& IsDefined( box )
&& isReallyAlive( self ) && self UseButtonPressed()
&& !( self IsOnLadder() ) // fix for 154828
&& !( self MeleeButtonPressed() ) // from gameobjects.gsc
&& !( IsDefined( self.throwingGrenade ) ) // from gameobjects.gsc
&& box.curProgress < box.useTime
&& ( !IsDefined( self.teleporting ) || !self.teleporting )
);
}