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:
- Explosive damage fields:
ItemWeaponAssetprovidesplayerDamageMultiplier,zombieDamageMultiplier,animalDamageMultiplier, and theBuildExplosiveDescription/BuildNonExplosiveDescriptionhelpers. Consumables configured withExplosioneffects use these fields for blast damage calculation. - Range field:
ItemWeaponAsset.rangeis reused for explosive blast radius. - NPC rewards: The
NPCRewardsListinfrastructure 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:
| Field | Type | .dat Key | Effect |
|---|---|---|---|
_health | byte | Health | Direct health restoration (0-255) |
_food | byte | Food | Food stat increase (0-255) |
_water | byte | Water | Water stat increase (0-255) |
_virus | byte | Virus | Virus/infection increase (worsens condition) |
_disinfectant | byte | Disinfectant | Reduces virus/infection level |
_energy | byte | Energy | Stamina restoration |
_vision | byte | Vision | Vision/awareness buff (night vision or highlight) |
oxygen | sbyte | Oxygen | Positive = refill; negative = depletion |
_warmth | uint | Warmth | Warmth duration, divided by 12.5 for seconds display |
experience | int | Experience | XP awarded or deducted |
Medical Condition Modifiers
csharp
public enum Bleeding { None, Heal, Cut }
public enum Bones { None, Heal, Break }| Field | Type | .dat Key(s) | Description |
|---|---|---|---|
bleedingModifier | Bleeding | Bleeding or Bleeding_Modifier | Heals bleeding (Heal), causes bleeding (Cut), or no effect |
bonesModifier | Bones | Broken or Bones_Modifier | Heals 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 valueAnd for bones:
csharp
if (p.data.ContainsKey("Broken"))
bonesModifier = Bones.Heal; // Simple "Broken" flag = Heal
else
bonesModifier = p.data.ParseEnum<Bones>("Bones_Modifier"); // Explicit valueConsumption Constraints
| Field | Type | .dat Key | Description |
|---|---|---|---|
foodConstrainsWater | bool | Implicit from food >= water | When true, the consumption system limits water based on food stat |
shouldDeleteAfterUse | bool | Should_Delete_After_Use | Whether the item is consumed on use (default true) |
_hasAid | bool | Aid (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:
| Field | Type | .dat Key | Description |
|---|---|---|---|
_explosionEffectGuid | Guid | Explosion (GUID) | Explosion effect reference |
_explosion | ushort | Explosion (legacy ID) | Legacy explosion effect ID |
IsExplosive | bool | Computed | True if explosion reference is non-null |
csharp
_explosion = p.data.ParseGuidOrLegacyId("Explosion", out _explosionEffectGuid);
IsExplosive = !IsExplosionEffectRefNull();When IsExplosive is true:
shouldFriendlySentryTargetUserreturnstrue— sentries treat the player as hostile.BuildDescriptionappends an explosive warning and callsBuildExplosiveDescription.- The
UseableConsumeablespawns the explosion at the player's position on use. - Explosive consumables always delete after detonation.
Explosive Damage Properties
| Damage Source | Value Used | Derived From |
|---|---|---|
| Player damage | playerDamageMultiplier.damage | ItemWeaponAsset |
| Zombie damage | zombieDamageMultiplier.damage | ItemWeaponAsset |
| Animal damage | animalDamageMultiplier.damage | ItemWeaponAsset |
| Barricade damage | barricadeDamageMultiplier.damage | ItemWeaponAsset |
| Structure damage | structureDamageMultiplier.damage | ItemWeaponAsset |
| Vehicle damage | vehicleDamageMultiplier.damage | ItemWeaponAsset |
| Resource damage | resourceDamageMultiplier.damage | ItemWeaponAsset |
| Blast radius | range | ItemWeaponAsset |
| Launch speed | playerDamageMultiplier.damage * 0.1f | Computed default |
Effect Resolution
csharp
public bool IsExplosionEffectRefNull()
{
return _explosion == 0 && _explosionEffectGuid.IsEmpty();
}
public EffectAsset FindExplosionEffectAsset()
{
return Assets.FindEffectAssetByGuidOrLegacyId(_explosionEffectGuid, _explosion);
}Quest and Item Rewards
| Field | Type | .dat Keys | Description |
|---|---|---|---|
questRewardsList | NPCRewardsList | Quest_Rewards / Quest_Reward_N | NPC quest rewards granted on consumption |
itemRewards | SpawnTableReward | Item_Reward_Spawn_ID, Min_Item_Rewards, Max_Item_Rewards | Random 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
| Field | Type | Source |
|---|---|---|
_use | AudioClip | Bundle "Use" or .dat "ConsumeAudioClip" |
ShouldRandomizeUseAudioPitch | bool | .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:
| Stat | Color | Sort Order | Condition |
|---|---|---|---|
| Health | Green | Beneficial | _health > 0 |
| Food | Green | Beneficial | _food > 0 |
| Water | Green | Beneficial | _water > 0 |
| Virus | Red | Detrimental | _virus > 0 |
| Disinfectant | Green | Beneficial | _disinfectant > 0 |
| Energy | Green | Beneficial | _energy > 0 |
| Oxygen (+) | Green | Beneficial | oxygen > 0 |
| Oxygen (-) | Red | Detrimental | oxygen < 0 |
| Warmth | Green | Beneficial | warmth / 12.5 > 0 |
| Bleeding heal | Green | Beneficial | bleedingModifier == Heal |
| Bleeding cut | Red | Detrimental | bleedingModifier == Cut |
| Bones heal | Green | Beneficial | bonesModifier == Heal |
| Bones break | Red | Detrimental | bonesModifier == Break |
| Explosive | Red | Important | IsExplosive |
| Moldy | Red | Detrimental | Quality < 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):
- Activation: Player left-clicks with the consumable equipped.
- Per-tick application: Each tick applies
statValue / totalTicksin order: Food → Water → Health → Virus → Disinfectant → Energy → Vision → Oxygen → Warmth. - Medical processing: After stat ticks,
bleedingModifierandbonesModifierare applied. - Completion:
GrantQuestRewards,itemRewardsspawn table roll,experienceapplied. - Interruption: If the player moves, takes damage, or switches items, partial stats are preserved.
- 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
- foodConstrainsWater calculation — The constraint is computed as
food >= water, not read from a.datkey. A meal with 80 food and 80 water provides only 20 water when the constraint is active. - Experience can be negative — The
experiencefield is a signedint. Negative values deduct XP with no lower-bound check. - Warmth rounding — Warmth is displayed as
warmth / 12.5seconds. Values below 12 show 0 seconds in the UI but still provide sub-second warmth in the simulation. - 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.
- Explosive range on consumables — The blast radius uses the
rangefield fromItemWeaponAsset, which is also used for ballistic weapons. This dual-purpose field can cause confusion. - Aid targeting — Without
Aidin the.dat, the useable defaults to self-use only. Aid items use a different raycast and validation path. - Item reward overflow — If the inventory is full, excess reward items drop on the ground via
ItemManager.dropItem. - Bleeding/Bones API — Setting
Bleeding_Modifier=Cutproduces a bleeding effect. SettingBones_Modifier=Breakbreaks the consumer's own bones. These items can be used for traps or poisons.
