Skip to content

Commit

Permalink
Separate Body And Head
Browse files Browse the repository at this point in the history
Previously, body and head overlapped. Shots from any direction could hit
the head and only if they missed would they be counted as body shots.

This changes that concept, body and head no longer overlap. Instead the
pawns hitbox is split into 2 cylinders, one for the body and one for the
head.
The arrangement of those cylinders is fixed such that the body
cylinder occupies everything from feet until the head cylinder starts
and has the same radius as the pawn.
The head cylinder occupies the top portion and has a total height of
2*HeadHalfHeight.

When crouching the head cylinder shrinks to nothing.

The head cylinder can also no longer be hit from everywhere. The impact
on the pawns collision cylinder must be more than 30% up from the middle
of the collision cylinder in order for a check against the head cylinder
to be performed.
  • Loading branch information
Deaod committed Mar 23, 2024
1 parent 48b27d6 commit 802ea83
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 45 deletions.
90 changes: 81 additions & 9 deletions Classes/ST_Mutator.uc
Original file line number Diff line number Diff line change
Expand Up @@ -1053,9 +1053,27 @@ final function EnhancedHurtRadius(
Source.bHurtEntry = false;
}

function bool CheckHeadshot(Pawn P, vector HitLocation, vector Direction) {
local float OffsetZ;
function float GetPawnDuckFraction(Pawn P) {
local bbPlayer bbP;
bbP = bbPlayer(P);
if (bbP != none) {
return FClamp(bbP.DuckFraction, 0.0, 1.0);
} else {
return FClamp(1.0 - (P.EyeHeight / P.default.BaseEyeHeight), 0.0, 1.0);
}
}

function float GetPawnBodyHalfHeight(Pawn P, float DuckFrac) {
return Lerp(DuckFrac,
P.CollisionHeight - WeaponSettings.HeadHalfHeight,
(1.3 * 0.5)*P.CollisionHeight
);
}

function bool CheckHeadShot(Pawn P, vector HitLocation, vector Direction) {
local bbPlayer bbP;
local float DuckFrac;
local float BodyHalfHeight, HeadHalfHeight;

local ST_HitTestHelper HitActor;
local vector HitLoc, HitNorm;
Expand All @@ -1067,21 +1085,75 @@ function bool CheckHeadshot(Pawn P, vector HitLocation, vector Direction) {
if (WeaponSettings.bEnhancedHeadshotDetection == false)
return (HitLocation.Z - P.Location.Z > 0.62 * P.CollisionHeight);

if (HitLocation.Z - P.Location.Z <= 0.3 * P.CollisionHeight)
return false;

if (CollChecker == none || CollChecker.bDeleteMe) {
CollChecker = Spawn(class'ST_HitTestHelper',self, , P.Location);
CollChecker.bCollideWorld = false;
}

bbP = bbPlayer(P);
if (bbP != none) {
OffsetZ = Lerp(bbP.DuckFraction, WeaponSettings.HeadOffsetZ, WeaponSettings.HeadDuckOffsetZ);
} else {
OffsetZ = Lerp(P.EyeHeight / P.default.BaseEyeHeight, WeaponSettings.HeadDuckOffsetZ, WeaponSettings.HeadOffsetZ);
}
DuckFrac = GetPawnDuckFraction(P);
BodyHalfHeight = GetPawnBodyHalfHeight(P, DuckFrac);
HeadHalfHeight = Lerp(DuckFrac,
WeaponSettings.HeadHalfHeight,
0
);

CollChecker.SetCollision(true, false, false);
CollChecker.SetCollisionSize(WeaponSettings.HeadRadius, WeaponSettings.HeadHalfHeight);
CollChecker.SetLocation(P.Location + vect(0,0,1)*WeaponSettings.HeadOffsetZ);
CollChecker.SetLocation(P.Location + vect(0,0,1)*(BodyHalfHeight + HeadHalfHeight));

Result = false;

foreach TraceActors(
class'ST_HitTestHelper',
HitActor, HitLoc, HitNorm,
HitLocation + Direction * (P.CollisionRadius + P.CollisionHeight),
HitLocation - Direction * (P.CollisionRadius + P.CollisionHeight)
) {
if (HitActor == CollChecker) {
Result = true;
break;
}
}

CollChecker.SetCollision(false, false, false);

return Result;
}

function bool CheckBodyShot(Pawn P, vector HitLocation, vector Direction) {
local float DuckFrac;
local float HalfHeight;
local float OffsetZ;
local bbPlayer bbP;

local ST_HitTestHelper HitActor;
local vector HitLoc, HitNorm;
local bool Result;

if (P == none)
return false;

if (WeaponSettings.bEnhancedHeadshotDetection == false)
return CheckHeadShot(P, HitLocation, Direction) == false;

if (CollChecker == none || CollChecker.bDeleteMe) {
CollChecker = Spawn(class'ST_HitTestHelper',self, , P.Location);
CollChecker.bCollideWorld = false;
}

DuckFrac = GetPawnDuckFraction(P);
HalfHeight = GetPawnBodyHalfHeight(P, DuckFrac);
OffsetZ = Lerp(DuckFrac,
-WeaponSettings.HeadHalfHeight,
-(0.7 * 0.5)*P.CollisionHeight
);

CollChecker.SetCollision(true, false, false);
CollChecker.SetCollisionSize(P.CollisionRadius, HalfHeight);
CollChecker.SetLocation(P.Location + vect(0,0,1)*OffsetZ);

Result = false;

Expand Down
28 changes: 13 additions & 15 deletions Classes/ST_Razor2.uc
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,37 @@ simulated function PostBeginPlay()

auto state Flying
{
simulated function ProcessTouch (Actor Other, Vector HitLocation)
{
if ( bCanHitInstigator || (Other != Instigator) )
{
if ( Role == ROLE_Authority )
{
if ( Other.bIsPawn && STM.CheckHeadshot(Pawn(Other), HitLocation, Normal(Velocity))
&& (!Instigator.IsA('Bot') || !Bot(Instigator).bNovice) )
{
simulated function ProcessTouch (Actor Other, Vector HitLocation) {
local vector Dir;

Dir = Normal(Velocity);
if (bCanHitInstigator || (Other != Instigator)) {
if (Role == ROLE_Authority) {
if (Other.bIsPawn && STM.CheckHeadshot(Pawn(Other), HitLocation, Dir) &&
(!Instigator.IsA('Bot') || !Bot(Instigator).bNovice)
) {
STM.PlayerHit(Instigator, 11, True); // 11 = Ripper Primary Headshot
Other.TakeDamage(
STM.WeaponSettings.RipperHeadshotDamage,
Instigator,
HitLocation,
STM.WeaponSettings.RipperHeadshotMomentum * MomentumTransfer * Normal(Velocity),
STM.WeaponSettings.RipperHeadshotMomentum * MomentumTransfer * Dir,
'decapitated'
);
STM.PlayerClear();
}
else
{
} else if (Other.bIsPawn == false || STM.CheckBodyShot(Pawn(Other), HitLocation, Dir)) {
STM.PlayerHit(Instigator, 11, False); // 11 = Ripper Primary
Other.TakeDamage(
STM.WeaponSettings.RipperPrimaryDamage,
instigator,
HitLocation,
STM.WeaponSettings.RipperPrimaryMomentum * MomentumTransfer * Normal(Velocity),
STM.WeaponSettings.RipperPrimaryMomentum * MomentumTransfer * Dir,
'shredded'
);
STM.PlayerClear();
}
}
if ( Other.bIsPawn )
if (Other.bIsPawn)
PlaySound(MiscSound, SLOT_Misc, 2.0);
else
PlaySound(ImpactSound, SLOT_Misc, 2.0);
Expand Down
31 changes: 14 additions & 17 deletions Classes/ST_SniperRifle.uc
Original file line number Diff line number Diff line change
Expand Up @@ -50,40 +50,37 @@ function ProcessTraceHit(Actor Other, Vector HitLocation, Vector HitNormal, Vect
local vector Momentum;

PawnOwner = Pawn(Owner);
STM.PlayerFire(PawnOwner, 18); // 18 = Sniper
STM.PlayerFire(PawnOwner, 18); // 18 = Sniper

s = Spawn(class'UT_ShellCase',, '', Owner.Location + CalcDrawOffset() + 30 * X + (2.8 * FireOffset.Y+5.0) * Y - Z * 1);
if ( s != None )
{
if (s != None) {
s.DrawScale = 2.0;
s.Eject(((FRand()*0.3+0.4)*X + (FRand()*0.2+0.2)*Y + (FRand()*0.3+1.0) * Z)*160);
}
if (Other == Level)

if (Other == Level) {
Spawn(class'UT_HeavyWallHitEffect',,, HitLocation+HitNormal, Rotator(HitNormal));
else if ( (Other != self) && (Other != Owner) && (Other != None) )
{
if ( Other.bIsPawn )
} else if ((Other != self) && (Other != Owner) && (Other != None)) {
if (Other.bIsPawn)
Other.PlaySound(Sound 'ChunkHit',, 4.0,,100);
if ( Other.bIsPawn && STM.CheckHeadshot(Pawn(Other), HitLocation, X)
&& (instigator.IsA('PlayerPawn') || (instigator.IsA('Bot') && !Bot(Instigator).bNovice)) )
{
STM.PlayerHit(PawnOwner, 18, True); // 18 = Sniper, Headshot
if (Other.bIsPawn && STM.CheckHeadshot(Pawn(Other), HitLocation, X) &&
(instigator.IsA('PlayerPawn') || (instigator.IsA('Bot') && !Bot(Instigator).bNovice))
) {
STM.PlayerHit(PawnOwner, 18, True); // 18 = Sniper, Headshot
Other.TakeDamage(
STM.WeaponSettings.SniperHeadshotDamage,
PawnOwner,
HitLocation,
STM.WeaponSettings.SniperHeadshotMomentum * 35000 * X,
AltDamageType);
STM.PlayerClear();
}
else
{
} else if (Other.bIsPawn == false || STM.CheckBodyShot(Pawn(Other), HitLocation, X)) {
if (Other.bIsPawn)
Momentum = STM.WeaponSettings.SniperMomentum * 30000.0*X;
else
Momentum = 30000.0*X;

STM.PlayerHit(PawnOwner, 18, False); // 18 = Sniper
STM.PlayerHit(PawnOwner, 18, False); // 18 = Sniper
Other.TakeDamage(
STM.WeaponSettings.SniperDamage,
PawnOwner,
Expand All @@ -92,8 +89,8 @@ function ProcessTraceHit(Actor Other, Vector HitLocation, Vector HitNormal, Vect
MyDamageType);
STM.PlayerClear();
}
if ( !Other.bIsPawn && !Other.IsA('Carcass') )
spawn(class'UT_SpriteSmokePuff',,,HitLocation+HitNormal*9);
if (!Other.bIsPawn && !Other.IsA('Carcass'))
Spawn(class'UT_SpriteSmokePuff',,,HitLocation+HitNormal*9);
}
}

Expand Down
4 changes: 0 additions & 4 deletions Classes/WeaponSettings.uc
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ var config float SplashMinDiffractionDistance;
var config bool bEnhancedHeadshotDetection;
var config float HeadHalfHeight;
var config float HeadRadius;
var config float HeadOffsetZ;
var config float HeadDuckOffsetZ;

var config float WarheadSelectTime;
var config float WarheadDownTime;
Expand Down Expand Up @@ -169,8 +167,6 @@ defaultproperties
bEnhancedHeadshotDetection=False
HeadHalfHeight=7.5
HeadRadius=10.0
HeadOffsetZ=31.5
HeadDuckOffsetZ=4.2

WarheadSelectTime=0.5
WarheadDownTime=0.233333
Expand Down

0 comments on commit 802ea83

Please sign in to comment.