2024-12-11 11:28:08 +01:00

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