aboutsummaryrefslogtreecommitdiff
path: root/Northstar.Coop/scripts/vscripts/sp/_coop_sp_utils.gnut
blob: 5783f28fc0bfcca9a99e57c00c4e7c5ce1af9887 (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
untyped
global const float COOP_RESPAWN_DELAY = 5.0

global function CoopSpUtils_Init

global function GetPlayerToSpawnOn
global function RestartWithoutDroppingPlayers
global function TeleportToEntitySafe
global function GetEntitiesByEditorClass

global function GetPlayersInTimeline

void function CoopSpUtils_Init()
{
	AddCallback_OnClientConnecting( OnClientConnecting )
	AddDeathCallback( "player", OnPlayerDeath )
}

void function OnClientConnecting( entity player )
{
	// add custom script vars
	if ( IsPlayingTimeshiftLevel() )
	{
		player.s.timeline <- 1 // TIMEZONE_NIGHT
		player.s.isTimeTraveling <- false
		player.s.lastGoodTimeshiftPosOvergrown <- <0, 0, 0>
		player.s.lastGoodTimeshiftPosPristine <- <0, 0, 0>
	}
}

void function OnPlayerDeath( entity player, var damageInfo )
{
	// add respawn delay, use networked entity var for client respawn timer
	player.nv.nextRespawnTime = Time() + COOP_RESPAWN_DELAY
	
	if ( AreAllPlayersDead() )
		thread FailLevel( COOP_RESPAWN_DELAY )
}

void function FailLevel( float respawnTime )
{
	wait respawnTime
	RestartWithoutDroppingPlayers()
}

entity ornull function GetPlayerToSpawnOn() 
{
	array< entity > possiblePlayers
	foreach ( entity player in GetPlayerArray())
	{
		if ( IsAlive( player ))
			possiblePlayers.append( player )
	}
		
	if ( possiblePlayers.len() == 0 )
		return null // everyone is dead
		
	return possiblePlayers[ RandomInt( possiblePlayers.len() ) ]
}

void function RestartWithoutDroppingPlayers()
{
	// todo: make this deal with checkpoints/saves
	ServerCommand( "changelevel " + GetMapName() )
}

void function TeleportToEntitySafe( entity teleported, entity target )
{
	// teleport to other entities without getting stuck in ceilings and shit
	// PLEASE dont call this often, it's weird when used on living players
	// was designed only to be used on spawn/respawn code
		
	vector targetPos = target.GetOrigin()
	
	// intelligent checks
	// is the spot already valid?
	if ( !PlayerPosInSolidIgnoreOtherPlayers( teleported, targetPos ) )
	{
		print( "TeleportToEntitySafe: pos was valid first time" )
		teleported.SetOrigin( targetPos )
		teleported.SetAngles( target.GetAngles() )
		return
	}
	else if ( target.IsCrouched() && !teleported.IsCrouched() && !PlayerPosInSolidIgnoreOtherPlayers( teleported, targetPos, true ) )
	{
		// teleporting to sliding players while not sliding can cause clipping
		// so if we set the player to be sliding maybe it'll kinda sorta work out
		
		// check if we can just offset ourselves downwards to a valid pos without crouching
		// 25 is the diff in collision height between stood up/crouched
		if ( !PlayerPosInSolidIgnoreOtherPlayers( teleported, targetPos + <0, 0, -25> ) )
		{
			print( "TeleportToEntitySafe: offset to valid position to account for crouch!" )
			teleported.SetOrigin( targetPos + <0, 0, -25> )
			teleported.SetAngles( target.GetAngles() )
			return
		}
		else
		{
			print( "TeleportToEntitySafe: couldn't offset to valid pos for crouch, waiting for crouch to finish" )
			teleported.ForceCrouch()
		}
	}
	
	// last resort - give up and wait for the pos to be valid
	// given this is mainly used for teleporting to players it can probably be abused
	
	float startTime = Time()
	float timeout = 5.0
	
	print( "TeleportToEntitySafe: couldn't get a safe pos with starting conditions, waiting for pos to be valid" )
	while ( PlayerPosInSolidIgnoreOtherPlayers( teleported, target.GetOrigin() ) )
	{
		WaitFrame()
		
		if ( Time() - startTime > timeout )
		{
			// if we take too long, we probably aren't gonna teleport anyway, so why bother wasting cpu time on it
			print( "TeleportToEntitySafe: timed out waiting for successful teleport attempt" )
			return
		}
	}
		
	print( "TeleportToEntitySafe: done waiting for pos to be valid!" )
	teleported.SetOrigin( target.GetOrigin() )
	teleported.SetAngles( target.GetAngles() )
	teleported.UnforceCrouch()
}

bool function PlayerPosInSolidIgnoreOtherPlayers( entity player, vector targetPos, bool fakeCrouch = false )
{
	// playerposinsolid by default doesn't ignore other players, only the player they're checking for
	// this reimplements it, but ignores all players
	// don't wanna patch the original function because the original behaviour could be useful
			
	vector maxs = player.GetPlayerMaxs()
	
	if ( maxs.z == 32 )
		maxs.z = 72 // maxs seem broken when connecting so make sure they're normal for this
	
	if ( fakeCrouch )
		maxs.z = 47 // 47 when crouched, 72 uncrouched
	
	TraceResults traceResult = TraceHull( targetPos, targetPos + <0, 0, 1>, player.GetPlayerMins(), maxs, GetPlayerArray(), TRACE_MASK_PLAYERSOLID, TRACE_COLLISION_GROUP_PLAYER )
	return traceResult.startSolid
}

array< entity > function GetEntitiesByEditorClass( string editorClass )
{
	array< entity > ret
	for ( int i = 0; i < 2048 /*max ents in source*/; i++ )
	{
		entity ent = GetEntByIndex( i )
		if ( ent == null )
			continue
			
		if ( GetEditorClass( ent ) == editorClass )
			ret.append( ent )
	}
	
	return ret
}


//EFFECT AND CAUSE/TIMESHIFT STUFF

array< entity > function GetPlayersInTimeline( int timeline )
{
	array< entity > playersInTimeline
	foreach ( entity player in GetPlayerArray() )
	{
		if ( player.s.timeline == timeline )
			playersInTimeline.append( player )
	}
	
	return playersInTimeline
}