How-to: Add Relics and Relic Affixes
This guide covers the full relic pipeline: items, types, rarities, stats, and affixes. For a minimal end-to-end walkthrough, see Tutorial 3.
1. Register a RelicItem
Every relic needs an Item registered with Forge. RelicItem is the minimal subclass:
public static final RegistryObject<Item> RING_ITEM =
ITEMS.register("ring", RelicItem::new);
If you need custom behavior (tooltips, durability, etc.) extend RelicItem directly.
2. Create a RelicType
RelicType links an Item to the relic system and sets the equipped limit.
JSON (data/my_mod/library_of_exile_relic_type/ring.json):
{
"id": "my_mod:ring",
"item_id": "my_mod:ring",
"weight": 1000,
"max_equipped": 2
}
Hardcoded alternative:
public class MyRelicTypeHolder extends ExileKeyHolder<RelicType> {
public MyRelicTypeHolder(String modid) {
super(modid, LibDatabase.RELIC_TYPE);
}
public ExileKey<RelicType, KeyInfo> RING = ExileKey.ofId(
this, "my_mod:ring",
x -> {
var rt = new RelicType();
rt.id = x.GUID();
rt.item_id = "my_mod:ring";
rt.weight = 1000;
rt.max_equipped = 2;
return rt;
}
);
}
3. Define RelicStat entries
Each stat is a named numeric quantity that can be rolled onto a relic.
Generic stat (manual_stat)
{
"id": "my_mod:attack_speed",
"serializer": "manual_stat",
"is_percent": true,
"min": 0,
"max": 100,
"base": 0
}
Map content weight stat (content_weight)
Increases the spawn-weight of a specific map_content entry when this relic is equipped:
{
"id": "my_mod:more_treasure_rooms",
"serializer": "content_weight",
"is_percent": false,
"min": 0,
"max": 200,
"base": 0,
"map_content_id": "library_of_exile:treasure_room"
}
Extra content stat (extra_content)
Adds additional map content rolls:
{
"id": "my_mod:extra_shrines",
"serializer": "extra_content",
"is_percent": false,
"min": 0,
"max": 3,
"base": 0,
"map_content_id": "my_mod:shrine",
"data": {
"type": "ADDITION",
"extra": 1
}
}
type is either "ADDITION" or "MULTIPLY".
4. Define RelicAffix entries
A RelicAffix packages one or more stat rolls and is associated with a specific RelicType.
{
"id": "my_mod:ring_attack_speed",
"weight": 600,
"relic_type": "my_mod:ring",
"mods": [
{ "stat": "my_mod:attack_speed", "min": 5.0, "max": 30.0 },
{ "stat": "my_mod:life_regen", "min": 2.0, "max": 10.0 }
]
}
Multiple mods in one affix are rolled together as a bundle — either all appear or none do.
5. Customize RelicRarity
The built-in rarities (Common through Mythic) are defined in LibRelicRarities. You can add
your own rarity tier via JSON:
{
"id": "my_mod:divine",
"base_data": {
"id": "my_mod:divine",
"text_format": "AQUA",
"tier": 7,
"weight": 25
},
"affixes": 4,
"min_affix_percent": 50,
"max_affix_percent": 100
}
Place in data/my_mod/library_of_exile_relic_rarity/divine.json.
6. Read stats at runtime
// Obtain the rolled mods from your capability or item NBT (implementation-specific).
List<ExactRelicStat> mods = getRelicMods(itemStack);
RelicStatsContainer stats = RelicStatsContainer.calculate(mods);
// By GUID:
float atkSpeed = stats.get(Database.get(LibDatabase.RELIC_STAT, "my_mod:attack_speed"));
// By ExileKey reference (avoids string lookups):
float atkSpeed = stats.get(MY_HOLDER.ATTACK_SPEED);
Notes
RelicAffixentries reference arelic_type— an affix is only eligible to roll onto relics of that type.- Relic system stat lookup respects the
basefield: if no affix provides a stat,stats.get()returnsbase. RelicType,RelicStat,RelicAffix, andRelicRarityall haveSyncTime.ON_LOGIN— they are synced to clients automatically.