> ## Documentation Index
> Fetch the complete documentation index at: https://items.lodemc.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Crops API

> Find, plant, grow and harvest custom crops from your plugin, and change the rules they grow and survive by.

Everything about [crops](https://items.lodemc.net/flora/crops.md) is in the `org.lodemc.api.crop` package. There are three ways to change how crops behave, and you can mix them:

| Use | To | Page |
| - | - | - |
| `LodeCrops` methods | Find crops and act on them: read or set the stage, plant, grow, harvest | This page |
| A `CropRule` | Change the rules every crop follows: where it can stay or be planted, its growth chance, bone meal | [Crop rules](#crop-rules) |
| Events | React to a single growth tick, stage change or harvest, and change or cancel it | [Events](https://items.lodemc.net/api/events.md) |

See [Setting up](https://items.lodemc.net/api.md#setting-up) first if your plugin doesn't depend on Lode Items yet.

## Example: crops that need a block nearby

This rule stops every custom crop growing unless a composter is within 4 blocks:

```java theme={null}
import org.lodemc.api.crop.*;

public final class FarmPlugin extends JavaPlugin {
    @Override
    public void onEnable() {
        LodeCrops.addRule(this, new CropRule() {
            @Override
            public double growthChance(CropContext context, double chance) {
                return context.isNear(4, 1, Material.COMPOSTER) ? chance : 0;
            }
        });
    }
}
```

The crop still grows at its normal speed next to a composter, because the rule passes the chance on unchanged. To make it pop off when the composter is broken, and stop it being planted away from one, override `canSurvive` the same way.

## LodeCrops

`org.lodemc.api.crop.LodeCrops` has static methods. All of them take Bukkit `Block`s and are called from the main thread.

### Finding crops

**crops()** `Collection\<CustomCrop\>`

Every crop configured right now.

**crop(Key id)** `CustomCrop`

The crop with this id, or `null` if there isn't one. Also takes the id as a `String` (`tomato_seeds` means `lodeitems:tomato_seeds`). A crop's id is its seed's id.

**at(Block block)** `PlantedCrop`

The crop planted at `block`, with its current stage, or `null` if there's no custom crop there.

**isCrop(Block block)** `boolean`

Whether a custom crop is planted at `block`.

**growthSpeed(Block block)** `double`

The growth factor vanilla uses for the crop at `block`: `1` on bare ground, higher with farmland under and around it (wet farmland counts 3 times as much), halved when the same crop is packed around it instead of in rows. Returns `0` if there's no crop. The crop's own `speed:` isn't included.

### Changing crops

**setStage(Block block, int stage, boolean fireEvent)** `boolean`

Sets the crop at `block` to `stage`. The stage is kept between 0 and the ripe stage. With `fireEvent` (the default when you leave it out), a [`CropGrowEvent`](https://items.lodemc.net/api/events#cropgrowevent) with cause `PLUGIN` fires first and can change or cancel it. Returns `false` if there's no crop, the event was cancelled, or the stage didn't change.

**grow(Block block, int stages, boolean fireEvent)** `boolean`

Grows the crop at `block` by `stages` (default 1). A negative number makes it younger. Works like `setStage`.

**plant(Block block, Key id, int stage)** `boolean`

Plants crop `id` at `block` at `stage` (default 0), replacing whatever is there. **The soil isn't checked**: call `canPlant` first if it matters. A crop that can't survive where it is pops off the next time a block next to it changes.

**canPlant(Block block, Key id)** `boolean`

Whether crop `id` could be planted at `block`: the spot can be replaced (air, grass...), the block below is one of the crop's `on:` soils, and every crop rule's `canSurvive` agrees.

**harvest(Block block, Player player)** `boolean`

Picks the crop at `block` the way a right-click does: only if it's ripe and its `harvest:` option is on. Fires a [`CropHarvestEvent`](https://items.lodemc.net/api/events#cropharvestevent), drops the produce and replants it. `player` is optional and can be `null`. Returns `true` if it was picked.

```java theme={null}
Block block = event.getClickedBlock();
PlantedCrop planted = LodeCrops.at(block);
if (planted != null && !planted.isRipe()) {
    LodeCrops.grow(block, 1);          // fires CropGrowEvent with cause PLUGIN
}
```

### Managing rules

**addRule(Plugin plugin, CropRule rule)** `void`

Adds a [crop rule](#crop-rules) for every crop. Rules run in the order they were added. The rule is removed when `plugin` disables, so you don't need to remove it yourself.

**addRule(Plugin plugin, Key id, CropRule rule)** `CropRule`

Adds a rule for crop `id` only; other crops skip it. Returns the rule that was really added, which is what you pass to `removeRule`.

**removeRule(CropRule rule)** `void`

Removes one rule.

**removeRules(Plugin plugin)** `void`

Removes every rule `plugin` added.

## CustomCrop

A crop as it's configured. It's read-only: change behaviour with rules and events. A reload makes new `CustomCrop` objects, so look crops up again after [`LodeItemsLoadEvent`](https://items.lodemc.net/api/events#lodeitemsloadevent) instead of keeping them.

| Method | Returns | What it is |
| - | - | - |
| `getId()` | `Key` | Its id, which is also its seed's item id |
| `getStageCount()` | `int` | How many stages it has |
| `getRipeStage()` | `int` | The last stage, when it's ripe (`getStageCount() - 1`). Stages start at 0. |
| `getSpeed()` | `double` | Its `speed:` option. 1 grows like wheat. |
| `getMinLight()` | `int` | Its `min-light:` option, the light it needs to grow |
| `getSoil()` | `List<String>` | Its `on:` option as written: block names and `#tags` |
| `getBoneMealAllowed()` | `boolean` | Its `bone-meal:` option |
| `getHarvestable()` | `boolean` | Its `harvest:` option |
| `isSoil(BlockData data)` | `boolean` | Whether `data` is one of its `on:` soils. Crop rules aren't asked. |
| `seed(int amount)` | `ItemStack` | A stack of its seed. `amount` defaults to 1. |
| `rollDrops(int stage)` | `List<ItemStack>` | One random roll of what it drops when broken at `stage`: its seed before it's ripe, its `drops:` once ripe |

## PlantedCrop

What `LodeCrops.at` returns: a crop in the world at the moment you asked.

| Method | Returns | What it is |
| - | - | - |
| `getBlock()` | `Block` | Where it is |
| `getCrop()` | `CustomCrop` | Which crop it is |
| `getStage()` | `int` | Its stage, from 0 to the ripe stage |
| `isRipe()` | `boolean` | Whether it's at its ripe stage |

## GrowCause

Why a crop changed stage, given by [`CropGrowEvent`](https://items.lodemc.net/api/events#cropgrowevent):

| Value | When |
| - | - |
| `NATURAL` | It grew by itself, like wheat does |
| `BONE_MEAL` | A player or a dispenser used bone meal on it |
| `PLUGIN` | A plugin called `LodeCrops.setStage` or `LodeCrops.grow` |

## Crop rules

A `CropRule` changes the rules every crop follows. It has three methods, and you override only the ones you need. Each one gets the answer so far, from Lode Items and then from rules added before yours, and returns its own answer. To leave a crop alone, return what you were given.

**canSurvive(CropContext context, boolean result)** `boolean`

Whether the crop can stay where it is. Returning `false` for a planted crop makes it pop off with its drops the next time a block next to it changes, like wheat on trampled farmland. While a player plants a seed, returning `false` stops the seed being planted. `result` starts as "the block below is one of its `on:` soils".

**growthChance(CropContext context, double chance)** `double`

The chance, from 0 to 1, that this growth tick grows the crop one stage. `chance` starts as vanilla's crop formula, or 0 when it's darker than the crop's `min-light`. Return `0` to stop growth or `1` to grow it every tick. Ripe crops don't get growth ticks, so this isn't called for them.

**canBoneMeal(CropContext context, boolean result)** `boolean`

Whether bone meal works on the crop. When it returns `false`, the bone meal isn't used up. `result` starts as "its `bone-meal:` option is on and it isn't ripe".

```java theme={null}
LodeCrops.addRule(this, new CropRule() {
    // Crops only survive in the overworld
    @Override
    public boolean canSurvive(CropContext c, boolean result) {
        return result && c.getWorld() != null && c.getWorld().getEnvironment() == World.Environment.NORMAL;
    }

    // Twice as fast in the rain
    @Override
    public double growthChance(CropContext c, double chance) {
        return c.getWorld() != null && c.getWorld().hasStorm() ? chance * 2 : chance;
    }

    // Bone meal doesn't work on tomatoes
    @Override
    public boolean canBoneMeal(CropContext c, boolean result) {
        return result && !c.getCrop().getId().value().equals("tomato_seeds");
    }
});
```

> [!WARNING]
>
> Rules run inside the game's own block logic, sometimes many times a tick. Keep them fast, read the world only through the `CropContext`, and **don't change blocks from a rule**. Change blocks from an [event](https://items.lodemc.net/api/events.md) instead.

### Rules or events?

Both can stop growth, so use whichever fits:

* **Use a rule** for anything about where a crop may stay or be planted, since only rules can answer that. Rules are also cheaper when you only change a number.
* **Use an event** when you need to know about the change after it's decided, change the target stage, or do something with the player or the world, such as messages, particles or placing blocks.

On each growth tick, rules run first and the [`CropGrowAttemptEvent`](https://items.lodemc.net/api/events#cropgrowattemptevent) gets the chance they returned.

## CropContext

What a rule knows about the crop it's being asked about.

**Reading blocks never loads chunks.** A block in an unloaded chunk reads as `null`, and the `isNear` and `countNear` checks skip it.

| Method | Returns | What it is |
| - | - | - |
| `getCrop()` | `CustomCrop` | Which crop |
| `getStage()` | `int` | Its stage now. 0 while it's being planted. |
| `getX()`, `getY()`, `getZ()` | `int` | Where it is |
| `getWorld()` | `World` | The world, or `null` when the game asks outside a loaded world |
| `getBlock()` | `Block` | The crop's block, or `null` when there's no world |
| `getLight()` | `int` | Light at the crop's spot, measured the way vanilla crops measure it |
| `blockData(int dx, int dy, int dz)` | `BlockData` | The block that far from the crop, or `null` if it isn't loaded. `0, -1, 0` is the soil. |
| `material(int dx, int dy, int dz)` | `Material` | The material that far from the crop. Custom blocks report generic materials; use `customId` for them. |
| `customId(int dx, int dy, int dz)` | `Key` | The Lode Items block that far from the crop, or `null` for a vanilla block |
| `isNear(int radius, int vertical, Material material)` | `boolean` | Whether `material` is within `radius` blocks sideways and `vertical` blocks up or down |
| `isNear(int radius, int vertical, Predicate<BlockData> test)` | `boolean` | Whether any block in that box passes `test` |
| `isNearCustom(int radius, int vertical, Key id)` | `boolean` | Whether Lode Items block `id` is in that box |
| `countNear(int radius, int vertical, Predicate<BlockData> test)` | `int` | How many blocks in that box pass `test`, for rules like "at least 3 lanterns nearby" |

The box is centred on the crop, and the crop itself doesn't count. `radius` and `vertical` can each be 0 to 32. A box of radius 4 and vertical 1 holds 242 blocks, so keep boxes small in rules that run on every growth tick.

```java theme={null}
// Needs at least 3 lanterns within 5 blocks to grow
@Override
public double growthChance(CropContext c, double chance) {
    int lanterns = c.countNear(5, 2, data -> data.getMaterial() == Material.LANTERN);
    return lanterns >= 3 ? chance : 0;
}

// Only survives next to a custom "scarecrow" block
@Override
public boolean canSurvive(CropContext c, boolean result) {
    return result && c.isNearCustom(8, 2, Key.key("lodeitems", "scarecrow"));
}
```

> [!NOTE]
>
> A `canSurvive` rule is checked when a block **next to** the crop changes, not when something further away does. If your rule depends on a block 5 blocks away, breaking that block doesn't pop the crop at once. It pops off the next time one of its neighbours changes, or you can check it yourself and break it from an event.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.