Developers
9 TOP-LEVEL ITEMSRitual Stand
Like the Infusor, the Ritual Stand is a dropped-item-driven module. It is more complex, though, because it cares not only about "what items were dropped here," but also:
- the current state of the Ritual Stand block
- whether the surrounding structure is valid
- whether a legal ritual recipe exists under that structure
So if the Infusor is "state + dropped-item recipes," the Ritual Stand is closer to "state + structure + dropped-item recipes."
Module role
Its core responsibilities are:
- accept items placed onto the Ritual Stand
- validate the surrounding ritual structure
- combine structure results and dropped items to match
RitualRecipe - export the result and give feedback when successful
So the center of this module is not inventory or UI. It is the way matching context is assembled.
Core classes
RitualStand- the block itself, and also an
ItemPlaceable
- the block itself, and also an
RitualStructureContainer- the container used for structure-recipe matching
RitualContainer- the container used for ritual-recipe matching
DropsCache- collects the dropped items participating in the ritual
FakePlayer- simulates player-style item use for special ritual results
There is no dedicated block entity here either, because the runtime core state lives in "current world state + current dropped items + current matched structure," not in a persistent inventory.
Entry flow
When the player right-clicks the block with an item, RitualStand.useItemOn(...) will:
- block direct placement from the recipe generator
- call
placeItem(...)on the main-hand item
As with the Infusor, this layer only places items into the world.
The real ritual logic starts in stepOn(...):
- run only on the server
- verify that the feature is enabled
- throttle through
DropsCache.isQueried(...) - begin structure and recipe matching
The presence of CRAFT_INTERVAL tells you something important about the design:
- dropped items at the same location are expected to trigger repeated collisions in a short time
- throttling is necessary to avoid processing one input set over and over
Matching flow
This is the most important part of the module to understand.
Step 1: structure matching
The implementation first runs:
Recipes.RITUAL_STRUCTURE.find(new RitualStructureContainer(level.getBlockState(pos)), level)
That means structure matching first looks only at the current Ritual Stand state and the surrounding world.
If a structure definition is found, the code then continues with:
structure.validate(pos, level)
That second step is what really decides whether the structure at the current position is valid.
Step 2: ritual recipe matching
Only after structure validation succeeds does the code construct:
RitualContainer.of(level.getBlockState(pos), DropsCache.queryStacks(world, pos), matched)
Then it performs:
Recipes.RITUAL.find(matcher, level)
So the matching context for RitualRecipe contains at least three categories of data:
- the current Ritual Stand block state
- the current set of dropped items
- the structure result that already matched successfully
This is the main reason the Ritual Stand is more complex than the Infusor.
Result handling
When a ritual succeeds, the default flow is:
- build the result with
ritual.assemble(...) - export it through
CifUtil.exportItem(...) - play the ritual completion sound
There is also one important special branch:
- if the result item is a
SpawnEggItem - then the module uses FakePlayer through
useAllItemsOn(...)
That means some ritual results are not treated as "drop this item and stop." Instead, they continue into "use this item the way a player would use it."
So if you are trying to understand why certain ritual results behave more like actions than plain item output, this is the branch to inspect first.
Player feedback
RitualStand also gives fairly complete feedback on failure paths:
- structure missing
overlay.croparia.ritual.404
- structure invalid
overlay.croparia.ritual.bad
- structure valid, but no ritual recipe matched
overlay.croparia.ritual.rejected
These overlays are sent through tryTell(...) to nearby players related to the dropped items.
That is worth noticing from a design perspective, because it shows that:
- the Ritual Stand treats "why this failed" as part of module behavior
- not all modules are designed only around their success path
Why it also depends on DropsCache
The Ritual Stand uses DropsCache just like the Infusor, but the dependency is even more important here.
In this module, DropsCache is not only about avoiding repeated reads. It is also what makes "the input set for this ritual attempt" stable. Without that, deterministic ritual matching after structure validation would be much harder.
If you want to build any system driven by "dropped items + world structure," some form of caching plus throttling is almost always necessary.
Extension notes
- When extending ritual logic, first separate whether you are changing structure matching or recipe matching.
- If you want more special result behaviors, look at the
SpawnEggItembranch first and ask whether it can become a more general execution layer. - If you build a similar module, keep structure validation and recipe matching as two distinct phases instead of merging them into one test.
- If you are debugging a ritual that does not work, follow this order: does the structure definition exist, does the structure validate, do the dropped items enter
DropsCache, and does the ritual recipe match?