aboutsummaryrefslogtreecommitdiff
path: root/Northstar.Custom/mod/scripts/vscripts/sh_bleedout_damage.gnut
blob: a0f272271db64663102f7d7f3049f8367fbeb1bf (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
// note: this is technically vanilla content, since bleedout was shipped with retail, but it needs custom remote functions which would break vanilla compatiblity, so it's not in Northstar.CustomServers
// idk why bleedout was even shipped in retail lmao
global function BleedoutDamage_PreInit
global function BleedoutDamage_Init
global function SetShouldPlayerStartBleedoutFunc

struct {
	array<entity> bleedingPlayers // this is in _bleedout already, but it doesn't expose a way to track it, so we have to track it ourselves
	bool functionref( entity, var ) shouldPlayerStartBleedoutFunc = null
} file

void function BleedoutDamage_PreInit()
{
	AddCallback_OnRegisteringCustomNetworkVars( Bleedout_RegisterRemoteFunctions )
	
	#if CLIENT
		// because playlist var overrides fucking suck, they aren't actually updated by this point
		// client bleedout can be inited late enough that we can just init it on local player spawn
		AddCallback_LocalClientPlayerSpawned( InitClientBleedoutForLocalPlayer )
	#endif
}

void function Bleedout_RegisterRemoteFunctions()
{
	Remote_RegisterFunction( "ServerCallback_BLEEDOUT_StartFirstAidProgressBar" )
	Remote_RegisterFunction( "ServerCallback_BLEEDOUT_StopFirstAidProgressBar" )
	Remote_RegisterFunction( "ServerCallback_BLEEDOUT_ShowWoundedMarker" )
	Remote_RegisterFunction( "ServerCallback_BLEEDOUT_HideWoundedMarker" )
}

// copied from sh_bleedout
const float DEFAULT_BLEEDOUT_TIME = 30.0
const float DEFAULT_FIRSTAID_TIME = 3.0
const float DEFAULT_FIRSTAID_TIME_SELF = -1.0
const float DEFAULT_FIRSTAID_HEAL_PERCENT = 1.0
const float DEFAULT_AI_BLEEDING_PLAYER_MISS_CHANCE = 0.0
const bool DEFAULT_FORCE_WEAPON_HOLSTER = false
const bool DEFAULT_DEATH_ON_TEAM_BLEEDOUT = false

void function BleedoutDamage_Init()
{
	AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_BLEEDOUT", "riff_player_bleedout", [ "#SETTING_DEFAULT", "#SETTING_DISABLED", "#SETTING_ENABLED" ], "0" )
	AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_BLEEDOUT", "player_bleedout_forceHolster", [ "#SETTING_DISABLED", "#SETTING_ENABLED" ], DEFAULT_FORCE_WEAPON_HOLSTER.tostring() )
	AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_BLEEDOUT", "player_bleedout_forceDeathOnTeamBleedout", [ "#SETTING_DISABLED", "#SETTING_ENABLED" ], DEFAULT_DEATH_ON_TEAM_BLEEDOUT.tostring() )
	AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_BLEEDOUT", "player_bleedout_bleedoutTime", DEFAULT_BLEEDOUT_TIME.tostring() )
	AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_BLEEDOUT", "player_bleedout_firstAidTime", DEFAULT_FIRSTAID_TIME.tostring() )
	AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_BLEEDOUT", "player_bleedout_firstAidTimeSelf", DEFAULT_FIRSTAID_TIME_SELF.tostring() )
	AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_BLEEDOUT", "player_bleedout_firstAidHealPercent", DEFAULT_FIRSTAID_HEAL_PERCENT.tostring() )
	AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_BLEEDOUT", "player_bleedout_aiBleedingPlayerMissChance", DEFAULT_AI_BLEEDING_PLAYER_MISS_CHANCE.tostring() )
	
	#if CLIENT
		// manually register signals here: defensive fix so we don't crash
		RegisterSignal( "Bleedout_OnRevive" )
		RegisterSignal( "Bleedout_StopFirstAid" )
	#elseif SERVER
		// sh_riff_settings should set this correctly on server
		if ( !Riff_PlayerBleedout() )
			return
		
		AddDamageCallback( "player", HandleDamageForBleedout ) // do this irregardless of whether scripts inited, given it should always be used for bleedout
		Bleedout_SetCallback_OnPlayerStartBleedout( OnPlayerBleedoutBegin ) // kinda sucks we have to use this callback since game scripts could be using it		
		
		// dont init if scripts already inited it manually
		if ( !Bleedout_IsBleedoutLogicActive() )
		{
			InitSharedBleedoutWithPlaylistVars()
			Bleedout_Init()
		}
	#endif
}

void function SetShouldPlayerStartBleedoutFunc( bool functionref( entity, var ) func )
{
	file.shouldPlayerStartBleedoutFunc = func
}

void function InitSharedBleedoutWithPlaylistVars()
{
	BleedoutShared_Init( 
		GetCurrentPlaylistVarFloat( "player_bleedout_bleedoutTime", DEFAULT_BLEEDOUT_TIME ),
		GetCurrentPlaylistVarFloat( "player_bleedout_firstAidTime", DEFAULT_FIRSTAID_TIME ),
		GetCurrentPlaylistVarFloat( "player_bleedout_firstAidTimeSelf", DEFAULT_FIRSTAID_TIME_SELF ),
		GetCurrentPlaylistVarFloat( "player_bleedout_firstAidHealPercent", DEFAULT_FIRSTAID_HEAL_PERCENT ),
		GetCurrentPlaylistVarFloat( "player_bleedout_aiBleedingPlayerMissChance", DEFAULT_AI_BLEEDING_PLAYER_MISS_CHANCE ),
		GetCurrentPlaylistVarInt( "player_bleedout_forceHolster", int( DEFAULT_FORCE_WEAPON_HOLSTER ) ) == 1,
		GetCurrentPlaylistVarInt( "player_bleedout_forceDeathOnTeamBleedout", int( DEFAULT_DEATH_ON_TEAM_BLEEDOUT ) ) == 1
	)
}

#if CLIENT
void function InitClientBleedoutForLocalPlayer( entity player )
{
	// dont init if bleedout is disabled or scripts already inited it
	if ( !Riff_PlayerBleedout() || Bleedout_IsBleedoutLogicActive() )
		return
	
	InitSharedBleedoutWithPlaylistVars()
	BleedoutClient_Init()
}
#endif

#if SERVER
void function HandleDamageForBleedout( entity player, var damageInfo )
{
	if ( IsInstantDeath( damageInfo ) || DamageInfo_GetForceKill( damageInfo ) || player.IsTitan() || file.bleedingPlayers.contains( player ) )
		return
	
	if ( file.shouldPlayerStartBleedoutFunc != null )
		if ( !file.shouldPlayerStartBleedoutFunc( player, damageInfo ) )
			return
	
	// check if damage would kill player
	if ( player.GetHealth() - DamageInfo_GetDamage( damageInfo ) <= 0 )
	{	
		Bleedout_StartPlayerBleedout( player, DamageInfo_GetAttacker( damageInfo ) )
		DamageInfo_SetDamage( damageInfo, 1 ) // prevent player from dying, but if we set it to 0, player won't receive any knockback from damage source
	}
}

void function OnPlayerBleedoutBegin( entity player )
{
	file.bleedingPlayers.append( player )
	EmitSoundOnEntityOnlyToPlayer( player, player, "Player_Death_Begin" )
	
	thread PlayerBleedoutGracePeriod( player )

	// would prefer to use Bleedout_SetCallback_OnPlayerGiveFirstAid for this, but it doesn't expose the player that's receiving first aid for some reason
	thread TrackPlayerBleedout( player )
}

void function PlayerBleedoutGracePeriod( entity player )
{
	player.EndSignal( "OnDeath" )
	player.EndSignal( "OnDestroy" )
	
	OnThreadEnd( function() : ( player )
	{
		player.ClearInvulnerable()
		StopSoundOnEntity( player, "Player_Death_Begin" )
	})
	
	player.SetInvulnerable()
	wait 0.25
}

void function TrackPlayerBleedout( entity player )
{
	player.EndSignal( "OnDeath" )
	player.EndSignal( "OnDestroy" )
	
	OnThreadEnd( function() : ( player )
	{
		file.bleedingPlayers.remove( file.bleedingPlayers.find( player ) )
	})
	
	WaitFrame() // wait a frame, since this gets called before this status effect is added
	
	while ( StatusEffect_Get( player, eStatusEffect.bleedoutDOF ) != 0 )
		WaitFrame() 
}
#endif