Page MenuHomeFeedback Tracker

Admin‑log accuracy & spam‑reduction pass (names/IDs, corpse‑shots, burst collapse)
New, NormalPublic

Description

A: When survivors spawned via events.xml are hit or killed, pluginAdminLog writes empty name and ID fields. This breaks automated log parsers and prevents admins from auditing PvE encounters. Example lines:

19:04:28 | Player "" (id= pos=<3683.9, 6001.2, 402.0>)[HP: 51.768] hit by Player "PLAYERnameHERE" (id=PLAYERidHERE pos=<3686.5, 6000.5, 402.0>) into Head(0) for 24.116 damage (Bullet_556x45) with M4‑A1 from 2.65726 meters  
19:04:28 | Player "" (DEAD) (id= pos=<3683.9, 6001.2, 402.0>) killed by Player "PLAYERnameHERE" (id=PLAYERidHERE pos=<3686.5, 6000.5, 402.0>) with M4‑A1 from 2.65726 meters

B: When a body is fired at after death, PlayerHitBy() logs every impact, flooding the log:

02:09:45 | Player "PlayerOne_Name" (DEAD) (id=PlayerOne_ID pos=<5065.5, 9992.1, 192.1>)[HP: 0] hit by Player "PlayerTwo_Name" (id=PlayerTwo_ID pos=<5064.5, 9990.2, 192.1>) into Torso(15) for 26.9018 damage (Bullet_762x39) with KA-M from 2.11794 meters 
02:09:45 | Player "PlayerOne_Name" (DEAD) (id=PlayerOne_ID pos=<5065.5, 9992.1, 192.1>)[HP: 0] hit by Player "PlayerTwo_Name" (id=PlayerTwo_ID pos=<5064.5, 9990.1, 192.1>) into Torso(16) for 26.8942 damage (Bullet_762x39) with KA-M from 2.2114 meters 
02:09:45 | Player "PlayerOne_Name" (DEAD) (id=PlayerOne_ID pos=<5065.5, 9992.1, 192.1>)[HP: 0] hit by Player "PlayerTwo_Name" (id=PlayerTwo_ID pos=<5064.5, 9990, 192.1>) into Torso(16) for 26.8844 damage (Bullet_762x39) with KA-M from 2.35586 meters

C: Vehicle damage can print 10‑plus nearly identical lines within one second, for example:

03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 88.2994] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 78.7425] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 69.1857] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 59.7092] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 50.2328] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 40.945] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 31.6573] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 22.4516] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 13.246] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 4.22963] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (DEAD) (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 0] hit by Offroad_02 with TransportHit
03:21:20 | Player "PlayerName" (DEAD) (id=PlayerID pos=<3804.7, 8770.1, 191.2>) killed by Offroad_02

Details

Severity
Tweak
Resolution
Open
Reproducibility
Always
Operating System
Windows 7
Category
Server
Steps To Reproduce

A:

  1. Add a survivor spawn to events.xml.
  2. Start a server and let the event spawn an AI survivor.
  3. Have a player shoot and kill the AI.
  4. Check .ADM logfile — the survivor’s name/id fields are blank.

B:

Kill any player (their HP reaches 0).
Keep shooting the corpse.
Watch .ADM – every bullet after death is still printed.

C:

Drive an Offroad_02 into a player.
Inspect .ADM – burst of lines with identical timestamp (second‑precision).

Additional Information

A: PluginAdminLog::GetPlayerPrefix() in pluginAdminLog only checks:

string GetPlayerPrefix( PlayerBase player, PlayerIdentity identity )  // player name + id + position prefix for log prints
	{	
		
		m_Position = player.GetPosition();
		m_PosArray[3] = { m_Position[0].ToString(), m_Position[2].ToString(), m_Position[1].ToString() };
		
		for ( int i = 0; i < 3; i++ )	// trim position precision
		{
			m_DotIndex = m_PosArray[i].IndexOf(".");
			if ( m_DotIndex != -1 )
			{
			m_PosArray[i] = m_PosArray[i].Substring( 0, m_DotIndex + 2 );
			}
		}
		
		if ( identity ) 	// return partial message even if it fails to fetch identity 
		{
			//return "Player \"" + "Unknown/Dead Entity" + "\" (id=" + "Unknown" + " pos=<" +  m_PosArray[0] + ", " + m_PosArray[1] + ", " + m_PosArray[2] + ">)";
			m_PlayerName = "\"" + identity.GetName() + "\"";
			m_Pid = identity.GetId();
		}
		else
		{
			m_PlayerName = "\"" + player.GetCachedName() + "\"";
			m_Pid = player.GetCachedID();
		}
		
		
		if ( !player.IsAlive() )
		{
		 	m_PlayerName = m_PlayerName + " (DEAD)"; 
		}
		
		return "Player " + m_PlayerName + " (id=" + m_Pid + " pos=<" +  m_PosArray[0] + ", " + m_PosArray[1] + ", " + m_PosArray[2] + ">)";
	}
• player.GetIdentity().GetName()/GetId()  
• player.GetCachedName()/GetCachedID()

Both return empty strings for AI, leaving "" in the log.

PROPOSED FIX:

string GetPlayerPrefix( PlayerBase player, PlayerIdentity identity )  // player name + id + position prefix for log prints
	{	
		
		m_Position = player.GetPosition();
		m_PosArray[3] = { m_Position[0].ToString(), m_Position[2].ToString(), m_Position[1].ToString() };
		
		for ( int i = 0; i < 3; i++ )	// trim position precision
		{
			m_DotIndex = m_PosArray[i].IndexOf(".");
			if ( m_DotIndex != -1 )
			{
			m_PosArray[i] = m_PosArray[i].Substring( 0, m_DotIndex + 2 );
			}
		}
		
		if ( identity ) 	// return partial message even if it fails to fetch identity 
		{
			//return "Player \"" + "Unknown/Dead Entity" + "\" (id=" + "Unknown" + " pos=<" +  m_PosArray[0] + ", " + m_PosArray[1] + ", " + m_PosArray[2] + ">)";
			m_PlayerName = "\"" + identity.GetName() + "\"";
			m_Pid = identity.GetId();
		}
		else
		{
			string cachedName = player.GetCachedName();   // may be ""
			string cachedID   = player.GetCachedID();     // may be ""
			if (cachedName == "")
				cachedName = "NPC/Survivor"; 
			if (cachedID == "")
				cachedID = "None"
			
			m_PlayerName = "\"" + cachedName + "\"";
			m_Pid = cachedID;
		}
		
		if ( !player.IsAlive() )
		{
			m_PlayerName = m_PlayerName + " (DEAD)"; 
		}
		
		return "Player " + m_PlayerName + " (id=" + m_Pid + " pos=<" +  m_PosArray[0] + ", " + m_PosArray[1] + ", " + m_PosArray[2] + ">)";
	}

Resulting log line:

19:04:28 | Player "NPC/Survivor" (id=None pos=<3683.9, 6001.2, 402.0>)[HP: 51.768] hit by Player "PLAYERnameHERE" (id=PLAYERidHERE pos=<3686.5, 6000.5, 402.0>) into Head(0) for 24.116 damage (Bullet_556x45) with M4‑A1 from 2.65726 meters

B: PluginAdminLog::PlayerHitBy() never checks player.IsAlive() or player.GetHealth(). It prints regardless of HP.

PROPOSED FIX:

void PlayerHitBy( TotalDamageResult damageResult, int damageType, PlayerBase player,
                  EntityAI source, int component, string dmgZone, string ammo )
{
    /* NEW EARLY RETURN ------------------------------------------- */
    if ( !player || !player.IsAlive() || player.GetHealth() <= 0 )
        return;        // ignore hits on dead bodies
    /* ------------------------------------------------------------ */

    // …existing function body unchanged…
}

After this patch, any impact recorded while HP == 0 is dropped, eliminating corpse‑spam while preserving valid combat lines.

C: PlayerHitBy() writes one line per call; there is no batching.

PROPOSED FIX:

/*  ======  Add inside PluginAdminLog class  ======  */
ref map<string, ref HitBucket> m_HitBuckets = new map<string, ref HitBucket>();

class HitBucket
{
    int         count;
    float       totalDmg;
    float       lastHP;
    float       flushAt;      // epoch ms when we output
    string      baseLine;     // all static text before "xN / dmg"
};

/*  ======  Modify PlayerHitBy()  ======  */
void PlayerHitBy( TotalDamageResult damageResult, int damageType, PlayerBase player,
                  EntityAI source, int component, string dmgZone, string ammo )
{
    if ( !player || !player.IsAlive() ) return;

    /* build base line exactly once per bucket */
    string basePrefix  = GetPlayerPrefix( player , player.GetIdentity() ) +
                         "[HP: %1] hit by ";         // %1 = HP inserted later
    string srcPrefix   = /* existing logic that builds attacker / item / distance */;
    string hitMessage  = GetHitMessage( damageResult, component, dmgZone, ammo );

    int epochSec  = Math.Floor(GetGame().GetTime() / 1000);   // second‑precision
    string bucketKey = player.GetIdentity().GetId() + "_" + srcPrefix + "_" + epochSec;

    HitBucket bucket;
    if ( !m_HitBuckets.Find(bucketKey, bucket) )
    {
        bucket = new HitBucket();
        m_HitBuckets.Insert(bucketKey, bucket);

        bucket.baseLine = basePrefix + srcPrefix + hitMessage;
        bucket.flushAt  = GetGame().GetTime() + 100;          // flush 0.1 s later
    }

    /* accumulate */
    bucket.count++;
    bucket.totalDmg += damageResult.GetHighestDamage("Health");
    bucket.lastHP    = player.GetHealth();
}

/*  ======  Flush on a short timer (add to constructor)  ======  */
m_TimerHits = new Timer();
m_TimerHits.Run( 0.11 , this, "FlushHitBuckets", NULL, true );

/*  ======  New method  ======  */
void FlushHitBuckets()
{
    float now = GetGame().GetTime();
    foreach ( string key, HitBucket bucket : m_HitBuckets )
    {
        if ( now >= bucket.flushAt )
        {
            LogPrint( bucket.baseLine.Replace("%1", bucket.lastHP.ToString()) +
                      "  x" + bucket.count.ToString() +
                      "  for a total of" + bucket.totalDmg.ToString() + " damage" );
            m_HitBuckets.Remove(key);
        }
    }
}

Resulting collapsed line example:

03:21:20 | Player "PlayerName" (id=PlayerID pos=<3804.7, 8770.1, 191.2>)[HP: 4.23] hit by Offroad_02 with TransportHit x10 for a total of 88.30 damage

Event Timeline

SoulsAggro renamed this task from Admin‑log accuracy & spam‑reduction pass (names/IDs, connect info, corpse‑shots, burst collapse) to Admin‑log accuracy & spam‑reduction pass (names/IDs, corpse‑shots, burst collapse).
SoulsAggro edited Additional Information. (Show Details)Sat, May 3, 5:59 PM