-
Notifications
You must be signed in to change notification settings - Fork 0
Addon Development
Auto Storage exposes a bounded NeoForge addon SDK for deterministic stations, recipes, typed resources, transfers, transforms, and station variants. Addons use the same public registries as Auto Storage's bundled compatibility modules. Storage, planning, validation, and mutation remain server-authoritative.
This document is the authoritative addon guide. The GitHub Wiki copy must match it at every SDK release; edit this file first. The Wiki page is intentionally not listed in the player-manual Home contents or sidebar.
Every GitHub release contains:
-
auto_storage-<version>.jar— the player/runtime mod; -
auto_storage-<version>-api.jar— the compile-only SDK; -
auto_storage-<version>-api-sources.jar; -
auto_storage-<version>-api-javadoc.jar.
The public GitHub release can be consumed without copying a jar into libs/:
def autoStorageVersion = "0.3.0"
repositories {
ivy {
name = "AutoStorageReleases"
url = uri(
"https://github.com/swear01/Auto_Storage/releases/download/"
+ "v${autoStorageVersion}")
patternLayout {
artifact("[artifact]-[revision](-[classifier]).[ext]")
}
metadataSources {
artifact()
}
}
}
dependencies {
compileOnly(
"com.swear.autostorage:auto_storage:${autoStorageVersion}:api")
runtimeOnly(
"com.swear.autostorage:auto_storage:${autoStorageVersion}")
}Use normal NeoForge dependency metadata to require Auto Storage. If the addon integrates another mod, declare that target dependency in the addon's own metadata as well. Do not pin player installations to Auto Storage's representative CI fixture versions.
Create addon-owned DeferredRegister instances with the focused API classes,
then wire all of them to the addon's mod event bus in one call:
public ExampleAddon(IEventBus modBus) {
AutoStorageAddon.register(MOD_ID, modBus, addon -> addon
.machineDescriptors(MACHINES)
.recipeFamilies(RECIPES)
.resourceKinds(RESOURCE_KINDS)
.containerStrategies(CONTAINERS)
.blockStrategies(BLOCKS)
.transformProviders(TRANSFORMS)
.machineVariantContributors(VARIANTS));
}The complete compilable example is
examples/addon/src/main/java/example/autostorage/ExampleAddon.java.
./gradlew compileAddonExampleJava compiles it against only the generated API
jar and NeoForge/Minecraft dependencies, not Auto Storage's implementation
output.
The facade verifies:
- every register targets the expected Auto Storage custom registry;
- every
DeferredRegisteruses the addon's namespace; - the same register is not wired twice;
- at least one Auto Storage registry is contributed;
- reload-hook IDs belong to the addon namespace.
Registry entries and reload hooks are registration-time contracts. Runtime hot registration is unsupported.
Use MachineDescriptorApi.createDeferredRegister(MOD_ID) and register one
stable descriptor per logical station family. A descriptor can represent one
station item or multiple exact variants with different rational work rates.
Choose MachineCategory.PROCESS, INSTANT, or TRANSFORM; addon code does not
depend on the internal MachineEnergyTable.
See Machine Descriptor API.
Use RecipeFamilyApi.createDeferredRegister(MOD_ID). One entry covers one exact
recipe class plus exact RecipeType, not individual recipe IDs.
-
singleItemToItemcovers one consumed item and one deterministic item output. -
deterministicResourcescovers bounded exact item/fluid/energy/chemical/addon inputs, catalysts, tools, remainders, multiple outputs, and station costs. -
deterministicResourceVariantscovers a bounded set of complete deterministic plans selected from exact available stacks.
See Recipe Family API.
Register a StorageResourceKind for a stable kind ID. Add
StorageResourceContainerStrategy entries for item-container deposit/withdraw
and StorageResourceBlockStrategy entries for sided world capabilities.
Multi-key Core mutation uses one StorageResourceTransaction.
If the registered representative item is not enough to render an exact resource variant, register a client-only icon renderer during client setup:
TerminalResourceRendererApi.register(
MY_RESOURCE_KIND,
GuiGraphics.class,
(graphics, key, amount, x, y, partialTick) -> {
renderExactResource(graphics, key, amount, x, y, partialTick);
return true;
});The generic context keeps auto_storage-<version>-api.jar free of
net.minecraft.client bytecode references while still giving client modules a
typed GuiGraphics lambda. Never call this hook from common or
dedicated-server initialization. IDs are bounded and unique; duplicate
renderers fail explicitly. Returning false asks the terminal to render the
resource kind's registered representative item instead.
Create a DeferredRegister<TransformProvider> through
TransformProviderApi.createDeferredRegister(MOD_ID). Each provider resolves
one exact inserted item into a positive typed output and, optionally, a matching
station-work cost. Resolvers must be deterministic and side-effect free.
Auto mode only discovers matching uses. The server revalidates the exact input, selected provider, output capacity, and station work before one simulate-then-commit mutation.
Create a DeferredRegister<MachineVariantContributor> through
MachineVariantContributorApi.createDeferredRegister(MOD_ID). A contribution
adds exact station items and rates to an existing logical descriptor without
replacing its ID or recipe family.
The facade also exposes the two bounded lifecycle hooks required by current integrations:
private static final BlockCapability<MyResourceView, Direction> CAPABILITY =
BlockCapability.createSided(id("resource_view"), MyResourceView.class);
AutoStorageAddon.register(MOD_ID, modBus, addon -> addon
.resourceKinds(RESOURCE_KINDS)
.capabilities(event ->
AutoStorageCapabilityApi.registerSidedResourceCapability(
event,
CAPABILITY,
(resources, side) -> new MyResourceView(resources)))
.recipeReload(id("runtime_recipes"), MyRecipes::refresh));capabilities receives NeoForge's RegisterCapabilitiesEvent.
AutoStorageCapabilityApi.registerSidedResourceCapability(...) exposes only a
server-owned StorageResourceHandler for the Core, Import Bus, and Export Bus;
it does not expose their implementation block entities. The returned wrapper
must obey the same simulation, exact accepted-amount, and side rules as every
other typed-resource handler.
recipeReload runs at server start and after a global datapack reload. Hooks
run in stable ID order and freeze before gameplay; duplicate, foreign, or late
hooks fail explicitly. Arbitrary tick, player, world, or mutation callbacks are
not part of the SDK.
Addon code must provide complete deterministic contracts. Auto Storage does not
infer recipes through reflection, generic Recipe#getIngredients(), EMI
widgets, serializer names, or machine names. It does not send resources into an
external machine and wait for world state.
Registration, linkage, or validation failures are startup errors. Auto Storage does not silently skip a loaded but incompatible integration. Runtime crafting uses current server recipe holders, checked long arithmetic, joint reservation, capacity planning, and one atomic storage transaction. Client UI and EMI are presentation/input surfaces only.
The player still installs one Auto Storage jar. Bundled integrations live in
isolated src/compat/<mod-id> source sets and are indexed by
META-INF/auto_storage/compat-modules.json. The loader checks every required mod
ID before resolving the module class. A present module uses
AutoStorageCompatModule plus the same registration facade available to
external addons. Every bundled entry must declare at least one required target
mod; an empty requirement list is rejected before classloading.
External addons do not add entries to Auto Storage's bundled module index. They are ordinary NeoForge mods with their own entrypoint and dependency metadata.
The API artifact version equals the Auto Storage mod version.
- Before Auto Storage 1.0, the SDK is alpha. Breaking API changes require a minor-version increase and release-note migration section.
- Patch releases must remain source- and binary-compatible with the preceding release in the same minor line.
- Registry IDs and persisted resource/descriptor IDs are data contracts and must not be reused for different semantics.
- Optional target-mod versions in CI are representative evidence only. Addons should declare semantic ranges they actually support and report incompatible target changes clearly.
CI compiles a different-package fixture and the example against only the
classified API jar, compares its public javap output with
api/api-surface.txt, scans API bytecode for client/optional-mod links, builds
each bundled module against the API jar plus only its target dependencies, and
runs isolated plus all-mod GameTest gates. Any intentional API change must
review and update that snapshot in the same change.