Skip to content

Instantly share code, notes, and snippets.

@voided
Last active December 19, 2021 13:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save voided/605e77dcac2b6090850d to your computer and use it in GitHub Desktop.
Save voided/605e77dcac2b6090850d to your computer and use it in GitHub Desktop.
#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