Developers
10 TOP-LEVEL ITEMSGreenhouse
The Greenhouse is much simpler than the Crop Transmuter, but it is a very good module for understanding a different kind of runtime design:
- no complex UI
- no dedicated network packets
- the focus is on "automatically reacting to world block states" and "storing results in an internal inventory"
If you want to study an automation-heavy module driven by world updates, this page is a better fit.
Module role
The Greenhouse mainly does three things:
- observe the crop state below itself
- trigger growth or harvesting at the right time
- store the resulting drops in its internal inventory when possible
It does not define a new recipe system of its own. Instead, it reuses:
- the vanilla or crop block's existing drop logic
- internal inventory plus
RepoProxy
So it is better understood as an automatic harvesting and storage module, not a recipe machine.
Core classes
Greenhouse- the block itself
- opens the container, triggers harvest timing, and registers automation proxies
GreenhouseBlockEntity- stores a 9-slot inventory
- performs the concrete harvesting and deposit logic
Unlike some of the other core modules, this one has no custom menu class or dedicated packets. It simply reuses DispenserMenu for its container UI.
Internal inventory and automation
GreenhouseBlockEntity stores its contents in a 9-slot Container, then wraps it through:
new ContainerRepo<>(this)RepoProxy.item(...)
This creates the uniform item proxy that outside automation systems can consume.
During block construction, Greenhouse registers that proxy with ProxyProvider, which means external systems can treat the Greenhouse as a normal item storage target.
If that layer is unfamiliar, the Repo API is the best background reading.
The key implementation point here is not complicated permission control. It is the stable exposure of internal storage to outside automation.
Harvest flow
The Greenhouse does not run harvesting through a dedicated tick loop. Instead, it mainly enters through the block update chain:
onPlace(...)updateShape(...)
On the server side, updateShape(...) will:
- read the block state below
- fetch the current block entity
- call
GreenhouseBlockEntity.tryHarvest(...)
tryHarvest(...) then dispatches based on the block type below:
CropBlock- handled through
tryHarvestCrop(...)
- handled through
AttachedStemBlock- handled through
tryHarvestMelon(...)
- handled through
So the core idea is not "simulate all plant growth in one algorithm," but:
- detect whether the block below is one of the supported plant types
- then route harvesting through the corresponding logic
Regular crop path
tryHarvestCrop(...) follows this process:
- check whether the crop is mature
- call
Block.getDrops(...)to obtain drops - remove one seed from those drops to simulate replanting
- try to store the remaining output in internal storage
- if storage succeeds, set the crop below back to a mid-growth state
Two details are especially worth noticing:
- it does not destroy and replant the crop; it rewinds the age property instead
- the world state is only modified after the storage side can actually accept the output
So a large part of this module's stability comes from "confirm storage first, then commit the harvest."
Melon path
The giant melon path works differently:
- find the fruit position from
AttachedStemBlock.FACING - read the fruit drops
- try to store them in the internal inventory
- remove the fruit block if storage succeeds
There is no age rewind here, because giant melons already use a two-part structure of vine plus fruit.
So the module does not try to force a single shared harvesting algorithm:
- regular crops focus on age reset
- giant melons focus on locating and removing the actual fruit block
Growth acceleration
Besides harvesting, the Greenhouse also calls vanilla randomTick(...) on the CropBlock below during its own randomTick(...).
That means the module really has two behaviors:
- help crops continue growing
- automatically harvest and store them when mature
So it is not only a container block, and not only a harvester. It is a full growth-support module.
Storage boundary
GreenhouseBlockEntity.tryDeposit(...) iterates through the drops and tries to accept them through RepoProxy.
The design intent is very clear:
- drop generation itself still belongs to the plant block
- the Greenhouse only decides whether it can absorb those drops
When extending this module, it is worth remembering that most of its flexibility lives in "harvest detection" and "storage strategy," not in how drops are generated. Drop generation still comes from the world block's existing behavior.
Extension notes
- If you want the Greenhouse to support more plant types, the first place to extend is usually the dispatch logic in
tryHarvest(...). - If you want to change storage behavior, adjust
tryDeposit(...)before changing the harvesting flow itself. - If you need more complex automation, reuse the current
RepoProxyexposure pattern instead of building a second ad hoc container integration. - If you want an example of "automatic logic built around existing world block behavior," this module is a very good starting point.