Skip to content

ItemConsumeableAsset — The Consumable Base

ItemConsumeableAsset is the base class for food, water, and medical items. It extends ItemWeaponAsset — a non-obvious inheritance choice driven by the need for explosive damage fields on consumable items that double as explosives. At 351 lines it defines all player stat modifiers, medical condition changes, consumption constraints, quality and mold mechanics, explosive consumable behavior, and quest/item reward systems.

Source code location: Unturned/Bundles/ItemConsumeableAsset.cs

Inheritance Chain

ItemAsset
  → ItemWeaponAsset
    → ItemConsumeableAsset
      → ItemFoodAsset      (empty)
      → ItemWaterAsset     (empty)
      → ItemMedicalAsset   (empty)

ItemConsumeableAsset extends ItemWeaponAsset rather than ItemAsset directly because:

  1. Explosive damage fields: ItemWeaponAsset provides playerDamageMultiplier, zombieDamageMultiplier, animalDamageMultiplier, and the BuildExplosiveDescription / BuildNonExplosiveDescription helpers. Consumables configured with Explosion effects use these fields for blast damage calculation.
  2. Range field: ItemWeaponAsset.range is reused for explosive blast radius.
  3. NPC rewards: The NPCRewardsList infrastructure is shared.

Class Definition (Fields)

csharp
public class ItemConsumeableAsset : ItemWeaponAsset
{
    protected AudioClip _use;
    public AudioClip use => _use;
    public bool ShouldRandomizeUseAudioPitch { get; set; }

    private byte _health;
    private byte _food;
    private byte _water;
    private byte _virus;
    private byte _disinfectant;
    private byte _energy;
    private byte _vision;
    public sbyte oxygen { get; protected set; }
    private uint _warmth;
    public int experience;

    public Bleeding bleedingModifier { get; protected set; }
    public Bones bonesModifier { get; protected set; }

    private bool _hasAid;
    public bool hasAid => _hasAid;
    public bool foodConstrainsWater { get; protected set; }
    public bool shouldDeleteAfterUse { get; protected set; }

    public override bool showQuality => type == EItemType.FOOD || type == EItemType.WATER;

    private Guid _explosionEffectGuid;
    protected ushort _explosion;
    public bool IsExplosive { get; private set; }

    protected NPCRewardsList questRewardsList;
    public SpawnTableReward itemRewards { get; protected set; }

    protected override bool doesItemTypeHaveSkins => id == 13;
}

Stat Modifiers

All stat fields are byte values except where noted:

FieldType.dat KeyEffect
_healthbyteHealthDirect health restoration (0-255)
_foodbyteFoodFood stat increase (0-255)
_waterbyteWaterWater stat increase (0-255)
_virusbyteVirusVirus/infection increase (worsens condition)
_disinfectantbyteDisinfectantReduces virus/infection level
_energybyteEnergyStamina restoration
_visionbyteVisionVision/awareness buff (night vision or highlight)
oxygensbyteOxygenPositive = refill; negative = depletion
_warmthuintWarmthWarmth duration, divided by 12.5 for seconds display
experienceintExperienceXP awarded or deducted

Medical Condition Modifiers

csharp
public enum Bleeding { None, Heal, Cut }
public enum Bones { None, Heal, Break }
FieldType.dat Key(s)Description
bleedingModifierBleedingBleeding or Bleeding_ModifierHeals bleeding (Heal), causes bleeding (Cut), or no effect
bonesModifierBonesBroken or Bones_ModifierHeals broken bones (Heal), breaks bones (Break), or no effect

The .dat parsing has two paths for backwards compatibility:

csharp
if (p.data.ContainsKey("Bleeding"))
    bleedingModifier = Bleeding.Heal;  // Simple flag = Heal
else
    bleedingModifier = p.data.ParseEnum<Bleeding>("Bleeding_Modifier");  // Explicit value

And for bones:

csharp
if (p.data.ContainsKey("Broken"))
    bonesModifier = Bones.Heal;  // Simple "Broken" flag = Heal
else
    bonesModifier = p.data.ParseEnum<Bones>("Bones_Modifier");  // Explicit value

Consumption Constraints

FieldType.dat KeyDescription
foodConstrainsWaterboolImplicit from food >= waterWhen true, the consumption system limits water based on food stat
shouldDeleteAfterUseboolShould_Delete_After_UseWhether the item is consumed on use (default true)
_hasAidboolAid (flag)Item can be applied to other players
csharp
foodConstrainsWater = food >= water;

The foodConstrainsWater field is computed from the stat values rather than read from a .dat key. When food >= water, the constraint is active: if food + water would exceed 100, water is clamped to prevent overflow.

Quality and Mold

showQuality returns true when type == EItemType.FOOD || type == EItemType.WATER. The quality value (0-100) affects consumption:

  • Quality ≥ 50: Normal stat restoration.
  • Quality < 50: The item is "moldy." Reduced food/water benefits, increased virus.

The mold check in BuildDescription:

csharp
if (itemInstance != null && itemInstance.quality < 50 && _food + _water > 0)

Items with zero food+water (medical items, vitamins) never show the mold warning. Quality degrades by 1 per consumption tick (configurable in game mode).

Explosive Consumables

Consumables can double as explosives when configured with an explosion effect reference:

FieldType.dat KeyDescription
_explosionEffectGuidGuidExplosion (GUID)Explosion effect reference
_explosionushortExplosion (legacy ID)Legacy explosion effect ID
IsExplosiveboolComputedTrue if explosion reference is non-null
csharp
_explosion = p.data.ParseGuidOrLegacyId("Explosion", out _explosionEffectGuid);
IsExplosive = !IsExplosionEffectRefNull();

When IsExplosive is true:

  • shouldFriendlySentryTargetUser returns true — sentries treat the player as hostile.
  • BuildDescription appends an explosive warning and calls BuildExplosiveDescription.
  • The UseableConsumeable spawns the explosion at the player's position on use.
  • Explosive consumables always delete after detonation.

Explosive Damage Properties

Damage SourceValue UsedDerived From
Player damageplayerDamageMultiplier.damageItemWeaponAsset
Zombie damagezombieDamageMultiplier.damageItemWeaponAsset
Animal damageanimalDamageMultiplier.damageItemWeaponAsset
Barricade damagebarricadeDamageMultiplier.damageItemWeaponAsset
Structure damagestructureDamageMultiplier.damageItemWeaponAsset
Vehicle damagevehicleDamageMultiplier.damageItemWeaponAsset
Resource damageresourceDamageMultiplier.damageItemWeaponAsset
Blast radiusrangeItemWeaponAsset
Launch speedplayerDamageMultiplier.damage * 0.1fComputed default

Effect Resolution

csharp
public bool IsExplosionEffectRefNull()
{
    return _explosion == 0 && _explosionEffectGuid.IsEmpty();
}

public EffectAsset FindExplosionEffectAsset()
{
    return Assets.FindEffectAssetByGuidOrLegacyId(_explosionEffectGuid, _explosion);
}

Quest and Item Rewards

FieldType.dat KeysDescription
questRewardsListNPCRewardsListQuest_Rewards / Quest_Reward_NNPC quest rewards granted on consumption
itemRewardsSpawnTableRewardItem_Reward_Spawn_ID, Min_Item_Rewards, Max_Item_RewardsRandom item drop on consumption

Item rewards are parsed as:

csharp
ushort itemRewardTableID = p.data.ParseUInt16("Item_Reward_Spawn_ID");
int minItemRewards = p.data.ParseInt32("Min_Item_Rewards");
int maxItemRewards = p.data.ParseInt32("Max_Item_Rewards");
itemRewards = new SpawnTableReward(itemRewardTableID, minItemRewards, maxItemRewards);

Granting quest rewards:

csharp
public void GrantQuestRewards(Player player)
{
    questRewardsList.Grant(player);
}

Audio

FieldTypeSource
_useAudioClipBundle "Use" or .dat "ConsumeAudioClip"
ShouldRandomizeUseAudioPitchbool.dat "Randomize_Consume_Audio_Pitch" (default true)
csharp
_use = LoadRedirectableAsset<AudioClip>(p.bundle, "Use", p.data, "ConsumeAudioClip");
ShouldRandomizeUseAudioPitch = p.data.ParseBool("Randomize_Consume_Audio_Pitch", true);

When pitch randomization is enabled, the consumption sound plays at pitch = 1.0 + Random.Range(-0.1, 0.1), adding small acoustic variety to repeated consuming.

BuildDescription

BuildDescription renders each non-zero stat with color coding:

StatColorSort OrderCondition
HealthGreenBeneficial_health > 0
FoodGreenBeneficial_food > 0
WaterGreenBeneficial_water > 0
VirusRedDetrimental_virus > 0
DisinfectantGreenBeneficial_disinfectant > 0
EnergyGreenBeneficial_energy > 0
Oxygen (+)GreenBeneficialoxygen > 0
Oxygen (-)RedDetrimentaloxygen < 0
WarmthGreenBeneficialwarmth / 12.5 > 0
Bleeding healGreenBeneficialbleedingModifier == Heal
Bleeding cutRedDetrimentalbleedingModifier == Cut
Bones healGreenBeneficialbonesModifier == Heal
Bones breakRedDetrimentalbonesModifier == Break
ExplosiveRedImportantIsExplosive
MoldyRedDetrimentalQuality < 50 and food+water > 0

Cargo Data Export

csharp
CargoDeclaration data = builder.GetOrAddDeclaration("Consumeable");
data.Append("GUID", GUID);
data.Append("Health", health);
data.Append("Food", food);
data.Append("Water", water);
data.Append("Virus", virus);
data.Append("Disinfectant", disinfectant);
data.Append("Energy", energy);
data.Append("Vision", vision);
data.Append("Oxygen", oxygen);
data.Append("Warmth", warmth);
data.Append("Experience", experience);
data.Append("Bleeding_Modifier", bleedingModifier);
data.Append("Bones_Modifier", bonesModifier);
data.Append("Aid", hasAid);
data.Append("Should_Delete_After_Use", shouldDeleteAfterUse);
data.Append("Item_Reward_Spawn_ID", itemRewards.tableID);
data.Append("Min_Item_Rewards", itemRewards.min);
data.Append("Max_Item_Rewards", itemRewards.max);
data.Append("Explosion", explosion);

The UseableConsumeable Pipeline

The runtime UseableConsumeable class drives consumption in a tick-based pipeline (32 ticks/second):

  1. Activation: Player left-clicks with the consumable equipped.
  2. Per-tick application: Each tick applies statValue / totalTicks in order: Food → Water → Health → Virus → Disinfectant → Energy → Vision → Oxygen → Warmth.
  3. Medical processing: After stat ticks, bleedingModifier and bonesModifier are applied.
  4. Completion: GrantQuestRewards, itemRewards spawn table roll, experience applied.
  5. Interruption: If the player moves, takes damage, or switches items, partial stats are preserved.
  6. Explosive detonation: If IsExplosive, the explosion triggers regardless of interruption.

Skins

csharp
protected override bool doesItemTypeHaveSkins => id == 13;

Only item ID 13 (Canned Beans) supports skins — an April Fools feature. All other consumable items return false for skin support.

Common Issues

  1. foodConstrainsWater calculation — The constraint is computed as food >= water, not read from a .dat key. A meal with 80 food and 80 water provides only 20 water when the constraint is active.
  2. Experience can be negative — The experience field is a signed int. Negative values deduct XP with no lower-bound check.
  3. Warmth rounding — Warmth is displayed as warmth / 12.5 seconds. Values below 12 show 0 seconds in the UI but still provide sub-second warmth in the simulation.
  4. Mold display ignores virus — The mold warning only checks quality < 50 and food+water > 0. A moldy item with high virus will have green and red stats but no specific virus warning.
  5. Explosive range on consumables — The blast radius uses the range field from ItemWeaponAsset, which is also used for ballistic weapons. This dual-purpose field can cause confusion.
  6. Aid targeting — Without Aid in the .dat, the useable defaults to self-use only. Aid items use a different raycast and validation path.
  7. Item reward overflow — If the inventory is full, excess reward items drop on the ground via ItemManager.dropItem.
  8. Bleeding/Bones API — Setting Bleeding_Modifier=Cut produces a bleeding effect. Setting Bones_Modifier=Break breaks the consumer's own bones. These items can be used for traps or poisons.