Skip to content

ItemTacticalAsset — Tactical Attachments

ItemTacticalAsset defines utility attachments that mount to the gun's tactical rail. It supports four independent functions — laser sights, flashlights, rangefinders, and bayonet melee attacks — which can be combined on a single attachment. It inherits from ItemCaliberAsset for stat modifiers and caliber compatibility. At 267 lines, it is the largest attachment class due to the full melee damage system embedded within it.

Source code location: Unturned/Bundles/ItemTacticalAsset.cs

Inheritance Chain

ItemAsset
  → ItemCaliberAsset
    → ItemTacticalAsset

Class Definition

csharp
public class ItemTacticalAsset : ItemCaliberAsset
{
    protected GameObject _tactical;
    public GameObject tactical => _tactical;

    private bool _isLaser;
    public bool isLaser => _isLaser;

    private bool _isLight;
    public bool isLight => _isLight;

    public PlayerSpotLightConfig lightConfig { get; protected set; }

    private bool _isRangefinder;
    public bool isRangefinder => _isRangefinder;

    private bool _isMelee;
    public bool isMelee => _isMelee;

    public ItemTacticalAssetMeleeProperties MeleeProperties { get; set; }

    public Color laserColor { get; protected set; }
}

Core Function Flags

Laser Sight

Field.dat KeyDescription
_isLaserLaser (flag)Projects a visible laser sight line
laserColorLaser_ColorLaser beam color (default red)

The laser color is parsed using LegacyParseColor which accepts named colors or RGB values:

csharp
Color _laserColor = p.data.LegacyParseColor("Laser_Color", Color.red);
_laserColor = MathfEx.Clamp01(_laserColor);
_laserColor.a = 1.0f;
laserColor = _laserColor;

The color is clamped to [0,1] range and alpha is forced to 1.0 (fully opaque). This means transparent lasers are not possible through the color picker — the alpha channel is always overwritten.

Flashlight

Field.dat KeyDescription
_isLightLight (flag)Mounted flashlight
lightConfigPlayerSpotLightConfig (nested key set)Flashlight properties

When isLight is true, a PlayerSpotLightConfig is created from the .dat data:

csharp
if (isLight)
{
    lightConfig = new PlayerSpotLightConfig(p.data);
}

PlayerSpotLightConfig reads nested keys for spotlight range, angle, intensity, and color. The flashlight is toggled by the player's tactical key bind.

Rangefinder

Field.dat KeyDescription
_isRangefinderRangefinder (flag)Distance measurement

When active, the rangefinder displays the distance to whatever the player is aiming at. The maximum range is the gun's rangeRangefinder value (which defaults to the weapon's range).

Melee / Bayonet

Field.dat KeyDescription
_isMeleeMelee (flag)Bayonet-style melee attachment
MeleePropertiesNested keysFull melee damage configuration

When isMelee is true, a set of melee-specific properties is loaded. The melee attack activates when the player uses the tactical attachment as a melee weapon. This is independent of the gun's melee system and uses its own damage multipliers.

ItemTacticalAssetMeleeProperties

The melee properties class provides a complete standalone melee damage system within the tactical attachment:

csharp
public class ItemTacticalAssetMeleeProperties
{
    public float MeleeRange { get; set; }
    public PlayerDamageMultiplier MeleePlayerDamageMultiplier { get; set; }
    public DamagePlayerParameters.Bleeding MeleePlayerDamageBleeding { get; set; }
    public DamagePlayerParameters.Bones MeleePlayerDamageBones { get; set; }
    public ZombieDamageMultiplier MeleeZombieDamageMultiplier { get; set; }
    public EZombieStunOverride MeleeZombieStunOverride { get; set; }
    public AnimalDamageMultiplier MeleeAnimalDamageMultiplier { get; set; }
    public float MeleeZombieRagdollForceMultiplier { get; set; }
}

Melee Damage Configuration

Field.dat KeyDefaultDescription
MeleeRangeMelee_Range2.0Bayonet reach in meters
MeleePlayerDamageMultiplierMelee_Player_Damage + limb multipliers40 basePlayer damage per body part
MeleePlayerDamageBleedingMelee_Player_Damage_BleedingNoneWhether bayonet causes bleeding
MeleePlayerDamageBonesMelee_Player_Damage_BonesNoneWhether bayonet breaks bones
MeleeZombieDamageMultiplierMelee_Zombie_Damage + limb multipliers40 baseZombie damage per body part
MeleeZombieStunOverrideMelee_Stun_Zombie_Always/NeverNoneForce or prevent zombie stun
MeleeAnimalDamageMultiplierMelee_Animal_Damage + limb multipliers40 baseAnimal damage per body part
MeleeZombieRagdollForceMultiplierMelee_Zombie_Ragdoll_Force_Multiplier1.0Knockback force on zombies

The damage multipliers follow the same pattern as ItemWeaponAsset with per-limb multipliers. The limb multiplier defaults differ between player (arm=0.6, leg=0.6, spine=0.8, skull=1.1) and zombie/animal (leg=0.3, arm=0.3, spine=0.6, skull=1.1).

Zombie Stun Override

csharp
public enum EZombieStunOverride { None, Always, Never }
KeyEffect
Melee_Stun_Zombie_AlwaysAlways stuns zombies
Melee_Stun_Zombie_NeverNever stuns zombies
(neither)Uses default stun behavior

Game Mode Config Compatibility

The MeleeAnimalOrPlayerDamageMultiplier and MeleeZombieOrPlayerDamageMultiplier properties select between animal and player damage based on the game mode config:

csharp
public IDamageMultiplier MeleeAnimalOrPlayerDamageMultiplier
{
    get
    {
        bool usePlayerDmg = Provider.modeConfigData.Animals.Weapons_Use_Player_Damage;
        return usePlayerDmg ? MeleePlayerDamageMultiplier : MeleeAnimalDamageMultiplier;
    }
}

This allows servers to configure whether animals take player-equivalent damage from bayonet attacks.

Damage Parameter Initialization

The melee properties can initialize damage parameters for the damage system:

csharp
public void InitPlayerDamageParameters(ref DamagePlayerParameters parameters)
{
    parameters.bleedingModifier = MeleePlayerDamageBleeding;
    parameters.bonesModifier = MeleePlayerDamageBones;
}

Melee Description UI

The BuildDescription method renders a full melee damage description including:

  • Melee range.
  • Player damage per limb (head, body, arm, leg).
  • Bleeding and bone break modifiers.
  • Zombie damage per limb.
  • Animal damage per limb.

PopulateAsset

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

    _tactical = loadRequiredAsset<GameObject>(p.bundle, "Tactical");

    _isLaser = p.data.ContainsKey("Laser");

    _isLight = p.data.ContainsKey("Light");
    if (isLight)
        lightConfig = new PlayerSpotLightConfig(p.data);

    _isRangefinder = p.data.ContainsKey("Rangefinder");
    _isMelee = p.data.ContainsKey("Melee");

    if (_isMelee)
    {
        MeleeProperties = new ItemTacticalAssetMeleeProperties();
        MeleeProperties.PopulateAsset(in p);
    }

    Color _laserColor = p.data.LegacyParseColor("Laser_Color", Color.red);
    _laserColor = MathfEx.Clamp01(_laserColor);
    _laserColor.a = 1.0f;
    laserColor = _laserColor;
}

All four function flags are parsed as key presence (ContainsKey). Any combination can be active simultaneously — a tactical attachment could be a laser, flashlight, rangefinder, and bayonet all at once.

BuildDescription

BuildDescription delegates to MeleeProperties.BuildDescription when the melee flag is set:

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

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

    if (MeleeProperties != null)
        MeleeProperties.BuildDescription(builder);
}

The base class (ItemCaliberAsset.BuildDescription) renders all stat modifiers. The melee properties add their damage breakdown separately.

Cargo Data Export

csharp
CargoDeclaration data = builder.GetOrAddDeclaration("Tactical");
data.Append("GUID", GUID);
data.Append("Laser", isLaser);
data.Append("Light", isLight);
data.Append("Rangefinder", isRangefinder);
data.Append("Melee", isMelee);
data.Append("Laser_Color", laserColor);

Attachment Integration

When a tactical attachment is equipped, UseableGun activates each enabled function:

  1. Laser: A LineRenderer from the tactical hook to the aim point. The color is laserColor. Visible to all players.
  2. Light: A PlayerSpotLightConfig spotlight is enabled from the tactical hook. Can be toggled on/off.
  3. Rangefinder: The HUD displays distance to the targeted point, up to the gun's rangeRangefinder.
  4. Melee: The attachment provides an alternate melee attack with its own damage values. This replaces or augments the gun's standard melee.

The _tactical prefab is instantiated and parented to the weapon's Hook_Tactical transform.

Common Issues

  1. Tactical melee stacking — The ItemTacticalAssetMeleeProperties provides a separate melee attack that activates when the tactical attachment is used as a melee weapon. This is independent of the gun's melee system and uses its own damage multipliers.
  2. Laser color alpha override — Alpha is forced to 1.0 regardless of what value is parsed. This means Laser_Color values with alpha channels are silently clamped. Transparent or semi-transparent laser beams are not possible.
  3. Light without Laser — A tactical attachment with Light but not Laser has a separate toggle key. Players may not realize the flashlight is available if the laser (which has a visible indicator) is absent.
  4. Melee damage defaults — The default melee damage values (40 base) are the same as ItemMeleeAsset defaults. If specific values are not set in the .dat, the bayonet may deal unexpected damage amounts.
  5. All four flags active — A tactical attachment with all four functions (laser, light, rangefinder, melee) may have conflicting key bindings or UI overload. The game allows it but the player experience may be confusing.