Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions code/swb_base/Weapon.Shoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,18 @@ public virtual void Shoot( ShootInfo shootInfo, bool isPrimary )
// Barrel smoke
barrelHeat += 1;

// Recoil
Owner.ApplyEyeAnglesOffset( GetRecoilAngles( shootInfo ) );
// Recoil / AimPunch
var recoilAngles = GetRecoilAngles( shootInfo );
if ( shootInfo.UseAimPunch )
{
// Apply aimpunch (camera recoil with recovery)
Owner.ApplyAimPunch( recoilAngles, shootInfo.AimPunchRecoverySpeed );
}
else
{
// Apply instant recoil (legacy behavior)
Owner.ApplyEyeAnglesOffset( recoilAngles );
}

// Screenshake
if ( shootInfo.ScreenShake is not null )
Expand Down
6 changes: 6 additions & 0 deletions code/swb_base/structures/ShootInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ public class ShootInfo : Component
/// <summary>Weapon recoil</summary>
[Property, Group( "Bullets" )] public float Recoil { get; set; } = 0.1f;

/// <summary>Enable aimpunch (camera recoil with recovery)</summary>
[Property, Group( "Bullets" )] public bool UseAimPunch { get; set; } = false;

/// <summary>Aimpunch recovery speed (higher = faster recovery)</summary>
[Property, Group( "Bullets" )] public float AimPunchRecoverySpeed { get; set; } = 5f;

/// <summary>Rate Per Minute, firing speed (higher is faster)</summary>
[Property, Group( "Bullets" )] public int RPM { get; set; } = 200;

Expand Down
3 changes: 3 additions & 0 deletions code/swb_player/Inventory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public void SetActive( GameObject gameObject )
oldActive.OnCarryStop();
}

// Reset aimpunch when switching weapons
player?.ResetAimPunch();

if ( gameObject.Components.TryGet<IInventoryItem>( out var newActive, FindMode.EverythingInSelf ) )
{
newActive.OnCarryStart();
Expand Down
72 changes: 72 additions & 0 deletions code/swb_player/PlayerBase.AimPunch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace SWB.Player;

public partial class PlayerBase
{
Angles aimPunchAngle;
float aimPunchRecoverySpeed = 5f;
RealTimeSince timeSinceLastPunch;

/// <summary>
/// Apply aimpunch recoil to the camera.
/// Similar to Flinch but with configurable recovery and shooting-aware delay.
/// </summary>
/// <param name="punchAmount">The angular offset to apply</param>
/// <param name="recoverySpeed">How fast the camera recovers (higher = faster)</param>
public virtual void ApplyAimPunch( Angles punchAmount, float recoverySpeed = 5f )
Comment thread
misterbubb marked this conversation as resolved.
{
aimPunchAngle += punchAmount;
aimPunchRecoverySpeed = recoverySpeed;
timeSinceLastPunch = 0;

ApplyEyeAnglesOffset( punchAmount );
}

/// <summary>
/// Reset aimpunch angle (called on weapon switch, death, etc.)
/// </summary>
public virtual void ResetAimPunch()
{
aimPunchAngle = Angles.Zero;
}

/// <summary>
/// Handle aimpunch recovery over time.
/// Recovery only starts when player stops shooting.
/// </summary>
public virtual void HandleAimPunch()
{
if ( !IsAlive )
{
aimPunchAngle = Angles.Zero;
return;
}

bool isShooting = Inventory?.ActiveItem?.IsShooting() ?? false;

if ( isShooting || timeSinceLastPunch < 0.1f )
return;

if ( aimPunchAngle.pitch != 0 || aimPunchAngle.yaw != 0 || aimPunchAngle.roll != 0 )
{
var decayAmount = aimPunchRecoverySpeed * Time.Delta;

var oldPitch = aimPunchAngle.pitch;
var oldYaw = aimPunchAngle.yaw;
var oldRoll = aimPunchAngle.roll;

aimPunchAngle = new Angles(
aimPunchAngle.pitch.Approach( 0, decayAmount ),
aimPunchAngle.yaw.Approach( 0, decayAmount ),
aimPunchAngle.roll.Approach( 0, decayAmount )
);

var deltaAngle = new Angles(
aimPunchAngle.pitch - oldPitch,
aimPunchAngle.yaw - oldYaw,
aimPunchAngle.roll - oldRoll
);

ApplyEyeAnglesOffset( deltaAngle );
}
}
}
4 changes: 3 additions & 1 deletion code/swb_player/PlayerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ protected override void OnUpdate()
ViewModelCamera.Enabled = IsFirstPerson && IsAlive;
HandleFlinch();
HandleScreenShake();
HandleAimPunch();
}

if ( IsAlive )
Expand Down Expand Up @@ -259,7 +260,8 @@ public void TriggerAnimation( Animations animation )

public void ApplyEyeAnglesOffset( Angles offset )
{
CameraMovement?.EyeAnglesOffset += offset;
if ( CameraMovement is null ) return;
CameraMovement.EyeAnglesOffset += offset;
Comment thread
misterbubb marked this conversation as resolved.
}

public void ParentToBone( GameObject weaponObject, string boneName )
Expand Down
3 changes: 3 additions & 0 deletions code/swb_shared/IInventory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ public interface IInventoryItem : IValid

/// <summary>Can the GameObject be switched out</summary>
public bool CanCarryStop();

/// <summary>Is the item currently being used (e.g. weapon is firing)</summary>
public bool IsShooting();
}
8 changes: 8 additions & 0 deletions code/swb_shared/IPlayerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ public interface IPlayerBase : IValid, Sandbox.Component.IDamageable
/// <param name="offset">The suggested angular offset to apply</param>
public void ApplyEyeAnglesOffset( Angles offset );

/// <summary>
/// Apply aimpunch (camera recoil with recovery) to the player's view.
/// Unlike ApplyEyeAnglesOffset, this will smoothly recover over time.
/// </summary>
/// <param name="punchAmount">The angular offset to apply</param>
/// <param name="recoverySpeed">How fast the camera recovers (higher = faster)</param>
public void ApplyAimPunch( Angles punchAmount, float recoverySpeed = 5f );

/// <summary>
/// Called when the weapon object should be attached/parented to the player's body
/// </summary>
Expand Down