Skip to main content
Version: 26.1.2 (Latest)

Research Scenarios

Common patterns for modeling progression through the research system.

Research and book are separate classes

Research content is authored in a SingleResearchSubProvider subclass (e.g. DemoResearch), while book entries and categories are authored in a BookProvider subclass (e.g. DemoBook). They cannot directly reference each other's local variables.

The standard pattern is to declare public static final refs on the research class and import them in the book class:

// In your ResearchSubProvider — declare static refs
public static final ResearchNodeRef MY_NODE = node("mymod/my_node");

// In your BookProvider — import and use
entry.withCondition(MyResearch.MY_NODE);

All scenarios below follow this pattern. See the demo research for a complete working example.

Advancement gating

Gate an entry behind a vanilla advancement by creating a research fact that is granted when the advancement is earned.

// In your ResearchSubProvider:
public static final ResearchNodeRef ADVANCEMENT_GATE = node("mymod/advancement_gate");

@Override
protected void generateResearch() {
var fact = this.ingress()
.onAdvancementEarned(this.mcLoc("story/mine_stone"))
.declareFact("mymod/advancement_gate_fact");

this.node(ADVANCEMENT_GATE, fact);
}
// In your BookProvider — import the static ref:
entry.withCondition(MyResearch.ADVANCEMENT_GATE);

Demo: DemoResearch.javaADVANCEMENT_MINE_STONE node, and ConditionAdvancementEntry.java for the book side.

Item crafted gating

Gate an entry behind crafting a specific item.

// In your ResearchSubProvider:
public static final ResearchNodeRef CRAFTED_GATE = node("mymod/crafted_gate");

@Override
protected void generateResearch() {
var fact = this.ingress()
.onItemCrafted(new ItemStackTemplate(Items.DIAMOND_SWORD))
.declareFact("mymod/crafted_gate_fact");

this.node(CRAFTED_GATE, fact);
}
// In your BookProvider:
entry.withCondition(MyResearch.CRAFTED_GATE);

With component matching (checks data components on the item):

this.ingress()
.onItemCrafted(new ItemStackTemplate(Items.DIAMOND_SWORD))
.grantFact("mymod/crafted_hook", factRef);

Demo: DemoResearch.javaCRAFTING_STICK node, and CraftingCategory.java.

Item acquired gating

Gate an entry behind acquiring a specific item (inventory change detection).

// In your ResearchSubProvider:
public static final ResearchNodeRef ACQUIRED_GATE = node("mymod/acquired_gate");

@Override
protected void generateResearch() {
var fact = this.ingress()
.onItemAcquired(new ItemStackTemplate(Items.NETHER_STAR))
.declareFact("mymod/acquired_gate_fact");

this.node(ACQUIRED_GATE, fact);
}
// In your BookProvider:
entry.withCondition(MyResearch.ACQUIRED_GATE);

Demo: DemoResearch.javaACQUIRE_COBBLESTONE node, and AcquiringCategory.java.

Numeric progression with values

Track cumulative progress using research values instead of boolean facts.

// In your ResearchSubProvider:
public static final ResearchNodeRef MINED_STONE = node("mymod/mined_stone");

@Override
protected void generateResearch() {
var value = this.value("mymod/mined_stone");

// Increment when stone is crafted
this.ingress()
.onItemCrafted(new ItemStackTemplate(Items.COBBLESTONE))
.incrementValue("mymod/stone_hook", value, 1);

// Node requires 64 cobblestone mined
this.node(MINED_STONE, List.of(), List.of(
new ResearchNodeSpec.ValueRequirement(value, 64)
));
}
// In your BookProvider:
entry.withCondition(MyResearch.MINED_STONE);

Demo: DemoResearch.javaCOLLECTOR_COMPLETE node (requires viewing 3 collector entries), and ValuesCategory.java.

Multi-stage progression

Break a single node into multiple stages with separate requirements.

// In your ResearchSubProvider:
public static final ResearchNodeRef BREWING_PROGRESS = node("mymod/brewing_progress");

@Override
protected void generateResearch() {
var stage1Ref = this.stageRef("mymod/brewing_stage_1");
var stage2Ref = this.stageRef("mymod/brewing_stage_2");
var stage3Ref = this.stageRef("mymod/brewing_stage_3");

var fact1 = this.ingress()
.onEntryViewedOnce(this.modLoc("entries/brewing_basic"))
.declareFact("mymod/brewing_basic_fact");
var fact2 = this.ingress()
.onEntryViewedOnce(this.modLoc("entries/brewing_intermediate"))
.declareFact("mymod/brewing_intermediate_fact");
var fact3 = this.ingress()
.onEntryViewedOnce(this.modLoc("entries/brewing_advanced"))
.declareFact("mymod/brewing_advanced_fact");

this.nodeBuilder(BREWING_PROGRESS)
.withStage(ResearchStageSpec.factsOnly(stage1Ref, List.of(fact1)))
.withStage(ResearchStageSpec.factsOnly(stage2Ref, List.of(fact2)))
.withStage(ResearchStageSpec.factsOnly(stage3Ref, List.of(fact3)))
.build();
}
// In your BookProvider — gate behind stage 2 completion:
entry.withCondition(MyResearch.BREWING_PROGRESS, stage2Ref);

Demo: DemoResearch.javaSTAGES_DEMO node (3 value-based stages) and STAGES_DEPENDENT (stage dependency), and StagesCategory.java.

Toast notifications

Show a toast when a node is fully completed.

var toast = new ResearchToastDefinition(
"research_toast.mymod.node_unlocked", // title key
List.of(), // title args
"research_toast.mymod.description", // description key
new BookIcon(new ItemStackTemplate(Items.DIAMOND)) // icon
);

this.nodeBuilder(this.node("mymod/my_node"))
.withToast(toast)
.build();

Toasts can also be attached to individual stages:

this.nodeBuilder(this.node("mymod/my_node"))
.withStage(ResearchStageSpec.valuesOnly(stage1Ref, List.of(
new ResearchNodeSpec.ValueRequirement(someValue, 10)
)).toast(new ResearchToastDefinition(
"research_toast.mymod.stage_progress",
List.of(),
"research_toast.mymod.stage_progress.desc",
new BookIcon(new ItemStackTemplate(Items.IRON_INGOT))
)))
.build();

Demo: DemoResearch.javaCRAFTING_STICK node toast and STAGES_DEMO stage toasts.

Combining research conditions

While it is possible to combine multiple research conditions with and/or, it is usually better to model the combined logic as a single research node with multiple required facts or values. This keeps the condition tree simpler and gives players a single progress point to track.

{
"condition": {
"type": "modonomicon:and",
"children": [
{
"type": "modonomicon:research_node_unlocked",
"node_id": "mymod:features/advanced_progression"
},
{
"type": "modonomicon:research_stage_completed",
"node_id": "mymod:features/my_node",
"stage_id": "mymod:features/my_node/stage_1"
}
]
}
}

Custom trigger types

Need a trigger type not covered above (e.g. entity killed, biome entered)? See Custom Research Hooks.