StructureLib API documentation.

Key API class outline

Here we outline a few key API classes/interfaces.

{@link com.gtnewhorizon.structurelib.structure.IStructureDefinition}

This is the structure definition of your multi. You will have one `IStructureDefinition` for each multi.

{@link com.gtnewhorizon.structurelib.structure.IStructureElement}

These are how you define which block will be accepted.

{@link com.gtnewhorizon.structurelib.structure.StructureUtility}

This is the standard library of {@link com.gtnewhorizon.structurelib.structure.IStructureElement IStructureElement}s. The Standard Library contains structure elements that handle basic block element and elements that allow composing these basic elements.

{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable}

This interface should be implemented by tile entities that support creative autoplacing and hint particle emitting. You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition IStructureDefinition} and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int) build()} or {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#hints(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int) hints()} method here.

{@link com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable}

This interface is an enhancement over {@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable IConstructable}. If your tile entities also support survival autoplacing then you should implement this. You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition IStructureDefinition} and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, IItemSource, EntityPlayerMP, boolean) survivalBuild()}.

{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructableProvider}

This interface should be implemented on tile entities that will delegate all constructable or survival construable functionality to some other class. This is mostly only to support gregtech meta tile entity system.

Return null if the delegate does not exist.

{@link com.gtnewhorizon.structurelib.util.InventoryUtility}

This class backs the standard implementations of {@link com.gtnewhorizon.structurelib.structure.IItemSource}. You might want to register new inventory provider or inventory extractor there to facilitate survival auto place.

{@link com.gtnewhorizon.structurelib.alignment.IAlignmentProvider}, {@link com.gtnewhorizon.structurelib.alignment.IAlignment} and {@link com.gtnewhorizon.structurelib.alignment.IAlignmentLimits}

These interfaces will be used to implement rotation. Provide a framework to rotate the block. You can skip this if you store ExtendedFacing and do rotation in other means.

{@link com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor}

This class contains the accessors for channel data held on a trigger item. See below for more information.

{@link com.gtnewhorizon.structurelib.StructureLibAPI}

This class contains the low level utility used for implementing new {@link com.gtnewhorizon.structurelib.structure.IStructureElement}. You will also need methods in this class to while implementing rotation.

IF THERE IS A SAME NAMED METHOD IN OTHER CLASS, ONLY USE METHODS FOUND IN THIS CLASS, NOT OTHER CLASS. This abstraction is necessary so we can change how underlying code work (e.g. how hint particles are rendered) without breaking client code.

{@link com.gtnewhorizon.structurelib.StructureEvent}

This class is the base class of all events that will be posted while a IStructureDefinition are visited. These events are disabled by default, and can be enabled by calling {@link com.gtnewhorizon.structurelib.StructureLibAPI#enableInstrument(Object)}. To receive these events, register an event handler to {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS}. Handlers should filter the events received by {@link com.gtnewhorizon.structurelib.StructureEvent#getInstrumentIdentifier()}.

These events are intended for third party tools to inspect structure details. Normal users should not use these at all.

Important Concepts

Context Object

Context Object are what you pass to {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)} and alike as first parameter. It will hold the necessary states for {@link com.gtnewhorizon.structurelib.structure.IStructureElement}. It's usually, but not necessarily, the multiblock controller.

Standard structure element library offers method to switch context objects if needed.

Trigger Item

Trigger Item is the item used to trigger hint spawn, creative build and survival auto place. The trigger item will be passed to relevant classes as an indicator of structure tier. The item type and stack tag compound of this item stack are opaque objects that should not be parsed.

StructureLib comes with a trigger item implementation. It does register it to the game registries, but does not come with a recipe. Usually there is no reason to reimplement this on your own.

Channels

Trigger item carries the player's choice of structure tier by its stack size. When one multi have multiple tiered component that can have interleaving tier, this becomes an obnoxious restriction, as one ItemStack cannot have two stack sizes at the same time. Here we use the channels to bypass this restriction.

Basically, channels are information stored on the trigger item's NBT tags. One trigger item can carry multiple channels. Each channel can be identified with lower case strings and has the value being one positive Java int. There will be one and only one master channel and 0 or more sub channel. When a non-existent sub channel is queried, master channel will be used instead to fill its place.

Structure Definition Author and/or Structure Element Author can choose whether they use a channel and which channel to use.

Structure Element Author will usually not need to consider channels, i.e. proceed as if this concept does not exist, unless it must take input from multiple channels.

Structure Definition Author will consider how to map each channel to each tiered component. He will be using `com.gtnewhorizon.structurelib.structure.StructureUtility.withChannel` to direct structure element on which channel to use. Conceptually, it will return an ItemStack with the master channel being the value of selected channel. In implementation, withChannel will return a new ItemStack with same item, nbt but different stack size.

It's worth mentioning that the returned new ItemStack will still have all the channel data on it, so a multi-channel structure element can still query the channel it wants just fine. It is however impossible to recover the original master channel.

Metadata-only channels

A channel does not need to represent a block or an element in a structure shape. Structure definitions can declare metadata-only channels for values such as a widget selection or a variable structure height. These declarations are independent of addElement and do not require a fake block or a shape character.

IStructureDefinition.<Controller>builder()
        .addChannel("height", 3, 100)
        .addChannel("length", 4, 50)
        .addChannel(IChannelDefinition.unbounded("widget"))
        .addElement('A', ...)
        .build();

Channel values are still stored as integers in the trigger item's channels NBT compound. The generic channel GUI has no active structure definition, so it edits integers and does not impose one structure's range on every other structure using the same channel name. Structure-aware code reads values through IStructureDefinition#getChannelData, which uses the definition's default when the subchannel is absent and then applies that definition's local normalization policy.

Existing elements wrapped with StructureUtility.withChannel are discovered automatically. An explicit channel declaration supplies its local range and normalization policy for that structure. The same channel name may therefore use different ranges in different structure definitions.

why is this called channel?

Just give me a better name and I'd do the renaming.

Channel Descriptions

Each channel can have one description associated with it per mod. Channel description should briefly describe how a specific channel is used for inside a specific mod. They will be displayed to player in channel editor GUI that comes with StructureLib. The registration process can be done either via direct method call to {@link com.gtnewhorizon.structurelib.StructureLibAPI#registerChannelDescription(String, String, String)}, or by sending an IMC message. After registration, the channel will also show up as autocomplete options when players are editing channels.

Here is an example of sending an IMC message to register two channels and their description. In this example, we assume your modid is my_mod and your mod will ship with localization entries for localization key channels.my_mod.channel_1 and channels.my_mod.channel_2

        NBTTagCompound command = new NBTTagCompound();
        NBTTagList tags = new NBTTagList();
        String[] channels = {"channel_1", "channel_2"};
        for (String channel : channels) {
            NBTTagCompound tag = new NBTTagCompound();
            tag.setString("Channel", channel);
            tag.setString("Description", "channels.my_mod." + channel);
            tags.appendTag(tag);
        }
        command.setTag("Channels", tags);
        FMLInterModComms.sendMessage("structurelib", "register_channel", command);

There is no strict rules over whether you should do direct API method call or send IMC messages. You can choose whichever you prefer.

Channel Indicator Items

You can associate an ItemStack with a given (channel, value) pair. It is usually (but not required to be) the ItemBlock for the blocks to be placed by this (channel, value) pair. Player will be able to drag and drop this item to the builtin channel configuration GUI. The registration process can be done either via direct method call to {@link com.gtnewhorizon.structurelib.StructureLibAPI#registerChannelItem(String, String, int, net.minecraft.item.ItemStack)}, or by sending an IMC message. After registration, the channel will also show up as autocomplete options when players are editing channels.

Here is an example of sending an IMC message to register 3 vanilla sandstone as different indicator for channel "sandstone", with block metadata plus 1 as channel value. In this example, we assume your modid is my_mod.

        NBTTagCompound command = new NBTTagCompound();
        NBTTagList items = new NBTTagList();
        int[] metas = {0, 1, 2};
        for (int meta : metas) {
            NBTTagCompound tag = new NBTTagCompound();
            tag.setTag("Item", new ItemStack(Blocks.sandstone, 1, meta).writeToNBT(new NBTTagCompound()));
            NBTTagList channels = new NBTTagList();
            NBTTagCompound channel = new NBTTagCompound();
            channel.setString("Channel", "sandstone");
            channel.setInteger("Value", meta + 1);
            channels.appendTag(channel);
            tag.setTag("Channels", channels);
            items.appendTag(tag);
        }
        command.setTag("Items", items);
        FMLInterModComms.sendMessage("structurelib", "register_channel_item", command);

There is no strict rules over whether you should do direct API method call or send IMC messages. You can choose whichever you prefer.

Examples

For Structure Definition Author

GT++ Chemical Plant is a multi with 4 distinctly tiered component.

It will use

Its structure code will then look like
.addElement('a', shellStructureElement())
.addElement('b', withChannel("casing", machineCasingStructureElement()))
.addElement('c', withChannel("pipe", pipeCasingStructureElement()))
.addElement('d', withChannel("coil", coilStructureElement()))
whereas all these structure elements are designed without considering channels whatsoever

Multi channel input Structure Element

Suppose your hatch element has a required facing and can accept arbitrary meta tile entity, so it will need both meta id and facing input.

It will use

Its code will then look like

import com.gtnewhorizon.structurelib.structure.IStructureElement;

public class MyHatchElement<T> implements IStructureElement<T> {
    //...
   {@literal @}Override
    public PlaceResult survivalPlaceBlock(
            Object o,
            World world,
            int x,
            int y,
            int z,
            ItemStack trigger,
            IItemSource s,
            EntityPlayerMP actor,
            Consumer chatter) {
        if (shouldSkip(o, world, x, y, z)) return PlaceResult.SKIP;
        if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
        ItemStack taken = s.takeOne(filterByID(ChannelDataAccessor.getChannelData(trigger, "id")).andThen(filterByFacing(trigger.stackSize)));
        if (taken == null) return PlaceResult.REJECT;
        return StructureUtility.survivalPlaceBlock(taken, ItemStackPredicate.NBTMode.IGNORE, null, true, world, x, y, z, s, actor);
    }
}
The code for placeBlock() and spawnHint() will be left as an exercise for the reader.

Instrumenting StructureLib

StructureLib support third party tools to poke into structure's internals via events. This is done by listening to {@link com.gtnewhorizon.structurelib.StructureEvent} and its subclasses on the {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS forge main event bus}.

Please refer to {@link com.gtnewhorizon.structurelib.StructureEvent} to see what events are available.

Extending Inventory Utility

{@link com.gtnewhorizon.structurelib.util.InventoryUtility} is how structurelib survival autoplace pulls item for autoplacing. It has two key concepts: inventory providers and stack extractors.

Inventory Providers are classes that retrieve an Iterable of ItemStacks for examination, given an EntityPlayerMP as identifier. They are also responsible for marking the relevant item container as dirty, so changes can be commited to disk properly when world save happens. Examples include: main player inventory, vanilla ender chest, baubles inventory. StructureLib comes with main player inventory and vanilla ender chest built in.

Stack Extractors are classes that retrieve items from another item. Examples include: Forestry backpacks, ME network over ME Wireless Terminals.

Both of these are managed internally by {@link com.gtnewhorizon.structurelib.SortedRegistry}. By default, elements registered will be sorted in lexicographical order in ascending order. However, facilities in StructureLib will allow an end user to tweak these registries as their wish. They can disable, reorder, reenable any element within the registry, either in game, at main menu or with the game stopped, Their changes will be synced to server as well, should they be playing on a dedicated server.

This has the following important implications

Therefore, if your stack extractor can somehow correlate with aonther one, make sure it still works if the calling order is inverted. At the very least, inform the player about this ordering constraint. A future StructrueLib refactor to specify this dependency info is possible, but the chance is low.

Localization

Both of these would show up in config GUI of StructureLib. Structurelib uses the following localization keys in that GUI.