#include "Autowall.h"
#include "Globals.h"
#include <intrin.h>
bool CAutoWall::DidHitNonWorldEntity(IClientEntity* m_pEnt)
{
return m_pEnt != nullptr && m_pEnt != ENTITYLIST->GetClientEntity(0);
}
void CAutoWall::TraceLine(Vector& absStart, Vector& absEnd, unsigned int mask, IClientEntity* ignore, CGameTrace* ptr)
{
Ray_t ray;
ray.Init(absStart, absEnd);
CTraceFilter filter;
filter.pSkip = ignore;
ENGINETRACE->TraceRay(ray, mask, &filter, ptr);
}
void CAutoWall::ClipTraceToPlayers(Vector& absStart, Vector absEnd, unsigned int mask, ITraceFilter* filter, CGameTrace* tr)
{
static uintptr_t clptrtp = Utils::FindSignature(Utils::get_client_dll(), XorStr("53 8B DC 83 EC ? 83 E4 ? 83 C4 ? 55 8B 6B 04 89 6C 24 ? 8B EC 81 EC ? ? ? ? 8B 43 10 56 57 52 F3 0F 10 40"));
if (!clptrtp)
return;
__asm
{
mov eax, filter
lea ecx, tr
push ecx
push eax
push mask
lea edx, absEnd
lea ecx, absStart
call clptrtp
add esp, 0xC
}
}
//Legacy Function
void CAutoWall::GetBulletTypeParameters(float& maxRange, float& maxDistance, char* bulletType, bool sv_penetration_type)
{
if (sv_penetration_type)
{
maxRange = 35.0;
maxDistance = 3000.0;
}
else
{
//Play tribune to framerate. Thanks, stringcompare
//Regardless I doubt anyone will use the old penetration system anyway; so it won't matter much.
if (!strcmp(bulletType, XorStr("BULLET_PLAYER_338MAG")))
{
maxRange = 45.0;
maxDistance = 8000.0;
}
if (!strcmp(bulletType, XorStr("BULLET_PLAYER_762MM")))
{
maxRange = 39.0;
maxDistance = 5000.0;
}
if (!strcmp(bulletType, XorStr("BULLET_PLAYER_556MM")) || !strcmp(bulletType, XorStr("BULLET_PLAYER_556MM_SMALL")) || !strcmp(bulletType, XorStr("BULLET_PLAYER_556MM_BOX")))
{
maxRange = 35.0;
maxDistance = 4000.0;
}
if (!strcmp(bulletType, XorStr("BULLET_PLAYER_57MM")))
{
maxRange = 30.0;
maxDistance = 2000.0;
}
if (!strcmp(bulletType, XorStr("BULLET_PLAYER_50AE")))
{
maxRange = 30.0;
maxDistance = 1000.0;
}
if (!strcmp(bulletType, XorStr("BULLET_PLAYER_357SIG")) || !strcmp(bulletType, XorStr("BULLET_PLAYER_357SIG_SMALL")) || !strcmp(bulletType, XorStr("BULLET_PLAYER_357SIG_P250")) || !strcmp(bulletType, XorStr("BULLET_PLAYER_357SIG_MIN")))
{
maxRange = 25.0;
maxDistance = 800.0;
}
if (!strcmp(bulletType, XorStr("BULLET_PLAYER_9MM")))
{
maxRange = 21.0;
maxDistance = 800.0;
}
if (!strcmp(bulletType, XorStr("BULLET_PLAYER_45ACP")))
{
maxRange = 15.0;
maxDistance = 500.0;
}
if (!strcmp(bulletType, XorStr("BULLET_PLAYER_BUCKSHOT")))
{
maxRange = 0.0;
maxDistance = 0.0;
}
}
}
void CAutoWall::scale_damage(CGameTrace& enterTrace, const CCSWeaponInfo* weaponData, float& currentDamage) const
{
//Cred. to N0xius for reversing this.
//TODO: _xAE^; look into reversing this yourself sometime
bool hasHeavyArmor = false;//((C_CSPlayer*)enterTrace.m_pEnt)->HeavyArmor();
int armorValue = ((C_CSPlayer*)enterTrace.m_pEnt)->GetArmorValue();
//screw making a new function, lambda beste. ~ Does the person have armor on for the hitbox checked?
auto IsArmored = [&enterTrace]()->bool
{
C_CSPlayer* targetEntity = (C_CSPlayer*)enterTrace.m_pEnt;
switch (enterTrace.hitgroup)
{
case HITGROUP_HEAD:
return !!(C_CSPlayer*)targetEntity->HasHelmet(); //screw compiler errors - force-convert it to a bool via (!!)
case HITGROUP_GENERIC:
case HITGROUP_CHEST:
case HITGROUP_STOMACH:
case HITGROUP_LEFTARM:
case HITGROUP_RIGHTARM:
return true;
default:
return false;
}
};
switch (enterTrace.hitgroup)
{
case HITGROUP_HEAD:
currentDamage *= hasHeavyArmor ? 2.f : 4.f; //Heavy Armor does 1/2 damage
break;
case HITGROUP_STOMACH:
currentDamage *= 1.25f;
break;
case HITGROUP_LEFTLEG:
case HITGROUP_RIGHTLEG:
currentDamage *= 0.75f;
break;
default:
break;
}
if (armorValue > 0 && IsArmored())
{
float bonusValue = 1.f, armorBonusRatio = 0.5f, armorRatio = weaponData->flArmorRatio / 2.f;
//Damage gets modified for heavy armor users
if (hasHeavyArmor)
{
armorBonusRatio = 0.33f;
armorRatio *= 0.5f;
bonusValue = 0.33f;
}
auto NewDamage = currentDamage * armorRatio;
if (hasHeavyArmor)
NewDamage *= 0.85f;
if (((currentDamage - (currentDamage * armorRatio)) * (bonusValue * armorBonusRatio)) > armorValue)
NewDamage = currentDamage - (armorValue / armorBonusRatio);
currentDamage = NewDamage;
}
}
bool CAutoWall::TraceToExit(CGameTrace& enterTrace, CGameTrace& exitTrace, Vector startPosition, Vector direction)
{
/*
Masks used:
MASK_SHOT_HULL = 0x0600400B
CONTENTS_HITBOX = 0x40000000
MASK_SHOT_HULL | CONTENTS_HITBOX = 0x4600400B
*/
float maxDistance = 90.f, rayExtension = 4.f, currentDistance = 0;
int firstContents = 0;
while (currentDistance <= maxDistance)
{
//Add extra distance to our ray
currentDistance += rayExtension;
//Multiply the direction vector to the distance so we go outwards, add our position to it.
Vector start = startPosition + direction * currentDistance;
if (!firstContents)
firstContents = ENGINETRACE->GetPointContents(start, MASK_SHOT_HULL | CONTENTS_HITBOX, nullptr); /*0x4600400B*/
int pointContents = ENGINETRACE->GetPointContents(start, MASK_SHOT_HULL | CONTENTS_HITBOX, nullptr);
if (!(pointContents & MASK_SHOT_HULL) || pointContents & CONTENTS_HITBOX && pointContents != firstContents) /*0x600400B, *0x40000000*/
{
//Let's setup our end position by deducting the direction by the extra added distance
Vector end = start - (direction * rayExtension);
//Let's cast a ray from our start pos to the end pos
TraceLine(start, end, MASK_SHOT_HULL | CONTENTS_HITBOX, nullptr, &exitTrace);
//Let's check if a hitbox is in-front of our enemy and if they are behind of a solid wall
if (exitTrace.startsolid && exitTrace.surface.flags & SURF_HITBOX)
{
TraceLine(start, startPosition, MASK_SHOT_HULL, exitTrace.m_pEnt, &exitTrace);
if (exitTrace.DidHit() && !exitTrace.startsolid)
{
start = exitTrace.endpos;
return true;
}
continue;
}
//Can we hit? Is the wall solid?
if (exitTrace.DidHit() && !exitTrace.startsolid)
{
//Is the wall a breakable? If so, let's shoot through it.
if (((C_CSPlayer*)enterTrace.m_pEnt)->IsBreakableEnt() && (((C_CSPlayer*)exitTrace.m_pEnt)->IsBreakableEnt()))
return true;
if (enterTrace.surface.flags & SURF_NODRAW || !(exitTrace.surface.flags & SURF_NODRAW) && (exitTrace.plane.normal.Dot(direction) <= 1.f))
{
float multAmount = exitTrace.fraction * 4.f;
start -= direction * multAmount;
return true;
}
continue;
}
if (!exitTrace.DidHit() || exitTrace.startsolid)
{
if (DidHitNonWorldEntity(enterTrace.m_pEnt) && ((C_CSPlayer*)enterTrace.m_pEnt)->IsBreakableEnt())
{
exitTrace = enterTrace;
exitTrace.endpos = start + direction;
return true;
}
continue;
}
}
}
return false;
}
bool CAutoWall::HandleBulletPenetration(C_CSPlayer* pLocal, const CCSWeaponInfo* weaponData, CGameTrace& enterTrace, Vector& eyePosition, Vector direction, int& possibleHitsRemaining, float& currentDamage, float penetrationPower, bool sv_penetration_type, float ff_damage_reduction_bullets, float ff_damage_bullet_penetration) const
{
trace_t exitTrace;
C_CSPlayer* pEnemy = (C_CSPlayer*)enterTrace.m_pEnt;
if (!pEnemy)
return false;
surfacedata_t *enterSurfaceData = PHYSPROPS->GetSurfaceData(enterTrace.surface.surfaceProps);
int enterMaterial = enterSurfaceData->game.material;
float enterSurfPenetrationModifier = enterSurfaceData->game.flPenetrationModifier;
float enterDamageModifier = enterSurfaceData->game.flDamageModifier;
float thickness, finalDamageModifier, combinedPenetrationModifier;
bool isSolidSurf = ((enterTrace.contents >> 3) & CONTENTS_SOLID);
bool isLightSurf = ((enterTrace.surface.flags >> 7) & SURF_LIGHT);
if (possibleHitsRemaining <= 0
|| (!possibleHitsRemaining && !isLightSurf && !isSolidSurf && enterMaterial != CHAR_TEX_GRATE && enterMaterial != CHAR_TEX_GLASS)
|| weaponData->flPenetration <= 0.f
|| !TraceToExit(enterTrace, exitTrace, enterTrace.endpos, direction)
&& !(ENGINETRACE->GetPointContents(enterTrace.endpos, MASK_SHOT_HULL, nullptr) & MASK_SHOT_HULL))
return false;
surfacedata_t *exitSurfaceData = PHYSPROPS->GetSurfaceData(exitTrace.surface.surfaceProps);
if (!exitSurfaceData)
return false;
int exitMaterial = exitSurfaceData->game.material;
float exitSurfPenetrationModifier = exitSurfaceData->game.flPenetrationModifier;
float exitDamageModifier = exitSurfaceData->game.flDamageModifier;
//Are we using the newer penetration system?
if (sv_penetration_type)
{
if (enterMaterial == CHAR_TEX_GRATE || enterMaterial == CHAR_TEX_GLASS)
{
combinedPenetrationModifier = 3.f;
finalDamageModifier = 0.05f;
}
else if (isSolidSurf || isLightSurf)
{
combinedPenetrationModifier = 1.f;
finalDamageModifier = 0.16f;
}
else if (enterMaterial == CHAR_TEX_FLESH && (pLocal->GetTeam() == pEnemy->GetTeam() && ff_damage_reduction_bullets == 0.f))
{
//Look's like you aren't shooting through your teammate today
if (ff_damage_bullet_penetration == 0.f)
return false;
//Let's shoot through teammates and get kicked for teamdmg! Whatever, atleast we did damage to the enemy. I call that a win.
combinedPenetrationModifier = ff_damage_bullet_penetration;
finalDamageModifier = 0.16f;
}
else
{
combinedPenetrationModifier = (enterSurfPenetrationModifier + exitSurfPenetrationModifier) / 2.f;
finalDamageModifier = 0.16f;
}
//Do our materials line up?
if (enterMaterial == exitMaterial)
{
if (exitMaterial == CHAR_TEX_CARDBOARD || exitMaterial == CHAR_TEX_WOOD)
combinedPenetrationModifier = 3.f;
else if (exitMaterial == CHAR_TEX_PLASTIC)
combinedPenetrationModifier = 2.f;
}
//Calculate thickness of the wall by getting the length of the range of the trace and squaring
thickness = (exitTrace.endpos - enterTrace.endpos).LengthSqr();
float modifier = fmaxf(1.f / combinedPenetrationModifier, 0.f);
//This calculates how much damage we've lost depending on thickness of the wall, our penetration, damage, and the modifiers set earlier
float lostDamage = fmaxf(
((modifier * thickness) / 24.f)
+ ((currentDamage * finalDamageModifier)
+ (fmaxf(3.75f / penetrationPower, 0.f) * 3.f * modifier)), 0.f);
//Did we loose too much damage?
if (lostDamage > currentDamage)
return false;
//We can't use any of the damage that we've lost
if (lostDamage > 0.f)
currentDamage -= lostDamage;
//Do we still have enough damage to deal?
if (currentDamage < 1.f)
return false;
eyePosition = exitTrace.endpos;
--possibleHitsRemaining;
return true;
}
else //Legacy penetration system
{
combinedPenetrationModifier = 1.f;
if (isSolidSurf || isLightSurf)
finalDamageModifier = 0.99f; //Good meme :^)
else
{
finalDamageModifier = fminf(enterDamageModifier, exitDamageModifier);
combinedPenetrationModifier = fminf(enterSurfPenetrationModifier, exitSurfPenetrationModifier);
}
if (enterMaterial == exitMaterial && (exitMaterial == CHAR_TEX_METAL || exitMaterial == CHAR_TEX_WOOD))
combinedPenetrationModifier += combinedPenetrationModifier;
thickness = (exitTrace.endpos - enterTrace.endpos).LengthSqr();
if (sqrt(thickness) <= combinedPenetrationModifier * penetrationPower)
{
currentDamage *= finalDamageModifier;
eyePosition = exitTrace.endpos;
--possibleHitsRemaining;
return true;
}
return false;
}
}
bool CAutoWall::FireBullet(C_CSPlayer* pLocal, CBaseCombatWeapon* pWeapon, Vector& direction, float& currentDamage, int& possibleHitsRemaining) const
{
if (!pWeapon)
return false;
static auto UTIL_ClipTraceToPlayers = [](const Vector& vecAbsStart, const Vector& vecAbsEnd, unsigned int mask, ITraceFilter* filter, trace_t* tr)
{
static uintptr_t clptrtp = Utils::FindSignature(Utils::get_client_dll(), XorStr("53 8B DC 83 EC ? 83 E4 ? 83 C4 ? 55 8B 6B 04 89 6C 24 ? 8B EC 81 EC ? ? ? ? 8B 43 10 56 57 52 F3 0F 10 40"));
if (!clptrtp)
return false;
__asm {
push tr
push filter
push mask
lea edx, vecAbsEnd
lea ecx, vecAbsStart
call clptrtp
add esp, 0xC
}
};
// Current bullet travel Power to penetrate Distance to penetrate Range Player bullet reduction convars Amount to extend ray by
float currentDistance = 0.f, penetrationPower, penetrationDistance, rayExtension = 40.f;
Vector eyePosition = pLocal->GetEyePos();
//For being superiour when the server owners think your autowall isn't well reversed. Imagine a meme HvH server with the old penetration system- pff
static ConVar* penetrationSystem = CVAR->FindVar(XorStr("sv_penetration_type"));
static ConVar* damageReductionBullets = CVAR->FindVar(XorStr("ff_damage_reduction_bullets"));
static ConVar* damageBulletPenetration = CVAR->FindVar(XorStr("ff_damage_bullet_penetration"));
if (!penetrationSystem || !damageReductionBullets || !damageBulletPenetration)
return false;
bool sv_penetration_type = penetrationSystem->GetBool();
float ff_damage_reduction_bullets = damageReductionBullets->GetFloat();
float ff_damage_bullet_penetration = damageBulletPenetration->GetFloat();
const CCSWeaponInfo* weaponData = pWeapon->GetCSWpnData();
if (!weaponData)
return false;
float maxRange = weaponData->flRange;
GetBulletTypeParameters(penetrationPower, penetrationDistance, weaponData->szBulletType, sv_penetration_type);
if (sv_penetration_type)
penetrationPower = weaponData->flPenetration;
//This gets set in FX_Firebullets to 4 as a pass-through value.
//CS:GO has a maximum of 4 surfaces a bullet can pass-through before it 100% stops.
//Excerpt from Valve: https://steamcommunity.com/sharedfiles/filedetails/?id=275573090
//"The total number of surfaces any bullet can penetrate in a single flight is capped at 4." -CS:GO Official
//Set our current damage to what our gun's initial damage reports it will do
currentDamage = weaponData->iDamage;
trace_t enterTrace;
CTraceFilter filter;
filter.pSkip = pLocal;
//If our damage is greater than (or equal to) 1, and we can shoot, let's shoot.
while (possibleHitsRemaining > 0 && currentDamage >= 1.f)
{
//Calculate max bullet range
maxRange -= currentDistance;
//Create endpoint of bullet
Vector end = eyePosition + direction * maxRange;
Utils::TraceLine(eyePosition, end, MASK_SHOT_HULL | CONTENTS_HITBOX, pLocal, &enterTrace);
UTIL_ClipTraceToPlayers(eyePosition, end + direction * rayExtension, MASK_SHOT_HULL | CONTENTS_HITBOX, &filter, &enterTrace);
//We have to do this *after* tracing to the player.
surfacedata_t* enterSurfaceData = PHYSPROPS->GetSurfaceData(enterTrace.surface.surfaceProps);
if (!enterSurfaceData)
return false;
float enterSurfPenetrationModifier = enterSurfaceData->game.flPenetrationModifier;
int enterMaterial = enterSurfaceData->game.material;
//"Fraction == 1" means that we didn't hit anything. We don't want that- so let's break on it.
if (enterTrace.fraction == 1.f)
break;
//calculate the damage based on the distance the bullet traveled.
currentDistance += enterTrace.fraction * maxRange;
//Let's make our damage drops off the further away the bullet is.
currentDamage *= pow(weaponData->flRangeModifier, (currentDistance / 500.f));
//Sanity checking / Can we actually shoot through?
if (currentDistance > penetrationDistance && weaponData->flPenetration > 0.f || enterSurfPenetrationModifier < 0.1f)
break;
//This looks dumb if we put it into 1 long line of code.
bool canDoDamage = (enterTrace.hitgroup != HITGROUP_GEAR && enterTrace.hitgroup != HITGROUP_GENERIC);
bool isPlayer = ((enterTrace.m_pEnt)->GetClientClass()->m_ClassID == CCSPlayer);
bool isEnemy = (((C_CSPlayer*)pLocal)->GetTeam() != ((C_CSPlayer*)enterTrace.m_pEnt)->GetTeam());
bool onTeam = (((C_CSPlayer*)enterTrace.m_pEnt)->GetTeam() == 3 || ((C_CSPlayer*)enterTrace.m_pEnt)->GetTeam() == 2);
//TODO: Team check config
if (canDoDamage && isPlayer && isEnemy && onTeam)
{
scale_damage(enterTrace, weaponData, currentDamage);
return true;
}
//Calling HandleBulletPenetration here reduces our penetrationCounter, and if it returns true, we can't shoot through it.
if (!HandleBulletPenetration(pLocal, weaponData, enterTrace, eyePosition, direction, possibleHitsRemaining, currentDamage, penetrationPower, sv_penetration_type, ff_damage_reduction_bullets, ff_damage_bullet_penetration))
break;
}
return false;
}
float CAutoWall::can_hit(Vector &point, C_CSPlayer* pLocal) const
{
if (!pLocal)
return 0;
Vector angles, direction;
Vector tmp = point - pLocal->GetEyePos();
Math::VectorAngles(tmp, angles);
Math::AngleVectors(angles, &direction);
direction.NormalizeInPlace();
float currentDamage = 0.f;
int possibleHitsRemaining = 4;
if (FireBullet(pLocal, pLocal->GetActiveWeapon(), direction, currentDamage, possibleHitsRemaining))
{
if (possibleHitsRemaining < 4 && !WeaponConfigVars::get().WeaponConfig[g::weaponconfig].ragebot.g_bAutoWall)
return 0.f;
return currentDamage;
}
return 0.f; //That wall is just a bit too thick buddy
}