705 lines
16 KiB
Plaintext
705 lines
16 KiB
Plaintext
#include maps\mp\_utility;
|
|
#include maps\mp\gametypes\_hud_util;
|
|
#include common_scripts\utility;
|
|
|
|
init()
|
|
{
|
|
if ( !IsDefined( level.placeableConfigs ) )
|
|
{
|
|
level.placeableConfigs = [];
|
|
}
|
|
}
|
|
|
|
givePlaceable( streakName ) // self == player
|
|
{
|
|
placeable = self createPlaceable( streakName );
|
|
|
|
// returning from this streak activation seems to strip this?
|
|
// manually removing and restoring
|
|
self removePerks();
|
|
|
|
self.carriedItem = placeable;
|
|
|
|
result = self onBeginCarrying( streakName, placeable, true );
|
|
|
|
self.carriedItem = undefined;
|
|
|
|
self restorePerks();
|
|
|
|
// if the placeable exist, then it was placed
|
|
return ( IsDefined( placeable ) );
|
|
}
|
|
|
|
createPlaceable( streakName )
|
|
{
|
|
if( IsDefined( self.isCarrying ) && self.isCarrying )
|
|
return;
|
|
|
|
config = level.placeableConfigs[ streakName ];
|
|
|
|
obj = Spawn( "script_model", self.origin );
|
|
obj SetModel( config.modelBase );
|
|
obj.angles = self.angles;
|
|
obj.owner = self;
|
|
obj.team = self.team;
|
|
obj.config = config;
|
|
obj.firstPlacement = true;
|
|
|
|
/*
|
|
obj = SpawnTurret( "misc_turret", self.origin + ( 0, 0, 25 ), "sentry_minigun_mp" );
|
|
|
|
obj.angles = self.angles;
|
|
obj.owner = self;
|
|
|
|
obj SetModel( config.modelBase );
|
|
|
|
obj MakeTurretInoperable();
|
|
obj SetTurretModeChangeWait( true );
|
|
obj SetMode( "sentry_offline" );
|
|
obj MakeUnusable();
|
|
obj SetSentryOwner( self );
|
|
*/
|
|
|
|
// inits happen here
|
|
if ( IsDefined( config.onCreateDelegate ) )
|
|
{
|
|
obj [[ config.onCreateDelegate ]]( streakName );
|
|
}
|
|
|
|
obj deactivate( streakName );
|
|
|
|
obj thread timeOut( streakName );
|
|
obj thread handleUse( streakName );
|
|
|
|
obj thread onKillstreakDisowned( streakName );
|
|
obj thread onGameEnded( streakName );
|
|
|
|
obj thread createBombSquadModel( streakName );
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
handleUse( streakName ) // self == placeable
|
|
{
|
|
self endon ( "death" );
|
|
level endon ( "game_ended" );
|
|
|
|
while ( true )
|
|
{
|
|
self waittill ( "trigger", player );
|
|
|
|
assert( player == self.owner );
|
|
assert( !IsDefined( self.carriedBy ) );
|
|
|
|
if ( !isReallyAlive( player ) )
|
|
continue;
|
|
|
|
if ( IsDefined( self GetLinkedParent() ) )
|
|
{
|
|
self Unlink();
|
|
}
|
|
|
|
// why does the IMS create a second one?
|
|
|
|
player onBeginCarrying( streakName, self, false );
|
|
}
|
|
}
|
|
|
|
// setCarrying
|
|
onBeginCarrying( streakName, placeable, allowCancel ) // self == player
|
|
{
|
|
self endon ( "death" );
|
|
self endon ( "disconnect" );
|
|
|
|
assert( isReallyAlive( self ) );
|
|
|
|
placeable thread onCarried( streakName, self );
|
|
|
|
self _disableWeapon();
|
|
|
|
if ( !IsAI(self) ) // Bots handle these internally
|
|
{
|
|
self notifyOnPlayerCommand( "placePlaceable", "+attack" );
|
|
self notifyOnPlayerCommand( "placePlaceable", "+attack_akimbo_accessible" ); // support accessibility control scheme
|
|
self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 4" );
|
|
if( !level.console )
|
|
{
|
|
self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 5" );
|
|
self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 6" );
|
|
self notifyOnPlayerCommand( "cancelPlaceable", "+actionslot 7" );
|
|
}
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
result = waittill_any_return( "placePlaceable", "cancelPlaceable", "force_cancel_placement" );
|
|
|
|
// object was deleted
|
|
if ( !IsDefined( placeable ) )
|
|
{
|
|
self _enableWeapon();
|
|
return true;
|
|
}
|
|
// !!! 2013-08-08 wallace: this force cancel problem is really ugly
|
|
// it's also being used to indicate player wading in water, which we should use the "bad placement" model instead of canceling outright. But it's too late to fix now.
|
|
else if ( (result == "cancelPlaceable" && allowCancel)
|
|
|| result == "force_cancel_placement" )
|
|
{
|
|
placeable onCancel( streakName, result == "force_cancel_placement" && !IsDefined( placeable.firstPlacement ) );
|
|
return false;
|
|
}
|
|
else if ( placeable.canBePlaced )
|
|
{
|
|
placeable thread onPlaced( streakName );
|
|
self _enableWeapon();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
onCancel( streakName, playDestroyVfx ) // self == placeable
|
|
{
|
|
if( IsDefined( self.carriedBy ) )
|
|
{
|
|
owner = self.carriedBy;
|
|
owner ForceUseHintOff();
|
|
owner.isCarrying = undefined;
|
|
|
|
owner.carriedItem = undefined;
|
|
|
|
owner _enableWeapon();
|
|
}
|
|
|
|
if( IsDefined( self.bombSquadModel ) )
|
|
{
|
|
self.bombSquadModel Delete();
|
|
}
|
|
|
|
if ( IsDefined( self.carriedObj ) )
|
|
{
|
|
self.carriedObj Delete();
|
|
}
|
|
|
|
config = level.placeableConfigs[ streakName ];
|
|
if ( IsDefined( config.onCancelDelegate ) )
|
|
{
|
|
self [[ config.onCancelDelegate ]]( streakName );
|
|
}
|
|
|
|
if ( IsDefined( playDestroyVfx ) && playDestroyVfx )
|
|
{
|
|
self maps\mp\gametypes\_weapons::equipmentDeleteVfx();
|
|
}
|
|
|
|
self Delete();
|
|
}
|
|
|
|
onPlaced( streakName ) // self == placeable
|
|
{
|
|
config = level.placeableConfigs[ streakName ];
|
|
|
|
self.origin = self.placementOrigin;
|
|
self.angles = self.carriedObj.angles;
|
|
|
|
self PlaySound( config.placedSfx );
|
|
|
|
self showPlacedModel( streakName );
|
|
|
|
if ( IsDefined( config.onPlacedDelegate ) )
|
|
{
|
|
self [[ config.onPlacedDelegate ]]( streakName );
|
|
}
|
|
|
|
self setCursorHint( "HINT_NOICON" );
|
|
self setHintString( config.hintString );
|
|
|
|
owner = self.owner;
|
|
owner ForceUseHintOff();
|
|
owner.isCarrying = undefined;
|
|
self.carriedBy = undefined;
|
|
self.isPlaced = true;
|
|
self.firstPlacement = undefined;
|
|
|
|
if ( IsDefined( config.headIconHeight ) )
|
|
{
|
|
if ( level.teamBased )
|
|
{
|
|
self maps\mp\_entityheadicons::setTeamHeadIcon( self.team, (0,0,config.headIconHeight) );
|
|
}
|
|
else
|
|
{
|
|
self maps\mp\_entityheadicons::setPlayerHeadIcon( owner, (0,0,config.headIconHeight) );
|
|
}
|
|
}
|
|
|
|
self thread handleDamage( streakName );
|
|
self thread handleDeath( streakName );
|
|
|
|
self MakeUsable();
|
|
|
|
self make_entity_sentient_mp( self.owner.team );
|
|
if ( IsSentient( self ) )
|
|
{
|
|
self SetThreatBiasGroup( "DogsDontAttack" );
|
|
}
|
|
|
|
foreach ( player in level.players )
|
|
{
|
|
if( player == owner )
|
|
self EnablePlayerUse( player );
|
|
else
|
|
self DisablePlayerUse( player );
|
|
}
|
|
|
|
if( IsDefined( self.shouldSplash ) )
|
|
{
|
|
level thread teamPlayerCardSplash( config.splashName, owner );
|
|
self.shouldSplash = false;
|
|
}
|
|
|
|
// Moving platforms.
|
|
data = SpawnStruct();
|
|
data.linkParent = self.moving_platform;
|
|
data.playDeathFx = true;
|
|
data.endonString = "carried";
|
|
if ( IsDefined( config.onMovingPlatformCollision ) )
|
|
{
|
|
data.deathOverrideCallback = config.onMovingPlatformCollision;
|
|
}
|
|
self thread maps\mp\_movers::handle_moving_platforms( data );
|
|
|
|
self thread watchPlayerConnected();
|
|
|
|
self notify ( "placed" );
|
|
|
|
self.carriedObj Delete();
|
|
self.carriedObj = undefined;
|
|
}
|
|
|
|
onCarried( streakName, carrier ) // self == placeable
|
|
{
|
|
config = level.placeableConfigs[ streakName ];
|
|
|
|
assert( isPlayer( carrier ) );
|
|
assertEx( carrier == self.owner, "_placeable::onCarried: specified carrier does not own this ims" );
|
|
|
|
self.carriedObj = carrier createCarriedObject( streakName );
|
|
|
|
// self SetModel( config.modelPlacement );
|
|
|
|
self.isPlaced = undefined;
|
|
self.carriedBy = carrier;
|
|
carrier.isCarrying = true;
|
|
|
|
self deactivate( streakName );
|
|
|
|
self hidePlacedModel( streakName );
|
|
|
|
if ( IsDefined( config.onCarriedDelegate ) )
|
|
{
|
|
self [[ config.onCarriedDelegate ]]( streakName );
|
|
}
|
|
|
|
self thread updatePlacement( streakName, carrier );
|
|
|
|
self thread onCarrierDeath( streakName, carrier );
|
|
|
|
self notify ( "carried" );
|
|
}
|
|
|
|
updatePlacement( streakName, carrier ) // self == placeable
|
|
{
|
|
carrier endon ( "death" );
|
|
carrier endon ( "disconnect" );
|
|
level endon ( "game_ended" );
|
|
|
|
self endon ( "placed" );
|
|
self endon ( "death" );
|
|
|
|
self.canBePlaced = true;
|
|
prevCanBePlaced = -1; // force initial update
|
|
|
|
config = level.placeableConfigs[ streakName ];
|
|
|
|
// allow the visuals be raised up slightly (e.g. iw6 Sat Com, so that player can see it)
|
|
placementOffset = (0 ,0, 0);
|
|
if ( IsDefined( config.placementOffsetZ ) )
|
|
{
|
|
placementOffset = (0 ,0 ,config.placementOffsetZ);
|
|
}
|
|
|
|
carriedObj = self.carriedObj;
|
|
|
|
while ( true )
|
|
{
|
|
placement = carrier CanPlayerPlaceSentry( true, config.placementRadius );
|
|
|
|
// NOTE TO SELF: Talk to Simon C about how to get vertical offset / additional rotation working with client prediction
|
|
self.placementOrigin = placement[ "origin" ];
|
|
carriedObj.origin = self.placementOrigin + placementOffset;
|
|
carriedObj.angles = placement[ "angles" ];
|
|
|
|
self.canBePlaced = carrier IsOnGround()
|
|
&& placement[ "result" ]
|
|
&& ( abs(self.placementOrigin[2] - carrier.origin[2]) < config.placementHeightTolerance );
|
|
|
|
if ( isdefined( placement[ "entity" ] ) )
|
|
{
|
|
self.moving_platform = placement[ "entity" ];
|
|
}
|
|
else
|
|
{
|
|
self.moving_platform = undefined;
|
|
}
|
|
|
|
if ( self.canBePlaced != prevCanBePlaced )
|
|
{
|
|
if ( self.canBePlaced )
|
|
{
|
|
carriedObj SetModel( config.modelPlacement );
|
|
carrier ForceUseHintOn( config.placeString );
|
|
}
|
|
else
|
|
{
|
|
carriedObj SetModel( config.modelPlacementFailed );
|
|
carrier ForceUseHintOn( config.cannotPlaceString );
|
|
}
|
|
}
|
|
|
|
prevCanBePlaced = self.canBePlaced;
|
|
wait ( 0.05 );
|
|
}
|
|
}
|
|
|
|
deactivate( streakName ) // self == placeable
|
|
{
|
|
self MakeUnusable();
|
|
|
|
self hideHeadIcons();
|
|
|
|
self FreeEntitySentient();
|
|
|
|
config = level.placeableConfigs[ streakName ];
|
|
if ( IsDefined( config.onDeactiveDelegate ) )
|
|
{
|
|
self [[ config.onDeactiveDelegate ]]( streakName );
|
|
}
|
|
}
|
|
|
|
hideHeadIcons()
|
|
{
|
|
if ( level.teamBased )
|
|
{
|
|
self maps\mp\_entityheadicons::setTeamHeadIcon( "none", ( 0, 0, 0 ) );
|
|
}
|
|
else if ( IsDefined( self.owner ) )
|
|
{
|
|
self maps\mp\_entityheadicons::setPlayerHeadIcon( undefined, ( 0, 0, 0 ) );
|
|
}
|
|
}
|
|
|
|
// important callbacks:
|
|
// onDamagedDelegate - filter out or amplify damage based on specifics
|
|
// onDestroyedDelegate - any extra handling when the object is killed
|
|
handleDamage( streakName ) // self == placeable
|
|
{
|
|
self endon( "carried" );
|
|
|
|
config = level.placeableConfigs[ streakName ];
|
|
|
|
self maps\mp\gametypes\_damage::monitorDamage(
|
|
config.maxHealth,
|
|
config.damageFeedback,
|
|
::handleDeathDamage,
|
|
::modifyDamage,
|
|
true // isKillstreak
|
|
);
|
|
}
|
|
|
|
modifyDamage( attacker, weapon, type, damage )
|
|
{
|
|
modifiedDamage = damage;
|
|
|
|
config = self.config;
|
|
if ( IsDefined( config.allowMeleeDamage ) && config.allowMeleeDamage )
|
|
{
|
|
modifiedDamage = self maps\mp\gametypes\_damage::handleMeleeDamage( weapon, type, modifiedDamage );
|
|
}
|
|
|
|
if ( IsDefined( config.allowEmpDamage ) && config.allowEmpDamage )
|
|
{
|
|
modifiedDamage = self maps\mp\gametypes\_damage::handleEmpDamage( 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 );
|
|
|
|
if ( IsDefined( config.modifyDamage ) )
|
|
{
|
|
modifiedDamage = self [[ config.modifyDamage ]]( weapon, type, modifiedDamage );
|
|
}
|
|
|
|
return modifiedDamage;
|
|
}
|
|
|
|
handleDeathDamage( attacker, weapon, type, damage )
|
|
{
|
|
config = self.config;
|
|
|
|
notifyAttacker = self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, type, damage, config.xpPopup, config.destroyedVO );
|
|
if ( notifyAttacker
|
|
&& IsDefined( config.onDestroyedDelegate )
|
|
)
|
|
{
|
|
self [[ config.onDestroyedDelegate ]]( self.streakName, attacker, self.owner, type );
|
|
}
|
|
}
|
|
|
|
handleDeath( streakName )
|
|
{
|
|
self endon( "carried" );
|
|
|
|
self waittill ( "death" );
|
|
|
|
config = level.placeableConfigs[ streakName ];
|
|
|
|
// this handles cases of deletion
|
|
if ( IsDefined( self ) )
|
|
{
|
|
// play sound
|
|
|
|
self deactivate( streakName );
|
|
|
|
// set destroyed model
|
|
if ( IsDefined( config.modelDestroyed ) )
|
|
{
|
|
self SetModel( config.modelDestroyed );
|
|
}
|
|
|
|
// or do it in the callbacks?
|
|
|
|
if ( IsDefined( config.onDeathDelegate ) )
|
|
{
|
|
self [[ config.onDeathDelegate ]]( streakName );
|
|
}
|
|
|
|
self Delete();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
onCarrierDeath( streakName, carrier ) // self == placeable
|
|
{
|
|
self endon ( "placed" );
|
|
self endon ( "death" );
|
|
carrier endon( "disconnect" );
|
|
|
|
carrier waittill ( "death" );
|
|
|
|
if ( self.canBePlaced )
|
|
{
|
|
self thread onPlaced( streakName );
|
|
}
|
|
else
|
|
{
|
|
self onCancel( streakName );
|
|
}
|
|
}
|
|
|
|
|
|
onKillstreakDisowned( streakName ) // self == placeable
|
|
{
|
|
self endon ( "death" );
|
|
level endon ( "game_ended" );
|
|
|
|
self.owner waittill ( "killstreak_disowned" );
|
|
|
|
self cleanup( streakName );
|
|
}
|
|
|
|
onGameEnded( streakName ) // self == placeable
|
|
{
|
|
self endon ( "death" );
|
|
|
|
level waittill ( "game_ended" );
|
|
|
|
self cleanup( streakName );
|
|
}
|
|
|
|
cleanup( streakName ) // self == placeable
|
|
{
|
|
if ( IsDefined( self.isPlaced ) )
|
|
{
|
|
self notify( "death" );
|
|
}
|
|
else
|
|
{
|
|
self onCancel( streakName );
|
|
}
|
|
}
|
|
|
|
watchPlayerConnected() // self == ims
|
|
{
|
|
self endon( "death" );
|
|
|
|
while( true )
|
|
{
|
|
// when new players connect they need to not be able to use the planted ims
|
|
level waittill( "connected", player );
|
|
self thread onPlayerConnected( player );
|
|
}
|
|
}
|
|
|
|
onPlayerConnected( owner ) // self == placeable
|
|
{
|
|
self endon( "death" );
|
|
owner endon( "disconnect" );
|
|
|
|
owner waittill( "spawned_player" );
|
|
|
|
// this can't possibly be the owner because the ims is destroyed if the owner leaves the game, so disable use for this player
|
|
self DisablePlayerUse( owner );
|
|
}
|
|
|
|
timeOut( streakName )
|
|
{
|
|
self endon( "death" );
|
|
level endon ( "game_ended" );
|
|
|
|
config = level.placeableConfigs[ streakName ];
|
|
lifeSpan = config.lifeSpan;
|
|
|
|
while ( lifeSpan > 0.0 )
|
|
{
|
|
wait ( 1.0 );
|
|
maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
|
|
|
|
if ( !IsDefined( self.carriedBy ) )
|
|
{
|
|
lifeSpan -= 1.0;
|
|
}
|
|
}
|
|
|
|
if ( IsDefined( self.owner ) && IsDefined( config.goneVO ) )
|
|
{
|
|
self.owner thread leaderDialogOnPlayer( config.goneVO );
|
|
}
|
|
|
|
self notify ( "death" );
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
removeWeapons()
|
|
{
|
|
if ( self HasWeapon( "iw6_riotshield_mp" ) )
|
|
{
|
|
self.restoreWeapon = "iw6_riotshield_mp";
|
|
self takeWeapon( "iw6_riotshield_mp" );
|
|
}
|
|
}
|
|
|
|
removePerks()
|
|
{
|
|
if ( self _hasPerk( "specialty_explosivebullets" ) )
|
|
{
|
|
self.restorePerk = "specialty_explosivebullets";
|
|
self _unsetPerk( "specialty_explosivebullets" );
|
|
}
|
|
}
|
|
|
|
restoreWeapons()
|
|
{
|
|
if ( IsDefined( self.restoreWeapon ) )
|
|
{
|
|
self _giveWeapon( self.restoreWeapon );
|
|
self.restoreWeapon = undefined;
|
|
}
|
|
}
|
|
|
|
restorePerks()
|
|
{
|
|
if ( IsDefined( self.restorePerk ) )
|
|
{
|
|
self givePerk( self.restorePerk, false );
|
|
self.restorePerk = undefined;
|
|
}
|
|
}
|
|
|
|
createBombSquadModel( streakName ) // self == box
|
|
{
|
|
config = level.placeableConfigs[ 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
showPlacedModel( streakname )
|
|
{
|
|
self Show();
|
|
|
|
if ( IsDefined( self.bombSquadModel ) )
|
|
{
|
|
self.bombSquadModel Show();
|
|
level notify( "update_bombsquad" );
|
|
}
|
|
}
|
|
|
|
hidePlacedModel( streakName )
|
|
{
|
|
self Hide();
|
|
|
|
if ( IsDefined( self.bombSquadModel ) )
|
|
{
|
|
self.bombSquadModel Hide();
|
|
}
|
|
}
|
|
|
|
createCarriedObject( streakName )
|
|
{
|
|
assertEx( IsDefined( self ), "createIMSForPlayer() called without owner specified" );
|
|
|
|
// need to make sure we aren't already carrying, this fixes a bug where you could start to pull a new one out as you picked the old one up
|
|
// this resulted in you being able to plant one and then pull your gun out while having another one attached to you like you're carrying it
|
|
if( IsDefined( self.isCarrying ) && self.isCarrying )
|
|
return;
|
|
|
|
carriedObj = SpawnTurret( "misc_turret", self.origin + ( 0, 0, 25 ), "sentry_minigun_mp" );
|
|
|
|
carriedObj.angles = self.angles;
|
|
carriedObj.owner = self;
|
|
|
|
config = level.placeableConfigs[ streakName ];
|
|
carriedObj SetModel( config.modelBase );
|
|
|
|
carriedObj MakeTurretInoperable();
|
|
carriedObj SetTurretModeChangeWait( true );
|
|
carriedObj SetMode( "sentry_offline" );
|
|
carriedObj MakeUnusable();
|
|
carriedObj SetSentryOwner( self );
|
|
carriedObj SetSentryCarrier( self );
|
|
|
|
carriedObj SetCanDamage( false );
|
|
carriedObj SetContents( 0 );
|
|
|
|
return carriedObj;
|
|
} |