Skip to content

ItemCaliberAsset — Attachment Stat Modifier Base

ItemCaliberAsset is the shared base class for all five gun attachment types: ItemBarrelAsset, ItemSightAsset, ItemTacticalAsset, ItemGripAsset, and ItemMagazineAsset. It extends ItemAsset and provides the common stat modifier system, caliber compatibility logic, special behavior flags, and description UI rendering. At 274 lines it defines the universal interface through which attachments modify weapon behavior.

Source code location: Unturned/Bundles/ItemCaliberAsset.cs

Inheritance Chain

ItemAsset
  → ItemCaliberAsset
    ├─ ItemBarrelAsset
    ├─ ItemSightAsset
    ├─ ItemTacticalAsset
    ├─ ItemGripAsset
    └─ ItemMagazineAsset

Class Definition

csharp
public class ItemCaliberAsset : ItemAsset
{
    private ushort[] _calibers;
    public ushort[] calibers => _calibers;

    private float _recoil_x;
    public float recoil_x => _recoil_x;

    private float _recoil_y;
    public float recoil_y => _recoil_y;

    public float aimingRecoilMultiplier;
    public float aimDurationMultiplier;

    private float _spread;
    public float spread => _spread;

    private float _sway;
    public float sway => _sway;

    private float _shake;
    public float shake => _shake;

    private int _firerateOffset;
    public int FirerateOffset => _firerateOffset;

    protected bool _isPaintable;
    public bool isPaintable => _isPaintable;

    public float ballisticDamageMultiplier { get; protected set; }
    public float BallisticGravityMultiplier { get; protected set; }
    public float aimingMovementSpeedMultiplier;

    protected bool _isBipod;
    public bool ShouldOnlyAffectAimWhileProne => _isBipod;

    public bool CanDamageInvulernableEntities { get; protected set; }
    public bool shouldDestroyAttachmentColliders { get; protected set; }
    public string instantiatedAttachmentName { get; protected set; }

    protected override bool doesItemTypeHaveSkins => true;
}

Stat Modifier Fields

All modifier fields default to 1.0 (multiplicative identity) or 0 (additive identity), meaning the attachment has no effect by default.

Recoil Modifiers

Field.dat KeyDefaultDescription
_recoil_xRecoil_X1.0Horizontal recoil multiplier
_recoil_yRecoil_Y1.0Vertical recoil multiplier
aimingRecoilMultiplierAiming_Recoil_Multiplier1.0ADS recoil multiplier

All three are multiplicative. A value of 0.8 means 20% reduction. A value of 1.2 means 20% increase. Values below 0 or above some practical maximum are not clamped by the asset layer — they are applied as-is to the weapon's runtime stats.

Spread and Sway Modifiers

Field.dat KeyDefaultDescription
_spreadSpread1.0Spread angle multiplier
_swaySway1.0Weapon sway multiplier
_shakeShake1.0Camera shake multiplier

Firerate Modifier

Field.dat KeyDefaultDescription
_firerateOffsetFirerate0Firerate adjustment

The FirerateOffset is an integer that is subtracted from the gun's firerate byte:

csharp
// For backwards compatibility this is *subtracted* from the gun's firerate,
// so a positive number decreases the time between shots and a negative
// number increases the time between shots.
public int FirerateOffset => _firerateOffset;

A positive offset = faster fire rate. A negative offset = slower fire rate. The legacy firerate property (returning byte) is marked [Obsolete] and clamps the integer to a byte.

Ballistic Modifiers

Field.dat KeyDefaultDescription
ballisticDamageMultiplierBallistic_Damage_Multiplier or Damage1.0Bullet damage multiplier
BallisticGravityMultiplierBallistic_Drop1.0Bullet drop multiplier

The Damage key is a legacy fallback specific to barrels:

csharp
// When "Ballistic_Damage_Multiplier" was added to all attachment types the
// existing barrel-only "Damage" property was accidentally forgotten about,
// so now it is used as a fallback default value.
float damage = p.data.ParseFloat("Damage", defaultValue: 1.0f);
ballisticDamageMultiplier = p.data.ParseFloat("Ballistic_Damage_Multiplier",
    defaultValue: damage);

ADS and Movement Modifiers

Field.dat KeyDefaultDescription
aimDurationMultiplierAim_Duration_Multiplier1.0ADS transition speed multiplier
aimingMovementSpeedMultiplierAiming_Movement_Speed_Multiplier1.0ADS move speed multiplier

Caliber System

Calibers are the compatibility layer between guns and attachments. Each ItemCaliberAsset carries a _calibers array of ushort IDs and two matching methods.

Parsing

csharp
_calibers = new ushort[p.data.ParseUInt8("Calibers")];
for (byte index = 0; index < calibers.Length; index++)
    _calibers[index] = p.data.ParseUInt16("Caliber_" + index);

The Calibers key specifies the count (as a byte, max 255). Each Caliber_N key provides an individual caliber ID. A caliber ID of 0 is the vanilla "universal" caliber that matches all guns.

Matching Methods

csharp
public bool CalibersContainId(ushort caliberId)
{
    foreach (ushort testId in calibers)
        if (testId == caliberId)
            return true;
    return false;
}

public bool CalibersContainAnyOfIds(ushort[] caliberIds)
{
    foreach (ushort caliberId in caliberIds)
        if (CalibersContainId(caliberId))
            return true;
    return false;
}

CalibersContainId checks if the attachment is compatible with a specific caliber. CalibersContainAnyOfIds checks if any of the gun's allowed calibers match the attachment's calibers. A caliber value of 0 (vanilla default) matches all guns.

Special Behavior Flags

Paintability

Field.dat KeyDefaultDescription
_isPaintablePaintable (flag)falseCan receive cosmetic paint

Bipod

Field.dat KeyDefaultDescription
_isBipod / ShouldOnlyAffectAimWhileProneBipod (flag)falseStat modifiers only active while prone and ADS

When ShouldOnlyAffectAimWhileProne is true, all stat modifiers (recoil, spread, sway, etc.) from this attachment only take effect when the player is aiming down sights while prone.

Invulnerable Entity Damage

Field.dat KeyDefaultDescription
CanDamageInvulernableEntitiesInvulnerablefalseCan damage entities with the Invulnerable tag

This flag allows the attached gun to damage entities that would normally be immune, such as admin-spawned invulnerable objects.

Attachment Collider Control

Field.dat KeyDefaultDescription
shouldDestroyAttachmentCollidersDestroy_Attachment_ColliderstrueRemove colliders from instantiated attachment prefab

When true (default), colliders are stripped from the attachment prefab after it's parented to the gun. This prevents the attachment's colliders from interfering with the gun's physics and hit detection.

Prefab Name Override

Field.dat KeyDefaultDescription
instantiatedAttachmentNameInstantiated_Attachment_Name_OverrideGUID stringGameObject name for the instantiated attachment
csharp
instantiatedAttachmentName = p.data.GetString("Instantiated_Attachment_Name_Override",
    defaultValue: GUID.ToString("N"));

By default, the instantiated prefab is named after the asset's GUID. This override exists because some modders rely on GameObject names for Unity's legacy animation component. Maps with duplicate animations can use this to simplify animation setup.

Skin Support

csharp
protected override bool doesItemTypeHaveSkins => true;

All attachment types support skins. This flag enables the skin lookup system for attachments.

BuildDescription

BuildDescription renders all non-default stat modifiers with color coding and sort ordering:

csharp
public override void BuildDescription(ItemDescriptionBuilder builder, Item itemInstance)
{
    base.BuildDescription(builder, itemInstance);

    if (!builder.HasFlag(EItemDescriptionFlags.Uncategorized))
        return;

    if (_recoil_x != 1.0f)
        // "Recoil X: -20%" (green if lower, red if higher)
    if (_recoil_y != 1.0f)
        // "Recoil Y: -20%"
    if (_spread != 1.0f)
        // "Spread: -15%"
    if (_sway != 1.0f)
        // "Sway: -10%"
    if (aimingRecoilMultiplier != 1.0f)
        // "ADS Recoil: +5%"
    if (aimDurationMultiplier != 1.0f)
        // "ADS Speed: -25%"
    if (aimingMovementSpeedMultiplier != 1.0f)
        // "ADS Move Speed: +10%"
    if (ballisticDamageMultiplier != 1.0f)
        // "Damage: +15%"
    if (BallisticGravityMultiplier != 1.0f)
        // "Bullet Drop: -30%"
    if (CanDamageInvulernableEntities)
        // "Pierces Invulnerable"
}

Each modifier uses DescSort_LowerIsBeneficial or DescSort_HigherIsBeneficial to determine color coding:

  • Lower is better: recoil, spread, sway, shake, bullet gravity.
  • Higher is better: damage, movement speed.

PopulateAsset

csharp
public override void PopulateAsset(in PopulateAssetParameters p)
{
    base.PopulateAsset(in p);

    _calibers = new ushort[p.data.ParseUInt8("Calibers")];
    for (byte index = 0; index < calibers.Length; index++)
        _calibers[index] = p.data.ParseUInt16("Caliber_" + index);

    _recoil_x = p.data.ParseFloat("Recoil_X", 1.0f);
    _recoil_y = p.data.ParseFloat("Recoil_Y", 1.0f);
    aimingRecoilMultiplier = p.data.ParseFloat("Aiming_Recoil_Multiplier", 1.0f);
    aimDurationMultiplier = p.data.ParseFloat("Aim_Duration_Multiplier", 1.0f);
    _spread = p.data.ParseFloat("Spread", 1.0f);
    _sway = p.data.ParseFloat("Sway", 1.0f);
    _shake = p.data.ParseFloat("Shake", 1.0f);

    _firerateOffset = p.data.ParseInt32("Firerate");

    float damage = p.data.ParseFloat("Damage", 1.0f);
    ballisticDamageMultiplier = p.data.ParseFloat("Ballistic_Damage_Multiplier", damage);
    BallisticGravityMultiplier = p.data.ParseFloat("Ballistic_Drop", 1.0f);
    aimingMovementSpeedMultiplier = p.data.ParseFloat("Aiming_Movement_Speed_Multiplier", 1.0f);

    _isPaintable = p.data.ContainsKey("Paintable");
    _isBipod = p.data.ContainsKey("Bipod");
    CanDamageInvulernableEntities = p.data.ParseBool("Invulnerable", false);

    shouldDestroyAttachmentColliders = p.data.ParseBool("Destroy_Attachment_Colliders", true);
    instantiatedAttachmentName = p.data.GetString("Instantiated_Attachment_Name_Override",
        GUID.ToString("N"));
}

Cargo Data Export

The BuildCargoData method writes to two Cargo tables:

  • Caliber: All stat modifier values and flag fields, keyed by GUID.
  • Caliber_Caliber: Keyless child table with one row per caliber ID.
csharp
CargoDeclaration data = builder.GetOrAddDeclaration("Caliber");
data.Append("GUID", GUID);
data.Append("Recoil_X", recoil_x);
data.Append("Recoil_Y", recoil_y);
data.Append("Aiming_Recoil_Multiplier", aimingRecoilMultiplier);
data.Append("Aim_Duration_Multiplier", aimDurationMultiplier);
data.Append("Spread", spread);
data.Append("Sway", sway);
data.Append("Shake", shake);
data.Append("Firerate", FirerateOffset);
data.Append("Ballistic_Damage_Multiplier", ballisticDamageMultiplier);
data.Append("Ballistic_Drop", BallisticGravityMultiplier);
data.Append("Aiming_Movement_Speed_Multiplier", aimingMovementSpeedMultiplier);
data.Append("Paintable", isPaintable);
data.Append("Bipod", ShouldOnlyAffectAimWhileProne);
data.Append("Invulnerable", CanDamageInvulernableEntities);
data.Append("Calibers", calibers.Length);

Child table entries:

csharp
for (byte index = 0; index < calibers.Length; index++)
{
    CargoDeclaration cal = builder.AddDeclaration("Caliber_Caliber");
    cal.Append("GUID", GUID);
    cal.Append("Caliber", calibers[index]);
}

Inventory Audio

csharp
protected override AudioReference GetDefaultInventoryAudio()
{
    return new AudioReference("core.masterbundle",
        "Sounds/Inventory/SmallGunAttachment.asset");
}

All attachment types share the "SmallGunAttachment" inventory pickup/place sound, regardless of the attachment's size or function.

Common Issues

  1. Caliber 0 universal match — A caliber ID of 0 in the attachment's calibers array matches any gun, including modded guns with non-zero calibers. The requiresNonZeroAttachmentCaliber flag on the gun can block this behavior.
  2. Firerate offset sign — The offset is subtracted from the gun's firerate. A positive value decreases the time between shots. Modders expecting "positive = slower" will get the opposite.
  3. Damage key legacy — The Damage key on barrels is a legacy fallback for Ballistic_Damage_Multiplier. Setting both will use Ballistic_Damage_Multiplier and ignore Damage.
  4. Bipod conditional modifiers — When ShouldOnlyAffectAimWhileProne is true, the description UI still shows the full modifier values. The player cannot see from the UI that the effects are conditional.
  5. Attachment colliders disabled by defaultshouldDestroyAttachmentColliders defaults to true. If a modded attachment needs its colliders for hit detection (e.g., a bayonet), this must be explicitly set to false using Destroy_Attachment_Colliders.