package com.gtnewhorizon.structurelib.structure; import static com.gtnewhorizon.structurelib.StructureLib.LOGGER; import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.IChatComponent; import net.minecraft.world.World; import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.util.ItemStackPredicate; import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode; /** * Use StructureUtility to instantiate. These are the building blocks for your {@link IStructureDefinition}. It * represents what a particular element can be and how this particular element should be autoplaced. * * @param Type of the context object. {@link IStructureElement} is contravariant over T, so parameters and fields of * type {@link IStructureElement} should be declared as {@code IStructureElement} */ public interface IStructureElement { boolean check(T t, World world, int x, int y, int z); /** * Pure (stateless and side effect free) function to test if current block could be valid. Used to give user hints * about incorrectly placed blocks given a controller and trigger item. If couldBeValid(...) == false for a set of * channels, with those same tiers, check(...) == false. Not required, defaults to true which is always safe but * won't give the user error hints. */ default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) { return true; } /** * Returns a human-readable description of what block(s) this element accepts. Used for diagnostic messages when * structure checks fail (e.g. "expected Input Bus at (1, 2, 3)"). *

* Unlike {@link #getBlocksToPlace}, which returns the full set of concrete block variants for autoplacing (e.g. * every tier of Input Bus), this method returns category-level descriptions suitable for display to the * player (e.g. just "Input Bus"). It also requires no parameters, no world, trigger item, or environment, making it * usable in contexts where those are unavailable. *

* Implementations should return lang keys (e.g. {@code "tile.blockGlass.name"}) rather than pre-translated * display names. The caller is responsible for translating these keys on the client side via * {@code StatCollector.translateToLocal()}. If a key has no matching translation, {@code translateToLocal} returns * the input string unchanged, so pre-translated fallback strings also work. * * @param context the context object (e.g. the multiblock controller), used to determine state-dependent * descriptions * @return a list of accepted block descriptions as lang keys (or display names as fallback), or null if no * description is available */ @Nullable default List getDescription(T context) { return null; } boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger); boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger); /** * Try place the block by taking resource from given IItemSource. *

* You might want to use * {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, net.minecraft.entity.player.EntityPlayer)} * or its overloads to implement this. * * @param trigger trigger item * @param s drain resources from this place * @param actor source of action. for very critical errors you can also just send the error messages here, * bypassing any filter that chatter might have. * @param chatter send error messages here. Caller will choose an appropriate way to forward it to player if the * other fallbacks also fails. * @deprecated caller should call the non deprecated overload. implementor for reusable {@link IStructureElement} * should still implement this. implementor for private implementations can freely ignore this as long * as you know the caller will not call this overload. */ @Deprecated default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, EntityPlayerMP actor, Consumer chatter) { if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit"); if (StructureLibAPI.isDebugEnabled()) LOGGER.error( "Default implementation of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}", getClass().getName()); if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT; return PlaceResult.REJECT_CONTINUE; } /** * A simplified version of survivalPlaceBlock. Return null if not implemented. *

* BY OVERRIDING THIS TO NULL, YOU ACKNOWLEDGE THAT THE {@link #check(Object, World, int, int, int)} OF THIS * CLASS IS SIDE EFFECT FREE AND CAN HAVE ITS CHECK CALLED WITHOUT BREAKING ANYTHING. OTHERWISE OVERRIDE THE * {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} TO PROVIDE A * SIDEEFFECT FREE {@link PlaceResult#SKIP}. *

* It should be noticed that this filter is only advisory and the actual * {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} or * {@link #check(Object, World, int, int, int)} are free to reject/accept/place blocks not contained in this list, * e.g. when the element can be placed anywhere in the structure, but no more than 3 overall; when the structure * element cannot know all blocks it will accept at the time of calling; when this is backed by a legacy * {@link IStructureElement} that just didn't implement this API. * * @param trigger trigger item * @param env autoplacing environment * @return null if not implemented, otherwise an instance describing what kind of blocks will be placed by * {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} */ @Nullable default BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger, AutoPlaceEnvironment env) { return null; } /** * Try place the block by taking resource from given {@link IItemSource}. *

* You might want to use * {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, net.minecraft.entity.player.EntityPlayer)} * or its overloads to implement this. *

* The default implementation provided will *

    *
  1. Get {@link BlocksToPlace} via * {@link #getBlocksToPlace(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}. If not null *
      *
    1. call {@link #check(Object, World, int, int, int)}. If this returns {@code true}, {@link PlaceResult#SKIP} * will be returned without further action.
    2. *
    3. Use the {@link BlocksToPlace} retrieved earlier and passed in {@link IItemSource} to determine an item to * place
    4. *
    5. Hand over control to * {@link StructureUtility#survivalPlaceBlock(ItemStack, NBTMode, NBTTagCompound, boolean, World, int, int, int, IItemSource, EntityPlayer, Consumer)}
    6. *
    *
  2. *
  3. Call legacy * {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)} if * {@code env.getActor()} contains an {@link EntityPlayerMP}
  4. *
  5. Emit warning under debug mode (or throw exception under panic mode), then return * {@link PlaceResult#SKIP}
  6. *
* It should be noticed that the default implementation is likely to cause unexpected issues if the * {@link #check(Object, World, int, int, int)} is not side effect free, or is not idempotent. E.g. the * {@link #check(Object, World, int, int, int)} will add current coord to a {@link java.util.List}. As this method * might be invoked at a location that is already accepted, this will cause the machine to have multiple of this * coord in its list. * * @param trigger trigger item * @param env autoplacing environment */ default 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 (e != null) { 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, true, world, x, y, z, source, actor, chatter); } for (ItemStack stack : e.getStacks()) { if (!source.takeOne(stack, true)) continue; return StructureUtility.survivalPlaceBlock( stack, NBTMode.EXACT, stack.stackTagCompound, true, world, x, y, z, source, actor, chatter); } return PlaceResult.REJECT; } if (actor instanceof EntityPlayerMP) return survivalPlaceBlock(t, world, x, y, z, trigger, source, (EntityPlayerMP) actor, chatter); if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit"); if (StructureLibAPI.isDebugEnabled()) LOGGER.info( "Fallback shim code of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}", getClass().getName()); return PlaceResult.REJECT_CONTINUE; } /** * Forget the messed up class dependency graph for now. this is just so convenient. */ default IStructureElementNoPlacement noPlacement() { return new IStructureElementNoPlacement<>() { @Override public boolean check(T t, World world, int x, int y, int z) { return IStructureElement.this.check(t, world, x, y, z); } @Override public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) { return IStructureElement.this.couldBeValid(t, world, x, y, z, trigger); } @Override public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return IStructureElement.this.spawnHint(t, world, x, y, z, trigger); } @Nullable @Override public List getDescription(T context) { return IStructureElement.this.getDescription(context); } }; } default int getStepA() { return 1; } default int getStepB() { return 0; } default int getStepC() { return 0; } default boolean resetA() { return false; } default boolean resetB() { return false; } default boolean resetC() { return false; } default boolean isNavigating() { return false; } enum PlaceResult { /** * The element's check passed, the position is already valid. No placement is needed. In the walker: continue * iteration (no error, not counted as placed). In ofChain: short-circuits, remaining alternatives are skipped. */ SKIP, /** * The element cannot handle this position (occupied, missing resources, etc.). In the walker: stop iteration * with error particles. In ofChain: try next alternative. */ REJECT, /** * This element has no survivalPlaceBlock implementation (default fallback), or otherwise cannot place but is * not an error condition. In the walker: continue iteration silently (like SKIP). In ofChain: try next * alternative (like REJECT), so a better-suited element can handle it. */ REJECT_CONTINUE, /** * Autoplace cannot proceed within this tick. To proceed further would require stopping the placement and wait * for next round. */ STOP, /** * Element placed successfully. */ ACCEPT, /** * Combination of ACCEPT and STOP. *

* Element placed successfully. To proceed further would require stopping the placement and wait for next round. */ ACCEPT_STOP; } final class BlocksToPlace { public static final BlocksToPlace errored = createEmpty(); private final Predicate predicate; private final Iterable stacks; public static BlocksToPlace createEmpty() { return new BlocksToPlace(s -> false, Collections.emptyList()); } public static BlocksToPlace create(ItemStack... stacks) { return create(Arrays.asList(stacks)); } public static BlocksToPlace create(Iterable stacks) { Predicate predicate = null; for (ItemStack stack : stacks) { ItemStackPredicate p = ItemStackPredicate.from(stack, NBTMode.EXACT); if (predicate == null) predicate = p; else predicate = predicate.or(p); } return new BlocksToPlace(predicate, stacks); } public static BlocksToPlace create(Block block, int meta) { Item itemBlock = Item.getItemFromBlock(block); if (itemBlock instanceof ISpecialItemBlock) { meta = ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta); } return create(itemBlock, meta); } public static BlocksToPlace create(Item item, int meta) { return new BlocksToPlace( ItemStackPredicate.from(item).setMeta(meta), Collections.singletonList(new ItemStack(item, 1, meta))); } public static BlocksToPlace create(ItemStack itemStack) { return new BlocksToPlace(ItemStackPredicate.from(itemStack), Collections.singletonList(itemStack)); } public static BlocksToPlace create(Predicate predicate) { return new BlocksToPlace(predicate, null); } BlocksToPlace(Predicate predicate, Iterable stacks) { this.predicate = predicate; this.stacks = stacks; } /** * Get the blocks to place filter. This is usually slower as it requires walking through inventories one by one. *

* Suitable for use with {@link IItemSource#takeOne(Predicate, boolean)} * * @return a predicate. never null. */ @Nonnull public Predicate getPredicate() { return predicate; } /** * Get the ItemStacks that this one is known to accept. Can be null if this info is not available at the time * this instance is constructed. *

* Suitable for use with {@link IItemSource#takeOne(ItemStack, boolean)}. *

* This should be equivalent to the predicate returned from {@link #getPredicate()} */ @Nullable public Iterable getStacks() { return stacks; } } }