aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/mod/scripts/vscripts/_bubble_shield.gnut
blob: f09ef9576b28d476260a4a915c2df053047a230c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
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
		neutralColoredFX.DisableHibernation()
		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 )
		friendlyColoredFX.DisableHibernation()

		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 )
		enemyColoredFX.DisableHibernation()

		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
}