Skip to content

ItemMagazineAsset — Magazine Attachments ​

ItemMagazineAsset defines magazine attachments that provide ammunition, alter projectile behavior, and can carry explosive payloads. At 293 lines, it is the third-largest attachment class behind ItemTacticalAsset and ItemSightAsset. It inherits from ItemCaliberAsset for stat modifiers and adds pellet count (shotgun), stuck chance, explosive damage tables, projectile overrides, tracer effects, impact effects, and projectile speed.

Source code location: Unturned/Bundles/ItemMagazineAsset.cs

Inheritance Chain ​

ItemAsset
  → ItemCaliberAsset
    → ItemMagazineAsset

Class Definition ​

csharp
public class ItemMagazineAsset : ItemCaliberAsset
{
    protected GameObject _magazine;
    public GameObject magazine => _magazine;

    public GameObject ProjectilePrefabOverride { get; set; }

    private byte _pellets;
    public byte pellets => _pellets;

    private byte _stuck;
    public byte stuck => _stuck;

    protected float _range;
    public float range => _range;

    public float projectileDamageMultiplier { get; protected set; }
    public float projectileBlastRadiusMultiplier { get; protected set; }
    public float projectileLaunchForceMultiplier { get; protected set; }
    public float ProjectileLifespanOverride { get; set; }

    public float playerDamage, zombieDamage, animalDamage;
    public float barricadeDamage, structureDamage, vehicleDamage;
    public float resourceDamage, objectDamage;
    public float explosionLaunchSpeed;

    public bool ExplosionPlaysImpactEffects { get; set; } = true;
    public bool ExplosionPenetratesBuildables { get; set; } = false;

    public Guid explosionEffectGuid;
    private ushort _explosion;
    public ushort explosion => _explosion;

    public Guid tracerEffectGuid;
    private ushort _tracer;

    private Guid _impactEffectGuid;
    private ushort _impact;

    public override bool showQuality => stuck > 0;

    private float _speed;
    public float speed => _speed;

    protected bool _isExplosive;
    public bool isExplosive => _isExplosive;

    public bool shouldFillAfterDetach { get; protected set; }
}

Core Fields ​

Prefab and Ammo ​

FieldTypeBundle/.datDefaultDescription
_magazineGameObjectBundle "Magazine"—Magazine attachment prefab
MaxAmountbyteInherited from ItemAsset—Maximum ammo capacity (from Amount key)
shouldDeleteAtZeroAmountboolInherited from ItemAsset—Delete magazine when empty

The MaxAmount field is inherited from ItemAsset and defines the maximum rounds the magazine holds. This is displayed in the gun's ammo line as currentAmmo / maxAmount.

Pellets (Shotgun) ​

FieldType.dat KeyDefaultDescription
_pelletsbytePellets1Number of pellets per shot
csharp
_pellets = p.data.ParseUInt8("Pellets");
if (pellets < 1)
    _pellets = 1;

The pellets value is clamped to a minimum of 1. A value of 5 creates a shotgun blast with 5 simultaneous projectiles per trigger pull. Each pellet follows the standard ballistic path and deals independent damage.

Stuck Chance ​

FieldType.dat KeyDefaultDescription
_stuckbyteStuck0Chance to get stuck when empty (reload required)

The showQuality override returns true when stuck > 0, enabling quality tracking for magazines that can malfunction. A stuck magazine requires a reload to clear.

Projectile Override ​

FieldTypeBundle/.datDescription
ProjectilePrefabOverrideGameObjectBundle "Projectile"Overrides the gun's projectile prefab
csharp
ProjectilePrefabOverride = p.bundle.load<GameObject>("Projectile");

If the magazine's bundle contains a "Projectile" prefab, it replaces the gun's own projectile. This allows magazines to completely change the weapon's projectile type (e.g., switching from hitscan to rocket, or changing the projectile model). The optional ProjectileLifespanOverride field extends or reduces the projectile's lifetime.

Projectile Multipliers ​

FieldType.dat KeyDefaultDescription
projectileDamageMultiplierfloatProjectile_Damage_Multiplier1.0Explosive projectile damage modifier
projectileBlastRadiusMultiplierfloatProjectile_Blast_Radius_Multiplier1.0Blast radius modifier
projectileLaunchForceMultiplierfloatProjectile_Launch_Force_Multiplier1.0Launch force modifier

Speed ​

FieldType.dat KeyDefaultDescription
_speedfloatSpeed1.0Projectile speed multiplier
csharp
_speed = p.data.ParseFloat("Speed");
if (speed < 0.01f)
    _speed = 1.0f;

The speed is clamped to a minimum of 0.01, with values below that threshold defaulting to 1.0.

Explosive Magazine System ​

Magazines can be explosive, dealing area damage independently of the gun's projectile system. This is most commonly used for under-barrel grenade launcher ammunition and explosive shotgun shells.

Explosive Configuration ​

FieldType.dat KeyDefaultDescription
_isExplosiveboolExplosive (flag)falseMagazine causes explosion on impact
_rangefloatRange—Blast radius in meters
explosionEffectGuid / _explosionGuid / ushortExplosion—Explosion visual effect

Explosive Damage Tables ​

Field.dat KeyDescription
playerDamagePlayer_DamageDamage to players
zombieDamageZombie_DamageDamage to zombies
animalDamageAnimal_DamageDamage to animals
barricadeDamageBarricade_DamageDamage to barricades
structureDamageStructure_DamageDamage to structures
vehicleDamageVehicle_DamageDamage to vehicles
resourceDamageResource_DamageDamage to resources
objectDamageObject_DamageDamage to objects (defaults to resourceDamage)
explosionLaunchSpeedExplosion_Launch_SpeedPhysics launch force (defaults to playerDamage * 0.1)

Explosive Behavior Flags ​

Field.dat KeyDefaultDescription
ExplosionPlaysImpactEffectsExplosion_Plays_Impact_EffectstrueWhether surface effects appear
ExplosionPenetratesBuildablesExplosion_Penetrate_BuildablesfalseWhether blast penetrates constructions
spawnExplosionOnDedicatedServerSpawn_Explosion_On_Dedicated_ServerfalseForce explosion on dedicated server

Effect Resolution ​

The magazine provides helper methods for effect asset resolution:

csharp
public bool IsExplosionEffectRefNull()
{
    return explosion == 0 && explosionEffectGuid.IsEmpty();
}

public EffectAsset FindExplosionEffect()
{
    return Assets.FindEffectAssetByGuidOrLegacyId(explosionEffectGuid, explosion);
}

Tracer and Impact Effects ​

Field.dat KeyDescription
tracerEffectGuid / _tracerTracerBullet tracer trail effect
_impactEffectGuid / _impactImpactBullet impact effect (overrides gun's default)

The tracer effect creates a visible trail behind each bullet. The impact effect replaces the gun's default impact particle. Helper methods resolve these via dual GUID/legacy ID lookup:

csharp
public EffectAsset FindTracerEffectAsset()
{
    return Assets.FindEffectAssetByGuidOrLegacyId(tracerEffectGuid, _tracer);
}

public EffectAsset FindImpactEffectAsset()
{
    return Assets.FindEffectAssetByGuidOrLegacyId(_impactEffectGuid, _impact);
}

Should Fill After Detach ​

Field.dat KeyDefaultDescription
shouldFillAfterDetachShould_Fill_After_DetachfalseRefill ammo capacity when detached

When true, the magazine's ammo count is automatically refilled to MaxAmount when removed from a gun. This is used for magazines that recharge or regenerate ammunition.

PopulateAsset ​

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

    _magazine = loadRequiredAsset<GameObject>(p.bundle, "Magazine");
    ProjectilePrefabOverride = p.bundle.load<GameObject>("Projectile");

    _pellets = p.data.ParseUInt8("Pellets");
    if (pellets < 1) _pellets = 1;

    _stuck = p.data.ParseUInt8("Stuck");

    projectileDamageMultiplier = p.data.ParseFloat("Projectile_Damage_Multiplier", 1.0f);
    projectileBlastRadiusMultiplier = p.data.ParseFloat("Projectile_Blast_Radius_Multiplier", 1.0f);
    projectileLaunchForceMultiplier = p.data.ParseFloat("Projectile_Launch_Force_Multiplier", 1.0f);

    _range = p.data.ParseFloat("Range");
    playerDamage = p.data.ParseFloat("Player_Damage");
    zombieDamage = p.data.ParseFloat("Zombie_Damage");
    animalDamage = p.data.ParseFloat("Animal_Damage");
    barricadeDamage = p.data.ParseFloat("Barricade_Damage");
    structureDamage = p.data.ParseFloat("Structure_Damage");
    vehicleDamage = p.data.ParseFloat("Vehicle_Damage");
    resourceDamage = p.data.ParseFloat("Resource_Damage");
    explosionLaunchSpeed = p.data.ParseFloat("Explosion_Launch_Speed", playerDamage * 0.1f);
    ExplosionPlaysImpactEffects = p.data.ParseBool("Explosion_Plays_Impact_Effects", true);
    ExplosionPenetratesBuildables = p.data.ParseBool("Explosion_Penetrate_Buildables");
    _explosion = p.data.ParseGuidOrLegacyId("Explosion", out explosionEffectGuid);

    if (p.data.ContainsKey("Object_Damage"))
        objectDamage = p.data.ParseFloat("Object_Damage");
    else
        objectDamage = resourceDamage;

    _tracer = p.data.ParseGuidOrLegacyId("Tracer", out tracerEffectGuid);
    _impact = p.data.ParseGuidOrLegacyId("Impact", out _impactEffectGuid);

    _speed = p.data.ParseFloat("Speed");
    if (speed < 0.01f) _speed = 1.0f;

    _isExplosive = p.data.ContainsKey("Explosive");
    spawnExplosionOnDedicatedServer = p.data.ContainsKey("Spawn_Explosion_On_Dedicated_Server");

    shouldFillAfterDetach = p.data.ParseBool("Should_Fill_After_Detach", false);
}

BuildDescription ​

BuildDescription shows pellet count, explosive properties, and caliber-level stat modifiers:

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

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

    if (_pellets > 1)
        builder.Append(localization.format("ItemDescription_PelletCount", _pellets), ...);

    if (isExplosive)
    {
        // Explosive bullet indicator
        // Blast radius
        // Damage per entity type (player, zombie, animal, barricade,
        //   structure, vehicle, resource, object)
    }
}

Cargo Data Export ​

csharp
CargoDeclaration data = builder.GetOrAddDeclaration("Magazine");
data.Append("GUID", GUID);
data.Append("Pellets", pellets);
data.Append("Stuck", stuck);
data.Append("Projectile_Damage_Multiplier", projectileDamageMultiplier);
data.Append("Projectile_Blast_Radius_Multiplier", projectileBlastRadiusMultiplier);
data.Append("Projectile_Launch_Force_Multiplier", projectileLaunchForceMultiplier);
data.Append("Range", range);
data.Append("Player_Damage", playerDamage);
// ... all other damage fields ...
data.Append("Explosion", explosion);
data.Append("Speed", speed);
data.Append("Explosive", isExplosive);
data.Append("Should_Fill_After_Detach", shouldFillAfterDetach);

Inherited Stat Modifiers ​

As with all ItemCaliberAsset subclasses, magazines inherit the full stat modifier system (recoil_x, recoil_y, spread, sway, shake, FirerateOffset, ballisticDamageMultiplier, BallisticGravityMultiplier, aimDurationMultiplier, aimingRecoilMultiplier, aimingMovementSpeedMultiplier). These allow magazines to affect weapon handling beyond just ammunition — for example, a drum magazine might increase sway or reduce ADS speed due to its weight.

Common Issues ​

  1. Pellet minimum enforcement — _pellets is clamped to a minimum of 1. A Pellets value of 0 in the .dat silently becomes 1. There is no way to create a magazine that fires zero pellets.
  2. Speed minimum enforcement — _speed is clamped to a minimum of 0.01. Values below this threshold silently default to 1.0. Ultra-slow projectiles require a minimum Speed value of 0.01.
  3. Object_Damage fallback — If Object_Damage is not explicitly set, it defaults to Resource_Damage. Modders setting only Resource_Damage may be surprised to find objects taking the same damage.
  4. Explosive damage vs projectile damage — The explosive damage table (playerDamage etc.) is separate from the projectileDamageMultiplier. Both apply independently — magazine explosive damage multiplies the gun's base damage, then projectileDamageMultiplier applies on top.
  5. Stuck quality tracking — showQuality returns true only when stuck > 0. A magazine without a stuck chance never shows quality and the quality byte (offset 17) is unused.
  6. Delete empty deprecation — The deleteEmpty property is marked [Obsolete] and redirects to ItemAsset.ShouldDeleteAtZeroAmount. The Delete_Empty key still works but the recommended key is Should_Delete_At_Zero_Amount.