swb_base/Weapon.Getters.cs
using SWB.Base.Attachments;
using SWB.Shared;
using System;

namespace SWB.Base;

public partial class Weapon
{
	public virtual SkinnedModelRenderer GetEffectRenderer()
	{
		SkinnedModelRenderer effectModel = WorldModelRenderer;

		if ( CanSeeViewModel )
			effectModel = ViewModelRenderer;

		return effectModel;
	}

	/// <summary>
	/// Gets the visible muzzle renderer and attachment name
	/// </summary>
	public virtual (SkinnedModelRenderer, string) GetMuzzleEffectDetails()
	{
		var activeAttachment = GetActiveAttachmentForCategory( AttachmentCategory.Muzzle );
		var effectRenderer = GetEffectRenderer();
		var effectAttachment = "muzzle";

		if ( activeAttachment is not null )
		{
			effectAttachment = activeAttachment.EffectAttachmentOrBone;

			// If we have custom models use the attachment from those models instead of the main weapon one
			if ( CanSeeViewModel && activeAttachment.ViewModelRenderer is not null )
			{
				effectRenderer = activeAttachment.ViewModelRenderer;
			}
			else if ( !CanSeeViewModel && activeAttachment.WorldModelRenderer is not null )
			{
				effectRenderer = activeAttachment.WorldModelRenderer;
			}
		}

		return (effectRenderer, effectAttachment);
	}

	/// <summary>
	/// Gets the muzzle attachment transform
	/// </summary>
	public virtual Transform? GetMuzzleTransform()
	{
		var muzzleDetails = GetMuzzleEffectDetails();
		return muzzleDetails.Item1?.GetAttachment( muzzleDetails.Item2 );
	}

	/// <summary>
	/// Gets the muzzle attachment gameobject
	/// </summary>
	public virtual GameObject GetMuzzleObject()
	{
		var muzzleDetails = GetMuzzleEffectDetails();
		return muzzleDetails.Item1?.GetAttachmentObject( muzzleDetails.Item2 );
	}

	/// <summary>
	/// Gets the correct shoot animation
	/// </summary>
	/// <param name="shootInfo">Info used for the current attack</param>
	/// <returns></returns>
	public virtual string GetShootAnimation( ShootInfo shootInfo )
	{
		if ( IsAiming && (!string.IsNullOrEmpty( shootInfo.ShootAimedAnim )) )
		{
			return shootInfo.ShootAimedAnim;
		}
		else if ( shootInfo.Ammo == 0 && !string.IsNullOrEmpty( shootInfo.ShootEmptyAnim ) )
		{
			return shootInfo.ShootEmptyAnim;
		}

		return shootInfo.ShootAnim;
	}

	/// <summary>
	/// If there is usable ammo left
	/// </summary>
	public bool HasAmmo()
	{
		if ( Primary.InfiniteAmmo == InfiniteAmmoType.clip )
			return true;

		if ( Primary.ClipSize == -1 )
		{
			return Owner.AmmoCount( Primary.AmmoType ) > 0;
		}

		if ( Primary.Ammo == 0 )
			return false;

		return true;
	}

	public ShootInfo GetShootInfo( bool isPrimary )
	{
		return isPrimary ? Primary : Secondary;
	}

	public bool IsShooting()
	{
		if ( Secondary is null )
			return GetRealRPM( Primary.RPM ) > TimeSincePrimaryShoot;

		return GetRealRPM( Primary.RPM ) > TimeSincePrimaryShoot || GetRealRPM( Secondary.RPM ) > TimeSinceSecondaryShoot;
	}

	public static float GetRealRPM( int rpm )
	{
		return 60f / rpm;
	}

	public virtual float GetRealSpread( float baseSpread = -1 )
	{
		if ( !Owner.IsValid() ) return 0;

		float spread = baseSpread != -1 ? baseSpread : Primary.Spread;
		float floatMod = 1f;

		// Ducking
		if ( IsCrouching && !IsAiming )
			floatMod -= 0.25f;

		// Aiming
		if ( IsAiming && Primary.Bullets == 1 )
			floatMod *= AimInfo.SpreadModifier;

		if ( !Owner.IsOnGround )
		{
			// Jumping
			floatMod += 0.75f;
		}
		else if ( Owner.Velocity.Length > 100 )
		{
			// Moving 
			floatMod += 0.25f;
		}

		var scopeMultiplier = IsScoping ? ScopeInfo.Spread : 1f;
		return spread * floatMod * scopeMultiplier;
	}

	public virtual Angles GetRecoilAngles( ShootInfo shootInfo )
	{
		var recoilX = IsAiming ? -shootInfo.Recoil * 0.4f : -shootInfo.Recoil;
		var recoilY = Game.Random.NextFloat( -0.2f, 0.2f ) * recoilX;
		var recoilAngles = new Angles( recoilX, recoilY, 0 );
		return recoilAngles;
	}

	public static MovementImpact GetMovementImpactFromForce( float force )
	{
		// 1.0 → 0.6 (40% max slow)
		var maxSlow = 0.4f;
		var t = Math.Clamp( force / 10f, 0f, 1f );
		var amount = 1f - t * maxSlow;

		return new()
		{
			Amount = amount,
			Duration = 0.4f,
		};
	}
}