aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut')
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut524
1 files changed, 524 insertions, 0 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut b/Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut
new file mode 100644
index 00000000..30758bec
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut
@@ -0,0 +1,524 @@
+global function BubbleShield_Init
+
+global function CreateBubbleShield
+global function IsTitanWithinBubbleShield
+global function TitanHasBubbleShieldWeapon
+global function LetTitanPlayerShootThroughBubbleShield
+global function CreateGenericBubbleShield
+global function CreateParentedBubbleShield
+
+global function WaitUntilTitanStandsOrDies
+global function DestroyBubbleShield
+global function CreateBubbleShieldWithSettings
+
+const float SHIELD_TITAN_DAMAGE_FLOOR = 250.0
+const float SHIELD_TITAN_DAMAGE_CEILING = 16000 //Some arbitrarily large number really
+const float SHIELD_PILOT_DAMAGE_FLOOR = 30.0
+const float SHIELD_PILOT_DAMAGE_CEILING = 60.0
+const float SHIELD_NPC_DAMAGE_FLOOR = 30.0
+
+const float SHIELD_FADE_ARBITRARY_DELAY = 3.0
+const float SHIELD_FADE_ENDCAP_DELAY = 1.0
+
+const float SHIELD_DISTANCE_TO_DESTROY = 40
+
+struct BubbleShieldDamageStruct
+{
+ float damageFloor
+ float damageCeiling
+ array<float> quadraticPolynomialCoefficients //Should actually be float[3], but because float[ 3 ] and array<float> are different types and this needs to be fed into EvaluatePolynomial make it an array<float> instead
+}
+
+struct
+{
+ BubbleShieldDamageStruct titanDamageStruct
+ BubbleShieldDamageStruct pilotDamageStruct
+ BubbleShieldDamageStruct aiDamageStruct
+
+}file
+
+
+void function BubbleShield_Init()
+{
+ RegisterSignal( "TitanBrokeBubbleShield" )
+ RegisterSignal( "NewBubbleShield" )
+ RegisterSignal( "StopBubbleShieldDamage" )
+
+ InitBubbleShieldDamageStructValues( file.titanDamageStruct, SHIELD_TITAN_DAMAGE_FLOOR, SHIELD_TITAN_DAMAGE_CEILING, [ 12.0, 5.0, 2.0 ] )
+ InitBubbleShieldDamageStructValues( file.pilotDamageStruct, SHIELD_PILOT_DAMAGE_FLOOR, SHIELD_PILOT_DAMAGE_CEILING, [ 2.0, 1.0, 1.0 ] )
+ InitBubbleShieldDamageStructValues( file.aiDamageStruct, SHIELD_PILOT_DAMAGE_FLOOR, SHIELD_PILOT_DAMAGE_CEILING, [ 2.0, 1.0, 1.0 ] )
+}
+
+void function InitBubbleShieldDamageStructValues( BubbleShieldDamageStruct damageStruct, float damageFloor, float damageCeiling, array<float> quadPolynomialCoeffs )
+{
+ damageStruct.damageFloor = damageFloor
+ damageStruct.damageCeiling = damageCeiling
+ damageStruct.quadraticPolynomialCoefficients = quadPolynomialCoeffs
+}
+
+void function CreateBubbleShield( entity titan, vector origin, vector angles )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ titan.Signal( "ClearDisableTitanfall" )
+
+ entity soul = titan.GetTitanSoul()
+ entity player = soul.GetBossPlayer()
+
+ if ( !IsValid( player ) )
+ return
+
+ if ( !svGlobal.bubbleShieldEnabled )
+ return
+
+ player.EndSignal( "OnDestroy" )
+
+ float embarkTime = GetBubbleShieldDuration( player )
+ float bubTime = embarkTime + SHIELD_FADE_ARBITRARY_DELAY + SHIELD_FADE_ENDCAP_DELAY
+
+ soul.Signal( "NewBubbleShield" )
+ entity bubbleShield = CreateBubbleShieldWithSettings( titan.GetTeam(), origin, angles, player, bubTime )
+ bubbleShield.SetBossPlayer( player ) // so code knows AI should try to shoot at titan inside shield
+ soul.soul.bubbleShield = bubbleShield
+
+ player.SetTitanBubbleShieldTime( Time() + GetBubbleShieldDuration( player ) ) //This sets the time to display "Titan Shielded" on the HUD
+
+ AI_CreateDangerousArea_Static( bubbleShield, null, TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE, titan.GetTeam(), true, true, origin )
+
+ //titan.SetNPCPriorityOverride( 1 )
+
+ OnThreadEnd(
+ function () : ( titan, soul, player, bubbleShield )
+ {
+ if ( IsValid( player ) )
+ player.SetTitanBubbleShieldTime( 0 ) //This sets the time to display "Titan Shielded" on the HUD
+
+ CleanupTitanBubbleShieldVars( titan, soul, bubbleShield )
+
+ }
+ )
+
+ waitthread WaitUntilShieldFades( player, titan, bubbleShield, bubTime + 4.0 )
+}
+
+void function MonitorTitanMovement( entity soul, entity bubbleShield )
+{
+ entity titan = soul.GetTitan()
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnTitanDeath" )
+ bubbleShield.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDestroy" )
+
+ vector startPos = titan.GetOrigin()
+ float endTime = Time() + SHIELD_FADE_ARBITRARY_DELAY
+ while( endTime >= Time() )
+ {
+ if ( Distance( titan.GetOrigin(), startPos ) > SHIELD_DISTANCE_TO_DESTROY )
+ break
+
+ wait 0.1
+ }
+
+ soul.Signal( "TitanBrokeBubbleShield" )
+}
+
+void function CreateGenericBubbleShield( entity titan, vector origin, vector angles, float duration = 9999.0 )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ entity soul = titan.GetTitanSoul()
+ soul.Signal( "NewBubbleShield" )
+ entity bubbleShield = CreateBubbleShieldWithSettings( titan.GetTeam(), origin, angles, titan, 9999 )
+ soul.soul.bubbleShield = bubbleShield
+
+ titan.SetNPCPriorityOverride( 10 )
+
+ OnThreadEnd(
+ function () : ( titan, soul, bubbleShield )
+ {
+ CleanupTitanBubbleShieldVars( titan, soul, bubbleShield )
+ }
+ )
+
+ waitthread WaitUntilShieldFades( null, titan, bubbleShield, duration )
+}
+
+void function CreateParentedBubbleShield( entity titan, vector origin, vector angles, float duration = 9999.0 )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ entity soul = titan.GetTitanSoul()
+ soul.Signal( "NewBubbleShield" )
+ entity bubbleShield = CreateBubbleShieldWithSettings( titan.GetTeam(), origin, angles, titan, 9999 )
+ soul.soul.bubbleShield = bubbleShield
+
+ titan.SetNPCPriorityOverride( 10 )
+
+ OnThreadEnd(
+ function () : ( titan, soul, bubbleShield )
+ {
+ CleanupTitanBubbleShieldVars( titan, soul, bubbleShield )
+ }
+ )
+
+ soul.EndSignal( "OnTitanDeath" )
+ soul.EndSignal( "OnDestroy" )
+
+ soul.soul.bubbleShield.SetParent( titan, "ORIGIN" )
+ table bubleshieldDotS = expect table( soul.soul.bubbleShield.s )
+ entity friendlyColoredFX = expect entity (bubleshieldDotS.friendlyColoredFX )
+ entity enemyColoredFX = expect entity (bubleshieldDotS.enemyColoredFX )
+ friendlyColoredFX.SetParent( soul.soul.bubbleShield )
+ enemyColoredFX.SetParent( soul.soul.bubbleShield )
+
+ wait duration
+}
+
+void function CleanupTitanBubbleShieldVars( entity titan, entity soul, entity bubbleShield )
+{
+ DestroyBubbleShield( bubbleShield )
+
+ if ( IsValid( soul ) ){
+ soul.soul.bubbleShield = null
+ }
+
+ if ( IsAlive( titan ) )
+ titan.ClearNPCPriorityOverride()
+}
+
+void function DestroyBubbleShield( entity bubbleShield )
+{
+ if ( IsValid( bubbleShield ) )
+ {
+ ClearChildren( bubbleShield )
+ bubbleShield.Destroy()
+ }
+}
+
+entity function CreateBubbleShieldWithSettings( int team, vector origin, vector angles, entity owner = null, float duration = 9999 )
+{
+ entity bubbleShield = CreateEntity( "prop_dynamic" )
+ bubbleShield.SetValueForModelKey( $"models/fx/xo_shield.mdl" )
+ bubbleShield.kv.solid = SOLID_VPHYSICS
+ bubbleShield.kv.rendercolor = "81 130 151"
+ bubbleShield.kv.contents = (int(bubbleShield.kv.contents) | CONTENTS_NOGRAPPLE)
+ bubbleShield.SetOrigin( origin )
+ bubbleShield.SetAngles( angles )
+ // Blocks bullets, projectiles but not players and not AI
+ bubbleShield.kv.CollisionGroup = TRACE_COLLISION_GROUP_BLOCK_WEAPONS
+ bubbleShield.SetBlocksRadiusDamage( true )
+ DispatchSpawn( bubbleShield )
+ bubbleShield.Hide()
+
+ SetTeam( bubbleShield, team )
+ array<entity> bubbleShieldFXs
+
+ vector coloredFXOrigin = origin + Vector( 0, 0, 25 )
+ table bubbleShieldDotS = expect table( bubbleShield.s )
+ if ( team == TEAM_UNASSIGNED )
+ {
+ entity neutralColoredFX = StartParticleEffectInWorld_ReturnEntity( BUBBLE_SHIELD_FX_PARTICLE_SYSTEM_INDEX, coloredFXOrigin, <0, 0, 0> )
+ SetTeam( neutralColoredFX, team )
+ bubbleShieldDotS.neutralColoredFX <- neutralColoredFX
+ bubbleShieldFXs.append( neutralColoredFX )
+ }
+ else
+ {
+ //Create friendly and enemy colored particle systems
+ entity friendlyColoredFX = StartParticleEffectInWorld_ReturnEntity( BUBBLE_SHIELD_FX_PARTICLE_SYSTEM_INDEX, coloredFXOrigin, <0, 0, 0> )
+ SetTeam( friendlyColoredFX, team )
+ friendlyColoredFX.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
+ EffectSetControlPointVector( friendlyColoredFX, 1, FRIENDLY_COLOR_FX )
+
+ entity enemyColoredFX = StartParticleEffectInWorld_ReturnEntity( BUBBLE_SHIELD_FX_PARTICLE_SYSTEM_INDEX, coloredFXOrigin, <0, 0, 0> )
+ SetTeam( enemyColoredFX, team )
+ enemyColoredFX.kv.VisibilityFlags = ENTITY_VISIBLE_TO_ENEMY
+ EffectSetControlPointVector( enemyColoredFX, 1, ENEMY_COLOR_FX )
+
+ bubbleShieldDotS.friendlyColoredFX <- friendlyColoredFX
+ bubbleShieldDotS.enemyColoredFX <- enemyColoredFX
+ bubbleShieldFXs.append( friendlyColoredFX )
+ bubbleShieldFXs.append( enemyColoredFX )
+ }
+
+ #if MP
+ DisableTitanfallForLifetimeOfEntityNearOrigin( bubbleShield, origin, TITANHOTDROP_DISABLE_ENEMY_TITANFALL_RADIUS )
+ #endif
+
+ EmitSoundOnEntity( bubbleShield, "BubbleShield_Sustain_Loop" )
+
+ thread CleanupBubbleShield( bubbleShield, bubbleShieldFXs, duration )
+ thread BubbleShieldDamageEnemies( bubbleShield, owner )
+
+ return bubbleShield
+}
+
+void function CleanupBubbleShield( entity bubbleShield, array<entity> bubbleShieldFXs, float fadeTime )
+{
+ bubbleShield.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function () : ( bubbleShield, bubbleShieldFXs )
+ {
+ if ( IsValid_ThisFrame( bubbleShield ) )
+ {
+ StopSoundOnEntity( bubbleShield, "BubbleShield_Sustain_Loop" )
+ EmitSoundOnEntity( bubbleShield, "BubbleShield_End" )
+ DestroyBubbleShield( bubbleShield )
+ }
+
+ foreach ( fx in bubbleShieldFXs )
+ {
+ if ( IsValid_ThisFrame( fx ) )
+ {
+ EffectStop( fx )
+ }
+ }
+ }
+ )
+
+ wait fadeTime
+}
+
+void function WaitUntilShieldFades( entity player, entity titan, entity bubbleShield, float failTime )
+{
+ bubbleShield.EndSignal( "OnDestroy" )
+ entity soul = titan.GetTitanSoul()
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnTitanDeath" )
+ soul.EndSignal( "NewBubbleShield" )
+
+ soul.EndSignal( "TitanBrokeBubbleShield" )
+
+ if ( player != null )
+ waitthread WaitUntilPlayerTitanStandsOrDies( player, titan, failTime )
+ else
+ waitthread WaitUntilTitanStandsOrDies( titan, failTime )
+
+ // have to add this since OnTitanDeath is somewhat unreliable, especially in the middle of titan transfer
+ if ( !IsAlive( soul.GetTitan() ) )
+ return
+
+ thread MonitorTitanMovement( soul, bubbleShield )
+ wait SHIELD_FADE_ARBITRARY_DELAY
+}
+
+void function WaitUntilPlayerTitanStandsOrDies( entity player, entity titan, float failTime )
+{
+ waitthread WaitUntilTitanStandsOrDies( titan, failTime )
+
+ if ( !IsAlive( player ) )
+ return
+
+ if ( IsPlayerEmbarking( player ) && player.Anim_IsActive() )
+ WaittillAnimDone( player )
+}
+
+void function WaitUntilTitanStandsOrDies( entity titan, float timeout = -1.0 )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "ChangedTitanMode" )
+ float endTime = Time() + timeout
+
+ for ( ;; )
+ {
+ if ( titan.GetTitanSoul().GetStance() == STANCE_STAND )
+ return
+
+ if ( Time() > endTime && timeout != -1 )
+ break
+
+ wait 0.2
+ }
+}
+
+void function BubbleShieldDamageEnemies( entity bubbleShield, entity bubbleShieldPlayer )
+{
+ bubbleShield.EndSignal( "OnDestroy" )
+ if ( IsValid( bubbleShieldPlayer ) )
+ bubbleShieldPlayer.EndSignal( "OnDestroy" )
+
+ bubbleShield.EndSignal( "StopBubbleShieldDamage" )
+
+ entity trigger = CreateEntity( "trigger_cylinder" )
+ trigger.SetRadius( TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE )
+ trigger.SetAboveHeight( TITAN_BUBBLE_SHIELD_CYLINDER_TRIGGER_HEIGHT ) //Still not quite a sphere, will see if close enough
+ trigger.SetBelowHeight( 0 )
+ trigger.SetOrigin( bubbleShield.GetOrigin() )
+ trigger.SetParent( bubbleShield )
+ DispatchSpawn( trigger )
+
+ trigger.SearchForNewTouchingEntity() //JFS: trigger.GetTouchingEntities() will not return entities already in the trigger unless this is called. See bug 202843
+
+ /*DebugDrawCylinder( trigger.GetOrigin(), <270,0,0>, TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE, TITAN_BUBBLE_SHIELD_CYLINDER_TRIGGER_HEIGHT, 255, 255, 255, true, 20.0 )
+ DebugDrawSphere( bubbleShield.GetOrigin(), TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE, 255, 0, 0, true, 20 )*/
+ OnThreadEnd(
+ function() : ( trigger )
+ {
+ trigger.Destroy()
+ }
+ )
+
+ float refreshLowerBound = 0.5
+ float refreshUpperBound = 0.8
+
+ table<entity, int> soulTable = {}
+ table<entity, int> npcTable = {}
+ table<entity, int> pilotTable = {}
+
+ table<entity, int> countTable
+
+ while ( true )
+ {
+ array<entity> touchingEnts = trigger.GetTouchingEntities()
+
+ foreach( touchingEnt in touchingEnts )
+ {
+ if ( touchingEnt.IsTitan() )
+ countTable = soulTable
+ else if( touchingEnt.IsPlayer() )
+ countTable = pilotTable
+ else
+ countTable = npcTable
+
+ DamageEntWithinBubbleShield( bubbleShield, bubbleShieldPlayer, touchingEnt, countTable )
+ }
+
+ wait RandomFloatRange( refreshLowerBound, refreshUpperBound )
+ }
+}
+
+void function LetTitanPlayerShootThroughBubbleShield( entity titanPlayer )
+{
+ Assert( titanPlayer.IsTitan() )
+
+ entity soul = titanPlayer.GetTitanSoul()
+ entity bubbleShield = soul.soul.bubbleShield
+
+ if ( !IsValid( bubbleShield ) )
+ return
+
+ bubbleShield.SetOwner( titanPlayer ) //After this, player is able to fire out from shield. WATCH OUT FOR POTENTIAL COLLISION BUGS!
+
+ thread MonitorLastFireTime( titanPlayer )
+ thread StopPlayerShootThroughBubbleShield( titanPlayer, bubbleShield )
+}
+
+void function StopPlayerShootThroughBubbleShield( entity player, entity bubbleShield )
+{
+ player.EndSignal( "OnDeath" )
+ player.WaitSignal( "OnChangedPlayerClass" ) //Kill this thread once player gets out of the Titan
+
+ if ( !IsValid( bubbleShield ) )
+ return
+
+ bubbleShield.SetOwner( null )
+}
+
+void function MonitorLastFireTime( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnChangedPlayerClass" ) //Kill this thread once player gets out of the Titan
+
+ player.WaitSignal( "OnPrimaryAttack" ) //Sent when player fires his weapon
+ //printt( "Player fired weapon! in MonitorLastFireTime" )
+
+ entity soul = player.GetTitanSoul()
+
+ if ( !IsValid( soul ) )
+ return
+
+ soul.Signal( "TitanBrokeBubbleShield" ) //WaitUntilShieldFades will end when this signal is sent
+}
+
+void function DamageEntWithinBubbleShield( entity bubbleShield, entity bubbleShieldPlayer, entity touchingEnt, table<entity, int> countTable, )
+{
+ int ownerTeam = IsValid( bubbleShieldPlayer ) ? bubbleShieldPlayer.GetTeam() : bubbleShield.GetTeam()
+ if ( !BubbleShieldShouldDamage( bubbleShield, ownerTeam, touchingEnt ) )
+ return
+
+ entity entInCountTable = null
+
+ if ( touchingEnt.IsTitan() )
+ {
+ entity soul = touchingEnt.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return
+
+ entInCountTable = soul
+ }
+ else
+ {
+ entInCountTable = touchingEnt
+ }
+
+ if ( IsValid( entInCountTable ) && !( entInCountTable in countTable ) )
+ countTable[ entInCountTable ] <- 0
+
+ int timesTouched = ++countTable[ entInCountTable ]
+
+ BubbleShieldDamageStruct damageStruct
+
+ if ( touchingEnt.IsTitan() )
+ damageStruct = file.titanDamageStruct
+ else if ( touchingEnt.IsPlayer() )
+ damageStruct = file.pilotDamageStruct
+ else
+ damageStruct = file.aiDamageStruct
+
+ float damageAmount = damageStruct.damageFloor + EvaluatePolynomial( float ( countTable[ entInCountTable ] ), damageStruct.quadraticPolynomialCoefficients )
+
+ //printt( "Damage amount: " + damageAmount + ", touchingEnt: " + touchingEnt )
+
+ touchingEnt.TakeDamage( damageAmount, bubbleShieldPlayer, bubbleShield, { origin = bubbleShield.GetOrigin(), damageSourceId=eDamageSourceId.bubble_shield } )
+ StatusEffect_AddTimed( touchingEnt, eStatusEffect.emp, 0.1, 1.0, 0.2 )
+
+ EmitSoundOnEntity( bubbleShield, "titan_energyshield_damage" )
+}
+
+bool function BubbleShieldShouldDamage( entity bubbleShield, int ownerTeam, entity ent )
+{
+ if ( !IsAlive( ent ) )
+ return false
+
+ if ( ownerTeam == ent.GetTeam() )
+ return false
+
+ /*if ( ent.IsTitan() && IsTitanWithinBubbleShield( ent ) )
+ return false*/
+
+ if ( ! ( ent instanceof CBaseCombatCharacter ) ) //Projectiles etc won't get damaged
+ return false
+
+ float distSqr = DistanceSqr( bubbleShield.GetOrigin(), ent.GetOrigin() )
+
+ return distSqr <= TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE_SQUARED
+}
+
+bool function IsTitanWithinBubbleShield( entity titan )
+{
+ if ( !IsAlive( titan ) )
+ return false
+
+ entity soul = titan.GetTitanSoul()
+
+ if ( !IsValid( soul ) ) //Bug 152438. Defensive coding, but there's a small window after embarking where the npc Titan doesn't have a soul anymore but can be damaged
+ return false
+
+ if ( !IsValid( soul.soul.bubbleShield ) )
+ return false
+
+ return DistanceSqr( soul.soul.bubbleShield.GetOrigin(), titan.GetOrigin() ) < TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE * TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE
+}
+
+bool function TitanHasBubbleShieldWeapon( entity titan )
+{
+ entity weapon = titan.GetActiveWeapon()
+ if ( IsValid( weapon ) && IsValid( weapon.w.bubbleShield ) )
+ return true
+
+ return false
+}