Last active
December 19, 2021 13:57
-
-
Save voided/605e77dcac2b6090850d to your computer and use it in GitHub Desktop.
TF2 spectator damage plugin - https://forums.alliedmods.net/showthread.php?t=248494
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma semicolon 1 | |
#include <sourcemod> | |
#include <sendproxy> | |
#pragma newdecls required | |
#define PLUGIN_VERSION "0.1" | |
public Plugin myinfo = | |
{ | |
name = "Spectator Damage", | |
author = "VoiDeD", | |
description = "Allow spectators to view combat text and hear hitsounds of spectated players", | |
version = PLUGIN_VERSION, | |
url = "http://saxtonhell.com" | |
}; | |
// Spectator Movement modes | |
enum ObsMode | |
{ | |
OBS_MODE_NONE = 0, // not in spectator mode | |
OBS_MODE_DEATHCAM, // special mode for death cam animation | |
OBS_MODE_FREEZECAM, // zooms to a target, and freeze-frames on them | |
OBS_MODE_FIXED, // view from a fixed camera position | |
OBS_MODE_IN_EYE, // follow a player in first person view | |
OBS_MODE_CHASE, // follow a player in third person view | |
OBS_MODE_ROAMING, // free roaming | |
NUM_OBSERVER_MODES, | |
}; | |
// m_lifeState values | |
#define LIFE_ALIVE 0 // alive | |
#define LIFE_DYING 1 // playing death animation or still falling off of a ledge waiting to hit ground | |
#define LIFE_DEAD 2 // dead. lying still. | |
#define LIFE_RESPAWNABLE 3 | |
#define LIFE_DISCARDBODY 4 | |
public bool g_bHasDamageText[ MAXPLAYERS + 1 ] = { false, ... }; | |
public bool g_bFakeAlive[ MAXPLAYERS + 1 ] = { false, ... }; | |
public float g_LastRelay[ MAXPLAYERS + 1 ] = { 0.0, ... }; | |
public void OnPluginStart() | |
{ | |
HookEvent( "player_hurt", OnPlayerHurt ); | |
// for late load | |
for ( int i = 1 ; i <= MaxClients ; ++i ) | |
{ | |
if ( !IsClientInGame( i ) ) | |
continue; | |
OnClientPutInServer( i ); | |
} | |
} | |
public void OnMapStart() | |
{ | |
CreateTimer( 0.5, Timer_CheckLastRelays, _, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT ); | |
} | |
public void OnClientPutInServer( int client ) | |
{ | |
g_bFakeAlive[ client ] = false; | |
g_LastRelay[ client ] = 0.0; | |
SendProxy_Hook( client, "m_lifeState", Prop_Int, ClientLifeStateProxy ); | |
} | |
public Action Timer_CheckLastRelays( Handle hTimer ) | |
{ | |
for ( int client = 1 ; client <= MaxClients ; ++client ) | |
{ | |
if ( !IsClientInGame( client ) || IsFakeClient( client ) ) | |
continue; | |
float timeSinceLastDamage = GetGameTime() - g_LastRelay[ client ]; | |
if ( timeSinceLastDamage < 1.0 ) | |
{ | |
// if it's been less than a second since the last damage the spectated client has done, we'll keep faking that they're alive | |
// this allows the hud damagetext enough time to fully draw | |
g_bFakeAlive[ client ] = true; | |
} | |
else | |
{ | |
// otherwise, the client hasn't spectated any damage being done recently so we'll reset them | |
g_bFakeAlive[ client ] = false; | |
} | |
} | |
} | |
public Action ClientLifeStateProxy( int entity, const char[] propName, int &value, int element ) | |
{ | |
if ( g_bFakeAlive[ entity ] ) | |
{ | |
value = LIFE_ALIVE; | |
return Plugin_Changed; | |
} | |
return Plugin_Continue; | |
} | |
public void OnClientSettingsChanged( int client ) | |
{ | |
char strCombatText[ 20 ]; | |
if ( !GetClientInfo( client, "hud_combattext", strCombatText, sizeof( strCombatText ) ) ) | |
{ | |
LogError( "Unable to get hud_combattext for \"%L\"", client ); | |
return; | |
} | |
g_bHasDamageText[ client ] = StringToInt( strCombatText ) != 0; | |
} | |
public void OnPlayerHurt( Handle event, const char[] name, bool dontBroadcast ) | |
{ | |
if ( dontBroadcast ) | |
return; | |
int attacker = GetClientOfUserId( GetEventInt( event, "attacker" ) ); | |
if ( !attacker ) | |
return; | |
int victim = GetClientOfUserId( GetEventInt( event, "userid" ) ); | |
if ( attacker == victim ) | |
return; | |
RelayDamageToSpectators( attacker, event ); | |
} | |
static stock void RelayDamageToSpectators( int attacker, Handle event ) | |
{ | |
for ( int i = 1 ; i <= MaxClients ; ++i ) | |
{ | |
if ( !IsClientInGame( i ) ) | |
continue; | |
if ( IsFakeClient( i ) ) | |
continue; | |
if ( !g_bHasDamageText[ i ] ) | |
continue; // client doesn't have damage text enabled | |
if ( !IsClientObserver( i ) ) | |
continue; // not a spectator | |
if ( !IsValidObserverMode( i ) ) | |
continue; // not in first or third person mode | |
if ( GetObserverTarget( i ) != attacker ) | |
continue; // not spectating the attacker | |
// at this point |i| is a client that is spectating |attacker| and wants to know about their damage | |
// begin tricking the client into thinking it's alive | |
g_bFakeAlive[ i ] = true; | |
g_LastRelay[ i ] = GetGameTime(); | |
// we're avoiding any double damage counting by other plugins by using npc_hurt instead of simply re-sending the player_hurt event | |
Handle hurtRelay = CreateEvent( "npc_hurt", true ); | |
SetEventInt( hurtRelay, "entindex", GetClientOfUserId( GetEventInt( event, "userid" ) ) ); | |
SetEventInt( hurtRelay, "health", GetEventInt( event, "health" ) ); | |
// magic happens right here: | |
// by setting the spectator as the attacker, that client will then care about this hurt event and do hud effects | |
SetEventInt( hurtRelay, "attacker_player", GetClientUserId( i ) ); | |
SetEventInt( hurtRelay, "weaponid", GetEventInt( event, "weaponid" ) ); | |
SetEventInt( hurtRelay, "damageamount", GetEventInt( event, "damageamount" ) ); | |
SetEventBool( hurtRelay, "crit", GetEventBool( event, "crit" ) ); | |
// delay re-firing this event by a little to allow the previous frames to pack in the alive lifestate entity update | |
Handle pack; | |
CreateDataTimer( 0.05, DelayEvent, pack, TIMER_FLAG_NO_MAPCHANGE ); | |
WritePackCell( pack, GetClientUserId( i ) ); | |
WritePackCell( pack, hurtRelay ); | |
} | |
} | |
public Action DelayEvent( Handle timer, Handle dataPack ) | |
{ | |
ResetPack( dataPack ); | |
int client = GetClientOfUserId( ReadPackCell( dataPack ) ); | |
Handle event = ReadPackCell( dataPack ); | |
if ( !client ) | |
{ | |
// client disconnected, cancel resending the event | |
CancelCreatedEvent( event ); | |
return; | |
} | |
FireEvent( event ); | |
} | |
stock ObsMode GetObserverMode( int client ) | |
{ | |
return ObsMode:GetEntProp( client, Prop_Send, "m_iObserverMode" ); | |
} | |
stock int GetObserverTarget( int client ) | |
{ | |
return GetEntPropEnt( client, Prop_Send, "m_hObserverTarget" ); | |
} | |
stock bool IsValidObserverMode( int client ) | |
{ | |
ObsMode obsMode = GetObserverMode( client ); | |
return ( obsMode == OBS_MODE_IN_EYE || obsMode == OBS_MODE_CHASE ); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment