Hytale Tag System
This skill documents Hytale's hierarchical tag system and how Hyforged integrates with it for stats, items, blocks, and other assets.
Overview
Hytale uses a hierarchical tag system where tags are defined as a map of categories to value arrays. This creates a rich, queryable tag structure that enables flexible asset lookups.
Key Concepts
| Concept |
Description |
| Tag Category |
A named group (e.g., "Type", "Element", "Domain") |
| Tag Value |
Values within a category (e.g., ["fire", "elemental"]) |
| Tag Index |
Integer index for O(1) lookups via AssetRegistry |
| Tag Expansion |
Hierarchical tags expand to multiple searchable strings |
JSON Format
Tags in Hytale assets use a map structure, not a flat array:
{
"Tags": {
"Category1": ["value1", "value2"],
"Category2": ["value3"]
}
}
Example: Item Tags
{
"Id": "hytale:adamantite_axe",
"Tags": {
"Type": ["Weapon"],
"Family": ["Axe"]
}
}
Example: Stat Tags
{
"Id": "hyforged:fire-resistance-bps",
"Tags": {
"Domain": ["defense"],
"Type": ["resistance"],
"Element": ["fire", "elemental"],
"Modifier": ["percent"],
"Source": ["derived"]
}
}
Tag Expansion
When tags are loaded, Hytale's AssetExtraInfo.Data.putTags() expands each entry into multiple searchable tags:
| Input |
Expanded Tags |
"Domain": ["offense"] |
Domain, offense, Domain=offense |
"Element": ["fire", "elemental"] |
Element, fire, elemental, Element=fire, Element=elemental |
Expansion Rules
For each entry "Category": ["val1", "val2", ...]:
- The category key becomes a tag:
Category
- Each value becomes a tag:
val1, val2
- Each category=value combination becomes a tag:
Category=val1, Category=val2
This enables flexible querying:
hasTag("fire") - matches any asset with "fire" in ANY category
hasTag("Element=fire") - matches only assets with "Element": ["fire"]
hasTag("Element") - matches any asset with an Element category
AssetRegistry API
Hytale's AssetRegistry provides the global tag index system:
Core Methods
// Get existing tag index (returns Integer.MIN_VALUE if not found)
int tagIndex = AssetRegistry.getTagIndex("fire");
// Get or create tag index (creates if not existing)
int tagIndex = AssetRegistry.getOrCreateTagIndex("fire");
Integer Indices
Tags are stored as integer indices for O(1) lookups:
// Fast membership test
IntSet entityTags = entity.getData().getExpandedTagIndexes();
int fireIndex = AssetRegistry.getTagIndex("fire");
if (entityTags.contains(fireIndex)) {
// Entity has fire tag
}
StatDefinitionRegistry Tag API
The Hyforged stat system provides convenience methods for tag queries:
Basic Tag Methods
StatDefinitionRegistry registry = StatDefinitionRegistry.get();
// Check if any stat has a tag
boolean exists = registry.hasTag("fire");
// Get stats by tag (any expanded tag)
Collection<StatDefinition> stats = registry.getStatsForTag("fire");
Set<Integer> indices = registry.getStatIndicesForTag("fire");
List<StatId> statIds = registry.getStatIdsForTag("fire");
Category-Based Methods (Recommended)
For hierarchical tags, use the explicit category-based API:
// Check if any stat has Type=resistance
boolean exists = registry.hasTagValue("Type", "resistance");
// Get all resistance stats
Collection<StatDefinition> resistances = registry.getStatsForTagValue("Type", "resistance");
// Get fire elemental stats
Set<Integer> fireStats = registry.getStatIndicesForTagValue("Element", "fire");
// Get all ability score stat IDs
List<StatId> abilityScores = registry.getStatIdsForTagValue("Type", "ability-score");
Integer Index Methods (Performance)
For hot paths, use pre-resolved integer indices:
// Resolve once, use many times
int fireTagIndex = registry.getOrCreateTagIndex("Element=fire");
// Fast O(1) lookup
IntSet stats = registry.getStatIndicesForTagIndex(fireTagIndex);
Standard Tag Categories
Stats
| Category |
Values |
Purpose |
Domain |
offense, defense, resource, utility, attributes |
Primary functional classification |
Element |
physical, fire, cold, lightning, chaos, elemental |
Damage/resistance element |
Type |
damage, resistance, rating, ability-score, speed, critical, ailment, leech, skill-level, area, resource |
What the stat represents |
Modifier |
flat, percent, more |
How the stat value applies |
Source |
derived, base |
Origin of the stat value |
Mechanic |
attack, spell, projectile, melee, ranged, minion, aura, totem, trap |
Usage mechanism |
Resource |
health, mana, stamina, rage |
Which resource it affects |
Ailment |
bleed, poison, ignite, chill, shock, freeze |
Specific ailment type |
Weapon |
sword, axe, mace, dagger, bow, crossbow, staff, unarmed |
Weapon type affinity |
Items (Hytale Native)
| Category |
Values |
Purpose |
Type |
Weapon, Armor, Tool, Consumable, Material |
Item classification |
Family |
Sword, Axe, Helmet, Chestplate, etc. |
Item family |
Material |
Wood, Stone, Iron, Gold, Adamantite |
Material type |
Tier |
Basic, Common, Rare, Epic, Legendary |
Quality tier |
Implementing Tags in New Assets
1. Define JSON Schema with Map Codec
// In your asset class
public static final AssetBuilderCodec<String, MyAsset> CODEC = AssetBuilderCodec
.builder(MyAsset.class, MyAsset::new, ...)
.appendInherited(
new KeyedCodec<>("Tags", new MapCodec<>(Codec.STRING_ARRAY, HashMap::new)),
(asset, value) -> asset.rawTags = value != null ? value : new HashMap<>(),
asset -> asset.rawTags,
(asset, parent) -> asset.rawTags = new HashMap<>(parent.rawTags)
)
.add()
.build();
private Map<String, String[]> rawTags = new HashMap<>();
2. Expand Tags on Load
public Set<String> getExpandedTags() {
Set<String> expanded = new HashSet<>();
for (Map.Entry<String, String[]> entry : rawTags.entrySet()) {
String category = entry.getKey();
expanded.add(category);
for (String value : entry.getValue()) {
expanded.add(value);
expanded.add(category + "=" + value);
}
}
return expanded;
}
3. Register Tags with AssetRegistry
// During asset registration
for (String tag : asset.getExpandedTags()) {
int tagIndex = AssetRegistry.getOrCreateTagIndex(tag);
tagToAssetIndices.computeIfAbsent(tagIndex, k -> new IntOpenHashSet()).add(assetIndex);
}
Best Practices
DO
- ✅ Use category-based API (
getStatsForTagValue("Type", "resistance")) for explicit queries
- ✅ Pre-resolve tag indices for hot paths
- ✅ Use consistent category names across asset types
- ✅ Document your tag categories in the asset schema
- ✅ Use IntSet for efficient tag membership tests
DON'T
- ❌ Use flat tag arrays (
"Tags": ["fire", "damage"]) - use hierarchical format
- ❌ Create duplicate tags across different categories with same meaning
- ❌ Store tag strings at runtime - resolve to indices
- ❌ Assume tag order matters - it doesn't
Related Files
AssetRegistry - Hytale's global tag index registry
AssetExtraInfo.Data.putTags() - Tag expansion logic
StatDefinitionRegistry - Stat-specific tag queries
StatDefinitionAsset - JSON codec for stat tags
TagSet / TagSetLookupTable - Advanced tag grouping (like NPCGroup)
ADR Reference
See ADR-0008 in .memory_bank/ADRs.md for the decision rationale behind adopting Hytale's tag system.
1---2name: hytale-tag-system3description: Documents Hytale's hierarchical tag system and how to use it in Hyforged. Use when implementing tag-based lookups, defining tagged assets, or understanding tag expansion patterns. Triggers - tags, tagging, AssetRegistry, tag categories, tag queries.4---56# Hytale Tag System78This skill documents Hytale's hierarchical tag system and how Hyforged integrates with it for stats, items, blocks, and other assets.910## Overview1112Hytale uses a **hierarchical tag system** where tags are defined as a map of categories to value arrays. This creates a rich, queryable tag structure that enables flexible asset lookups.1314### Key Concepts1516| Concept | Description |17|---------|-------------|18| **Tag Category** | A named group (e.g., `"Type"`, `"Element"`, `"Domain"`) |19| **Tag Value** | Values within a category (e.g., `["fire", "elemental"]`) |20| **Tag Index** | Integer index for O(1) lookups via `AssetRegistry` |21| **Tag Expansion** | Hierarchical tags expand to multiple searchable strings |2223---2425## JSON Format2627Tags in Hytale assets use a **map structure**, not a flat array:2829```json30{31 "Tags": {32 "Category1": ["value1", "value2"],33 "Category2": ["value3"]34 }35}36```3738### Example: Item Tags39```json40{41 "Id": "hytale:adamantite_axe",42 "Tags": {43 "Type": ["Weapon"],44 "Family": ["Axe"]45 }46}47```4849### Example: Stat Tags50```json51{52 "Id": "hyforged:fire-resistance-bps",53 "Tags": {54 "Domain": ["defense"],55 "Type": ["resistance"],56 "Element": ["fire", "elemental"],57 "Modifier": ["percent"],58 "Source": ["derived"]59 }60}61```6263---6465## Tag Expansion6667When tags are loaded, Hytale's `AssetExtraInfo.Data.putTags()` **expands** each entry into multiple searchable tags:6869| Input | Expanded Tags |70|-------|---------------|71| `"Domain": ["offense"]` | `Domain`, `offense`, `Domain=offense` |72| `"Element": ["fire", "elemental"]` | `Element`, `fire`, `elemental`, `Element=fire`, `Element=elemental` |7374### Expansion Rules7576For each entry `"Category": ["val1", "val2", ...]`:771. The **category key** becomes a tag: `Category`782. Each **value** becomes a tag: `val1`, `val2`793. Each **category=value** combination becomes a tag: `Category=val1`, `Category=val2`8081This enables flexible querying:82- `hasTag("fire")` - matches any asset with "fire" in ANY category83- `hasTag("Element=fire")` - matches only assets with `"Element": ["fire"]`84- `hasTag("Element")` - matches any asset with an Element category8586---8788## AssetRegistry API8990Hytale's `AssetRegistry` provides the global tag index system:9192### Core Methods9394```java95// Get existing tag index (returns Integer.MIN_VALUE if not found)96int tagIndex = AssetRegistry.getTagIndex("fire");9798// Get or create tag index (creates if not existing)99int tagIndex = AssetRegistry.getOrCreateTagIndex("fire");100```101102### Integer Indices103104Tags are stored as integer indices for O(1) lookups:105106```java107// Fast membership test108IntSet entityTags = entity.getData().getExpandedTagIndexes();109int fireIndex = AssetRegistry.getTagIndex("fire");110if (entityTags.contains(fireIndex)) {111 // Entity has fire tag112}113```114115---116117## StatDefinitionRegistry Tag API118119The Hyforged stat system provides convenience methods for tag queries:120121### Basic Tag Methods122123```java124StatDefinitionRegistry registry = StatDefinitionRegistry.get();125126// Check if any stat has a tag127boolean exists = registry.hasTag("fire");128129// Get stats by tag (any expanded tag)130Collection<StatDefinition> stats = registry.getStatsForTag("fire");131Set<Integer> indices = registry.getStatIndicesForTag("fire");132List<StatId> statIds = registry.getStatIdsForTag("fire");133```134135### Category-Based Methods (Recommended)136137For hierarchical tags, use the explicit category-based API:138139```java140// Check if any stat has Type=resistance141boolean exists = registry.hasTagValue("Type", "resistance");142143// Get all resistance stats144Collection<StatDefinition> resistances = registry.getStatsForTagValue("Type", "resistance");145146// Get fire elemental stats147Set<Integer> fireStats = registry.getStatIndicesForTagValue("Element", "fire");148149// Get all ability score stat IDs150List<StatId> abilityScores = registry.getStatIdsForTagValue("Type", "ability-score");151```152153### Integer Index Methods (Performance)154155For hot paths, use pre-resolved integer indices:156157```java158// Resolve once, use many times159int fireTagIndex = registry.getOrCreateTagIndex("Element=fire");160161// Fast O(1) lookup162IntSet stats = registry.getStatIndicesForTagIndex(fireTagIndex);163```164165---166167## Standard Tag Categories168169### Stats170171| Category | Values | Purpose |172|----------|--------|---------|173| `Domain` | `offense`, `defense`, `resource`, `utility`, `attributes` | Primary functional classification |174| `Element` | `physical`, `fire`, `cold`, `lightning`, `chaos`, `elemental` | Damage/resistance element |175| `Type` | `damage`, `resistance`, `rating`, `ability-score`, `speed`, `critical`, `ailment`, `leech`, `skill-level`, `area`, `resource` | What the stat represents |176| `Modifier` | `flat`, `percent`, `more` | How the stat value applies |177| `Source` | `derived`, `base` | Origin of the stat value |178| `Mechanic` | `attack`, `spell`, `projectile`, `melee`, `ranged`, `minion`, `aura`, `totem`, `trap` | Usage mechanism |179| `Resource` | `health`, `mana`, `stamina`, `rage` | Which resource it affects |180| `Ailment` | `bleed`, `poison`, `ignite`, `chill`, `shock`, `freeze` | Specific ailment type |181| `Weapon` | `sword`, `axe`, `mace`, `dagger`, `bow`, `crossbow`, `staff`, `unarmed` | Weapon type affinity |182183### Items (Hytale Native)184185| Category | Values | Purpose |186|----------|--------|---------|187| `Type` | `Weapon`, `Armor`, `Tool`, `Consumable`, `Material` | Item classification |188| `Family` | `Sword`, `Axe`, `Helmet`, `Chestplate`, etc. | Item family |189| `Material` | `Wood`, `Stone`, `Iron`, `Gold`, `Adamantite` | Material type |190| `Tier` | `Basic`, `Common`, `Rare`, `Epic`, `Legendary` | Quality tier |191192---193194## Implementing Tags in New Assets195196### 1. Define JSON Schema with Map Codec197198```java199// In your asset class200public static final AssetBuilderCodec<String, MyAsset> CODEC = AssetBuilderCodec201 .builder(MyAsset.class, MyAsset::new, ...)202 .appendInherited(203 new KeyedCodec<>("Tags", new MapCodec<>(Codec.STRING_ARRAY, HashMap::new)),204 (asset, value) -> asset.rawTags = value != null ? value : new HashMap<>(),205 asset -> asset.rawTags,206 (asset, parent) -> asset.rawTags = new HashMap<>(parent.rawTags)207 )208 .add()209 .build();210211private Map<String, String[]> rawTags = new HashMap<>();212```213214### 2. Expand Tags on Load215216```java217public Set<String> getExpandedTags() {218 Set<String> expanded = new HashSet<>();219 for (Map.Entry<String, String[]> entry : rawTags.entrySet()) {220 String category = entry.getKey();221 expanded.add(category);222 for (String value : entry.getValue()) {223 expanded.add(value);224 expanded.add(category + "=" + value);225 }226 }227 return expanded;228}229```230231### 3. Register Tags with AssetRegistry232233```java234// During asset registration235for (String tag : asset.getExpandedTags()) {236 int tagIndex = AssetRegistry.getOrCreateTagIndex(tag);237 tagToAssetIndices.computeIfAbsent(tagIndex, k -> new IntOpenHashSet()).add(assetIndex);238}239```240241---242243## Best Practices244245### DO246247- ✅ Use category-based API (`getStatsForTagValue("Type", "resistance")`) for explicit queries248- ✅ Pre-resolve tag indices for hot paths249- ✅ Use consistent category names across asset types250- ✅ Document your tag categories in the asset schema251- ✅ Use IntSet for efficient tag membership tests252253### DON'T254255- ❌ Use flat tag arrays (`"Tags": ["fire", "damage"]`) - use hierarchical format256- ❌ Create duplicate tags across different categories with same meaning257- ❌ Store tag strings at runtime - resolve to indices258- ❌ Assume tag order matters - it doesn't259260---261262## Related Files263264- `AssetRegistry` - Hytale's global tag index registry265- `AssetExtraInfo.Data.putTags()` - Tag expansion logic266- `StatDefinitionRegistry` - Stat-specific tag queries267- `StatDefinitionAsset` - JSON codec for stat tags268- `TagSet` / `TagSetLookupTable` - Advanced tag grouping (like NPCGroup)269270## ADR Reference271272See ADR-0008 in `.memory_bank/ADRs.md` for the decision rationale behind adopting Hytale's tag system.