Entity Stats System
Hytale’s Entity Stats System provides a flexible framework for managing entity attributes like Health, Stamina, Mana, and Oxygen. The system supports dynamic modifiers, conditional regeneration, and custom stat types.
Architecture Overview
Section titled “Architecture Overview”EntityStatMap (Component)├── EntityStatValue[] - Individual stat instances│ ├── value, min, max - Current and bounds│ ├── RegeneratingValue[] - Regeneration handlers│ └── Map<String, Modifier> - Active modifiers├── StatModifiersManager - Recalculates modifiers from equipment/effects└── EntityStatType (Asset) - Stat definition from JSONCore Classes
Section titled “Core Classes”EntityStatValue
Section titled “EntityStatValue”Represents a single stat instance on an entity:
import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue;
public class EntityStatValue { // Get the current value public float get();
// Get as percentage between min and max (0.0 to 1.0) public float asPercentage();
// Bounds public float getMin(); public float getMax();
// Modifiers @Nullable public Modifier getModifier(String key);
@Nullable public Map<String, Modifier> getModifiers();}EntityStatMap
Section titled “EntityStatMap”A component that holds all stat values for an entity:
import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap;import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule;
// Get the component typeComponentType<EntityStore, EntityStatMap> statMapType = EntityStatsModule.get().getEntityStatMapComponentType();
// Get from entityEntityStatMap stats = store.getComponent(entityRef, statMapType);
// Get stat by indexEntityStatValue health = stats.get(DefaultEntityStatTypes.getHealth());Modifying Stats
Section titled “Modifying Stats”EntityStatMap stats = /* get from entity */;int healthIndex = DefaultEntityStatTypes.getHealth();
// Set to specific value (clamped to min/max)stats.setStatValue(healthIndex, 50.0f);
// Add to current valuestats.addStatValue(healthIndex, 10.0f);
// Subtract from current valuestats.subtractStatValue(healthIndex, 5.0f);
// Set to minimumstats.minimizeStatValue(healthIndex);
// Set to maximumstats.maximizeStatValue(healthIndex);Predictable Updates
Section titled “Predictable Updates”For client prediction, use the Predictable enum to control network synchronization:
import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap.Predictable;
// NONE - Normal server update (default)stats.setStatValue(Predictable.NONE, healthIndex, 50.0f);
// SELF - Client can predict this change locallystats.addStatValue(Predictable.SELF, staminaIndex, -10.0f);
// ALL - All viewers can predict this changestats.subtractStatValue(Predictable.ALL, healthIndex, 25.0f);Default Stat Types
Section titled “Default Stat Types”Hytale provides several built-in stat types:
import com.hypixel.hytale.server.core.modules.entitystats.asset.DefaultEntityStatTypes;
int health = DefaultEntityStatTypes.getHealth();int oxygen = DefaultEntityStatTypes.getOxygen();int stamina = DefaultEntityStatTypes.getStamina();int mana = DefaultEntityStatTypes.getMana();int signatureEnergy = DefaultEntityStatTypes.getSignatureEnergy();int ammo = DefaultEntityStatTypes.getAmmo();| Stat Type | Description |
|---|---|
Health | Entity health points |
Oxygen | Breath underwater |
Stamina | Used for sprinting and actions |
Mana | Magic resource |
SignatureEnergy | Special ability resource |
Ammo | Ranged weapon ammunition |
Stat Modifiers
Section titled “Stat Modifiers”Modifiers adjust stat bounds (min/max) dynamically. They’re used by armor, effects, and items.
StaticModifier
Section titled “StaticModifier”The most common modifier type:
import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier;import com.hypixel.hytale.server.core.modules.entitystats.modifier.Modifier.ModifierTarget;
// Additive: value + amountStaticModifier armorBonus = new StaticModifier( ModifierTarget.MAX, StaticModifier.CalculationType.ADDITIVE, 20.0f // +20 max health);
// Multiplicative: value * amountStaticModifier percentBoost = new StaticModifier( ModifierTarget.MAX, StaticModifier.CalculationType.MULTIPLICATIVE, 1.5f // 1.5x max health);Applying Modifiers
Section titled “Applying Modifiers”EntityStatMap stats = /* ... */;int healthIndex = DefaultEntityStatTypes.getHealth();
// Add a modifier with a unique keyStaticModifier modifier = new StaticModifier( ModifierTarget.MAX, StaticModifier.CalculationType.ADDITIVE, 50.0f);stats.putModifier(healthIndex, "my_plugin_bonus", modifier);
// Get existing modifierModifier existing = stats.getModifier(healthIndex, "my_plugin_bonus");
// Remove modifierstats.removeModifier(healthIndex, "my_plugin_bonus");Built-in Modifier Keys
Section titled “Built-in Modifier Keys”| Key Pattern | Source |
|---|---|
Effect_ADDITIVE | Entity effects |
Effect_MULTIPLICATIVE | Entity effects |
Armor_ADDITIVE | Equipped armor |
Armor_MULTIPLICATIVE | Equipped armor |
Creating Custom Stat Types
Section titled “Creating Custom Stat Types”Custom stat types are defined in JSON asset files.
JSON Structure
Section titled “JSON Structure”Create a file at Entity/Stats/MyCustomStat.json:
{ "Id": "MyCustomStat", "InitialValue": 100.0, "Min": 0.0, "Max": 100.0, "Shared": true, "IgnoreInvulnerability": false, "ResetType": "InitialValue", "Regenerating": [ { "Interval": 1.0, "Amount": 5.0, "RegenType": "ADDITIVE", "ClampAtZero": true, "Conditions": [ { "Type": "OutOfCombat", "DelaySeconds": 3.0 } ] } ], "MinValueEffects": { "TriggerAtZero": false, "SoundEventId": "MyMod:StatEmpty" }}Configuration Options
Section titled “Configuration Options”| Field | Type | Description |
|---|---|---|
Id | String | Unique identifier |
InitialValue | Float | Starting value |
Min | Float | Minimum bound |
Max | Float | Maximum bound |
Shared | Boolean | Visible to other players |
IgnoreInvulnerability | Boolean | Can decrease when invulnerable |
MinValueEffects | Object | Effects triggered at min value (sound, particles, interactions) |
MaxValueEffects | Object | Effects triggered at max value (sound, particles, interactions) |
ResetType | Enum | InitialValue or MaxValue |
Regenerating | Array | Regeneration rules |
Accessing Custom Stats
Section titled “Accessing Custom Stats”import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType;
// Get stat index by IDint customStatIndex = EntityStatType.getAssetMap().getIndex("MyCustomStat");
// Use with EntityStatMapEntityStatMap stats = /* ... */;EntityStatValue customStat = stats.get(customStatIndex);float current = customStat.get();float percent = customStat.asPercentage();Built-in Conditions
Section titled “Built-in Conditions”Conditions control when regeneration occurs:
| Condition | Description |
|---|---|
OutOfCombat | True after delay since last combat action |
Gliding | True when entity is gliding |
Charging | True when entity is charging an attack |
Environment | True when in specific environments |
LogicCondition | Combine conditions with AND/OR |
Stat | Compare stat value against threshold |
Alive | True when entity is alive |
NoDamageTaken | True after delay since taking damage |
Suffocating | True when entity cannot breathe at its current position |
Sprinting | True when entity is sprinting |
Player | Check player game mode |
RegenHealth | Always true (reserved for health regen rules) |
Wielding | True when entity is wielding an item |
Example: Custom Resource System
Section titled “Example: Custom Resource System”import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent;import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType;
public class MyResourcePlugin extends JavaPlugin { private int focusStatIndex = Integer.MIN_VALUE;
@Override protected void setup() { // Register listener to cache stat index when assets are loaded getEventRegistry().register( LoadedAssetsEvent.class, EntityStatType.class, this::onEntityStatTypesLoaded ); }
private void onEntityStatTypesLoaded(LoadedAssetsEvent<?, ?, ?> event) { // Called when EntityStatType assets are loaded or reloaded focusStatIndex = EntityStatType.getAssetMap().getIndex("Focus"); }
public void consumeFocus(Ref<EntityStore> ref, Store<EntityStore> store, float amount) { if (focusStatIndex == Integer.MIN_VALUE) return; // Assets not loaded yet
EntityStatMap stats = store.getComponent(ref, EntityStatMap.getComponentType()); if (stats == null) return;
EntityStatValue focus = stats.get(focusStatIndex); if (focus == null || focus.get() < amount) { return; // Not enough focus }
stats.subtractStatValue(Predictable.SELF, focusStatIndex, amount); }
public void addFocusModifier(Ref<EntityStore> ref, Store<EntityStore> store) { if (focusStatIndex == Integer.MIN_VALUE) return; // Assets not loaded yet
EntityStatMap stats = store.getComponent(ref, EntityStatMap.getComponentType()); if (stats == null) return;
StaticModifier bonus = new StaticModifier( Modifier.ModifierTarget.MAX, StaticModifier.CalculationType.ADDITIVE, 25.0f ); stats.putModifier(focusStatIndex, "focus_mastery", bonus); }}Best Practices
Section titled “Best Practices”- Use stat indices - Cache indices from
EntityStatType.getAssetMap().getIndex()for performance - Unique modifier keys - Use plugin-prefixed keys like
"myplugin_bonus"to avoid conflicts - Respect invulnerability - Check
IgnoreInvulnerabilitywhen dealing damage - Use Predictable wisely - Only use
SELForALLfor changes the client can accurately predict - Clean up modifiers - Remove modifiers when effects expire or equipment is removed
- Consider network traffic -
Shared: falsestats don’t sync to other players - Handle missing stats - Always null-check when getting
EntityStatValue