mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 12:41:43 +00:00
🧹
This commit is contained in:
+8
-5
@@ -18,6 +18,9 @@ else.
|
||||
PlaceholderAPI keys are not a compile surface, but they are a contract an operator depends on:
|
||||
[placeholders.md](placeholders.md).
|
||||
|
||||
Writing a **mod** rather than a plugin? The Fabric, Forge and NeoForge jars carry a different surface,
|
||||
`art.arcane.iris.modded.api`: [modded.md](modded.md).
|
||||
|
||||
Anything outside `art.arcane.iris.api` is internal. `art.arcane.iris.core.*`,
|
||||
`art.arcane.iris.engine.*`, `art.arcane.iris.util.*` and `art.arcane.iris.spi.*` change without
|
||||
notice and without a deprecation cycle. If you find yourself importing `Engine`, `IrisBiome` or
|
||||
@@ -29,12 +32,12 @@ notice and without a deprecation cycle. If you find yourself importing `Engine`,
|
||||
|
||||
`art.arcane.iris.api` ships in the **Bukkit plugin jar only**. The Fabric, Forge and NeoForge mod
|
||||
jars contain the same generator but not this package — there is no Bukkit `World`, no
|
||||
`ServicesManager` and no `Event` bus to hang it on. A mod that wants generator data uses the mod
|
||||
loader's own registries.
|
||||
`ServicesManager` and no `Event` bus to hang it on.
|
||||
|
||||
The mod jars carry a separate, unrelated surface at `art.arcane.iris.modded.api`, for supplying
|
||||
custom block data to the generator from a mod. It is not covered by these documents, is absent from
|
||||
the Bukkit plugin jar, and shares no types with `art.arcane.iris.api`.
|
||||
The mod jars carry a separate surface instead: `art.arcane.iris.modded.api`, documented in
|
||||
[modded.md](modded.md). It is where a mod detects Iris levels, drives pregeneration, reads and writes
|
||||
mantle data, and registers a provider so an Iris pack can place the mod's own blocks, items and mobs.
|
||||
It is absent from the Bukkit plugin jar and shares no types with `art.arcane.iris.api`.
|
||||
|
||||
Everything in these documents assumes Paper, Purpur, Leaf, Canvas, Folia or Spigot, Minecraft 26.2,
|
||||
Java 25.
|
||||
|
||||
@@ -0,0 +1,423 @@
|
||||
# Iris on Fabric, Forge and NeoForge
|
||||
|
||||
`art.arcane.iris.modded.api` is the surface a **mod** compiles against. It answers three questions: is this
|
||||
level generated by Iris, how do I drive Iris from my mod, and how do I get my own blocks, items and mobs
|
||||
placed by an Iris pack.
|
||||
|
||||
It ships in the Fabric, Forge and NeoForge jars only. It is absent from the Bukkit plugin jar, shares no types
|
||||
with `art.arcane.iris.api`, and is not covered by [terrain.md](terrain.md), [world-events.md](world-events.md)
|
||||
or [tree-feller.md](tree-feller.md) — those describe the Bukkit surface, which does not exist on a mod loader.
|
||||
|
||||
Everything here assumes Minecraft 26.2, Java 25, and one of Fabric, Forge or NeoForge. The mod id is
|
||||
`irisworldgen` on all three.
|
||||
|
||||
| What you want | Where |
|
||||
|---|---|
|
||||
| Detect Iris, read the engine, start a pregeneration, read/write mantle data | `IrisModdedAPI` |
|
||||
| Have Iris place *your* blocks, items and mobs | `ModdedDataProvider` |
|
||||
| Alias one custom key onto a fixed vanilla state, with no provider class | `IrisModdedAPI.registerCustomBlockData` |
|
||||
|
||||
---
|
||||
|
||||
## Depending on Iris
|
||||
|
||||
**There is no published Maven artifact for the mod jars.** No module in this repository applies
|
||||
`maven-publish`, and the JitPack route documented in [README.md](README.md) resolves the Bukkit sources, not
|
||||
the modded adapter. Until that changes, building from source is the only path.
|
||||
|
||||
The three adapters are standalone Gradle builds — each `adapters/<loader>/settings.gradle` does
|
||||
`includeBuild('../..')` to substitute `art.arcane:core` and `art.arcane:spi` from the root build, which is what
|
||||
keeps Loom, ForgeGradle and ModDevGradle off one plugin classpath. The root build drives them through their own
|
||||
wrappers:
|
||||
|
||||
```bash
|
||||
./gradlew buildFabric # -> dist/Iris v<version> [Fabric] <mc>+<loader>.jar
|
||||
./gradlew buildForge # -> dist/Iris v<version> [Forge] <mc>+<loader>.jar
|
||||
./gradlew buildNeoforge # -> dist/Iris v<version> [NeoForge] <mc>+<loader>.jar
|
||||
```
|
||||
|
||||
Then compile against the jar you will actually run:
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
compileOnly(files('libs/Iris-fabric.jar'))
|
||||
}
|
||||
```
|
||||
|
||||
The adapters are **not** in the root `settings.gradle` by default. Add `-PincludeModdedAdapters=true` for IDE
|
||||
import only; it closes a composite build cycle (root -> adapter -> root) and Gradle may reject it.
|
||||
|
||||
### Soft dependency
|
||||
|
||||
Declare the optional relationship, then do not rely on load order — the ServiceLoader path below works
|
||||
regardless of it, because Iris does the discovering.
|
||||
|
||||
Fabric (`fabric.mod.json`) — `suggests`, not `depends`; a hard `depends` makes Iris mandatory:
|
||||
|
||||
```json
|
||||
{
|
||||
"suggests": { "irisworldgen": "*" }
|
||||
}
|
||||
```
|
||||
|
||||
NeoForge (`META-INF/neoforge.mods.toml`):
|
||||
|
||||
```toml
|
||||
[[dependencies.yourmod]]
|
||||
modId = "irisworldgen"
|
||||
type = "optional"
|
||||
ordering = "AFTER"
|
||||
side = "BOTH"
|
||||
```
|
||||
|
||||
Forge (`META-INF/mods.toml`) is the same with `mandatory = false` instead of `type`.
|
||||
|
||||
### Detecting Iris
|
||||
|
||||
Two checks, and they answer different questions.
|
||||
|
||||
**Is the mod present?** Ask the loader — `FabricLoader.getInstance().isModLoaded("irisworldgen")`, or
|
||||
`ModList.get().isLoaded("irisworldgen")` on Forge and NeoForge. Cheap, but tells you nothing about whether
|
||||
Iris actually generates anything.
|
||||
|
||||
**Are Iris classes on the classpath?** If your integration lives in a class that imports
|
||||
`art.arcane.iris.modded.api.*`, that class must not load unless Iris is present. Keep the imports behind a
|
||||
presence check and a separate class, or probe reflectively:
|
||||
|
||||
```java
|
||||
private static final boolean IRIS_PRESENT = irisPresent();
|
||||
|
||||
private static boolean irisPresent() {
|
||||
try {
|
||||
Class.forName("art.arcane.iris.modded.api.IrisModdedAPI");
|
||||
return true;
|
||||
} catch (Throwable absent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Do not gate on a version string. Probe for the class or method you need.
|
||||
|
||||
**Is *this* level Iris?** `IrisModdedAPI.isIrisLevel(level)`. A server can mix Iris and vanilla dimensions
|
||||
freely, so presence of the mod is not presence of an Iris world.
|
||||
|
||||
---
|
||||
|
||||
## `IrisModdedAPI`
|
||||
|
||||
All static, all null-tolerant: a null or non-Iris `ServerLevel` yields `false`, `null` or a no-op. You never
|
||||
have to pre-check.
|
||||
|
||||
| Method | What it does |
|
||||
|---|---|
|
||||
| `isIrisLevel(ServerLevel)` | Whether the level's chunk generator is `IrisModdedChunkGenerator`. The cheapest check |
|
||||
| `isStudioLevel(ServerLevel)` | Whether it is a throwaway pack-authoring world. Persist nothing against one |
|
||||
| `getEngine(ServerLevel)` | The `Engine` behind the level, or null. See the stability warning below |
|
||||
| `pregenerate(ServerLevel, int radiusBlocks)` | Starts a cached async pregeneration around the origin |
|
||||
| `pregenerate(ServerLevel, int, int centerX, int centerZ, boolean sync, boolean cached)` | Same, with a centre and write mode |
|
||||
| `getMantleData(ServerLevel, int x, int y, int z, Class<T>)` | Reads Iris's per-block sidecar storage |
|
||||
| `setMantleData(ServerLevel, int x, int y, int z, T)` | Writes it |
|
||||
| `deleteMantleData(ServerLevel, int x, int y, int z, Class<T>)` | Removes a value of that type |
|
||||
| `retainMantleDataForSlice(Class<?>)` | Declares a mantle type Iris must keep rather than discard |
|
||||
| `registerProvider(ModdedDataProvider)` | Registers a custom content provider imperatively |
|
||||
| `registerCustomBlockData(String namespace, String key, String state)` | Aliases one key onto a fixed block state |
|
||||
|
||||
### `Engine` is internal
|
||||
|
||||
`getEngine` returns `art.arcane.iris.engine.framework.Engine`. That type is **internal to Iris** and changes
|
||||
without a deprecation cycle — as do `art.arcane.iris.core.*`, `art.arcane.iris.util.*` and
|
||||
`art.arcane.iris.spi.*`. Treat the returned `Engine` as an opaque token to hand back to Iris. Every method in
|
||||
the table above that needs one resolves it for you; prefer those.
|
||||
|
||||
Never cache an `Engine`. A pack hotload or a level unload replaces it, and the old instance goes inert.
|
||||
`getEngine` already returns null while the generator is binding and during shutdown.
|
||||
|
||||
### Pregeneration
|
||||
|
||||
`pregenerate` returns as soon as the job is queued. Progress goes to Iris's own logging and boss bar, not to
|
||||
your caller. Only one job runs server-wide, so it returns `false` if one is already active — and `false` also
|
||||
means "not an Iris level", so check `isIrisLevel` first if you need to tell those apart. Call it on the server
|
||||
thread.
|
||||
|
||||
`cached = true` writes an on-disk pregeneration cache so an interrupted run resumes instead of regenerating.
|
||||
`sync = true` writes chunks synchronously: slower, but it bypasses the async write queue.
|
||||
|
||||
### Mantle data
|
||||
|
||||
The mantle is Iris's own per-block storage, independent of chunk NBT, and it is how Iris carries data between
|
||||
generation stages. Three things to know:
|
||||
|
||||
1. **Coordinates are world-space.** `y` is translated by the engine's minimum height internally. A `y` outside
|
||||
the engine's height range reads as null and writes as a no-op — no exception.
|
||||
2. **Reads never create storage; writes do.** `getMantleData` returns null when no mantle region exists for
|
||||
that column yet. `setMantleData` and `deleteMantleData` create the region, which can touch disk — do not
|
||||
call them per block in a tick loop on the server thread.
|
||||
3. **Declare your types or lose them.** Iris discards mantle slices it does not need once a region's
|
||||
generation data has served its purpose. Any type you write and expect to read back later must be declared
|
||||
once during mod setup:
|
||||
|
||||
```java
|
||||
IrisModdedAPI.retainMantleDataForSlice(MyMarker.class);
|
||||
```
|
||||
|
||||
Registration is by canonical class name, process-wide across every Iris world, and cannot be undone. All three
|
||||
mantle methods throw `IllegalStateException` if the engine's mantle has already been closed.
|
||||
|
||||
---
|
||||
|
||||
## `ModdedDataProvider`
|
||||
|
||||
The extension point. Implement it and a pack can name `yourmod:something` in a block palette, a loot table or a
|
||||
spawn entry, and Iris will ask you to resolve it.
|
||||
|
||||
```java
|
||||
public interface ModdedDataProvider {
|
||||
String modId();
|
||||
default boolean isReady(); // default true
|
||||
Collection<Identifier> getTypes(ModdedDataType type);
|
||||
boolean isValidProvider(Identifier id, ModdedDataType type);
|
||||
default ModdedBlockData getBlockData(Identifier blockId, Map<String, String> state); // default null
|
||||
default void processBlockPlacement(ModdedBlockPlacementContext context); // default no-op
|
||||
default Entity spawnMob(ServerLevel level, double x, double y, double z, Identifier entityId); // default null
|
||||
default void init(); // default no-op
|
||||
}
|
||||
```
|
||||
|
||||
`ModdedDataType` is `BLOCK`, `ITEM` or `ENTITY`. Constants may be added — write a `default` arm in any switch
|
||||
expression over it.
|
||||
|
||||
### The contract
|
||||
|
||||
`modId()` is your identity, not decoration. It de-duplicates registrations and labels every log line Iris emits
|
||||
about your provider. It must be non-null and stable; returning null aborts discovery.
|
||||
|
||||
`isValidProvider(id, type)` is the gate. Iris calls it before every resolution callback, on generation threads,
|
||||
for every key it could not resolve itself. Keep it to a namespace comparison or a set lookup.
|
||||
|
||||
`isReady()` is how a provider whose registries populate late excuses itself. Iris **skips** a provider that
|
||||
returns false rather than treating it as absent, so returning false is strictly better than returning wrong
|
||||
answers.
|
||||
|
||||
`getTypes(type)` feeds command suggestion and pack tooling. It is not the resolution path — return an empty
|
||||
collection rather than null, and do not do work here that `isValidProvider` should do.
|
||||
|
||||
`getBlockData(blockId, state)` resolves a claimed block. `state` holds the `[prop=value]` pairs from the pack's
|
||||
key, already parsed, possibly empty, never null. Return null to decline and Iris tries the next provider, then
|
||||
falls back to air. Return `ModdedBlockData.direct(blockState)` when the state is final.
|
||||
|
||||
`processBlockPlacement(context)` finishes a **deferred** placement. Return
|
||||
`ModdedBlockData.deferred(placeholder)` from `getBlockData` when the real block needs a loaded level — a block
|
||||
entity, neighbour state, or mod registries not reachable from a generation thread. Iris writes your placeholder
|
||||
during generation and calls you back later on the server thread with the chunk loaded. Pick a placeholder with
|
||||
the same shape and occlusion as the final block so terrain around it generates correctly. Only the *first*
|
||||
provider claiming the identifier is called for a given position.
|
||||
|
||||
`ModdedBlockPlacementContext` is an immutable record: `engine`, `level`, `position`, `blockId`, `state`,
|
||||
`blockState`. `blockState` is what is currently at `position` — normally your placeholder, though a later
|
||||
generation stage may have replaced it. `state` is defensively copied and unmodifiable.
|
||||
|
||||
`spawnMob(...)` spawns a claimed custom entity on the server thread. Return null to decline.
|
||||
|
||||
`init()` runs once, immediately after Iris accepts your provider.
|
||||
|
||||
### Threading
|
||||
|
||||
| Callback | Thread | Notes |
|
||||
|---|---|---|
|
||||
| `isValidProvider`, `getBlockData` | Generation threads, many at once | Must be fast. Must not touch world state |
|
||||
| `processBlockPlacement` | Server thread, chunk loaded | Safe to write blocks and attach block entities |
|
||||
| `spawnMob` | Server thread | |
|
||||
| `init` | Whichever thread registered you | Mod init for ServiceLoader, your caller otherwise |
|
||||
| `modId`, `isReady`, `getTypes` | Any | |
|
||||
|
||||
Implementations must be thread-safe. `getBlockData` in particular is called concurrently for every unresolved
|
||||
key a pack names, which during a pregeneration is a lot.
|
||||
|
||||
### Registration by ServiceLoader
|
||||
|
||||
Iris discovers providers with `java.util.ServiceLoader`. Ship a service file naming your implementation's
|
||||
binary name; the class needs a public no-argument constructor.
|
||||
|
||||
`src/main/resources/META-INF/services/art.arcane.iris.modded.api.ModdedDataProvider`:
|
||||
|
||||
```
|
||||
com.example.yourmod.iris.YourIrisProvider
|
||||
```
|
||||
|
||||
One binary name per line. Nested classes use `$`, for example
|
||||
`com.example.yourmod.YourMod$IrisProvider`.
|
||||
|
||||
Iris loads the service with **its own** class loader:
|
||||
`ServiceLoader.load(ModdedDataProvider.class, ModdedCustomContentRegistry.class.getClassLoader())`. Your service
|
||||
file therefore has to be visible from Iris's loader, which is the normal case on all three loaders but is the
|
||||
thing to suspect first if nothing happens. Confirm with the registration log line below; if it never appears,
|
||||
fall back to `registerProvider`.
|
||||
|
||||
### When discovery runs
|
||||
|
||||
`ModdedCustomContentRegistry.discover()` runs inside `ModdedEngineBootstrap.bootCommon(...)`, which is the very
|
||||
first thing each loader's entrypoint calls — `IrisFabricBootstrap.onInitialize`,
|
||||
`IrisForgeBootstrap`/`IrisNeoForgeBootstrap` construction. That is **before** the Iris chunk generator is
|
||||
registered and long before any server starts. Consequences:
|
||||
|
||||
- A ServiceLoader-declared provider is available before any world could resolve a block. This is the safe path.
|
||||
- Discovery runs **once per process**. A second `discover()` is a no-op.
|
||||
- Your `init()` must not assume a server, a level, or a fully populated game registry. Defer that work and gate
|
||||
it behind `isReady()`.
|
||||
|
||||
### Registration imperatively
|
||||
|
||||
`IrisModdedAPI.registerProvider(provider)` works at any time and is the option if you would rather not ship a
|
||||
service file, or need to build the provider from your own config.
|
||||
|
||||
Ordering is the catch: Iris only consults providers registered **before** a pack resolves the block in
|
||||
question, and blocks already resolved are not revisited. Register during mod setup. Registering after Iris's
|
||||
own ServiceLoader pass is fine; registering after a world has generated is not.
|
||||
|
||||
A second registration under a `modId()` already present is logged and ignored. `init()` runs during the call.
|
||||
|
||||
### How discovery and failures are reported
|
||||
|
||||
Everything below is logged under the `Iris` logger. One line per accepted provider confirms registration —
|
||||
this is what to grep for when checking whether your service file was seen:
|
||||
|
||||
```
|
||||
Iris registered custom content provider 'yourmod'
|
||||
```
|
||||
|
||||
A duplicate `modId()` is rejected with `already registered; ignoring duplicate`.
|
||||
|
||||
Iris catches throwables from every provider callback, logs them against your `modId()`, and continues with the
|
||||
remaining providers — one broken provider does not stop world generation:
|
||||
|
||||
```
|
||||
Iris custom content provider 'yourmod' failed resolving block yourmod:thing
|
||||
Iris custom content provider 'yourmod' failed post-placement for yourmod:thing at BlockPos{...}
|
||||
Iris custom content provider 'yourmod' failed spawning mob yourmod:critter
|
||||
Iris custom content provider 'yourmod' failed to initialize # registerProvider path only
|
||||
```
|
||||
|
||||
`init()` during ServiceLoader discovery is the one exception, and it is all-or-nothing: a throwable there
|
||||
aborts the pass, restores the registry to its pre-discovery state, and rethrows. The log line names the
|
||||
provider that failed, by mod id and class name:
|
||||
|
||||
```
|
||||
Iris custom content provider discovery failed at provider 'yourmod' (com.example.yourmod.iris.YourIrisProvider)
|
||||
```
|
||||
|
||||
If your provider's own `modId()` throws while Iris is building that message, the class name alone is logged; if
|
||||
the failure happened outside any provider, it reads `the provider service loader`. A `null` provider or a null
|
||||
`modId()` from the ServiceLoader fails the pass with a message naming which.
|
||||
|
||||
### Static aliases, no provider class
|
||||
|
||||
For the common case of "my key is really this vanilla block", skip the provider:
|
||||
|
||||
```java
|
||||
IrisModdedAPI.registerCustomBlockData("yourmod", "fancy_log", "minecraft:oak_log[axis=y]");
|
||||
```
|
||||
|
||||
The state string uses the same syntax packs use and is parsed **immediately** — a typo is logged at startup and
|
||||
the registration dropped, rather than surfacing later as missing blocks. Aliases take precedence over provider
|
||||
lookups for the same key. Null arguments are ignored.
|
||||
|
||||
---
|
||||
|
||||
## How pack resolution works on modded
|
||||
|
||||
Paths are relative to the loader's config directory (`config/` on a normal server install).
|
||||
|
||||
| Path | What it is |
|
||||
|---|---|
|
||||
| `config/irisworldgen/packs/<pack>/` | Installed packs. A pack is valid when `dimensions/<dimension>.json` exists |
|
||||
| `config/irisworldgen/generated/datapack/iris/` | The generated forced datapack. Iris owns this — do not edit it |
|
||||
| `config/irisworldgen/modded.json` | Mod-side config: default pack, auto-download, primary world routing |
|
||||
| `config/iris/` | Engine data directory: settings and per-world engine state |
|
||||
|
||||
Note the split: the engine's data folder is `config/iris`, but every modded pack path — installer, validator,
|
||||
command suggestions, forced datapack, engine creation — resolves under `config/irisworldgen/packs`. Install
|
||||
packs there.
|
||||
|
||||
At `bootCommon`, Iris kicks off an async default-pack prefetch. If `modded.json` has
|
||||
`autoDownloadDefaultPack` enabled and the configured `defaultPack` is missing, Iris downloads
|
||||
`IrisDimensions/<pack>` from the `master` branch into the packs folder. If that fails it logs a pointer to
|
||||
`/iris download <pack>`. A pack that is already installed is left alone.
|
||||
|
||||
When a level asks for its pack, `ModdedWorldEngines.packFolder(pack)` resolves
|
||||
`config/irisworldgen/packs/<pack>`. A missing pack is a hard failure with the expected absolute path printed —
|
||||
Iris does not silently generate vanilla terrain in its place.
|
||||
|
||||
### The forced datapack
|
||||
|
||||
Iris cannot register dimension types and per-pack biomes through a mod registry, because vanilla world creation
|
||||
reads them from the datapack layer. So Iris **generates a datapack** from the installed packs and injects it as
|
||||
a built-in, top-priority repository source:
|
||||
|
||||
- Fabric: a `PackRepository` mixin, via `FabricForcedDatapackSources`
|
||||
- Forge and NeoForge: `event.addRepositorySource(ModdedForcedDatapack.repositorySource())`
|
||||
|
||||
It contributes world presets, dimension types and biomes under the `irisworldgen` namespace, with ids derived
|
||||
from the pack and dimension names — `irisworldgen:packs/<pack>/dimensions/<dimension>/preset`,
|
||||
`.../dimension_type`, `.../biomes/<biome>`. This is why an Iris dimension shows up in the vanilla world
|
||||
creation screen as `IRIS:<Pack>`.
|
||||
|
||||
It is regenerated when the pack changes: a studio hotload calls `ModdedForcedDatapack.regenerate()`. If
|
||||
regeneration fails and a previously published pack is still readable, Iris keeps the last known-good one and
|
||||
logs the failure rather than starting with no dimension types.
|
||||
|
||||
**The failure you need to recognise.** If injection did not happen for your loader — a mixin that failed to
|
||||
apply, an event that never fired — Iris logs this once at startup and world creation will fail no matter how
|
||||
many times you restart:
|
||||
|
||||
```
|
||||
Iris forced datapack 'iris_worldgen' was never loaded by this server.
|
||||
N installed pack(s) at <path> contributed no dimension types or custom biomes.
|
||||
Datapack source injection failed for this loader (mixin/event not applied), so world
|
||||
creation will fail and restarting will not fix it.
|
||||
```
|
||||
|
||||
That is a loader/environment problem, not a pack problem. Nothing an integrating mod does can fix it.
|
||||
|
||||
### Commands worth knowing
|
||||
|
||||
The modded command tree is `/iris`, aliased `/ir` and `/irs`, gated at gamemaster permission level.
|
||||
|
||||
| Command | What it tells you |
|
||||
|---|---|
|
||||
| `/iris pack validate [pack]` | Validates every installed pack, or one. Runs on a worker thread, reports per pack, and counts unloadable packs |
|
||||
| `/iris pack status [pack]` | Replays the **recorded** validation results — blocking errors and warnings per pack. Says so and returns nothing if `validate` has not run this session |
|
||||
| `/iris pack cleanup <pack> [apply]` | Previews unused pack resources; `apply` deletes them |
|
||||
| `/iris pack restore <pack> [apply]` | Previews a restore of pack resources; `apply` performs it |
|
||||
| `/iris datapack status` | Per Iris dimension: active dimension type, its min/max/logical height, what the pack wants, and whether they match |
|
||||
| `/iris datapack install` | Writes the pack's dimension type into `<world>/datapacks/iris/data/irisworldgen/dimension_type/` as an override |
|
||||
| `/iris datapack list` | Datapack URLs declared by installed pack dimensions, plus the datapacks actually present in `<world>/datapacks/` |
|
||||
| `/iris download <pack> [branch] [overwrite]` | Installs a pack from `IrisDimensions/<pack>` into the packs folder. Aliased `dl` |
|
||||
| `/iris version` | Iris version and loader |
|
||||
|
||||
`/iris datapack status` is the first thing to run when an Iris dimension generates at the wrong height. A
|
||||
mismatch means the level's active dimension type disagrees with the pack, which happens when a world was
|
||||
created before a pack's height range changed. `install` writes the override; the world still needs a restart.
|
||||
|
||||
`/iris datapack ingest`, `pull` and `remove` exist but refuse on modded, with a message explaining why: the
|
||||
Modrinth ingest workflow is Bukkit tooling. Native vanilla and datapack structure placement **does** work on
|
||||
modded — install the datapack into `<world>/datapacks/` and restart, and its registered structures generate.
|
||||
|
||||
---
|
||||
|
||||
## What is not supported
|
||||
|
||||
- **No published artifact.** Build from source, as above. There is no Maven coordinate for the modded jars and
|
||||
no `maven-publish` in this build.
|
||||
- **Core types are internal.** `art.arcane.iris.engine.*`, `art.arcane.iris.core.*`,
|
||||
`art.arcane.iris.util.*` and `art.arcane.iris.spi.*` change without notice. `Engine`, reachable through
|
||||
`getEngine` and `ModdedBlockPlacementContext.engine()`, is the one internal type this surface exposes, and it
|
||||
is exposed as a token to hand back rather than an API to call.
|
||||
- **No event bus.** `IrisPlatform.callEvent` is a no-op on every mod loader adapter. There is no modded
|
||||
equivalent of `IrisWorldEngineEvent` or `IrisPregenerationEvent`; poll `isIrisLevel`/`getEngine` instead.
|
||||
- **No `art.arcane.iris.api`.** The Bukkit terrain, world-event, pregen and tree-feller interfaces are not in
|
||||
the mod jars. There is no modded terrain-query surface yet.
|
||||
- **No PlaceholderAPI.** [placeholders.md](placeholders.md) is Bukkit-only.
|
||||
- **Datapack ingest is Bukkit-only**, per the command note above.
|
||||
- **`ModdedCustomContentRegistry`'s resolution methods are Iris internals.** They are public only because the
|
||||
adapter's generation code lives in another package. Go through `IrisModdedAPI`.
|
||||
Reference in New Issue
Block a user