aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
blob: c42899e3f0db0e01f0f1efd612b2163f3fc03cb7 (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
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
untyped
global function BaseGametype_Init_MPSP
global function CodeCallback_OnClientConnectionStarted
global function CodeCallback_OnClientConnectionCompleted
global function CodeCallback_OnClientDisconnected
global function CodeCallback_OnPlayerRespawned
global function CodeCallback_OnPlayerKilled
global function DecideRespawnPlayer
global function RespawnAsPilot
global function RespawnAsTitan
global function TryGameModeAnnouncement

global function SetKillcamsEnabled
global function KillcamsEnabled
global function SetPlayerDeathsHidden
global function TrackTitanDamageInPlayerGameStat

global function ShouldEntTakeDamage_SPMP
global function GetTitanBuildTime
global function TitanPlayerHotDropsIntoLevel

struct {
	bool killcamsEnabled = true
	bool playerDeathsHidden = false
	int titanDamageGameStat = -1
	
	entity intermissionCamera
	array<entity> specCams 
} file

void function BaseGametype_Init_MPSP()
{
	AddSpawnCallback( "info_intermission", SetIntermissionCamera )
	AddCallback_EntitiesDidLoad( SetSpecCams )
	
	RegisterSignal( "ObserverTargetChanged" )
	AddClientCommandCallback( "spec_next", ClientCommandCallback_spec_next )
	AddClientCommandCallback( "spec_prev", ClientCommandCallback_spec_prev )
	AddClientCommandCallback( "spec_mode", ClientCommandCallback_spec_mode )
	
	AddDamageCallback( "player", AddToTitanDamageStat )
	AddDamageCallback( "npc_titan", AddToTitanDamageStat )
}

void function SetIntermissionCamera( entity camera )
{
	file.intermissionCamera = camera
}

void function SetSpecCams()
{
	// spec cams are called spec_cam1,2,3 etc by default, so this is the easiest way to get them imo
	int camNum = 1 
	entity lastCam = null
	do {
		lastCam = GetEnt( "spec_cam" + camNum++ )
		
		if ( lastCam != null )
			file.specCams.append( lastCam )
	} while ( lastCam != null )
}

void function CodeCallback_OnClientConnectionStarted( entity player )
{
	// not a real player?
	#if DEV
	if ( player.GetPlayerName() == "Replay" )
		return
	#endif

	if ( IsLobby() )
	{
		Lobby_OnClientConnectionStarted( player )
		return
	}

//	ScreenFade( player, 0, 0, 0, 255, 2.0, 0.5, FFADE_IN | FFADE_PURGE )

	SetTargetName( player, "player" + player.entindex() )

	player.p.controllableProjectiles_scriptManagedID = CreateScriptManagedEntArray()
	player.p.npcFollowersArrayID = CreateScriptManagedEntArray()

	player.s = {}
	player.s.attackerInfo <- {}
	player.p.clientScriptInitialized = player.IsBot()
	player.s.inPostDeath <- null
	player.s.respawnCount <- 0
	player.s.respawnTime <- 0
	player.s.lostTitanTime <- 0
	player.s.cloakedShotsAllowed <- 0
	player.s.startDashMeleeTime <- 0
	player.s.respawnSelectionDone <- true  // this gets set to false in postdeaththread but we need it to be true when connecting
	player.s.waveSpawnProtection <- false

	player.s.nextStatUpdateFunc <- null

	player.s.activeTrapArrayId <- CreateScriptManagedEntArray()

	player.s.restartBurnCardEffectOnSpawn <- false
	player.s.replacementDropInProgress <- false

	player.s.inGracePeriod <- true

	// should I just add these when playing coop?
	player.s.usedLoadoutCrate <- false
	player.s.restockAmmoTime <- 0
	player.s.restockAmmoCrate <- null

	player.s.autoTitanLastEngageCalloutTime <- 0
	player.s.autoTitanLastEngageCallout <- null
	player.s.lastAIConversationTime <- {} // when was a conversation last played?

	player.s.updatedPersistenceOnDisconnect <- false

	player.s.lastFriendlySpawnedOn <- null
	player.s.nextWaveSpawnTime <- 0.0

	player.s.meleeSlowMoEndTime <- 0.0
	
	player.p.connectTime = Time()

	Assert( !player._entityVars )
	InitEntityVars( player )

	// Added via AddCallback_OnClientConnecting
	foreach ( callbackFunc in svGlobal.onClientConnectingCallbacks )
	{
		callbackFunc( player )
	}

	printl( "Player connect started: " + player )
	
	InitPassives( player )
}

// playerconnected
void function CodeCallback_OnClientConnectionCompleted( entity player )
{
	if ( IsLobby() )
	{
		Lobby_OnClientConnectionCompleted( player )
		return
	}

	player.hasConnected = true

	InitMeleeAnimEventCallbacks( player )
	ZiplineInit( player )
	
	UpdateMinimapStatus( player )
	UpdateMinimapStatusToOtherPlayers( player )
	MinimapPlayerConnected( player )
	NotifyClientsOfConnection( player, 1 )
	PlayCurrentTeamMusicEventsOnPlayer( player )
	SetCurrentTeamObjectiveForPlayer( player )

	entity skycam = GetEnt( "skybox_cam_level" )
	if ( skycam != null )
		player.SetSkyCamera( skycam )

	FinishClientScriptInitialization( player )

	// Added via AddCallback_OnClientConnected
	foreach ( callbackFunc in svGlobal.onClientConnectedCallbacks )
	{
		callbackFunc( player )
	}

	if ( !Flag( "PlayerDidSpawn") )
		__PlayerDidSpawn( player )

	svGlobal.levelEnt.Signal( "PlayerDidSpawn", { player = player } )

	// handle spawning late joiners
	if ( GetGameState() == eGameState.Playing )
	{
		if ( RespawnsEnabled() )
		{
			// likely temp, deffo needs some work
			if ( Riff_SpawnAsTitan() == 1 )	// spawn as titan
				thread RespawnAsTitan( player )
			else // spawn as pilot
				RespawnAsPilot( player )
		}
		else
			thread PlayerBecomesSpectator( player )
	}
}

void function CodeCallback_OnClientDisconnected( entity player, string reason )
{
	if ( IsLobby() )
	{
		player.Signal( "_disconnectedInternal" )
		UpdateBadRepPresent()
		return
	}

	if ( !player.hasConnected )
		return

	// Added via AddCallback_OnClientDisconnected
	foreach ( callbackFunc in svGlobal.onClientDisconnectedCallbacks )
	{
		callbackFunc( player )
	}

	player.Disconnected()
	player.p.isDisconnected = true
	player.CleanupMPClasses()
}

void function CodeCallback_OnPlayerRespawned( entity player )
{
	player.Signal( "OnRespawned" ) // kill any postdeaththreads that could be running

	Remote_CallFunction_NonReplay( player, "ServerCallback_YouRespawned" )
	player.s.respawnTime = Time()
	
	Loadouts_TryGivePilotLoadout( player )
	SetHumanRagdollImpactTable( player )
		
	foreach ( void functionref( entity ) callback in svGlobal.onPlayerRespawnedCallbacks )
		callback( player )
}

void function CodeCallback_OnPlayerKilled( entity player, var damageInfo )
{
	PlayerOrNPCKilled( player, damageInfo )
	thread PostDeathThread_MP( player, damageInfo )
}

void function PostDeathThread_MP( entity player, var damageInfo ) // based on gametype_sp: postdeaththread_sp
{
	if ( player.s.inPostDeath )
		return

	float timeOfDeath = Time()
	player.p.postDeathThreadStartTime = Time()

	Assert( IsValid( player ), "Not a valid player" )
	player.EndSignal( "OnDestroy" )
	player.EndSignal( "OnRespawned" )

	player.p.deathOrigin = player.GetOrigin()
	player.p.deathAngles = player.GetAngles()

	player.s.inPostDeath = true
	player.s.respawnSelectionDone = false

	player.cloakedForever = false
	player.stimmedForever = false
	player.SetNoTarget( false )
	player.SetNoTargetSmartAmmo( false )
	player.ClearExtraWeaponMods()
	
	player.AddToPlayerGameStat( PGS_DEATHS, 1 )
	
	if ( player.IsTitan() )
		SoulDies( player.GetTitanSoul(), damageInfo ) // cleanup some titan stuff, no idea where else to put this
	
	ClearRespawnAvailable( player )

	OnThreadEnd( function() : ( player )
	{
		if ( !IsValid( player ) )
			return
	
		player.SetPredictionEnabled( true )
		player.s.inPostDeath = false
	})

	entity attacker = DamageInfo_GetAttacker( damageInfo )
	int methodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )

	player.p.rematchOrigin = player.p.deathOrigin
	if ( IsValid( attacker ) && methodOfDeath == eDamageSourceId.titan_execution )
	{
		// execution can throw you out of the map
		player.p.rematchOrigin = attacker.GetOrigin()
	}

	player.Signal( "RodeoOver" )
	player.ClearParent()
	
	// do some pre-replay stuff if we're gonna do a replay
	float replayLength = CalculateLengthOfKillReplay( player, methodOfDeath )
	bool shouldDoReplay = Replay_IsEnabled() && KillcamsEnabled() && IsValid( attacker ) && ShouldDoReplay( player, attacker, replayLength, methodOfDeath )
	table replayTracker = { validTime = null }
	if ( shouldDoReplay )
		thread TrackDestroyTimeForReplay( attacker, replayTracker )
	
	int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
	//if ( damageSource == eDamageSourceId.fall )
	//{
	//	// this is straight up just incorrect lol, based off tf1 stuff
	//
	//	player.SetObserverModeStaticPosition( player.GetOrigin() )
	//	player.SetObserverModeStaticAngles( player.GetVelocity() * -1 )
	//	
	//	player.StartObserverMode( OBS_MODE_STATIC_LOCKED )
	//	player.SetObserverTarget( null )
	//}
	//else
	//{
		player.StartObserverMode( OBS_MODE_DEATHCAM )
		if ( ShouldSetObserverTarget( attacker ) )
			player.SetObserverTarget( attacker )
		else
			player.SetObserverTarget( null )
	//}
	
	if ( !file.playerDeathsHidden )
		Remote_CallFunction_NonReplay( player, "ServerCallback_YouDied", attacker.GetEncodedEHandle(), GetHealthFrac( attacker ), methodOfDeath )

	float deathcamLength = GetDeathCamLength( player )
	wait deathcamLength
	
	// use info_intermission camera after deathcam, if it exists
	if ( file.intermissionCamera != null )
	{
		player.SetObserverModeStaticPosition( file.intermissionCamera.GetOrigin() )
		player.SetObserverModeStaticAngles( file.intermissionCamera.GetAngles() )
		player.StartObserverMode( OBS_MODE_STATIC_LOCKED )
		player.SetObserverTarget( null )
	}

	// quick note: in cases where player.Die() is called: e.g. for round ends, player == attacker
	if ( shouldDoReplay )
	{
		player.SetPredictionEnabled( false )
	
		player.watchingKillreplayEndTime = Time() + replayLength		
		float beforeTime = GetKillReplayBeforeTime( player, methodOfDeath )
		
		replayTracker.validTime <- null 
		
		float respawnTime = Time() - 2 // seems to get the killreplay to end around the actual kill
		if ( "respawnTime" in attacker.s )
			respawnTime = Time() - expect float ( attacker.s.respawnTime )
		
		thread PlayerWatchesKillReplayWrapper( player, attacker, respawnTime, timeOfDeath, beforeTime, replayTracker )		
	}

	player.SetPlayerSettings( "spectator" ) // prevent a crash with going from titan => pilot on respawn

	if ( RespawnsEnabled() )
	{
		// is it a good idea to do respawn code in postdeaththread? fuck if i know lol
		float respawnDelay = max( 0, GetCurrentPlaylistVarFloat( "respawn_delay", 0.0 ) - deathcamLength )
	
		print( "respawn delay " + respawnDelay )
		
		UpdateNextRespawnTime( player, Time() + respawnDelay )
		SetRespawnAvailable( player )
		
		wait respawnDelay
				
		player.WaitSignal( "RespawnMe" ) // set in base_gametype: ClientCommand_RespawnPlayer
		
		player.SetPredictionEnabled( true )
		ClearRespawnAvailable( player ) // need so the respawn icon doesn't show for like a frame on next death
			
		if ( ( expect bool( player.GetPersistentVar( "spawnAsTitan" ) ) && IsTitanAvailable( player ) ) || ( Riff_SpawnAsTitan() > 0 && Riff_ShouldSpawnAsTitan( player ) ) )	// spawn as titan
			thread RespawnAsTitan( player )
		else // spawn as pilot
			RespawnAsPilot( player )
	}
	else
	{
		thread PlayerBecomesSpectator( player )
	}
}

void function PlayerWatchesKillReplayWrapper( entity player, entity attacker, float timeSinceAttackerSpawned, float timeOfDeath, float beforeTime, table replayTracker )
{
	PlayerWatchesKillReplay( player, attacker.GetEncodedEHandle(), attacker.GetIndexForEntity(), timeSinceAttackerSpawned, timeOfDeath, beforeTime, replayTracker )
	player.ClearReplayDelay()
	player.ClearViewEntity()
	player.SetPredictionEnabled( true )
}

void function EndReplayOnTime( entity player, float replayLength )
{
	player.EndSignal( "RespawnMe" )
	player.EndSignal( "OnRespawned" )
	
	wait replayLength
	if ( IsValid( player ) && KillcamsEnabled() )
	{
		print( "fucking how" )
	
		player.ClearReplayDelay()
		player.ClearViewEntity()
		player.SetPredictionEnabled( true )
		
		player.SetObserverTarget( null )
	}   
}

void function DecideRespawnPlayer( entity player )
{
	// this isn't even used atm, could likely be removed if some vanilla code didn't rely on it
}

void function RespawnAsPilot( entity player, bool manualPosition = false )
{
	player.RespawnPlayer( FindSpawnPoint( player, false, ShouldStartSpawn( player ) && !IsFFAGame() ) )
}

void function RespawnAsTitan( entity player, bool manualPosition = false )
{
	player.isSpawning = true

	entity spawnpoint = FindSpawnPoint( player, true, ShouldStartSpawn( player ) && !IsFFAGame() )

	TitanLoadoutDef titanLoadout = GetTitanLoadoutForPlayer( player )
	
	asset model = GetPlayerSettingsAssetForClassName( titanLoadout.setFile, "bodymodel" )
	Attachment warpAttach = GetAttachmentAtTimeFromModel( model, "at_hotdrop_01", "offset", spawnpoint.GetOrigin(), spawnpoint.GetAngles(), 0 )
	PlayFX( TURBO_WARP_FX, warpAttach.position, warpAttach.angle )
	
	player.RespawnPlayer( null ) // spawn player as pilot so they get their pilot loadout on embark
	
	entity titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, titanLoadout, spawnpoint.GetOrigin(), spawnpoint.GetAngles() )
	DispatchSpawn( titan )
	player.SetPetTitan( null ) // prevent embark prompt from showing up
	
	AddCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD ) // hide hud
	player.HolsterWeapon() // hide crosshair
	
	// do titanfall scoreevent
	AddPlayerScore( player, "Titanfall", player )
	
	entity camera = CreateTitanDropCamera( spawnpoint.GetAngles(), < 90, titan.GetAngles().y, 0 > )
	camera.SetParent( titan )
	
	// calc offset for spawnpoint angle
	// todo this seems bad but too lazy to figure it out rn
	//vector xyOffset = RotateAroundOrigin2D( < 44, 0, 0 >, < 0, 0, 0>, spawnpoint.GetAngles().y )
	//xyOffset.z = 520 // < 44, 0, 520 > at 0,0,0, seems to be the offset used in tf2
	//print( xyOffset )
	
	vector xyOffset = RotateAroundOrigin2D( < 44, 0, 520 >, < 0, 0, 0 >, spawnpoint.GetAngles().y )
	
	camera.SetLocalOrigin( xyOffset )
	camera.SetLocalAngles( < camera.GetAngles().x, spawnpoint.GetAngles().y, camera.GetAngles().z > ) // this straight up just does not work lol
	camera.Fire( "Enable", "!activator", 0, player )
	
	waitthread TitanHotDrop( titan, "at_hotdrop_01", spawnpoint.GetOrigin(), spawnpoint.GetAngles(), player, camera ) // do hotdrop anim
	
	camera.Fire( "Disable", "!activator", 0, player ) // stop using the camera
	camera.Destroy()
	RemoveCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD ) // show hud
	player.DeployWeapon() // let them use weapons again
	player.isSpawning = false
		
	PilotBecomesTitan( player, titan ) // make player titan
	titan.Destroy() // pilotbecomestitan leaves an npc titan that we need to delete
}


// spectator stuff

void function PlayerBecomesSpectator( entity player )
{
	player.StartObserverMode( OBS_MODE_CHASE )

	player.EndSignal( "OnRespawned" )
	player.EndSignal( "OnDestroy" )

	int targetIndex = 0
	
	while ( true )
	{	
		table result = player.WaitSignal( "ObserverTargetChanged" )
		
		array<entity> targets
		
		targets.append( file.intermissionCamera )
		foreach( entity cam in file.specCams )
			targets.append( cam )
			
		array<entity> targetPlayers
		if ( IsFFAGame() )
			targetPlayers = GetPlayerArray_Alive()
		else
			targetPlayers = GetPlayerArrayOfTeam_Alive( player.GetTeam() )
		
		foreach( entity player in targetPlayers )
			targets.append( player )
		
		if ( result.next )
			targetIndex = ( targetIndex + 1 ) % targets.len()
		else
		{
			if ( targetIndex == 0 )
				targetIndex = ( targets.len() - 1 )
			else
				targetIndex--
		}
		
		entity target = targets[ targetIndex ]
		
		player.StopObserverMode()
		player.SetSpecReplayDelay( 0.0 ) // clear spectator replay
		
		if ( target.IsPlayer() )
		{
			player.SetObserverTarget( target )
			player.StartObserverMode( OBS_MODE_CHASE )
		}
		else
		{
			player.SetObserverModeStaticPosition( target.GetOrigin() )
			player.SetObserverModeStaticAngles( target.GetAngles() )
			player.StartObserverMode( OBS_MODE_STATIC )
		}  
	}
}

bool function ClientCommandCallback_spec_next( entity player, array<string> args )
{
	if ( player.GetObserverMode() == OBS_MODE_CHASE || player.GetObserverMode() == OBS_MODE_STATIC || player.GetObserverMode() == OBS_MODE_IN_EYE )
		player.Signal( "ObserverTargetChanged", { next = true } )
		
	return true
}

bool function ClientCommandCallback_spec_prev( entity player, array<string> args )
{
	if ( player.GetObserverMode() == OBS_MODE_CHASE || player.GetObserverMode() == OBS_MODE_STATIC || player.GetObserverMode() == OBS_MODE_IN_EYE )
		player.Signal( "ObserverTargetChanged", { next = false } )
		
	return true
}

bool function ClientCommandCallback_spec_mode( entity player, array<string> args )
{
	// currently unsure how this actually gets called on client, works through console and has references in client.dll tho
	if ( player.GetObserverMode() == OBS_MODE_CHASE )
	{
		// set to first person spectate		
		player.SetSpecReplayDelay( FIRST_PERSON_SPECTATOR_DELAY )
		player.SetViewEntity( player.GetObserverTarget(), true )
		player.StartObserverMode( OBS_MODE_IN_EYE )
	}
	else if ( player.GetObserverMode() == OBS_MODE_IN_EYE )
	{	
		// set to third person spectate
		player.SetSpecReplayDelay( 0.0 )
		player.StartObserverMode( OBS_MODE_CHASE )
	}
	
	return true
}


void function TryGameModeAnnouncement( entity player ) // only putting this here because it's here in gametype_sp lol
{
	Remote_CallFunction_NonReplay( player, "ServerCallback_GameModeAnnouncement" )
	PlayFactionDialogueToPlayer( GameMode_GetGameModeAnnouncement( GAMETYPE ), player )
}

void function SetKillcamsEnabled( bool enabled )
{
	file.killcamsEnabled = enabled
}

bool function KillcamsEnabled()
{
	return file.killcamsEnabled
}

void function SetPlayerDeathsHidden( bool hidden )
{
	file.playerDeathsHidden = hidden
}

void function TrackTitanDamageInPlayerGameStat( int playerGameStat )
{
	file.titanDamageGameStat = playerGameStat
}

void function AddToTitanDamageStat( entity victim, var damageInfo )
{
	if ( !victim.IsTitan() || file.titanDamageGameStat == -1 )
		return

	entity attacker = DamageInfo_GetAttacker( damageInfo )
	float amount = DamageInfo_GetDamage( damageInfo )

	if ( attacker.IsPlayer() && attacker != victim )
		attacker.AddToPlayerGameStat( file.titanDamageGameStat, amount ) // titan damage on 
}


// stuff to change later

bool function ShouldEntTakeDamage_SPMP( entity ent, var damageInfo )
{
	return true
}

float function GetTitanBuildTime(entity player)
{
	return 100.0
}

void function TitanPlayerHotDropsIntoLevel( entity player )
{

}