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
*
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)}
*
* 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)}
*
* 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)}
*
* 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 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)}
*
* 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 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) {
ICustomBlockSetting blockCustom = (ICustomBlockSetting) block;
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)}
*
* 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)}
*
* 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)}
*
* 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 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)}
*
* 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 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 IStructureElement 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 IStructureElement 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 IStructureElement 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 IStructureElementNoPlacement ofHint(int dots) {
int meta = dots - 1;
return new IStructureElementNoPlacement() {
@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 IStructureElementNoPlacement ofHintDeferred(Supplier icons) {
return new IStructureElementNoPlacement() {
@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 IStructureElementNoPlacement ofHintDeferred(Supplier icons, short[] RGBa) {
return new IStructureElementNoPlacement() {
@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.
*
* Note that if tierExtractor returns a null, this block will be rejected immediately. This makes null a potent
* candidate for notSet.
*
* See documentation on the other overload for more information.
*
* @see #ofBlocksTiered(ITierConverter, Object, BiConsumer, Function)
*/
public static IStructureElementCheckOnly ofBlocksTiered(ITierConverter tierExtractor,
@Nullable TIER notSet, BiConsumer setter, Function 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.
*
* 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}.
*
* 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.
*
* 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
*
* There is yet no TileEntity counterpart of this utility. Feel free to submit a PR to add it.
*
* (*): 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.
*
*
WARNING
You SHOULD NOT return notSet from your tierExtractor. If you do so, we will have this
* chain of events:
*
* - at check start, tier is reset to notSet
*
- 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
*
- at 2nd, 3rd..., same thing in 2 happens
*
- 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
*
- check got notified structure is sound, and tier is not notSet. it marks the structure as complete and start
* doing its business
*
- Player enjoy a (probably) hilariously shaped multi and (probably) reduced build cost.
*
* Example Implementation
*
* Assume you have 16 tier, each map to one particular block's 16 different meta. You will usually want something
* like this
*
*
* public class Tile extends TileEntity {
* private static final Block B = getTargetBlock();
* private static final IStructureDefinition<Tile> S = IStructureDefinition.<Tile>builder()
* // ... addShape, and other elements
* .addElement('c', ofBlocksTiered((b, m) -> b == B ? m : null,
* IntStream.range(0, 16).mapToObj(i -> Pair.of(B, i)).collect(Collectors.toList()),
* -1,
* (t, m) -> t.tier = m,
* t -> t.tier))
* .build();
* private int tier;
*
* public boolean check() {
* tier = -1;
* if (!S.check(...))
* return false;
* return isTierAcceptable(tier);
* }
* }
*
*
* Important bits of this example implementation:
*
* - The supplied ITierConverter returns null for an invalid block
* - Possible tier returned by ITierConverter is Integer object 0 to 15.
* - 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)}
* - notSet is never a tier returned by ITierConverter.
*
*
* 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 IStructureElement ofBlocksTiered(ITierConverter tierExtractor,
@Nullable List> allKnownTiers, @Nullable TIER notSet, BiConsumer setter,
Function getter) {
List descriptions = null;
if (allKnownTiers != null) {
descriptions = new java.util.ArrayList<>();
for (Pair 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 IStructureElement ofBlocksTiered(ITierConverter tierExtractor,
@Nullable List> allKnownTiers, @Nullable TIER notSet, BiConsumer setter,
Function getter, @Nullable List description) {
List> hints = allKnownTiers == null ? Collections.emptyList() : allKnownTiers;
if (hints.stream().anyMatch(Objects::isNull)) throw new IllegalArgumentException();
IStructureElementCheckOnly check = ofBlocksTiered(tierExtractor, notSet, setter, getter);
return new StructureElement_Bridge() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
return check.check(t, world, x, y, z);
}
private Pair 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 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 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 hint = getHint(trigger);
if (hint == null) return false;
if (hint.getKey() instanceof ICustomBlockSetting) {
ICustomBlockSetting block = (ICustomBlockSetting) hint.getKey();
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 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 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 getDescription(T context) {
TIER currentTier = getter.apply(context);
if (Objects.equals(currentTier, notSet)) return description;
for (Pair 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.
*
* DUE TO HISTORICAL REASONS, THIS WAS CALLED ofBlockUnlocalizedName, BUT IT ACTUALLY USES REGISTRY NAME
* INSTEAD.
*
* 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 IStructureElement 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.
*
* DUE TO HISTORICAL REASONS, THIS WAS CALLED ofBlockUnlocalizedName, BUT IT ACTUALLY USES REGISTRY NAME
* INSTEAD.
*
* 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()}.
*
* Will place block or hint using the given meta if wildcard is true.
*/
public static IStructureElement 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() {
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.
*
* DUE TO HISTORICAL REASONS, THIS WAS CALLED ofBlockUnlocalizedName, BUT IT ACTUALLY USES REGISTRY NAME
* INSTEAD.
*
* 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 IStructureElement ofBlockUnlocalizedName(String modid, String unlocalizedName, int meta,
IStructureElement 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() {
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 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.
*
* 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 IStructureElementNoPlacement ofBlocksFlatHint(Map blocsMap, Block hintBlock,
int hintMeta) {
if (blocsMap == null || blocsMap.isEmpty() || hintBlock == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement() {
@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.
*
* 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 IStructureElementNoPlacement ofBlocksMapHint(Map> blocsMap,
Block hintBlock, int hintMeta) {
if (blocsMap == null || blocsMap.isEmpty() || hintBlock == null) {
throw new IllegalArgumentException();
}
for (Collection value : blocsMap.values()) {
if (value.isEmpty()) {
throw new IllegalArgumentException();
}
}
return new IStructureElementNoPlacement() {
@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 IStructureElementNoPlacement ofBlockHint(Block block, int meta, Block hintBlock,
int hintMeta) {
if (block == null || hintBlock == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement() {
@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 IStructureElementNoPlacement 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.
*
* Useful when your logic is very complex. Does not support autoplace.
*/
public static IStructureElementNoPlacement ofBlockAdderHint(IBlockAdder iBlockAdder, Block hintBlock,
int hintMeta) {
if (iBlockAdder == null || hintBlock == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement() {
@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 IStructureElement ofBlocksFlat(Map blocsMap, Block defaultBlock,
int defaultMeta) {
if (blocsMap == null || blocsMap.isEmpty() || defaultBlock == null) {
throw new IllegalArgumentException();
}
if (defaultBlock instanceof ICustomBlockSetting) {
return new IStructureElement() {
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 blocks = ImmutableList.builder();
Predicate predicate = s -> true;
for (Entry 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() {
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 blocks = ImmutableList.builder();
Predicate predicate = s -> true;
for (Entry 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 IStructureElement ofBlocksMap(Map> blocsMap, Block defaultBlock,
int defaultMeta) {
if (blocsMap == null || blocsMap.isEmpty() || defaultBlock == null) {
throw new IllegalArgumentException();
}
for (Collection value : blocsMap.values()) {
if (value.isEmpty()) {
throw new IllegalArgumentException();
}
}
if (defaultBlock instanceof ICustomBlockSetting) {
return new IStructureElement() {
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 blocks = ImmutableList.builder();
Predicate predicate = s -> true;
for (Entry> 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() {
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 blocks = ImmutableList.builder();
Predicate predicate = s -> true;
for (Entry> 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 IStructureElement 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() {
@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 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() {
@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 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 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 IStructureElement ofBlockAnyMeta(Block block, Block defaultBlock, int defaultMeta) {
if (block == null || defaultBlock == null) {
throw new IllegalArgumentException();
}
if (block instanceof ICustomBlockSetting) {
return new IStructureElement() {
@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 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() {
@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 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 IStructureElement ofBlock(Block block, int meta) {
return ofBlock(block, meta, block, meta);
}
/**
* Accept a single block, but accept any meta.
*/
public static IStructureElement ofBlockAnyMeta(Block block) {
return ofBlockAnyMeta(block, block, 0);
}
/**
* Accept a single block, but accept any meta. Spawn hint/autoplace using given meta.
*/
public static IStructureElement 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.
*
* Useful when your logic is very complex.
*/
public static IStructureElement ofBlockAdder(IBlockAdder iBlockAdder, Block defaultBlock,
int defaultMeta) {
if (iBlockAdder == null || defaultBlock == null) {
throw new IllegalArgumentException();
}
if (defaultBlock instanceof ICustomBlockSetting) {
return new StructureElement_Bridge() {
@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() {
@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 IStructureElement ofBlockAdder(IBlockAdder 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 IStructureElementNoPlacement ofTileAdder(ITileAdder iTileAdder, Block hintBlock,
int hintMeta) {
if (iTileAdder == null || hintBlock == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement() {
@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 IStructureElementNoPlacement ofSpecificTileAdder(BiPredicate iTileAdder,
Class tileClass, Block hintBlock, int hintMeta) {
if (iTileAdder == null || hintBlock == null || tileClass == null) {
throw new IllegalArgumentException();
}
return new IStructureElementNoPlacement() {
@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 , T> IStructureElement onElementPass(Consumer onCheckPass,
B element) {
return new IStructureElement() {
@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 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 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 , T> IStructureElement withDescription(
@Nullable List description, B element) {
return new IStructureElement() {
@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 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 getDescription(T context) {
return description;
}
};
}
/**
* Call a callback if downstream element returned false in check.
*
* @param onFail side effect
* @param element downstream
*/
public static , T> IStructureElement onElementFail(Consumer onFail,
B element) {
return new IStructureElement() {
@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 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 getDescription(T context) {
return element.getDescription(context);
}
};
}
// endregion
/**
* Enable this structure element only if given predicate returns true.
*
* Return REJECT_CONTINUE when survival auto place if given predicate returns false.
*/
public static IStructureElement 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 IStructureElement onlyIf(Predicate super T> predicate,
IStructureElement super T> downstream, PlaceResult placeResultWhenDisabled) {
return new IStructureElement() {
@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 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 getDescription(T context) {
return downstream.getDescription(context);
}
};
}
/**
* This allows you to compose different {@link IStructureElement} to form a OR 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. (*)
*
* 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.
*
* (*): 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.
*
* 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 IStructureElementChain ofChain(IStructureElement... elementChain) {
if (elementChain == null || elementChain.length == 0) {
throw new IllegalArgumentException();
}
for (IStructureElement iStructureElement : elementChain) {
if (iStructureElement == null) {
throw new IllegalArgumentException();
}
}
return () -> elementChain;
}
/**
* This allows you to compose different {@link IStructureElement} to form a OR chain.
*
* Practically no difference with the other overload.
*
* @see #ofChain(IStructureElement[])
*/
@SuppressWarnings("unchecked")
public static IStructureElementChain ofChain(List> 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 extended context type
* @param existing context object type
*/
public static > IStructureElement withContext(
IStructureElement elem) {
return new IStructureElement() {
@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 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 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 IStructureElementDeferred lazy(Supplier> to) {
if (to == null) {
throw new IllegalArgumentException();
}
return new LazyStructureElement<>(t -> to.get());
}
/**
* This will defer the actual instantiation of structure element until the first time structure code is
* actually called.
*
* 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 constructor 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 IStructureElementDeferred lazy(Function> 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 constructor 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 IStructureElementDeferred defer(Supplier> to) {
if (to == null) {
throw new IllegalArgumentException();
}
return new IStructureElementDeferred() {
@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 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 constructor 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 IStructureElementDeferred defer(Function> to) {
if (to == null) {
throw new IllegalArgumentException();
}
return new IStructureElementDeferred() {
@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 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.
*
* 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 IStructureElementDeferred defer(Function keyExtractor,
Map> map) {
return partitionBy(keyExtractor, map);
}
/**
* This is the switch block for structure code.
*
* 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.
*
* 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 IStructureElementDeferred partitionBy(Function keyExtractor,
Map> 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.
*
* 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()}
*
* 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 IStructureElementDeferred defer(Function keyExtractor,
Map> map, IStructureElement defaultElem) {
return partitionBy(keyExtractor, map, defaultElem);
}
/**
* This is the switch block for structure code, with default case support.
*
* 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()}
*
* 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 IStructureElementDeferred partitionBy(Function keyExtractor,
Map> map, IStructureElement 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.
*
* 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
*
* 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 IStructureElementDeferred defer(Function keyExtractor,
IStructureElement... array) {
return partitionBy(keyExtractor, array);
}
/**
* This is the switch block for structure code.
*
* 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
*
* 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 IStructureElementDeferred partitionBy(Function keyExtractor,
IStructureElement... array) {
if (keyExtractor == null || array == null) {
throw new IllegalArgumentException();
}
return defer(keyExtractor.andThen(i -> array[i]));
}
/**
* This is the switch block for structure code.
*
* 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
*
* 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 IStructureElementDeferred defer(Function keyExtractor,
List> array) {
return partitionBy(keyExtractor, array);
}
/**
* This is the switch block for structure code.
*
* 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
*
* 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 IStructureElementDeferred partitionBy(Function keyExtractor,
List