Files
StructureLib/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java
T
Nikolay S.andGlease 2c5fa02a9e Simple Cleanups (#61)
Cleans up the code a tiny bit, moving comments around, removing some
`@SuppressWarnings` and some unused code, and using modern Java
features.

These changes should require no modifications to any outside mods.

---------

Co-authored-by: Glease <4586901+Glease@users.noreply.github.com>
2026-06-19 15:31:17 +02:00

3477 lines
157 KiB
Java

package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
import static java.lang.Integer.MIN_VALUE;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.ChatStyle;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizon.structurelib.StructureEvent.StructureElementVisitedEvent;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult;
import com.gtnewhorizon.structurelib.structure.adders.IBlockAdder;
import com.gtnewhorizon.structurelib.structure.adders.ITileAdder;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import cpw.mods.fml.common.registry.GameRegistry;
/**
* A brief index of everything contained
* <h2>Control blocks</h2> A lot of these might seem to make more sense as a default function on
* {@link IStructureElement}. However, in that case javac generic will often fail to infer the type for you, so we have
* to define them as static methods
* <h2>If block</h2> Provide if block, allowing downstream call only if user specified conditions are meet (i.e.
* predicate returns true)
* <ul>
* <li>{@link #onlyIf(Predicate, IStructureElement)} and its overloads</li>
* </ul>
* <h2>Switch block</h2> Provide switch block, allowing to select a downstream element by using a key or index computed
* from trigger item and/or context object.
* <ul>
* <li>{@link #partitionBy(Function, IStructureElement[])} and its overloads</li>
* </ul>
* <h2>Or Chain</h2> Provide a short-circuiting OR chain.
* <ul>
* <li>{@link #ofChain(IStructureElement[])} and its overloads</li>
* </ul>
* <h3>Side effect</h3> Provide callbacks on various occasion
* <ul>
* <li>{@link #onElementPass(Consumer, IStructureElement)}: Call an callback upon element check success</li>
* <li>{@link #onElementFail(Consumer, IStructureElement)}: Call an callback upon element check failure</li>
* </ul>
*
* <h2>Other Primitives</h2>
* <h3>Error statement</h3> Unconditionally return false.
* <ul>
* <li>{@link #error()}</li>
* </ul>
* <h3>Context Object Change</h3> Switch the context object.
* <ul>
* <li>{@link #withContext(IStructureElement)}: switch context to the extended context</li>
* </ul>
* <h3>Channel Change</h3> Change the channel. See <a href="{@docRoot}/overview-summary.html#channels">Channels section
* on overview</a> for more information.
* <ul>
* <li>{@link #withChannel(String, IStructureElement)}: switch channel using
* {@link ChannelDataAccessor#withChannel(ItemStack, String)} when a trigger item is present.</li>
* </ul>
* <h3>Spawn hint particle</h3> Just spawn a hint. Do nothing otherwise. Useful when you want to override the hint
* provided by another structure element, or as a fallback to a structure element that is check only.
* <ul>
* <li>{@link #ofHint(int)}, {@link #ofHintDeferred(Supplier)} and its overloads</li>
* </ul>
*
* <h2>Actual Element</h2> These elements are elements that can actually do structure check, spawn hint and do auto
* place. Elements listed in above 2 sections are not going to be helpful without elements like these. They also serve
* as a reference implementation for your own custom {@link IStructureElement} implementations.
* <h3>Simple Block Element</h3> These accept one or more different block types in one location.
* <ul>
* <li>{@link #ofBlock(Block, int, Block, int)}, {@link #ofBlocksFlat(Map, Block, int)},
* {@link #ofBlocksMap(Map, Block, int)} and their overloads: the most basic form</li>
* <li>{@link #ofBlockHint(Block, int, Block, int)}, {@link #ofBlocksFlatHint(Map, Block, int)},
* {@link #ofBlocksMapHint(Map, Block, int)}: these are the same as above, but will not do autoplace</li>
* <li>{@link #isAir()}, {@link #notAir()}: They are supplied by default under the identifier {@code '-'} and
* {@code '+'} respectively, but are provided here regardless in case you want to use them as a fallback.</li>
* </ul>
* <h3>Complex Block Element</h3> In case your logic on determining which block is accepted is complex, use these.
* <ul>
* <li>{@link #ofBlockAdder(IBlockAdder, Block, int)}, {@link #ofBlockAdderHint(IBlockAdder, Block, int)} and their
* overloads: hands off actual adder logic to an {@link IBlockAdder} you supplied. Save you the boilerplate of querying
* block type and block meta from {@link World}</li>
* <li>{@link #ofBlocksTiered(ITierConverter, Object, BiConsumer, Function)} and its overloads: accept a series of
* blocks that each may have different tier, but only allow one single tier for anywhere this element is used (e.g.
* coils).</li>
* <li>{@link #ofTileAdder(ITileAdder, Block, int)}, {@link #ofSpecificTileAdder(BiPredicate, Class, Block, int)}:
* Similar to block adder, but for tile entities.</li>
* </ul>
*
* <h2>Helper Methods</h2> These don't construct a {@link IStructureElement}, but is helpful to the instantiation or
* implementation of these.
* <ul>
* <li>{@link #survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, EntityPlayer, Consumer)} and its
* overloads: Helper method to cut common boilerplate for implementing
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}</li>
* <li>{@link #getPseudoJavaCode(World, ExtendedFacing, int, int, int, int, int, int, Function, int, int, int, boolean)}:
* Generate a structure code template from existing structure.</li>
* <li>{@link #iterate(World, ExtendedFacing, int, int, int, int, int, int, boolean, int, int, int, IBlockPosConsumer, Runnable, Runnable)}
* and its overloads: help iterate over a coordinate space.</li>
* </ul>
*/
public class StructureUtility {
private static final String NICE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz=|!@#$%&()[]{};:<>/?_,.*^'`";
@SuppressWarnings("rawtypes")
private static final Map<Vec3Impl, IStructureNavigate> STEP = new HashMap<>();
@SuppressWarnings("rawtypes")
private static final IStructureElement AIR = new StructureElement_Bridge() {
@Override
public boolean check(Object t, World world, int x, int y, int z) {
return world.isAirBlock(x, y, z);
}
@Override
public boolean couldBeValid(Object t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(Object o, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), 13);
return true;
}
@Override
public boolean placeBlock(Object o, World world, int x, int y, int z, ItemStack trigger) {
world.setBlock(x, y, z, Blocks.air, 0, 2);
return false;
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(Object o, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (check(o, world, x, y, z)) return PlaceResult.SKIP;
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, env.getActor())) return PlaceResult.REJECT;
world.setBlock(x, y, z, Blocks.air, 0, 2);
return PlaceResult.ACCEPT;
}
};
@SuppressWarnings("rawtypes")
private static final IStructureElement NOT_AIR = new StructureElement_Bridge() {
@Override
public boolean check(Object t, World world, int x, int y, int z) {
return !world.isAirBlock(x, y, z);
}
@Override
public boolean couldBeValid(Object t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(Object o, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), 14);
return true;
}
@Override
public boolean placeBlock(Object o, World world, int x, int y, int z, ItemStack trigger) {
world.setBlock(x, y, z, StructureLibAPI.getBlockHint(), 14, 2);
return true;
}
@Override
public BlocksToPlace getBlocksToPlace(Object o, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return BlocksToPlace.create(Objects::nonNull);
}
@Override
public PlaceResult survivalPlaceBlock(Object o, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (check(o, world, x, y, z)) return PlaceResult.SKIP;
// user should place anything here.
// maybe make this configurable, but for now we try to take some cobble from user
if (env.getSource().takeOne(new ItemStack(Blocks.cobblestone), false)) {
world.setBlock(x, y, z, Blocks.cobblestone, 0, 2);
}
return PlaceResult.REJECT;
}
};
@SuppressWarnings("rawtypes")
private static final IStructureElement ERROR = new StructureElement_Bridge() {
@Override
public boolean check(Object t, World world, int x, int y, int z) {
return false;
}
@Override
public boolean couldBeValid(Object t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(Object o, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), 15);
return true;
}
@Override
public boolean placeBlock(Object o, World world, int x, int y, int z, ItemStack trigger) {
return true;
}
@Override
public PlaceResult survivalPlaceBlock(Object o, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return PlaceResult.REJECT;
}
};
private StructureUtility() {}
/**
* This is a helper method for implementing
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
* <p>
* This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at
* given location. This method assume block at given coord is NOT acceptable, but may or may not be trivially
* replaceable This method might yield error messages to the player only if there is likely a programming error or
* exploit.
*
* @param s drain resources from this place
* @param actor source of action. cannot be null.
*/
public static PlaceResult survivalPlaceBlock(Block block, int meta, World world, int x, int y, int z, IItemSource s,
EntityPlayerMP actor) {
return survivalPlaceBlock(block, meta, world, x, y, z, s, (EntityPlayer) actor);
}
/**
* This is a helper method for implementing
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
* <p>
* This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at
* given location. This method assume block at given coord is NOT acceptable, but may or may not be trivially
* replaceable This method might yield error messages to the player only if there is likely a programming error or
* exploit.
*
* @param s drain resources from this place
* @param actor source of action. cannot be null.
*/
public static PlaceResult survivalPlaceBlock(Block block, int meta, World world, int x, int y, int z, IItemSource s,
EntityPlayer actor) {
return survivalPlaceBlock(block, meta, world, x, y, z, s, actor, null);
}
/**
* This is a helper method for implementing
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
* <p>
* This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at
* given location. This method assume block at given coord is NOT acceptable, but may or may not be trivially
* replaceable This method might yield error messages to the player only if there is likely a programming error or
* exploit. This method might yield error messages to the chatter.
*
* @param s drain resources from this place
* @param actor source of action. cannot be null.
* @param chatter normal error destination. can be null to suppress them.
*/
public static PlaceResult survivalPlaceBlock(Block block, int meta, World world, int x, int y, int z, IItemSource s,
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return survivalPlaceBlock(block, meta, world, x, y, z, s, (EntityPlayer) actor, chatter);
}
/**
* This is a helper method for implementing
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
* <p>
* This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at
* given location. This method assume block at given coord is NOT acceptable, but may or may not be trivially
* replaceable This method might yield error messages to the player only if there is likely a programming error or
* exploit. This method might yield error messages to the chatter.
*
* @param s drain resources from this place
* @param actor source of action. cannot be null.
* @param chatter normal error destination. can be null to suppress them.
*/
public static PlaceResult survivalPlaceBlock(Block block, int meta, World world, int x, int y, int z, IItemSource s,
EntityPlayer actor, Consumer<IChatComponent> chatter) {
if (block == null) throw new NullPointerException();
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
Item itemBlock = Item.getItemFromBlock(block);
int itemMeta = itemBlock instanceof ISpecialItemBlock
? ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta)
: meta;
ItemStack stack = new ItemStack(itemBlock, 1, itemMeta);
if (!s.takeOne(stack, true)) {
if (chatter != null) chatter.accept(
new ChatComponentTranslation("structurelib.autoplace.missing_block", stack.func_151000_E()));
return PlaceResult.REJECT;
}
if (block instanceof ICustomBlockSetting blockCustom) {
blockCustom.setBlock(world, x, y, z, meta);
} else if (!stack.copy()
.tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f)) {
return PlaceResult.REJECT;
}
if (!s.takeOne(stack, false)) {
// rollback
world.setBlockToAir(x, y, z);
}
return PlaceResult.ACCEPT;
}
/**
* This is a helper method for implementing
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
* <p>
* This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at
* given location. This method assume block at given coord is NOT acceptable, but may or may not be trivially
* replaceable This method might yield error messages to the player only if there is likely a programming error or
* exploit.
*
* @param stack a valid stack with stack size of exactly 1. Must be of an ItemBlock!
* @param s drain resources from this place
* @param actor source of action. cannot be null.
*/
public static PlaceResult survivalPlaceBlock(ItemStack stack, NBTMode nbtMode, NBTTagCompound tag,
boolean assumeStackPresent, World world, int x, int y, int z, IItemSource s, EntityPlayerMP actor) {
return survivalPlaceBlock(stack, nbtMode, tag, assumeStackPresent, world, x, y, z, s, (EntityPlayer) actor);
}
/**
* This is a helper method for implementing
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
* <p>
* This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at
* given location. This method assume block at given coord is NOT acceptable, but may or may not be trivially
* replaceable This method might yield error messages to the player only if there is likely a programming error or
* exploit.
*
* @param stack a valid stack with stack size of exactly 1. Must be of an ItemBlock!
* @param s drain resources from this place
* @param actor source of action. cannot be null.
*/
public static PlaceResult survivalPlaceBlock(ItemStack stack, NBTMode nbtMode, NBTTagCompound tag,
boolean assumeStackPresent, World world, int x, int y, int z, IItemSource s, EntityPlayer actor) {
return survivalPlaceBlock(stack, nbtMode, tag, assumeStackPresent, world, x, y, z, s, actor, null);
}
/**
* This is a helper method for implementing
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
* <p>
* This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at
* given location. This method assume block at given coord is NOT acceptable, but may or may not be trivially
* replaceable This method might yield error messages to the player only if there is likely a programming error or
* exploit. This method might yield error messages to the chatter.
*
* @param stack a valid stack with stack size of exactly 1. Must be of an ItemBlock!
* @param s drain resources from this place
* @param actor source of action. cannot be null.
* @param chatter normal error destination. can be null to suppress them.
*/
public static PlaceResult survivalPlaceBlock(ItemStack stack, NBTMode nbtMode, NBTTagCompound tag,
boolean assumeStackPresent, World world, int x, int y, int z, IItemSource s, EntityPlayerMP actor,
@Nullable Consumer<IChatComponent> chatter) {
return survivalPlaceBlock(
stack,
nbtMode,
tag,
assumeStackPresent,
world,
x,
y,
z,
s,
(EntityPlayer) actor,
chatter);
}
/**
* This is a helper method for implementing
* {@link IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)}
* <p>
* This method will try to look up an {@link ItemBlock} for given block and meta from {@code s} and place it at
* given location. This method assume block at given coord is NOT acceptable, but may or may not be trivially
* replaceable This method might yield error messages to the player only if there is likely a programming error or
* exploit. This method might yield error messages to the chatter.
*
* @param stack a valid stack with stack size of exactly 1. Must be of an ItemBlock!
* @param s drain resources from this place
* @param actor source of action. cannot be null.
* @param chatter normal error destination. can be null to suppress them.
*/
public static PlaceResult survivalPlaceBlock(ItemStack stack, NBTMode nbtMode, NBTTagCompound tag,
boolean assumeStackPresent, World world, int x, int y, int z, IItemSource s, EntityPlayer actor,
@Nullable Consumer<IChatComponent> chatter) {
if (stack == null) throw new NullPointerException();
if (stack.stackSize != 1) throw new IllegalArgumentException();
if (!(stack.getItem() instanceof ItemBlock)) throw new IllegalArgumentException();
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
if (!assumeStackPresent && !s.takeOne(stack, true)) {
if (chatter != null) chatter.accept(
new ChatComponentTranslation("structurelib.autoplace.missing_block", stack.func_151000_E()));
return PlaceResult.REJECT;
}
if (!stack.copy().tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f))
return PlaceResult.REJECT;
if (!s.takeOne(stack, false))
// this is bad! probably an exploit somehow. Let's nullify the block we just placed instead
world.setBlockToAir(x, y, z);
return PlaceResult.ACCEPT;
}
/**
* Return a structure element that only allow air (or air equivalents, like Railcraft hidden blocks). You usually
* don't need to call this yourselves. Use {@code -} in shape to automatically use this. Provided nontheless in case
* you want this as a fallback to something else.
*/
@SuppressWarnings("unchecked")
public static <T> IStructureElement<T> isAir() {
return AIR;
}
/**
* Return a structure element that allow anything but air (or air equivalents, like Railcraft hidden blocks). You
* usually don't need to call this yourselves. Use {@code +} in shape to automatically use this. Provided nontheless
* in case you want this as a fallback to something else.
*/
@SuppressWarnings("unchecked")
public static <T> IStructureElement<T> notAir() {
return NOT_AIR;
}
/**
* Check returns false. Placement is always handled by this and does nothing. Makes little to no use it in fallback
* chain.
*/
@SuppressWarnings("unchecked")
public static <T> IStructureElement<T> error() {
return ERROR;
}
// region hint only
/**
* Spawn a hint with given amount of dots. Check always returns: true. Only useful as a fallback, e.g.
* {@link #ofBlockUnlocalizedName(String, String, int, IStructureElement)}
*/
public static <T> IStructureElementNoPlacement<T> ofHint(int dots) {
int meta = dots - 1;
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return true;
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), meta);
return false;
}
};
}
/**
* Spawn a hint with given textures. Check always returns: true. Only useful as a fallback, e.g.
* {@link #ofBlockUnlocalizedName(String, String, int, IStructureElement)}
*/
public static <T> IStructureElementNoPlacement<T> ofHintDeferred(Supplier<IIcon[]> icons) {
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return true;
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, icons.get());
return false;
}
};
}
/**
* Spawn a hint with given amount of textures and tint. Check always returns: true. Only useful as a fallback, e.g.
* {@link #ofBlockUnlocalizedName(String, String, int, IStructureElement)}
*/
public static <T> IStructureElementNoPlacement<T> ofHintDeferred(Supplier<IIcon[]> icons, short[] RGBa) {
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return true;
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticleTinted(world, x, y, z, icons.get(), RGBa);
return false;
}
};
}
// endregion
// region block
/**
* A more primitive variant of {@link #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)} Main
* difference is this one is check only.
* <p>
* Note that if tierExtractor returns a null, this block will be rejected immediately. This makes null a potent
* candidate for notSet.
* <p>
* See documentation on the other overload for more information.
*
* @see #ofBlocksTiered(ITierConverter, Object, BiConsumer, Function)
*/
public static <T, TIER> IStructureElementCheckOnly<T> ofBlocksTiered(ITierConverter<TIER> tierExtractor,
@Nullable TIER notSet, BiConsumer<T, TIER> setter, Function<T, TIER> getter) {
if (tierExtractor == null) throw new IllegalArgumentException();
if (setter == null) throw new IllegalArgumentException();
if (getter == null) throw new IllegalArgumentException();
return (t, world, x, y, z) -> {
Block block = world.getBlock(x, y, z);
int meta = world.getBlockMetadata(x, y, z);
TIER tier = tierExtractor.convert(block, meta);
if (tier == null) return false;
TIER current = getter.apply(t);
if (Objects.equals(notSet, current)) {
if (Objects.equals(notSet, tier)) {
if (PANIC_MODE) {
throw new AssertionError("tierExtractor should never return notSet: " + notSet);
} else {
StructureLib.LOGGER.error("#########################################");
StructureLib.LOGGER.error("#########################################");
StructureLib.LOGGER
.error("tierExtractor should never return notSet: {}", notSet, new Throwable());
StructureLib.LOGGER.error("#########################################");
StructureLib.LOGGER.error("#########################################");
}
}
setter.accept(t, tier);
return true;
}
return Objects.equals(current, tier);
};
}
/**
* Element representing a component with different tiers. Your multi will accept any of them (as long as player does
* not mix tier (*)), but would like to know which is used. Player can use more blueprint to get hints denoting more
* advanced components.
* <p>
* Above all else, you need to decide what type of tier you are going to use. For simpler cases, Integer is a good
* choice. You can also use an existing Enum. It can also be a String or a
* {@link net.minecraft.util.ResourceLocation}, or basically anything. It doesn't even have to implement
* {@link Comparable}.
* <p>
* If your tier extractor function returns null on any (Block, meta) pair, then it means this block is rejected.
* This also implies null can never be a valid tier.
* <p>
* This structure element require you will reset the backing storage of getter in your own code, such that in every
* round of structure checking, the first call to getter would always return notSet. You can also make getter to
* return other value on that occasion to forcefully select a certain tier, but then you'd probably be better off
* using block adders or {@link #ofBlock(Block, int, Block, int)} and its overloads
* <p>
* There is yet no TileEntity counterpart of this utility. Feel free to submit a PR to add it.
* <p>
* (*): Implementation wise, this structure element will only accept a block if its tier is the same as existing
* tier or if existing tier is unset (i.e. value of third argument). If there are multiple (block, tier) tuple for a
* given tier, then player will be allowed to use multiple kinds of blocks, but their tier is guaranteed to be the
* same.
* <p>
* <h4>WARNING</h4> <b>You SHOULD NOT return notSet from your tierExtractor.</b> If you do so, we will have this
* chain of events:
* <ul>
* <li>at check start, tier is reset to notSet
* <li>the tier extractor returns notSet for first block, so since ofBlocksTiered finds your current tier is notSet,
* and the new block's tier is the same as that, so it's accepted
* <li>at 2nd, 3rd..., same thing in 2 happens
* <li>at last occurrence, player placed a valid block with a tier not notSet. ofBlocksTiered now sets your current
* tier to this tier, and accept the block
* <li>check got notified structure is sound, and tier is not notSet. it marks the structure as complete and start
* doing its business
* <li>Player enjoy a (probably) hilariously shaped multi and (probably) reduced build cost.
* </ul>
* <h4>Example Implementation</h4>
* <p>
* Assume you have 16 tier, each map to one particular block's 16 different meta. You will usually want something
* like this
*
* <pre>
* public class Tile extends TileEntity {
* private static final Block B = getTargetBlock();
* private static final IStructureDefinition&lt;Tile&gt; S = IStructureDefinition.&lt;Tile&gt;builder()
* // ... addShape, and other elements
* .addElement('c', ofBlocksTiered((b, m) -&gt; b == B ? m : null,
* IntStream.range(0, 16).mapToObj(i -&gt; Pair.of(B, i)).collect(Collectors.toList()),
* -1,
* (t, m) -&gt; t.tier = m,
* t -&gt; t.tier))
* .build();
* private int tier;
*
* public boolean check() {
* tier = -1;
* if (!S.check(...))
* return false;
* return isTierAcceptable(tier);
* }
* }
* </pre>
*
* Important bits of this example implementation:
* <ul>
* <li>The supplied ITierConverter returns null for an invalid block</li>
* <li>Possible tier returned by ITierConverter is Integer object 0 to 15.</li>
* <li>notSet is set to -1. tier got reset to this value by caller before calling into
* {@link IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)}</li>
* <li>notSet is never a tier returned by ITierConverter.</li>
* </ul>
* <p>
* As a friendly reminder, the above characteristics are only for this simply demo, not as a general restriction
* imposed by ofBlocksTiered.
*
* @param tierExtractor a function to extract tier info from a block. This function can return null and will never
* be passed a null block or an invalid block meta. If this function returns null, then the
* block would be considered invalid.
* @param allKnownTiers A list of all known tiers as of calling. Can be empty or null. No hint will be spawned if
* empty or null. Cannot have null elements. First element denotes the most primitive tier.
* Last element denotes the most advanced tier. If not all tiers are available at definition
* construction time, use {@link #lazy(Supplier)} or its overloads to delay a bit. This list is
* only for hint particle spawning and survival build. The hint/autoplace code will choose 1st
* pair to spawn/place if trigger item has master channel data of 1, 2nd pair if 2, and so on.
* @param notSet The value returned from {@code getter} when there were no tier info found in T yet. Cannot
* be null.
* @param getter a function to retrieve the current tier from context object
* @param setter a function to set the current tier into context object
*/
public static <T, TIER> IStructureElement<T> ofBlocksTiered(ITierConverter<TIER> tierExtractor,
@Nullable List<Pair<Block, Integer>> allKnownTiers, @Nullable TIER notSet, BiConsumer<T, TIER> setter,
Function<T, TIER> getter) {
List<String> descriptions = null;
if (allKnownTiers != null) {
descriptions = new java.util.ArrayList<>();
for (Pair<Block, Integer> tier : allKnownTiers) {
Item item = Item.getItemFromBlock(tier.getLeft());
if (item != null) {
descriptions.add(new ItemStack(item, 1, tier.getRight()).getUnlocalizedName() + ".name");
}
}
}
return ofBlocksTiered(tierExtractor, allKnownTiers, notSet, setter, getter, descriptions);
}
/**
* Like {@link #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)}, but with an explicit
* description that will be returned by {@link IStructureElement#getDescription(Object)}.
*
* @param description the description lang keys to attach to this element, or null for no description
* @see #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)
*/
public static <T, TIER> IStructureElement<T> ofBlocksTiered(ITierConverter<TIER> tierExtractor,
@Nullable List<Pair<Block, Integer>> allKnownTiers, @Nullable TIER notSet, BiConsumer<T, TIER> setter,
Function<T, TIER> getter, @Nullable List<String> description) {
List<Pair<Block, Integer>> hints = allKnownTiers == null ? Collections.emptyList() : allKnownTiers;
if (hints.stream().anyMatch(Objects::isNull)) throw new IllegalArgumentException();
IStructureElementCheckOnly<T> check = ofBlocksTiered(tierExtractor, notSet, setter, getter);
return new StructureElement_Bridge<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return check.check(t, world, x, y, z);
}
private Pair<Block, Integer> getHint(ItemStack trigger) {
if (hints.isEmpty()) return null;
return hints.get(Math.min(Math.max(trigger.stackSize, 1), hints.size()) - 1);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
Pair<Block, Integer> hint = getHint(trigger);
if (hint == null) return true;
TIER hintTier = tierExtractor.convert(hint.getKey(), hint.getValue());
Block block = world.getBlock(x, y, z);
int meta = world.getBlockMetadata(x, y, z);
TIER worldTier = tierExtractor.convert(block, meta);
// if the block is in the same tier as the hint block, this could be valid
return Objects.equals(hintTier, worldTier);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
Pair<Block, Integer> hint = getHint(trigger);
if (hint == null) return false;
StructureLibAPI.hintParticle(world, x, y, z, hint.getKey(), hint.getValue());
return true;
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
Pair<Block, Integer> hint = getHint(trigger);
if (hint == null) return false;
if (hint.getKey() instanceof ICustomBlockSetting block) {
block.setBlock(world, x, y, z, hint.getValue());
} else {
world.setBlock(x, y, z, hint.getKey(), hint.getValue(), 2);
}
return true;
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
Pair<Block, Integer> hint = getHint(trigger);
return BlocksToPlace.create(hint.getKey(), hint.getValue());
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
Pair<Block, Integer> hint = getHint(trigger);
if (hint == null) return PlaceResult.REJECT;
Block block = world.getBlock(x, y, z);
int meta = world.getBlockMetadata(x, y, z);
TIER tier = tierExtractor.convert(block, meta);
if (Objects.equals(tier, tierExtractor.convert(hint.getKey(), hint.getValue())))
return PlaceResult.SKIP;
return StructureUtility.survivalPlaceBlock(
hint.getKey(),
hint.getValue(),
world,
x,
y,
z,
env.getSource(),
env.getActor(),
env.getChatter());
}
@Nullable
@Override
public List<String> getDescription(T context) {
TIER currentTier = getter.apply(context);
if (Objects.equals(currentTier, notSet)) return description;
for (Pair<Block, Integer> pair : hints) {
TIER pairTier = tierExtractor.convert(pair.getKey(), pair.getValue());
if (!Objects.equals(pairTier, currentTier)) continue;
Item item = Item.getItemFromBlock(pair.getKey());
if (item == null) continue;
return Collections
.singletonList(new ItemStack(item, 1, pair.getValue()).getUnlocalizedName() + ".name");
}
return description;
}
};
}
/**
* Denote a block using unlocalized names. This can be useful to get around mod loading order issues.
* <p>
* <b>DUE TO HISTORICAL REASONS, THIS WAS CALLED ofBlockUnlocalizedName, BUT IT ACTUALLY USES REGISTRY NAME
* INSTEAD.</b>
* <p>
* While no immediate error will be thrown, client code should ensure said mod is loaded and said mod is present,
* otherwise bad things will happen later!
*/
public static <T> IStructureElement<T> ofBlockUnlocalizedName(String modid, String unlocalizedName, int meta) {
return ofBlockUnlocalizedName(modid, unlocalizedName, meta, false);
}
/**
* Denote a block using unlocalized names. This can be useful to get around mod loading order issues.
* <p>
* <b>DUE TO HISTORICAL REASONS, THIS WAS CALLED ofBlockUnlocalizedName, BUT IT ACTUALLY USES REGISTRY NAME
* INSTEAD.</b>
* <p>
* While no immediate error will be thrown, client code should ensure said mod is loaded and said mod is present,
* otherwise the element will effectively become an {@link #error()}.
* <p>
* Will place block or hint using the given meta if wildcard is true.
*/
public static <T> IStructureElement<T> ofBlockUnlocalizedName(String modid, String registryName, int meta,
boolean wildcard) {
if (StringUtils.isBlank(registryName)) throw new IllegalArgumentException();
if (meta < 0) throw new IllegalArgumentException();
if (meta > 15) throw new IllegalArgumentException();
if (StringUtils.isBlank(modid)) throw new IllegalArgumentException();
return new StructureElement_Bridge<T>() {
private Block block;
private Block getBlock() {
if (block == null) block = GameRegistry.findBlock(modid, registryName);
return block;
}
@Override
public boolean check(T t, World world, int x, int y, int z) {
return world.getBlock(x, y, z) == getBlock() && (wildcard || world.getBlockMetadata(x, y, z) == meta);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
if (getBlock() == null) return error().spawnHint(t, world, x, y, z, trigger);
StructureLibAPI.hintParticle(world, x, y, z, getBlock(), meta);
return true;
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
if (getBlock() == null) return error().placeBlock(t, world, x, y, z, trigger);
world.setBlock(x, y, z, getBlock(), meta, 2);
return true;
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (getBlock() == null) return error().getBlocksToPlace(t, world, x, y, z, trigger, env);
return BlocksToPlace.create(getBlock(), meta);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
if (getBlock() == null) return PlaceResult.REJECT;
return StructureUtility.survivalPlaceBlock(
getBlock(),
meta,
world,
x,
y,
z,
env.getSource(),
env.getActor(),
env.getChatter());
}
};
}
/**
* Similiar to the other overload, but allows client code to specify a fallback in case said block was not found
* later when the element got called.
* <p>
* <b>DUE TO HISTORICAL REASONS, THIS WAS CALLED ofBlockUnlocalizedName, BUT IT ACTUALLY USES REGISTRY NAME
* INSTEAD.</b>
* <p>
* This is slightly different to using the other overload and another element in a
* {@link #ofChain(IStructureElement[])}. Here fallback will only ever be used if this fails, where ofChain would
* form an OR relationship even if the mod is loaded and the block exists in registry.
*/
public static <T> IStructureElement<T> ofBlockUnlocalizedName(String modid, String unlocalizedName, int meta,
IStructureElement<T> fallback) {
if (StringUtils.isBlank(unlocalizedName)) throw new IllegalArgumentException();
if (meta < 0) throw new IllegalArgumentException();
if (meta > 15) throw new IllegalArgumentException();
if (StringUtils.isBlank(modid)) throw new IllegalArgumentException();
if (fallback == null) throw new IllegalArgumentException();
return new IStructureElement<T>() {
private Block block;
private boolean initialized;
private boolean init() {
if (!initialized) {
block = GameRegistry.findBlock(modid, unlocalizedName);
initialized = true;
}
return block != null;
}
@Override
public boolean check(T t, World world, int x, int y, int z) {
if (init()) return world.getBlock(x, y, z) == block && world.getBlockMetadata(x, y, z) == meta;
else return fallback.check(t, world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
if (init()) return world.getBlock(x, y, z) == block && world.getBlockMetadata(x, y, z) == meta;
else return fallback.couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
if (init()) {
StructureLibAPI.hintParticle(world, x, y, z, block, meta);
return true;
} else return fallback.spawnHint(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
if (init()) {
world.setBlock(x, y, z, block, meta, 2);
return true;
} else return fallback.placeBlock(t, world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (init()) return BlocksToPlace.create(block, meta);
return fallback.getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
if (init()) return StructureUtility.survivalPlaceBlock(block, meta, world, x, y, z, s, actor, chatter);
return fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
if (init()) return StructureUtility.survivalPlaceBlock(
block,
meta,
world,
x,
y,
z,
env.getSource(),
env.getActor(),
env.getChatter());
return fallback.survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
};
}
/**
* Accept a set of blocks. Less cumbersome to use than {@link #ofBlocksMapHint(Map, Block, int)} when for any
* accepted block type we accept only one meta for each.
* <p>
* Does not have autoplace.
*
* @param blocsMap Accepted (block, meta) pairs.
* @param hintBlock hint block to use
* @param hintMeta hint meta to use
* @see #ofBlocksMapHint(Map, Block, int)
*/
public static <T> IStructureElementNoPlacement<T> ofBlocksFlatHint(Map<Block, Integer> blocsMap, Block hintBlock,
int hintMeta) {
if (blocsMap == null || blocsMap.isEmpty() || hintBlock == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return blocsMap.getOrDefault(worldBlock, MIN_VALUE) == worldBlock.getDamageValue(world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
return true;
}
};
}
/**
* Accept a set of blocks.
* <p>
* Does not have autoplace.
*
* @param blocsMap Accepted (block, meta) pairs.
* @param hintBlock hint block to use
* @param hintMeta hint meta to use
* @see #ofBlocksFlatHint(Map, Block, int)
*/
public static <T> IStructureElementNoPlacement<T> ofBlocksMapHint(Map<Block, Collection<Integer>> blocsMap,
Block hintBlock, int hintMeta) {
if (blocsMap == null || blocsMap.isEmpty() || hintBlock == null) {
throw new IllegalArgumentException();
}
for (Collection<Integer> value : blocsMap.values()) {
if (value.isEmpty()) {
throw new IllegalArgumentException();
}
}
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return blocsMap.getOrDefault(worldBlock, Collections.emptySet())
.contains(worldBlock.getDamageValue(world, x, y, z));
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
return true;
}
};
}
/**
* Accept one (block, meta). Spawn hint particles using an alternative block and meta. Not very useful...
*/
public static <T> IStructureElementNoPlacement<T> ofBlockHint(Block block, int meta, Block hintBlock,
int hintMeta) {
if (block == null || hintBlock == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return block == worldBlock && meta == worldBlock.getDamageValue(world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
return true;
}
};
}
/**
* Accept one (block, meta). Same as {@link #ofBlock(Block, int)}, except it explicitly turns off creative/survival
* build.
*/
public static <T> IStructureElementNoPlacement<T> ofBlockHint(Block block, int meta) {
return ofBlockHint(block, meta, block, meta);
}
/**
* Add a block using block adder. Spawn hints using given hint block and meta.
* <p>
* Useful when your logic is very complex. Does not support autoplace.
*/
public static <T> IStructureElementNoPlacement<T> ofBlockAdderHint(IBlockAdder<T> iBlockAdder, Block hintBlock,
int hintMeta) {
if (iBlockAdder == null || hintBlock == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return iBlockAdder.apply(t, worldBlock, worldBlock.getDamageValue(world, x, y, z));
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
// calling iBlockAdder can potentially modify external state
// therefore we assume this can always be valid.
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
return true;
}
};
}
/**
* Accept a set of blocks. Less cumbersome to use than {@link #ofBlocksMap(Map, Block, int)} when for any accepted
* block type we accept only one meta for each.
*
* @param blocsMap Accepted (block, meta) pairs.
* @param defaultBlock default block to place/spawn hint
* @param defaultMeta default meta to place/spawn hint
* @see #ofBlocksMapHint(Map, Block, int)
*/
public static <T> IStructureElement<T> ofBlocksFlat(Map<Block, Integer> blocsMap, Block defaultBlock,
int defaultMeta) {
if (blocsMap == null || blocsMap.isEmpty() || defaultBlock == null) {
throw new IllegalArgumentException();
}
if (defaultBlock instanceof ICustomBlockSetting) {
return new IStructureElement<T>() {
private BlocksToPlace blocksToPlace;
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return blocsMap.getOrDefault(worldBlock, MIN_VALUE) == worldBlock.getDamageValue(world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (blocksToPlace == null) {
ImmutableList.Builder<ItemStack> blocks = ImmutableList.builder();
Predicate<ItemStack> predicate = s -> true;
for (Entry<Block, Integer> e : blocsMap.entrySet()) {
Item i = Item.getItemFromBlock(e.getKey());
int meta = e.getValue();
if (i instanceof ISpecialItemBlock)
meta = ((ISpecialItemBlock) i).getItemMetaFromBlockMeta(e.getKey(), meta);
ItemStack stack = new ItemStack(i, 1, meta);
blocks.add(stack);
predicate = predicate.and(ItemStackPredicate.from(stack));
}
blocksToPlace = new BlocksToPlace(predicate, blocks.build());
}
return blocksToPlace;
}
};
} else {
return new IStructureElement<T>() {
private BlocksToPlace blocksToPlace;
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return blocsMap.getOrDefault(worldBlock, MIN_VALUE) == worldBlock.getDamageValue(world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (blocksToPlace == null) {
ImmutableList.Builder<ItemStack> blocks = ImmutableList.builder();
Predicate<ItemStack> predicate = s -> true;
for (Entry<Block, Integer> e : blocsMap.entrySet()) {
Item i = Item.getItemFromBlock(e.getKey());
int meta = e.getValue();
if (i instanceof ISpecialItemBlock)
meta = ((ISpecialItemBlock) i).getItemMetaFromBlockMeta(e.getKey(), meta);
ItemStack stack = new ItemStack(i, 1, meta);
blocks.add(stack);
predicate = predicate.and(ItemStackPredicate.from(stack));
}
blocksToPlace = new BlocksToPlace(predicate, blocks.build());
}
return blocksToPlace;
}
};
}
}
/**
* Accept a set of blocks.
*
* @param blocsMap Accepted (block, meta) pairs.
* @param defaultBlock default block to place/spawn hint
* @param defaultMeta default meta to place/spawn hint
* @see #ofBlocksMapHint(Map, Block, int)
*/
public static <T> IStructureElement<T> ofBlocksMap(Map<Block, Collection<Integer>> blocsMap, Block defaultBlock,
int defaultMeta) {
if (blocsMap == null || blocsMap.isEmpty() || defaultBlock == null) {
throw new IllegalArgumentException();
}
for (Collection<Integer> value : blocsMap.values()) {
if (value.isEmpty()) {
throw new IllegalArgumentException();
}
}
if (defaultBlock instanceof ICustomBlockSetting) {
return new IStructureElement<T>() {
private BlocksToPlace blocksToPlace;
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return blocsMap.getOrDefault(worldBlock, Collections.emptySet())
.contains(worldBlock.getDamageValue(world, x, y, z));
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (blocksToPlace == null) {
ImmutableList.Builder<ItemStack> blocks = ImmutableList.builder();
Predicate<ItemStack> predicate = s -> true;
for (Entry<Block, Collection<Integer>> e : blocsMap.entrySet()) {
Item i = Item.getItemFromBlock(e.getKey());
for (int meta : e.getValue()) {
if (i instanceof ISpecialItemBlock)
meta = ((ISpecialItemBlock) i).getItemMetaFromBlockMeta(e.getKey(), meta);
ItemStack stack = new ItemStack(i, 1, meta);
blocks.add(stack);
predicate = predicate.and(ItemStackPredicate.from(stack));
}
}
blocksToPlace = new BlocksToPlace(predicate, blocks.build());
}
return blocksToPlace;
}
};
} else {
return new IStructureElement<T>() {
private BlocksToPlace blocksToPlace;
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return blocsMap.getOrDefault(worldBlock, Collections.emptySet())
.contains(worldBlock.getDamageValue(world, x, y, z));
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (blocksToPlace == null) {
ImmutableList.Builder<ItemStack> blocks = ImmutableList.builder();
Predicate<ItemStack> predicate = s -> true;
for (Entry<Block, Collection<Integer>> e : blocsMap.entrySet()) {
Item i = Item.getItemFromBlock(e.getKey());
for (int meta : e.getValue()) {
if (i instanceof ISpecialItemBlock)
meta = ((ISpecialItemBlock) i).getItemMetaFromBlockMeta(e.getKey(), meta);
ItemStack stack = new ItemStack(i, 1, meta);
blocks.add(stack);
predicate = predicate.and(ItemStackPredicate.from(stack));
}
}
blocksToPlace = new BlocksToPlace(predicate, blocks.build());
}
return blocksToPlace;
}
};
}
}
/**
* Accept a block. Spawn hint/autoplace using another.
*
* @param block accepted block
* @param meta accepted meta
* @param defaultBlock hint block
* @param defaultMeta hint meta
*/
public static <T> IStructureElement<T> ofBlock(Block block, int meta, Block defaultBlock, int defaultMeta) {
if (block == null || defaultBlock == null) {
throw new IllegalArgumentException();
}
if (block == Blocks.air) {
if (PANIC_MODE) {
throw new IllegalArgumentException("ofBlock() does not accept air. use isAir() instead");
} else {
LOGGER.warn("ofBlock() does not accept air. use isAir() instead");
return isAir();
}
}
if (block instanceof ICustomBlockSetting) {
return new IStructureElement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return block == worldBlock && meta == worldBlock.getDamageValue(world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return BlocksToPlace.create(block, meta);
}
@Nullable
@Override
public List<String> getDescription(T context) {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, meta).getUnlocalizedName() + ".name");
}
};
} else {
return new IStructureElement<T>() {
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
BlocksToPlace e = getBlocksToPlace(t, world, x, y, z, trigger, env);
IItemSource source = env.getSource();
EntityPlayer actor = env.getActor();
Consumer<IChatComponent> chatter = env.getChatter();
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
if (e.getStacks() == null) {
ItemStack taken = source.takeOne(e.getPredicate(), true);
return StructureUtility.survivalPlaceBlock(
taken,
NBTMode.EXACT,
taken.stackTagCompound,
false,
world,
x,
y,
z,
source,
actor,
chatter);
}
for (ItemStack stack : e.getStacks()) {
if (!source.takeOne(stack, true)) {
IChatComponent name = new ChatComponentText(stack.getDisplayName());
name.setChatStyle(new ChatStyle().setColor(EnumChatFormatting.YELLOW));
env.getChatter()
.accept(new ChatComponentTranslation("structurelib.autoplace.missing_block", name));
continue;
}
return StructureUtility.survivalPlaceBlock(
stack,
NBTMode.EXACT,
stack.stackTagCompound,
false,
world,
x,
y,
z,
source,
actor,
chatter);
}
return PlaceResult.REJECT;
}
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return block == worldBlock && meta == worldBlock.getDamageValue(world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return BlocksToPlace.create(block, meta);
}
@Nullable
@Override
public List<String> getDescription(T context) {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, meta).getUnlocalizedName() + ".name");
}
};
}
}
/**
* Same as {@link #ofBlock(Block, int, Block, int)} but ignores target meta id
*/
public static <T> IStructureElement<T> ofBlockAnyMeta(Block block, Block defaultBlock, int defaultMeta) {
if (block == null || defaultBlock == null) {
throw new IllegalArgumentException();
}
if (block instanceof ICustomBlockSetting) {
return new IStructureElement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return block == world.getBlock(x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
// there is getSubItems on ItemBlock, but it's client only
return BlocksToPlace.create(defaultBlock, defaultMeta);
}
@Nullable
@Override
public List<String> getDescription(T context) {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, 0).getUnlocalizedName() + ".name");
}
};
} else {
return new IStructureElement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return block == world.getBlock(x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return check(t, world, x, y, z);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
// there is getSubItems on ItemBlock, but it's client only
return BlocksToPlace.create(defaultBlock, defaultMeta);
}
@Nullable
@Override
public List<String> getDescription(T context) {
Item item = Item.getItemFromBlock(block);
if (item == null) return null;
return Collections.singletonList(new ItemStack(item, 1, 0).getUnlocalizedName() + ".name");
}
};
}
}
/**
* Accept a single block with a fixed meta. Most primitive form of structure.
*/
public static <T> IStructureElement<T> ofBlock(Block block, int meta) {
return ofBlock(block, meta, block, meta);
}
/**
* Accept a single block, but accept any meta.
*/
public static <T> IStructureElement<T> ofBlockAnyMeta(Block block) {
return ofBlockAnyMeta(block, block, 0);
}
/**
* Accept a single block, but accept any meta. Spawn hint/autoplace using given meta.
*/
public static <T> IStructureElement<T> ofBlockAnyMeta(Block block, int defaultMeta) {
return ofBlockAnyMeta(block, block, defaultMeta);
}
// endregion
// region adders
/**
* Add a block using block adder. Spawn hints/autoplace using given hint block and meta.
* <p>
* Useful when your logic is very complex.
*/
public static <T> IStructureElement<T> ofBlockAdder(IBlockAdder<T> iBlockAdder, Block defaultBlock,
int defaultMeta) {
if (iBlockAdder == null || defaultBlock == null) {
throw new IllegalArgumentException();
}
if (defaultBlock instanceof ICustomBlockSetting) {
return new StructureElement_Bridge<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return iBlockAdder.apply(t, worldBlock, worldBlock.getDamageValue(world, x, y, z));
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
// calling iBlockAdder can potentially modify external state
// therefore we assume this can always be valid.
return true;
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (world.getBlock(x, y, z) == defaultBlock && world.getBlockMetadata(x, y, z) == defaultMeta)
return PlaceResult.SKIP;
return StructureUtility.survivalPlaceBlock(
defaultBlock,
defaultMeta,
world,
x,
y,
z,
env.getSource(),
env.getActor(),
env.getChatter());
}
};
} else {
return new StructureElement_Bridge<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
Block worldBlock = world.getBlock(x, y, z);
return iBlockAdder.apply(t, worldBlock, worldBlock.getDamageValue(world, x, y, z));
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
// calling iBlockAdder can potentially modify external state
// therefore we assume this can always be valid.
return true;
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta);
return true;
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (world.getBlock(x, y, z) == defaultBlock && world.getBlockMetadata(x, y, z) == defaultMeta)
return PlaceResult.SKIP;
return StructureUtility.survivalPlaceBlock(
defaultBlock,
defaultMeta,
world,
x,
y,
z,
env.getSource(),
env.getActor(),
env.getChatter());
}
};
}
}
public static <T> IStructureElement<T> ofBlockAdder(IBlockAdder<T> iBlockAdder, int dots) {
return ofBlockAdder(iBlockAdder, StructureLibAPI.getBlockHint(), dots - 1);
}
/**
* Try to add a structure element with a tile entity. Note that tile adder will be called with a null argument at
* locations without tile entity.
*/
public static <T> IStructureElementNoPlacement<T> ofTileAdder(ITileAdder<T> iTileAdder, Block hintBlock,
int hintMeta) {
if (iTileAdder == null || hintBlock == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
TileEntity tileEntity = world.getTileEntity(x, y, z);
// This used to check if it's a GT tile. Since this is now an standalone mod we no longer do this
return iTileAdder.apply(t, tileEntity);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
// calling iTileAdder can potentially modify external state
// therefore we assume this can always be valid.
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
return true;
}
};
}
/**
* Try to add a structure element with a particular type of tile entity. Note that tile adder will not be called at
* locations without a tile entity.
*/
public static <T, E> IStructureElementNoPlacement<T> ofSpecificTileAdder(BiPredicate<T, E> iTileAdder,
Class<E> tileClass, Block hintBlock, int hintMeta) {
if (iTileAdder == null || hintBlock == null || tileClass == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
TileEntity tileEntity = world.getTileEntity(x, y, z);
// This used to check if it's a GT tile. Since this is now an standalone mod we no longer do this
return tileClass.isInstance(tileEntity) && iTileAdder.test(t, tileClass.cast(tileEntity));
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
// calling iTileAdder can potentially modify external state
// therefore we assume this can always be valid.
return true;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
return true;
}
};
}
// No more hatch adder. Implement it via tile adder. We could of course add a wrapper around it in gregtech, but not
// any more in this standalone mod.
// endregion
// region side effects
/**
* Call a callback if downstream element returned true in check.
*
* @param onCheckPass side effect
* @param element downstream
*/
public static <B extends IStructureElement<T>, T> IStructureElement<T> onElementPass(Consumer<T> onCheckPass,
B element) {
return new IStructureElement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
boolean check = element.check(t, world, x, y, z);
if (check) {
onCheckPass.accept(t);
}
return check;
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.spawnHint(t, world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return element.getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return element.getDescription(context);
}
};
}
/**
* Wraps an element and overrides its description. Useful for providing human-readable names to elements whose
* underlying implementation doesn't provide a description (e.g. tiered block elements).
*
* @param description the description to use, or null to clear any existing description
* @param element the element to wrap
*/
public static <B extends IStructureElement<T>, T> IStructureElement<T> withDescription(
@Nullable List<String> description, B element) {
return new IStructureElement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return element.check(t, world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.spawnHint(t, world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return element.getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return description;
}
};
}
/**
* Call a callback if downstream element returned false in check.
*
* @param onFail side effect
* @param element downstream
*/
public static <B extends IStructureElement<T>, T> IStructureElement<T> onElementFail(Consumer<T> onFail,
B element) {
return new IStructureElement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
boolean check = element.check(t, world, x, y, z);
if (!check) {
onFail.accept(t);
}
return check;
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return element.spawnHint(t, world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return element.getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return element.getDescription(context);
}
};
}
// endregion
/**
* Enable this structure element only if given predicate returns true.
* <p>
* Return REJECT_CONTINUE when survival auto place if given predicate returns false.
*/
public static <T> IStructureElement<T> onlyIf(Predicate<? super T> predicate,
IStructureElement<? super T> downstream) {
return onlyIf(predicate, downstream, PlaceResult.REJECT_CONTINUE);
}
/**
* Enable this structure element only if given predicate returns true.
*
* @param placeResultWhenDisabled value to return for survival auto place when predicate returns false
*/
public static <T> IStructureElement<T> onlyIf(Predicate<? super T> predicate,
IStructureElement<? super T> downstream, PlaceResult placeResultWhenDisabled) {
return new IStructureElement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return predicate.test(t) && downstream.check(t, world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
// this COULD potentially impact external state
// but predicate really should not have side effects
return predicate.test(t) && downstream.couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return predicate.test(t) && downstream.spawnHint(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return predicate.test(t) && downstream.placeBlock(t, world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (predicate.test(t)) return downstream.getBlocksToPlace(t, world, x, y, z, trigger, env);
return BlocksToPlace.createEmpty();
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
if (predicate.test(t))
return downstream.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
return placeResultWhenDisabled;
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (predicate.test(t)) return downstream.survivalPlaceBlock(t, world, x, y, z, trigger, env);
return placeResultWhenDisabled;
}
@Nullable
@Override
public List<String> getDescription(T context) {
return downstream.getDescription(context);
}
};
}
/**
* This allows you to compose different {@link IStructureElement} to form a <b>OR</b> chain. As with any other OR
* operator, this one exhibits short-circuiting behavior, i.e. it will not call next structure element if previous
* one succeeded. (*)
* <p>
* This allows you e.g. accept both a glass block using {@link #ofBlock(Block, int, Block, int)} and a piece of air
* using {@link #isAir()}. It will not attempt to capture any errors though, so next one will not be tried if
* previous one would crash.
* <p>
* (*): For survival auto place, it will: REJECT, if any structure element returns REJECT (and none returned ACCEPT,
* STOP, ACCEPT_STOP, or SKIP); REJECT_CONTINUE, if all structure elements return REJECT_CONTINUE (none returned
* REJECT); SKIP, as soon as any structure element returns SKIP; any other result, immediately upon any structure
* element returning those.
* <p>
* Take care while chaining, as it will try to call every structure element until it returns true. If none does it
* will finally return false.
*/
@SafeVarargs
public static <T> IStructureElementChain<T> ofChain(IStructureElement<T>... elementChain) {
if (elementChain == null || elementChain.length == 0) {
throw new IllegalArgumentException();
}
for (IStructureElement<T> iStructureElement : elementChain) {
if (iStructureElement == null) {
throw new IllegalArgumentException();
}
}
return () -> elementChain;
}
/**
* This allows you to compose different {@link IStructureElement} to form a <b>OR</b> chain.
* <p>
* Practically no difference with the other overload.
*
* @see #ofChain(IStructureElement[])
*/
@SuppressWarnings("unchecked")
public static <T> IStructureElementChain<T> ofChain(List<IStructureElement<T>> elementChain) {
return ofChain(elementChain.toArray(new IStructureElement[0]));
}
// region context
/**
* Switch to the extended context object in the downstream element
*
* @param elem downstream element
* @param <CTX> extended context type
* @param <T> existing context object type
*/
public static <CTX, T extends IWithExtendedContext<CTX>> IStructureElement<T> withContext(
IStructureElement<CTX> elem) {
return new IStructureElement<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return elem.check(t.getCurrentContext(), world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return elem.couldBeValid(t.getCurrentContext(), world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return elem.spawnHint(t.getCurrentContext(), world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return elem.placeBlock(t.getCurrentContext(), world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return elem.getBlocksToPlace(t.getCurrentContext(), world, x, y, z, trigger, env);
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return elem.survivalPlaceBlock(t.getCurrentContext(), world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return elem.survivalPlaceBlock(t.getCurrentContext(), world, x, y, z, trigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return elem.getDescription(context.getCurrentContext());
}
};
}
// endregion
// region defer
/**
* Similar to defer, but caches the first returned element returned and won't call it again. Initialization is not
* thread safe. These both allow the structure element **constructor** to access properties only present on the
* context object (e.g. GT5 multiblock controller), e.g. hatch texture index to use. Use `lazy` if the data you
* access will remain constant across different context object.
*/
public static <T> IStructureElementDeferred<T> lazy(Supplier<IStructureElement<T>> to) {
if (to == null) {
throw new IllegalArgumentException();
}
return new LazyStructureElement<>(t -> to.get());
}
/**
* This will defer the actual instantiation of structure element until the <b>first time</b> structure code is
* actually called.
* <p>
* Similar to defer, but caches the first returned element returned and won't call it again. Initialization is not
* thread safe. This will allow the structure element <b>constructor</b> to access properties only present on the
* context object (e.g. GT5 multiblock controller), e.g. hatch texture index to use. Use this if the data you access
* will remain constant.
*
* @param to create structure element from the first context object passed in
*/
public static <T> IStructureElementDeferred<T> lazy(Function<T, IStructureElement<T>> to) {
if (to == null) {
throw new IllegalArgumentException();
}
return new LazyStructureElement<>(to);
}
/**
* This will defer the actual instantiation of structure element until the structure code is actually called. This
* will allow the structure element <b>constructor</b> to access properties only present on the context object (e.g.
* GT5 multiblock controller), e.g. hatch texture index to use. Use this if it might change for the same structure
* definition, e.g. if your structure might switch mode and change some parameter, while basically remain the same
* shape. Using this while {@link #lazy(Supplier)} is enough will not break anything, but would incur unnecessary
* performance overhead.
*
* @param to downstream element supplier
*/
public static <T> IStructureElementDeferred<T> defer(Supplier<IStructureElement<T>> to) {
if (to == null) {
throw new IllegalArgumentException();
}
return new IStructureElementDeferred<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return to.get().check(t, world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.get().couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.get().placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.get().spawnHint(t, world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return to.get().getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@Override
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return to.get().survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return to.get().survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
};
}
/**
* This will defer the actual instantiation of structure element until the structure code is actually called. This
* will allow the structure element <b>constructor</b> to access properties only present on the context object (e.g.
* GT5 multiblock controller), e.g. hatch texture index to use. Use this if it might change for the same structure
* definition, e.g. if your structure might switch mode and change some parameter, while basically remain the same
* shape. Using this while {@link #lazy(Supplier)} is enough will not break anything, but would incur unnecessary
* performance overhead.
*
* @param to downstream element supplier
*/
public static <T> IStructureElementDeferred<T> defer(Function<T, IStructureElement<T>> to) {
if (to == null) {
throw new IllegalArgumentException();
}
return new IStructureElementDeferred<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return to.apply(t).check(t, world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.apply(t).couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.apply(t).placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.apply(t).spawnHint(t, world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return to.apply(t).getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return to.apply(t).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return to.apply(t).survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
};
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use.
*
* @param keyExtractor extract a key from the context object
* @param map all possible structure element
* @deprecated renamed to partitionBy
*/
@Deprecated
public static <T, K> IStructureElementDeferred<T> defer(Function<T, K> keyExtractor,
Map<K, IStructureElement<T>> map) {
return partitionBy(keyExtractor, map);
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key not
* found in map.
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
*
* @param keyExtractor extract a key from the context object
* @param map all possible structure element
*/
public static <T, K> IStructureElementDeferred<T> partitionBy(Function<T, K> keyExtractor,
Map<K, IStructureElement<T>> map) {
if (keyExtractor == null || map == null) {
throw new IllegalArgumentException();
}
return defer(keyExtractor.andThen(map::get));
}
/**
* This is the switch block for structure code, with default case support.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. The fallback will only be used when keyExtractor returns a value not found in given map. This will play
* nicely in combination with {@link #error()}
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
*
* @param keyExtractor extract a key from the context object
* @param map all possible structure element
* @param defaultElem element to use when keyExtractor returns a value not found in given map
* @deprecated renamed to partitionBy
*/
@Deprecated
public static <T, K> IStructureElementDeferred<T> defer(Function<T, K> keyExtractor,
Map<K, IStructureElement<T>> map, IStructureElement<T> defaultElem) {
return partitionBy(keyExtractor, map, defaultElem);
}
/**
* This is the switch block for structure code, with default case support.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. The fallback will only be used when keyExtractor returns a value not found in given map. This will play
* nicely in combination with {@link #error()}
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
*
* @param keyExtractor extract a key from the context object
* @param map all possible structure element
* @param defaultElem element to use when keyExtractor returns a value not found in given map
*/
public static <T, K> IStructureElementDeferred<T> partitionBy(Function<T, K> keyExtractor,
Map<K, IStructureElement<T>> map, IStructureElement<T> defaultElem) {
if (keyExtractor == null || map == null) {
throw new IllegalArgumentException();
}
return defer(keyExtractor.andThen(key -> map.getOrDefault(key, defaultElem)));
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* Do pay place null values in the array.
*
* @param keyExtractor extract an index from the context object
* @param array all possible structure element
* @deprecated renamed to partitionBy
*/
@SafeVarargs
@Deprecated
public static <T> IStructureElementDeferred<T> defer(Function<T, Integer> keyExtractor,
IStructureElement<T>... array) {
return partitionBy(keyExtractor, array);
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* Do pay place null values in the array.
*
* @param keyExtractor extract an index from the context object
* @param array all possible structure element
*/
@SafeVarargs
public static <T> IStructureElementDeferred<T> partitionBy(Function<T, Integer> keyExtractor,
IStructureElement<T>... array) {
if (keyExtractor == null || array == null) {
throw new IllegalArgumentException();
}
return defer(keyExtractor.andThen(i -> array[i]));
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* Do pay place null values in the array.
*
* @param keyExtractor extract an index from the context object
* @param array all possible structure element
* @deprecated renamed to partitionBy
*/
@Deprecated
public static <T> IStructureElementDeferred<T> defer(Function<T, Integer> keyExtractor,
List<IStructureElement<T>> array) {
return partitionBy(keyExtractor, array);
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* Do pay place null values in the array.
*
* @param keyExtractor extract an index from the context object
* @param array all possible structure element
*/
@SuppressWarnings("unchecked")
public static <T> IStructureElementDeferred<T> partitionBy(Function<T, Integer> keyExtractor,
List<IStructureElement<T>> array) {
return partitionBy(keyExtractor, array.toArray(new IStructureElement[0]));
}
/**
* This will defer the actual instantiation of structure element until the structure code is actually called. This
* will allow the structure element <b>constructor</b> to access properties only present on the context object (e.g.
* GT5 multiblock controller), e.g. hatch texture index to use. Use this if it might change for the same structure
* definition, e.g. if your structure might switch mode and change some parameter, while basically remain the same
* shape. Using this while {@link #lazy(Supplier)} is enough will not break anything, but would incur unnecessary
* performance overhead.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
*
* @param to downstream element supplier
*/
public static <T> IStructureElementDeferred<T> defer(BiFunction<T, ItemStack, IStructureElement<T>> to) {
if (to == null) {
throw new IllegalArgumentException();
}
return new IStructureElementDeferred<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return to.apply(t, null).check(t, world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.apply(t, trigger).couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.apply(t, trigger).placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.apply(t, trigger).spawnHint(t, world, x, y, z, trigger);
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return to.apply(t, trigger).getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return to.apply(t, trigger).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return to.apply(t, trigger).survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
};
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key not
* found in map.
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
*
* @param keyExtractor extract a key from the context object and trigger item
* @param map all possible structure element
* @deprecated renamed to partitionBy
*/
@Deprecated
public static <T, K> IStructureElementDeferred<T> defer(BiFunction<T, ItemStack, K> keyExtractor,
Map<K, IStructureElement<T>> map) {
return partitionBy(keyExtractor, map);
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key not
* found in map.
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
*
* @param keyExtractor extract a key from the context object and trigger item
* @param map all possible structure element
*/
public static <T, K> IStructureElementDeferred<T> partitionBy(BiFunction<T, ItemStack, K> keyExtractor,
Map<K, IStructureElement<T>> map) {
if (keyExtractor == null || map == null) {
throw new IllegalArgumentException();
}
return defer(keyExtractor.andThen(map::get));
}
/**
* This is the switch block for structure code, with default case support.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. The fallback will only be used when keyExtractor returns a value not found in given map. This will play
* nicely in combination with {@link #error()}
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
*
* @param keyExtractor extract a key from the context object and trigger item
* @param map all possible structure element
* @param defaultElem element to use when keyExtractor returns a value not found in given map
* @deprecated renamed to partitionBy
*/
@Deprecated
public static <T, K> IStructureElementDeferred<T> defer(BiFunction<T, ItemStack, K> keyExtractor,
Map<K, IStructureElement<T>> map, IStructureElement<T> defaultElem) {
return partitionBy(keyExtractor, map, defaultElem);
}
/**
* This is the switch block for structure code, with default case support.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. The fallback will only be used when keyExtractor returns a value not found in given map. This will play
* nicely in combination with {@link #error()}
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
*
* @param keyExtractor extract a key from the context object and trigger item
* @param map all possible structure element
* @param defaultElem element to use when keyExtractor returns a value not found in given map
*/
public static <T, K> IStructureElementDeferred<T> partitionBy(BiFunction<T, ItemStack, K> keyExtractor,
Map<K, IStructureElement<T>> map, IStructureElement<T> defaultElem) {
if (keyExtractor == null || map == null) {
throw new IllegalArgumentException();
}
return defer(keyExtractor.andThen(key -> map.getOrDefault(key, defaultElem)));
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* Do pay place null values in the array.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
*
* @param keyExtractor extract a key from the context object and trigger item
* @param array all possible structure element
*/
@SafeVarargs
public static <T> IStructureElementDeferred<T> defer(BiFunction<T, ItemStack, Integer> keyExtractor,
IStructureElement<T>... array) {
if (keyExtractor == null || array == null) {
throw new IllegalArgumentException();
}
return defer(keyExtractor.andThen(i -> array[i]));
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* Do pay place null values in the array.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
*
* @param keyExtractor extract a key from the context object and trigger item
* @param array all possible structure element
*/
public static <T> IStructureElementDeferred<T> defer(BiFunction<T, ItemStack, Integer> keyExtractor,
List<IStructureElement<T>> array) {
return defer(keyExtractor.andThen(array::get));
}
/**
* This will defer the actual instantiation of structure element until the <b>first time</b> structure code is
* actually called.
* <p>
* Similar to defer, but caches the first returned element returned and won't call it again. Initialization is not
* thread safe. This will allow the structure element <b>constructor</b> to access properties only present on the
* context object (e.g. GT5 multiblock controller), e.g. hatch texture index to use. Use this if the data you access
* will remain constant.
* <p>
* This variant will override the check function of the structure element from second function with the one returned
* from the structure element from first function.
*
* @param toCheck override the check function with the returned element
* @param to create structure element from the context object passed in
*/
public static <T> IStructureElementDeferred<T> defer(Function<T, IStructureElement<T>> toCheck,
BiFunction<T, ItemStack, IStructureElement<T>> to) {
if (to == null) {
throw new IllegalArgumentException();
}
return new IStructureElementDeferred<T>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return toCheck.apply(t).check(t, world, x, y, z);
}
@Override
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.apply(t, trigger).couldBeValid(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.apply(t, trigger).placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return to.apply(t, trigger).spawnHint(t, world, x, y, z, trigger);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return to.apply(t, trigger).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return to.apply(t, trigger).survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
};
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key not
* found in map.
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
* <p>
* This variant will override the check function of the structure element from second function with the one returned
* from the structure element from first function.
*
* @param keyExtractorCheck key extractor of the structure element that will be used for check.
* @param keyExtractor extract a key from the context object and trigger item
* @param map all possible structure element
* @deprecated renamed to partitionBy
*/
@Deprecated
public static <T, K> IStructureElementDeferred<T> defer(Function<T, K> keyExtractorCheck,
BiFunction<T, ItemStack, K> keyExtractor, Map<K, IStructureElement<T>> map) {
return partitionBy(keyExtractorCheck, keyExtractor, map);
}
/**
* This is the switch block for structure code.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns a key not
* found in map.
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
* <p>
* This variant will override the check function of the structure element from second function with the one returned
* from the structure element from first function.
*
* @param keyExtractorCheck key extractor of the structure element that will be used for check.
* @param keyExtractor extract a key from the context object and trigger item
* @param map all possible structure element
*/
public static <T, K> IStructureElementDeferred<T> partitionBy(Function<T, K> keyExtractorCheck,
BiFunction<T, ItemStack, K> keyExtractor, Map<K, IStructureElement<T>> map) {
if (keyExtractor == null || map == null) {
throw new IllegalArgumentException();
}
return defer(keyExtractorCheck.andThen(map::get), keyExtractor.andThen(map::get));
}
/**
* This is the switch block for structure code, with default case.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. defaultElem will be used if either extractor returned a key not found in map.
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
* <p>
* This variant will override the check function of the structure element from second function with the one returned
* from the structure element from first function.
*
* @param keyExtractorCheck key extractor of the structure element that will be used for check.
* @param keyExtractor extract a key from the context object and trigger item
* @param map all possible structure element
* @deprecated renamed to partitionBy
*/
@Deprecated
public static <T, K> IStructureElementDeferred<T> defer(Function<T, K> keyExtractorCheck,
BiFunction<T, ItemStack, K> keyExtractor, Map<K, IStructureElement<T>> map,
IStructureElement<T> defaultElem) {
return partitionBy(keyExtractorCheck, keyExtractor, map, defaultElem);
}
/**
* This is the switch block for structure code, with default case.
* <p>
* This allows you to extract or compute a key from context object, and lookup a map for actual structure element to
* use. defaultElem will be used if either extractor returned a key not found in map.
* <p>
* Do pay attention to what properties your map has though. You need to pay attention to how they consider equality
* and how they consider null key/values. Guava ImmutableMap is usually a good enough choice.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
* <p>
* This variant will override the check function of the structure element from second function with the one returned
* from the structure element from first function.
*
* @param keyExtractorCheck key extractor of the structure element that will be used for check.
* @param keyExtractor extract a key from the context object and trigger item
* @param map all possible structure element
*/
public static <T, K> IStructureElementDeferred<T> partitionBy(Function<T, K> keyExtractorCheck,
BiFunction<T, ItemStack, K> keyExtractor, Map<K, IStructureElement<T>> map,
IStructureElement<T> defaultElem) {
if (keyExtractor == null || map == null) {
throw new IllegalArgumentException();
}
return defer(
keyExtractorCheck.andThen(k -> map.getOrDefault(k, defaultElem)),
keyExtractor.andThen(k -> map.getOrDefault(k, defaultElem)));
}
/**
* This is the switch block for structure code, with default case.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* Do pay place null values in the array.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
* <p>
* This variant will override the check function of the structure element from second function with the one returned
* from the structure element from first function.
*
* @param keyExtractorCheck key extractor of the structure element that will be used for check.
* @param keyExtractor extract a key from the context object and trigger item
* @param array all possible structure element
* @deprecated renamed to partitionBy
*/
@SafeVarargs
@Deprecated
public static <T> IStructureElementDeferred<T> defer(Function<T, Integer> keyExtractorCheck,
BiFunction<T, ItemStack, Integer> keyExtractor, IStructureElement<T>... array) {
return partitionBy(keyExtractorCheck, keyExtractor, array);
}
/**
* This is the switch block for structure code, with default case.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* Do pay place null values in the array.
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
* <p>
* This variant will override the check function of the structure element from second function with the one returned
* from the structure element from first function.
*
* @param keyExtractorCheck key extractor of the structure element that will be used for check.
* @param keyExtractor extract a key from the context object and trigger item
* @param array all possible structure element
*/
@SafeVarargs
public static <T> IStructureElementDeferred<T> partitionBy(Function<T, Integer> keyExtractorCheck,
BiFunction<T, ItemStack, Integer> keyExtractor, IStructureElement<T>... array) {
if (keyExtractor == null || array == null) {
throw new IllegalArgumentException();
}
return defer(keyExtractorCheck.andThen(i -> array[i]), keyExtractor.andThen(i -> array[i]));
}
/**
* This is the switch block for structure code, with default case.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
* <p>
* This variant will override the check function of the structure element from second function with the one returned
* from the structure element from first function.
*
* @param keyExtractorCheck key extractor of the structure element that will be used for check.
* @param keyExtractor extract a key from the context object and trigger item
* @param array all possible structure element
* @deprecated renamed to partitionBy
*/
@Deprecated
public static <T> IStructureElementDeferred<T> defer(Function<T, Integer> keyExtractorCheck,
BiFunction<T, ItemStack, Integer> keyExtractor, List<IStructureElement<T>> array) {
return partitionBy(keyExtractorCheck, keyExtractor, array);
}
/**
* This is the switch block for structure code, with default case.
* <p>
* This allows you to extract or compute an index from context object, and lookup an array for actual structure
* element to use. This will usually, but not guaranteed, to throw an exception at runtime if keyExtractor returns
* an index not within the array bound
* <p>
* This variant also passes the trigger item to the function, allowing it to get more info.
* <p>
* This variant will override the check function of the structure element from second function with the one returned
* from the structure element from first function.
*
* @param keyExtractorCheck key extractor of the structure element that will be used for check.
* @param keyExtractor extract a key from the context object and trigger item
* @param array all possible structure element
*/
@SuppressWarnings("unchecked")
public static <T> IStructureElementDeferred<T> partitionBy(Function<T, Integer> keyExtractorCheck,
BiFunction<T, ItemStack, Integer> keyExtractor, List<IStructureElement<T>> array) {
return partitionBy(keyExtractorCheck, keyExtractor, array.toArray(new IStructureElement[0]));
}
// endregion
// region channels
/**
* See channels.md in docs folder
*/
public static <T> IStructureElement<T> withChannel(String channel, IStructureElement<T> backing) {
return new IStructureElement<T>() {
public boolean check(T t, World world, int x, int y, int z) {
return backing.check(t, world, x, y, z);
}
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
ItemStack newTrigger = ChannelDataAccessor.withChannel(trigger, channel);
return backing.couldBeValid(t, world, x, y, z, newTrigger);
}
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
ItemStack newTrigger = ChannelDataAccessor.withChannel(trigger, channel);
// We know spawnHint will only be called on client side, so the lack of player isn't quite an issue
// we check if it's null again just in case.
if (newTrigger == trigger && StructureLib.getCurrentPlayer() != null)
warnNoExplicitSubChannel(StructureLib.getCurrentPlayer());
return backing.spawnHint(t, world, x, y, z, newTrigger);
}
private void warnNoExplicitSubChannel(EntityPlayer currentPlayer) {
// throttle this warning a bit.
// I'm sure we can finish a check/autoplace round within 100 milliseconds.
StructureLibAPI.addThrottledChat(
new ChatThrottleKey.NoExplicitChannel(channel),
currentPlayer,
new ChatComponentTranslation("structurelib.autoplace.warning.no_explicit_channel", channel),
(short) 100);
}
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
// I hope a CREATIVE player know what he is doing...
// no warning for yah
return backing.placeBlock(t, world, x, y, z, ChannelDataAccessor.withChannel(trigger, channel));
}
@Nullable
@Override
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return backing.getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
ItemStack newTrigger = ChannelDataAccessor.withChannel(trigger, channel);
if (newTrigger == trigger)
// we will bypass the chatter filter here, as this is a warning that player definitively want to see
// instead of some false positive error messages like item not find
warnNoExplicitSubChannel(actor);
return backing.survivalPlaceBlock(t, world, x, y, z, newTrigger, s, actor, chatter);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
ItemStack newTrigger = ChannelDataAccessor.withChannel(trigger, channel);
if (newTrigger == trigger)
// we will bypass the chatter filter here, as this is a warning that player definitively want to see
// instead of some false positive error messages like item not find
warnNoExplicitSubChannel(env.getActor());
return backing.survivalPlaceBlock(t, world, x, y, z, newTrigger, env);
}
@Nullable
@Override
public List<String> getDescription(T context) {
return backing.getDescription(context);
}
};
}
// endregion
/**
* Used internally, to generate skips for structure definitions
*/
public static <T> IStructureNavigate<T> step(int a, int b, int c) {
return step(new Vec3Impl(a, b, c));
}
/**
* Used internally, to generate skips for structure definitions
*/
@SuppressWarnings("unchecked")
public static <T> IStructureNavigate<T> step(Vec3Impl step) {
if (step == null || step.get0() < 0 || step.get1() < 0 || step.get2() < 0) {
throw new IllegalArgumentException();
}
return STEP.computeIfAbsent(step, vec3 -> {
if (vec3.get2() > 0) {
return stepC(vec3.get0(), vec3.get1(), vec3.get2());
} else if (vec3.get1() > 0) {
return stepB(vec3.get0(), vec3.get1(), vec3.get2());
} else {
return stepA(vec3.get0(), vec3.get1(), vec3.get2());
}
});
}
private static <T> IStructureNavigate<T> stepA(int a, int b, int c) {
return new IStructureNavigate<T>() {
@Override
public int getStepA() {
return a;
}
@Override
public int getStepB() {
return b;
}
@Override
public int getStepC() {
return c;
}
};
}
private static <T> IStructureNavigate<T> stepB(int a, int b, int c) {
return new IStructureNavigate<T>() {
@Override
public int getStepA() {
return a;
}
@Override
public int getStepB() {
return b;
}
@Override
public int getStepC() {
return c;
}
@Override
public boolean resetA() {
return true;
}
};
}
private static <T> IStructureNavigate<T> stepC(int a, int b, int c) {
return new IStructureNavigate<T>() {
@Override
public int getStepA() {
return a;
}
@Override
public int getStepB() {
return b;
}
@Override
public int getStepC() {
return c;
}
@Override
public boolean resetA() {
return true;
}
@Override
public boolean resetB() {
return true;
}
};
}
/**
* Used only to get pseudo code in structure writer...
* <p>
* NOTE: GT specific code got removed. TODO add a mean
*
* @param tileEntityClassifier return a string that denote the type of a tile entity, or null if it's nothing
* special. useful if the tile entity cannot be simply distinguished via getClass.
*/
public static String getPseudoJavaCode(World world, ExtendedFacing extendedFacing, int basePositionX,
int basePositionY, int basePositionZ, int basePositionA, int basePositionB, int basePositionC,
Function<? super TileEntity, String> tileEntityClassifier, int sizeA, int sizeB, int sizeC,
boolean transpose) {
Map<Block, Set<Integer>> blocks = new TreeMap<>(Comparator.comparing(Block::getUnlocalizedName));
Set<Class<? extends TileEntity>> tiles = new HashSet<>();
Set<String> specialTiles = new HashSet<>();
iterate(
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
sizeA,
sizeB,
sizeC,
((w, x, y, z) -> {
TileEntity tileEntity = w.getTileEntity(x, y, z);
if (tileEntity == null) {
Block block = w.getBlock(x, y, z);
if (block != null && block != Blocks.air) {
blocks.compute(block, (b, set) -> {
if (set == null) {
set = new TreeSet<>();
}
set.add(block.getDamageValue(world, x, y, z));
return set;
});
}
} else {
String classification = tileEntityClassifier.apply(tileEntity);
if (classification == null) {
tiles.add(tileEntity.getClass());
} else specialTiles.add(classification);
}
}));
Map<String, Character> map = new HashMap<>();
StringBuilder builder = new StringBuilder();
{
int i = 0;
char c;
builder.append("\n\nStructure:\n").append("\nBlocks:\n");
for (Map.Entry<Block, Set<Integer>> entry : blocks.entrySet()) {
Block block = entry.getKey();
Set<Integer> set = entry.getValue();
for (Integer meta : set) {
c = NICE_CHARS.charAt(i++);
if (i > NICE_CHARS.length()) {
return "Too complicated for nice chars";
}
map.put(block.getUnlocalizedName() + '\0' + meta, c);
builder.append(c).append(" -> ofBlock...(").append(block.getUnlocalizedName()).append(", ")
.append(meta).append(", ...);\n");
}
}
builder.append("\nTiles:\n");
for (Class<? extends TileEntity> tile : tiles) {
c = NICE_CHARS.charAt(i++);
if (i > NICE_CHARS.length()) {
return "Too complicated for nice chars";
}
map.put(tile.getCanonicalName(), c);
builder.append(c).append(" -> ofTileAdder(").append(tile).append(", ...);\n");
}
builder.append("\nSpecial Tiles:\n");
for (String tile : specialTiles) {
c = NICE_CHARS.charAt(i++);
if (i > NICE_CHARS.length()) {
return "Too complicated for nice chars";
}
map.put(tile, c);
builder.append(c).append(" -> ofSpecialTileAdder(").append(tile)
.append(", ...); // You will probably want to change it to something else\n");
}
}
builder.append("\nOffsets:\n").append(basePositionA).append(' ').append(basePositionB).append(' ')
.append(basePositionC).append('\n');
if (transpose) {
builder.append("\nTransposed Scan:\n").append("new String[][]{\n").append(" {\"");
iterate(
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
true,
sizeA,
sizeB,
sizeC,
((w, x, y, z) -> {
TileEntity tileEntity = w.getTileEntity(x, y, z);
if (tileEntity == null) {
Block block = w.getBlock(x, y, z);
if (block != null && block != Blocks.air) {
builder.append(
map.get(
block.getUnlocalizedName() + '\0'
+ block.getDamageValue(world, x, y, z)));
} else {
builder.append(' ');
}
} else {
String classification = tileEntityClassifier.apply(tileEntity);
if (classification == null) {
classification = tileEntity.getClass().getCanonicalName();
}
builder.append(map.get(classification));
}
}),
() -> builder.append("\",\""),
() -> {
builder.setLength(builder.length() - 2);
builder.append("},\n {\"");
});
builder.setLength(builder.length() - 8);
builder.append("\n}\n\n");
} else {
builder.append("\nNormal Scan:\n").append("new String[][]{{\n").append(" \"");
iterate(
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
false,
sizeA,
sizeB,
sizeC,
((w, x, y, z) -> {
TileEntity tileEntity = w.getTileEntity(x, y, z);
if (tileEntity == null) {
Block block = w.getBlock(x, y, z);
if (block != null && block != Blocks.air) {
builder.append(
map.get(
block.getUnlocalizedName() + '\0'
+ block.getDamageValue(world, x, y, z)));
} else {
builder.append(' ');
}
} else {
String classification = tileEntityClassifier.apply(tileEntity);
if (classification == null) {
classification = tileEntity.getClass().getCanonicalName();
}
builder.append(map.get(classification));
}
}),
() -> builder.append("\",\n").append(" \""),
() -> {
builder.setLength(builder.length() - 7);
builder.append("\n").append("},{\n").append(" \"");
});
builder.setLength(builder.length() - 8);
builder.append("}\n\n");
}
return (builder.toString().replaceAll("\"\"", "E"));
}
static <T> boolean iterateV2(IStructureElement<T>[] elements, World world, ExtendedFacing extendedFacing,
int basePositionX, int basePositionY, int basePositionZ, int basePositionA, int basePositionB,
int basePositionC, IStructureWalker<T> predicate, String iterateType) {
// change base position to base offset
basePositionA = -basePositionA;
basePositionB = -basePositionB;
basePositionC = -basePositionC;
int[] abc = new int[] { basePositionA, basePositionB, basePositionC };
int[] xyz = new int[3];
for (IStructureElement<T> element : elements) {
if (element.isNavigating()) {
abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA();
abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB();
abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC();
} else {
extendedFacing.getWorldOffset(abc, xyz);
xyz[0] += basePositionX;
xyz[1] += basePositionY;
xyz[2] += basePositionZ;
if (StructureLibAPI.isDebugEnabled()) StructureLib.LOGGER.info(
"Multi [{}, {}, {}] {} step @ {} {}",
basePositionX,
basePositionY,
basePositionZ,
iterateType,
Arrays.toString(xyz),
Arrays.toString(abc));
if (world.blockExists(xyz[0], xyz[1], xyz[2])) {
if (StructureLibAPI.isInstrumentEnabled()) {
StructureElementVisitedEvent.fireEvent(
world,
xyz[0],
xyz[1],
xyz[2],
abc[0] - basePositionA,
abc[1] - basePositionB,
abc[2] - basePositionC,
element);
}
if (!predicate.visit(element, world, xyz[0], xyz[1], xyz[2], abc[0], abc[1], abc[2])) {
if (StructureLibAPI.isDebugEnabled()) {
StructureLib.LOGGER.info(
"Multi [{}, {}, {}] {} stop @ {} {}",
basePositionX,
basePositionY,
basePositionZ,
iterateType,
Arrays.toString(xyz),
Arrays.toString(abc));
}
return false;
}
} else {
if (StructureLibAPI.isDebugEnabled()) {
StructureLib.LOGGER.info(
"Multi [{}, {}, {}] {} !blockExists @ {} {}",
basePositionX,
basePositionY,
basePositionZ,
iterateType,
Arrays.toString(xyz),
Arrays.toString(abc));
}
if (!predicate.blockNotLoaded(element, world, xyz[0], xyz[1], xyz[2], abc[0], abc[1], abc[2]))
return false;
}
abc[0] += 1;
}
}
return true;
}
public static void iterate(World world, ExtendedFacing extendedFacing, int basePositionX, int basePositionY,
int basePositionZ, int basePositionA, int basePositionB, int basePositionC, int sizeA, int sizeB, int sizeC,
IBlockPosConsumer iBlockPosConsumer) {
sizeA -= basePositionA;
sizeB -= basePositionB;
sizeC -= basePositionC;
int[] abc = new int[3];
int[] xyz = new int[3];
for (abc[2] = -basePositionC; abc[2] < sizeC; abc[2]++) {
for (abc[1] = -basePositionB; abc[1] < sizeB; abc[1]++) {
for (abc[0] = -basePositionA; abc[0] < sizeA; abc[0]++) {
extendedFacing.getWorldOffset(abc, xyz);
iBlockPosConsumer
.consume(world, xyz[0] + basePositionX, xyz[1] + basePositionY, xyz[2] + basePositionZ);
}
}
}
}
public static void iterate(World world, ExtendedFacing extendedFacing, int basePositionX, int basePositionY,
int basePositionZ, int basePositionA, int basePositionB, int basePositionC, boolean transpose, int sizeA,
int sizeB, int sizeC, IBlockPosConsumer iBlockPosConsumer, Runnable nextB, Runnable nextC) {
sizeA -= basePositionA;
sizeB -= basePositionB;
sizeC -= basePositionC;
int[] abc = new int[3];
int[] xyz = new int[3];
if (transpose) {
for (abc[1] = -basePositionB; abc[1] < sizeB; abc[1]++) {
for (abc[2] = -basePositionC; abc[2] < sizeC; abc[2]++) {
for (abc[0] = -basePositionA; abc[0] < sizeA; abc[0]++) {
extendedFacing.getWorldOffset(abc, xyz);
iBlockPosConsumer
.consume(world, xyz[0] + basePositionX, xyz[1] + basePositionY, xyz[2] + basePositionZ);
}
nextB.run();
}
nextC.run();
}
} else {
for (abc[2] = -basePositionC; abc[2] < sizeC; abc[2]++) {
for (abc[1] = -basePositionB; abc[1] < sizeB; abc[1]++) {
for (abc[0] = -basePositionA; abc[0] < sizeA; abc[0]++) {
extendedFacing.getWorldOffset(abc, xyz);
iBlockPosConsumer
.consume(world, xyz[0] + basePositionX, xyz[1] + basePositionY, xyz[2] + basePositionZ);
}
nextB.run();
}
nextC.run();
}
}
}
/**
* Transposes shape (swaps B and C axis, can be used to un-transpose transposed shape) WARNING! Do not use on old
* api...
*
* @param structurePiece shape (transposed shape)
* @return transposed shape (untransposed shape)
*/
public static String[][] transpose(String[][] structurePiece) {
String[][] shape = new String[structurePiece[0].length][structurePiece.length];
for (int i = 0; i < structurePiece.length; i++) {
for (int j = 0; j < structurePiece[i].length; j++) {
shape[j][i] = structurePiece[i][j];
}
}
return shape;
}
}