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
└─ ItemMagazineAssetClass 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 Key | Default | Description |
|---|---|---|---|
_recoil_x | Recoil_X | 1.0 | Horizontal recoil multiplier |
_recoil_y | Recoil_Y | 1.0 | Vertical recoil multiplier |
aimingRecoilMultiplier | Aiming_Recoil_Multiplier | 1.0 | ADS 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 Key | Default | Description |
|---|---|---|---|
_spread | Spread | 1.0 | Spread angle multiplier |
_sway | Sway | 1.0 | Weapon sway multiplier |
_shake | Shake | 1.0 | Camera shake multiplier |
Firerate Modifier
| Field | .dat Key | Default | Description |
|---|---|---|---|
_firerateOffset | Firerate | 0 | Firerate 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 Key | Default | Description |
|---|---|---|---|
ballisticDamageMultiplier | Ballistic_Damage_Multiplier or Damage | 1.0 | Bullet damage multiplier |
BallisticGravityMultiplier | Ballistic_Drop | 1.0 | Bullet 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 Key | Default | Description |
|---|---|---|---|
aimDurationMultiplier | Aim_Duration_Multiplier | 1.0 | ADS transition speed multiplier |
aimingMovementSpeedMultiplier | Aiming_Movement_Speed_Multiplier | 1.0 | ADS 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 Key | Default | Description |
|---|---|---|---|
_isPaintable | Paintable (flag) | false | Can receive cosmetic paint |
Bipod
| Field | .dat Key | Default | Description |
|---|---|---|---|
_isBipod / ShouldOnlyAffectAimWhileProne | Bipod (flag) | false | Stat 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 Key | Default | Description |
|---|---|---|---|
CanDamageInvulernableEntities | Invulnerable | false | Can 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 Key | Default | Description |
|---|---|---|---|
shouldDestroyAttachmentColliders | Destroy_Attachment_Colliders | true | Remove 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 Key | Default | Description |
|---|---|---|---|
instantiatedAttachmentName | Instantiated_Attachment_Name_Override | GUID string | GameObject 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
- Caliber 0 universal match — A caliber ID of 0 in the attachment's
calibersarray matches any gun, including modded guns with non-zero calibers. TherequiresNonZeroAttachmentCaliberflag on the gun can block this behavior. - 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.
- Damage key legacy — The
Damagekey on barrels is a legacy fallback forBallistic_Damage_Multiplier. Setting both will useBallistic_Damage_Multiplierand ignoreDamage. - Bipod conditional modifiers — When
ShouldOnlyAffectAimWhileProneis true, the description UI still shows the full modifier values. The player cannot see from the UI that the effects are conditional. - Attachment colliders disabled by default —
shouldDestroyAttachmentCollidersdefaults to true. If a modded attachment needs its colliders for hit detection (e.g., a bayonet), this must be explicitly set to false usingDestroy_Attachment_Colliders.
