Skip to content

Instantly share code, notes, and snippets.

@Pinsplash
Created January 11, 2024 12:57
Show Gist options
  • Save Pinsplash/27a72c6271e850cec6e2cf479c3f81cd to your computer and use it in GitHub Desktop.
Save Pinsplash/27a72c6271e850cec6e2cf479c3f81cd to your computer and use it in GitHub Desktop.
Source gauss debug mod
This is a debug mod which was used in the making of and briefly featured in this video: https://www.youtube.com/watch?v=xmv_4XxzeFs
This mod recreates the HL1/HLS tau cannon beam behavior in HL2.
Pro: This mod can visualize a beam's path. Very easy to understand.
Con: This is not necessarily 1:1 with beam behavior in either game because the player entity acts differently in all 3 games and line traces work differently in HL1 than in Source.
If you see a result that doesn't match expectations, the result should be confirmed in HL1/HLS. Debug lines can be placed in HLS with drawline.
If you want to test something from an HL1 map, you can copy maps and textures from HLS without needing to recompile.
To build, add into an existing 2013 SP SDK mod. https://www.youtube.com/watch?v=BtiAvNkEpC0
To use, type gauss in the console to fire a primary beam. To fire a secondary beam, specify an amount of damage with the command (e.g. gauss 200). To use HLS behavior, use gauss_hls instead.
Beams are white until they penetrate, where they become yellow. Orange is the line that finds space on the other side of the wall when penetrating. Blue is the line that traces back to the point of impact. Green represents thickness.
Lines are offset in 1 unit increments to prevent overlap, which makes it hard to understand what's happening. This can be mostly disabled with gauss_offset.
Text debug messages have been formatted to still be decently readable if lines print out of order.
//in CBaseEntity::OnTakeDamage
Msg("%s took %0.01f damage\n", GetClassname(), info.GetDamage());
CON_COMMAND(gauss, "")//hl1
{
CBasePlayer* pPlayer = UTIL_GetLocalPlayer();
CHL2_Player *pHL2Player = static_cast<CHL2_Player*>(pPlayer);
bool bPrimaryFire = !(args.ArgC() > 1);//argument is damage amount. if no argument, then we're primary fire.
float flDamage = bPrimaryFire ? 20 : atoi(args[1]);//we always know the damage for primary fire.
pHL2Player->Gauss(bPrimaryFire, flDamage, false);
}
CON_COMMAND(gauss_hls, "")//half life source
{
CBasePlayer* pPlayer = UTIL_GetLocalPlayer();
CHL2_Player *pHL2Player = static_cast<CHL2_Player*>(pPlayer);
bool bPrimaryFire = !(args.ArgC() > 1);//argument is damage amount. if no argument, then we're primary fire.
float flDamage = bPrimaryFire ? 20 : atoi(args[1]);//we always know the damage for primary fire.
pHL2Player->Gauss(bPrimaryFire, flDamage, true);
}
ConVar gauss_offset("gauss_offset", "1");
void CHL2_Player::Gauss(bool bPrimaryFire, float flDamage, bool bHalfLifeSource)
{
CBasePlayer *pPlayer = this;
Vector vecDir = pPlayer->GetAutoaimVector(0);
Vector vecSrc = pPlayer->Weapon_ShootPosition();
Msg("Starting %s fire with damage %f\n", bPrimaryFire ? "primary" : "secondary", flDamage);
if (flDamage < 25 && !bPrimaryFire) Msg("Note: Damage of %f is impossibly low.\n", flDamage);
if (!(flDamage > 10)) Msg("Note: Beam will do nothing if damage <= 10.\n");
if (flDamage > 200) Msg("Note: Damage of %f is impossibly high.\n", flDamage);
Vector vecDest = vecSrc + vecDir * 8192;
CBaseEntity *pentIgnore = pPlayer;
trace_t traceBeam, tracePunch, traceBack;
bool bHasPunched = false;
int nMaxHits = 10;
float flExpDmg = 0;//total amount of damage from explosions
while (flDamage > 10 && nMaxHits > 0)
{
nMaxHits--;
UTIL_TraceLine(vecSrc, vecDest, MASK_SHOT, pentIgnore, COLLISION_GROUP_NONE, &traceBeam);
//NDebugOverlay::Line(vecSrc, traceBeam.endpos, 255, 255, bPrimaryFire ? 0 : 255, false, 30);
if (bHasPunched)
UTIL_AddDebugLineColor(vecSrc - Vector(0, 0, (gauss_offset.GetBool() ? 9 - nMaxHits : 0)), traceBeam.endpos - Vector(0, 0, (gauss_offset.GetBool() ? 9 - nMaxHits : 0)), 255, 255, 0);
else
UTIL_AddDebugLineColor(vecSrc, traceBeam.endpos, 255, 255, 255);
//if a beam segment went nowhere, represent it with a downward line, otherwise the line is invisible
if (vecSrc == traceBeam.endpos)
UTIL_AddDebugLineColor(vecSrc - Vector(0, 0, (gauss_offset.GetBool() ? 9 - nMaxHits : 0) + 1), traceBeam.endpos - Vector(0, 0, (gauss_offset.GetBool() ? 9 - nMaxHits : 0) - 1), 255, 255, 0);
Msg("Hit %i, s1, %f damage. Beam went from %0.1f %0.1f %0.1f to %0.1f %0.1f %0.1f. vecDest %0.1f %0.1f %0.1f.\n", 10 - nMaxHits, flDamage, vecSrc.x, vecSrc.y, vecSrc.z, traceBeam.endpos.x, traceBeam.endpos.y, traceBeam.endpos.z, vecDest.x, vecDest.y, vecDest.z);
if (traceBeam.allsolid)
{
Msg("Hit %i, s1.1, Beam stuck in solid, ending.\n", 10 - nMaxHits);
break;
}
CBaseEntity *pEntity = traceBeam.m_pEnt;
if (pEntity == NULL)
{
Msg("Hit %i, s1.2, Beam hit nothing, ending.\n", 10 - nMaxHits);
break;
}
bool bShouldDamageEntity = (pEntity->m_takedamage != DAMAGE_NO);
if (bShouldDamageEntity)
{
Msg("Hit %i, s1.3, Hurting %s named %s for %f damage.\n", 10 - nMaxHits, pEntity->GetClassname(), STRING(pEntity->GetEntityName()), flDamage);
ClearMultiDamage();
CTakeDamageInfo info(pPlayer, pPlayer, flDamage, bHalfLifeSource ? DMG_ENERGYBEAM : DMG_BULLET);//inflictor was originally [this] (the gun). this is an okay concession.
CalculateMeleeDamageForce(&info, vecDir, traceBeam.endpos);
pEntity->DispatchTraceAttack(info, vecDir, &traceBeam);
ApplyMultiDamage();
}
else
Msg("Hit %i, s1.4, Entity %s named %s cannot be hurt.\n", 10 - nMaxHits, pEntity->GetClassname(), STRING(pEntity->GetEntityName()));
if (pEntity->IsBSPModel() && !bShouldDamageEntity)
{
Msg("Hit %i, s2.1, Entity is an undamageable brush.\n", 10 - nMaxHits);
pentIgnore = NULL;
float n = -DotProduct(traceBeam.plane.normal, vecDir);
if (n < 0.5)//60 degrees
{
Msg("Hit %i, s3.1, n is %f. Reflecting. Reducing damage by %f percent.\n", 10 - nMaxHits, n, 100 * n);
Vector r = 2.0 * traceBeam.plane.normal * n + vecDir;
vecDir = r;
if (bHalfLifeSource)
vecSrc = traceBeam.endpos;
else
vecSrc = traceBeam.endpos + vecDir * 8;
vecDest = vecSrc + vecDir * 8192;
//added a print statement in CBaseEntity::OnTakeDamage to see how much damage the explosion does to each entity, cannot be seen from here.
RadiusDamage(CTakeDamageInfo(pPlayer, pPlayer, flDamage * n, DMG_BLAST), traceBeam.endpos, flDamage * n * 2.5, CLASS_NONE, NULL);
flExpDmg += flDamage * n;
if (n == 0)
{
Warning("Hit %i, s3.1.1, Sanity Check: n was 0, set to 0.1.\n", 10 - nMaxHits);//implies beam is coplanar to surface.
n = 0.1;
}
flDamage = flDamage * (1 - n);
}
else
{
Msg("Hit %i, s3.2, n = %f, penetrating.\n", 10 - nMaxHits, n);
if (bHasPunched)
{
Msg("Hit %i, s3.2.1, Beam already penetrated once, ending.\n", 10 - nMaxHits);
break;
}
bHasPunched = true;
if (!bPrimaryFire)
{
UTIL_TraceLine(traceBeam.endpos + vecDir * 8, vecDest, MASK_SHOT, pentIgnore, COLLISION_GROUP_NONE, &tracePunch);
//NDebugOverlay::Line(traceBeam.endpos + vecDir * 8, tracePunch.endpos, 255, 128, 0, false, 30);
UTIL_AddDebugLineColor(traceBeam.endpos + vecDir * 8 + Vector(0, 0, 1), tracePunch.endpos + Vector(0, 0, 1), 255, 128, 0);//to avoid occupying the same space, making the lines difficult to understand, these 2 lines have a small vertical offset.
//if line went nowhere, represent it with a downward line, otherwise the line is invisible
if (tracePunch.startpos == tracePunch.endpos)
UTIL_AddDebugLineColor(tracePunch.endpos + Vector(0, 0, 1), tracePunch.endpos - Vector(0, 0, 1), 255, 128, 0);
Msg("Hit %i, s4.1, Using secondary fire. TracePunch landed at %0.1f %0.1f %0.1f.\n", 10 - nMaxHits, tracePunch.endpos.x, tracePunch.endpos.y, tracePunch.endpos.z);
if (!tracePunch.allsolid)
{
UTIL_TraceLine(tracePunch.endpos, traceBeam.endpos, MASK_SHOT, pentIgnore, COLLISION_GROUP_NONE, &traceBack);
//NDebugOverlay::Line(tracePunch.endpos, traceBack.endpos, 0, 0, 255, false, 30);
UTIL_AddDebugLineColor(tracePunch.endpos + Vector(0, 0, 2), traceBack.endpos + Vector(0, 0, 2), 0, 0, 255);
float m = (traceBack.endpos - traceBeam.endpos).Length();
UTIL_AddDebugLineColor(traceBack.endpos + Vector(0, 0, 3), traceBeam.endpos + Vector(0, 0, 3), 0, 255, 0);
//if line went nowhere, represent it with a downward line, otherwise the line is invisible
if (m == 0)
UTIL_AddDebugLineColor(traceBack.endpos + Vector(0, 0, 1), traceBack.endpos - Vector(0, 0, 1), 0, 255, 0);
Msg("Hit %i, s5.1, Wall has other side. TraceBack lands at %0.1f %0.1f %0.1f. Wall is %f thick while damage is %f, %s.\n", 10 - nMaxHits, traceBack.endpos.x, traceBack.endpos.y, traceBack.endpos.z, m, flDamage, (m < flDamage) ? "penetrating" : "cannot penetrate");
if (m < flDamage)
{
if (m == 0)
{
Warning("Hit %i, s6.1, Sanity Check: m was 0, set to 1.\n", 10 - nMaxHits);//implies wall is infinitely thin.
m = 1;
}
Msg("Hit %i, s6.2, Damage (%f) reduced by m (%f) to %f\n", 10 - nMaxHits, flDamage, m, flDamage - m);
flDamage -= m;
float damage_radius = flDamage * 2.5;
RadiusDamage(CTakeDamageInfo(pPlayer, pPlayer, flDamage, DMG_BLAST), traceBack.endpos + vecDir * 8, damage_radius, CLASS_NONE, NULL);
flExpDmg += flDamage;
vecSrc = traceBack.endpos + vecDir;
}
}
else
{
Msg("Hit %i, s5.2, Nothing on other side of wall, ending.\n", 10 - nMaxHits);
flDamage = 0;
}
}
else
{
Msg("Hit %i, s4.2, Cannot penetrate with primary fire, ending.\n", 10 - nMaxHits);
flDamage = 0;
}
}
}
else
{
if (pEntity->IsBSPModel() && bShouldDamageEntity) Msg("Hit %i, s2.2, Entity is damageable, piercing.\n", 10 - nMaxHits);
if (!pEntity->IsBSPModel() && !bShouldDamageEntity) Msg("Hit %i, s2.2, Entity is not a brush, piercing.\n", 10 - nMaxHits);
if (!pEntity->IsBSPModel() && bShouldDamageEntity) Msg("Hit %i, s2.2, Entity is damageable AND not a brush, piercing.\n", 10 - nMaxHits);
vecSrc = traceBeam.endpos + vecDir;
pentIgnore = pEntity;
}
}
if (!(flDamage > 10))
Msg("End: Damage (%0.01f) fell below 10, ending beam.\n", flDamage);
else if (!(nMaxHits > 0))
Msg("End: Reached max interations.\n");
Msg("Explosion Damage Total: %f\n", flExpDmg);
}
//in CHL2_Player definition
void Gauss(bool bPrimaryFire, float flDamage, bool bHalfLifeSource);
//For unclear reasons the original debug line function was not working well, so I made this.
//-----------------------------------------------------------------------------
// Purpose: Adds a debug line to be drawn on the screen
// Input : If testLOS is true, color is based on line of sight test
// Output :
//-----------------------------------------------------------------------------
void UTIL_AddDebugLineColor(const Vector &startPos, const Vector &endPos, int r, int g, int b)
{
OverlayLine_t* debugLine = GetDebugOverlayLine();
debugLine->origin = startPos;
debugLine->dest = endPos;
//debugLine->noDepthTest = noDepthTest;
debugLine->draw = true;
debugLine->r = r;
debugLine->g = g;
debugLine->b = b;
}
extern void UTIL_AddDebugLineColor(const Vector &startPos, const Vector &endPos, int r, int g, int b);
//in CEngineSprite::GetMaterial line 420 (nice) add this to prevent a crash in c3a1a
if (!pMaterial)//randomly encountered in c3a1a HLS
return NULL;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment