diff options
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut')
-rw-r--r-- | Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut | 524 |
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 +} |