ItemRefillAsset — Refillable Water Containers
ItemRefillAsset defines refillable water containers — canteens, water bottles, and other portable vessels that can be filled from environmental sources and consumed for hydration. At 402 lines it is the largest of the vehicle service asset classes and one of the most complex ItemAsset subclasses. It carries 18 stat fields (6 stats × 3 water types) and a sophisticated water source detection and consumption system.
Source code location: Unturned/Bundles/ItemRefillAsset.cs
Inheritance Chain
ItemAsset
→ ItemRefillAssetClass Definition
csharp
public enum ERefillWaterType { EMPTY, CLEAN, SALTY, DIRTY }
public class ItemRefillAsset : ItemAsset
{
protected AudioClip _use;
public AudioClip use => _use;
[Obsolete("Replaced by separate stats for each water type")]
public byte water => MathfEx.RoundAndClampToByte(cleanWater);
// 6 stats × 3 water types = 18 fields
public float cleanHealth { get; protected set; }
public float saltyHealth { get; protected set; }
public float dirtyHealth { get; protected set; }
public float cleanFood { get; protected set; }
public float saltyFood { get; protected set; }
public float dirtyFood { get; protected set; }
public float cleanWater { get; protected set; }
public float saltyWater { get; protected set; }
public float dirtyWater { get; protected set; }
public float cleanVirus { get; protected set; }
public float saltyVirus { get; protected set; }
public float dirtyVirus { get; protected set; }
public float cleanStamina { get; protected set; }
public float saltyStamina { get; protected set; }
public float dirtyStamina { get; protected set; }
public float cleanOxygen { get; protected set; }
public float saltyOxygen { get; protected set; }
public float dirtyOxygen { get; protected set; }
}Water Type System
ERefillWaterType
csharp
public enum ERefillWaterType
{
EMPTY, // No contents, can be filled
CLEAN, // Safe water source (rain, purifier)
SALTY, // Ocean/seawater
DIRTY // Puddle, swamp, contaminated source
}| Type | Source Examples | Quality |
|---|---|---|
EMPTY | Initial state | No contents |
CLEAN | Rain catcher, water purifier, admin spawn | Full benefits, no penalties |
SALTY | Ocean, seawater | Partial benefits, virus penalty |
DIRTY | Puddles, swamp, non-clean objects | Partial benefits, virus penalty |
Six Stat Categories per Type
Each water type carries six float stat fields:
| Stat | CLEAN | SALTY | DIRTY |
|---|---|---|---|
| Health | cleanHealth | saltyHealth | dirtyHealth |
| Food | cleanFood | saltyFood | dirtyFood |
| Water | cleanWater | saltyWater | dirtyWater |
| Virus | cleanVirus | saltyVirus | dirtyVirus |
| Stamina | cleanStamina | saltyStamina | dirtyStamina |
| Oxygen | cleanOxygen | saltyOxygen | dirtyOxygen |
State Management
The water type is stored as a single byte in the item state:
csharp
public override byte[] getState(EItemOrigin origin)
{
byte[] state = new byte[1];
if (origin == EItemOrigin.ADMIN)
state[0] = (byte)ERefillWaterType.CLEAN;
else
state[0] = (byte)ERefillWaterType.EMPTY;
return state;
}| Origin | State |
|---|---|
ADMIN | CLEAN |
| All others | EMPTY |
The state changes when the player fills the container from a water source. The type is determined by the source's water quality.
Type-Aware Getters
Six getter methods resolve the correct stat value based on the current water type:
csharp
public float GetRefillHealth(ERefillWaterType refillWaterType)
{
switch (refillWaterType)
{
case ERefillWaterType.CLEAN: return cleanHealth;
case ERefillWaterType.SALTY: return saltyHealth;
case ERefillWaterType.DIRTY: return dirtyHealth;
default: return 0.0f;
}
}Identical patterns exist for GetRefillFood, GetRefillWater, GetRefillVirus, GetRefillStamina, and GetRefillOxygen. The EMPTY type returns 0.0 for all stats.
PopulateAsset — Default Value System
The parsing uses a cascading default system. CLEAN values are explicit; SALTY and DIRTY default to fractions of the CLEAN values:
csharp
float legacyWaterValue = p.data.ParseFloat("Water");
const float defaultSaltyFactor = 0.25f;
const float defaultDirtyFactor = 0.6f;
cleanHealth = p.data.ParseFloat("Clean_Health", 0.0f);
saltyHealth = p.data.ParseFloat("Salty_Health", cleanHealth * defaultSaltyFactor);
dirtyHealth = p.data.ParseFloat("Dirty_Health", cleanHealth * defaultDirtyFactor);
cleanWater = p.data.ParseFloat("Clean_Water", legacyWaterValue);
saltyWater = p.data.ParseFloat("Salty_Water", cleanWater * defaultSaltyFactor);
dirtyWater = p.data.ParseFloat("Dirty_Water", cleanWater * defaultDirtyFactor);
cleanVirus = p.data.ParseFloat("Clean_Virus", 0.0f);
saltyVirus = p.data.ParseFloat("Salty_Virus", cleanWater * (-1.0f + defaultSaltyFactor));
dirtyVirus = p.data.ParseFloat("Dirty_Virus", cleanWater * (-1.0f + defaultDirtyFactor));Default Value Rationale
- SALTY = 25%: Salty water provides about a quarter of the clean benefits.
- DIRTY = 60%: Dirty water provides about 60% of clean benefits.
- Virus negative: The virus default for salty/dirty is negative (adds virus):
cleanWater * (-1 + factor). For dirty water:cleanWater * (-1 + 0.6) = cleanWater * -0.4.
Legacy Water Key
The legacy Water key (deprecated) provides the default for Clean_Water:
csharp
float legacyWaterValue = p.data.ParseFloat("Water");
cleanWater = p.data.ParseFloat("Clean_Water", defaultValue: legacyWaterValue);BuildDescription
The description renders water-type-specific information with full color coding:
- Water type label: DISPLAYS "Empty," "Clean," "Salty," or "Dirty" based on state byte.
- Per-stat display: Each non-zero stat appears with green/red color:
- Positive: green,
DescSort_RefillStat + DescSort_Beneficial. - Negative: red,
DescSort_RefillStat + DescSort_Detrimental. - Zero: omitted.
- Positive: green,
- Values rounded: All stat values are rounded to integers for display.
csharp
ERefillWaterType waterType = (ERefillWaterType)itemInstance.state[0];
string waterKey = waterType switch
{
ERefillWaterType.EMPTY => "Empty",
ERefillWaterType.CLEAN => "Clean",
ERefillWaterType.SALTY => "Salty",
ERefillWaterType.DIRTY => "Dirty",
_ => "Full"
};
builder.Append(localization.format("Refill", localization.format(waterKey)), DescSort_Important);UseableRefill Runtime Behavior
Water Source Detection
The UseableRefill class detects water sources through multiple methods:
| Source Type | Detection Method | Resulting Type |
|---|---|---|
| Rain barrel | Barricade raycast + EBuild.BARREL_RAIN check | CLEAN |
| Water purifier | GameObject tag check | CLEAN |
Clean tank (ETankSource.WATER) | InteractableTank check | CLEAN |
| Ocean water | WaterUtility.isInWater + ocean biome | SALTY |
| Swamp/puddle | WaterUtility.isInWater + biome check | DIRTY |
| Dirty tank | InteractableTank source check | DIRTY |
Fill and Consumption
- Fill: Player faces a water source with an empty or partially filled container.
- State update: The state byte changes to the source's water type.
- Consumption: Player left-clicks to drink from the container.
- Stat application: Stats from the current water type are applied to the player.
- Persistence: The container is not deleted after drinking — the state changes from CLEAN/SALTY/DIRTY to EMPTY.
- Refill: An empty container can be refilled at any water source.
Water Quality Comparison (Default Values)
Assuming Clean_Water=30:
| Quality | Health | Food | Water | Virus | Stamina | Oxygen |
|---|---|---|---|---|---|---|
| CLEAN | 0 | 0 | +30 | 0 | 0 | 0 |
| SALTY | 0 | 0 | +7.5 | -22.5 | 0 | 0 |
| DIRTY | 0 | 0 | +18 | -12 | 0 | 0 |
The virus penalty for salty water is severe (30 * -0.75 = -22.5), making it dangerous without purification. Dirty water's penalty is moderate (30 * -0.4 = -12).
Common Issues
- Refill stat legacy field — The deprecated
waterfield providescleanWaterfor backwards compatibility. Mods that set onlyWaterinstead ofClean_Waterwill have correct clean values but default salty/dirty values (via the* 0.25/* 0.6fallback). - Refill item reuse —
ItemRefillAssetdoes not have ashouldDeleteAfterUsefield. All refill items persist after use (the state changes from CLEAN to EMPTY rather than the item being consumed). An empty refill item can be refilled at any water source. - Salty water virus default — The default virus for salty water is
cleanWater * (-1 + 0.25)=cleanWater * -0.75. This can produce unexpectedly high virus values. Modders who set onlyClean_Watermay not realize the auto-calculated salty virus value. - Empty state shows full stat potential — The description UI shows stats based on the current water type, not the potential. An empty container shows all stats as zero, making it hard to compare containers in inventory.
- No quality tracking — Unlike
ItemConsumeableAsset, refill items have noshowQualityor mold system. Water type and purity are determined by the source, not by quality decay.
