Developers
9 TOP-LEVEL ITEMSInfusor
The Infusor is Croparia IF's most typical example of a "block-state-driven + dropped-item recipe" module.
Compared with the Greenhouse, it emphasizes recipe processing much more. Compared with the Crop Transmuter, it depends far less on GUI flow. It is especially useful for understanding:
- how a block state participates in recipe matching
- why dropped items need to work together with
DropsCache - how
ItemPlaceablebecomes meaningful inside a real module
Module role
The Infusor really has two layers of responsibility:
- switch itself into a specific elemental state through Elemental Potions
- once it has an element, treat dropped items on top of it as recipe input for
InfusorRecipe
So this is not a plain "fixed block + fixed recipes" relationship. Instead:
- the block state determines the current element
- that current element then becomes part of later recipe matching
Core classes
Infusor- the block itself, and also an
ItemPlaceable
- the block itself, and also an
ElementalPotion- can infuse or defuse an
Infusordirectly
- can infuse or defuse an
InfusorRecipe- the actual recipe type
InfusorContainer- the context container used during recipe matching
DropsCache- collects and caches dropped items near the block
This group does not need a dedicated block entity, because its runtime state is simple enough to live entirely in the block state.
Block state
The most important state field is:
ELEMENT
It is an EnumProperty<Element> whose default value is Element.EMPTY.
That means:
- when the block is in the
EMPTYstate, it does not participate in actual infusion recipes - once it holds a specific element, items dropped on top of it can enter recipe matching
So the first thing to accept when reading this module is:
- the Infusor's recipe conditions do not come only from the input items
- they also come from the current state of the block itself
Player interaction flow
useItemOn(...) covers the three main player interactions for this module.
Elemental infusion
If the player is holding an ElementalPotion, the Infusor first tries tryInfuse(...).
On success it will:
- switch the block state to the corresponding element
- play the infusion sound
- consume the potion
- return the empty bottle or other remainder
Defusion
If the block already has an element, and the held item matches the crafting remainder of that element's potion, the flow goes through tryDefuse(...).
On success it will:
- reset the block state back to
Element.EMPTY - consume the held item
- return the elemental potion
Dropping an item onto the block
If the item is neither the recipe generator nor part of the elemental interactions above, the block calls placeItem(...) to drop the item at its center.
This is important because it connects the Infusor directly into the unified ItemPlaceable item placement path.
Recipe processing flow
The real recipe logic does not happen in useItemOn(...). It happens in stepOn(...).
When an ItemEntity steps on the block, Infusor.stepOn(...) will:
- verify that the world is server-side
- verify that the feature is enabled in config
- read the current block element
- pass the dropped items plus the element into
tryCraft(...)
tryCraft(...) then continues:
- collect nearby drops through
DropsCache.queryStacks(...) - build an
InfusorContainer - look up a matching recipe with
Recipes.INFUSOR.find(...) - if one matches, execute
onCrafting(...)
onCrafting(...) will:
- assemble the recipe result
- export it through
CifUtil.exportItem(...) - reset the block state back to default
- play the crafting sound
The key design point is:
- players and dispensers only deliver items into the world
- the actual recipe match happens after those items enter the world as dropped entities
Why it depends on DropsCache
At first glance, the Infusor might look like it only needs the one ItemEntity currently touching the block. But the implementation does not work that way. It uses DropsCache to collect nearby drops into one matching context.
The reason is straightforward:
- an infusion recipe may require more than one dropped item
- several dropped items may need to participate in the same tick
- if each entity handled itself independently, the match could become duplicated or lose context
So DropsCache acts as the temporary layer that turns "nearby dropped entities in the world" into "the input set for one recipe evaluation."
Relationship with ItemPlaceable
The value of implementing ItemPlaceable here is not just to save a few lines of item entity spawning code. It connects the Infusor into an entire interaction chain:
- players can right-click to place items directly onto the block
- the dispenser behavior of
ElementalPotionfalls back toInfusor.placeItem(...)when no infusion happens DropperBlockMixincan also redirect dropper output into it
That means the module has one unified input story:
- whether the item comes from a player, a dropper, or a dispenser
- it is eventually turned into an
ItemEntitydropped on the block - then
stepOn(...)and the recipe layer process it in one consistent path
This is one of the clearest real uses of ItemPlaceable in the codebase.
Extension notes
- If you want to change how elemental state switching works, start with
tryInfuse(...)andtryDefuse(...). - If you want to change recipe matching semantics, inspect
InfusorContainerandInfusorRecipebefore touching the player interaction layer. - If you want to study how to build a dropped-item-driven recipe machine, this module is easier to enter than the Ritual Stand.
- If you want to support new delivery paths, try to preserve the main chain of
ItemPlaceable -> ItemEntity -> stepOn(...).