2021-05-17 03:02:24 +08:00
package com.gtnewhorizon.structurelib.structure ;
2022-08-08 17:40:12 +08:00
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER ;
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE ;
import com.gtnewhorizon.structurelib.StructureLibAPI ;
2022-09-13 21:22:34 +08:00
import com.gtnewhorizon.structurelib.util.ItemStackPredicate ;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode ;
import java.util.Arrays ;
import java.util.Collections ;
2022-08-08 17:40:12 +08:00
import java.util.function.Consumer ;
2022-09-13 21:22:34 +08:00
import java.util.function.Predicate ;
import javax.annotation.Nonnull ;
import javax.annotation.Nullable ;
2022-08-30 23:27:18 +08:00
import net.minecraft.block.Block ;
2022-09-11 02:54:11 +08:00
import net.minecraft.entity.player.EntityPlayer ;
2022-08-08 17:40:12 +08:00
import net.minecraft.entity.player.EntityPlayerMP ;
2022-09-13 21:22:34 +08:00
import net.minecraft.item.Item ;
2021-05-17 03:02:24 +08:00
import net.minecraft.item.ItemStack ;
2022-09-15 15:58:17 +08:00
import net.minecraft.nbt.NBTTagCompound ;
2022-08-08 17:40:12 +08:00
import net.minecraft.util.IChatComponent ;
2021-05-17 03:02:24 +08:00
import net.minecraft.world.World ;
/**
* Use StructureUtility to instantiate
*/
public interface IStructureElement < T > {
2022-07-08 17:14:18 +08:00
boolean check ( T t , World world , int x , int y , int z );
2021-05-17 03:02:24 +08:00
2022-07-08 17:14:18 +08:00
boolean spawnHint ( T t , World world , int x , int y , int z , ItemStack trigger );
2021-05-17 03:02:24 +08:00
2022-07-08 17:14:18 +08:00
boolean placeBlock ( T t , World world , int x , int y , int z , ItemStack trigger );
2021-05-17 03:02:24 +08:00
2022-08-08 17:40:12 +08:00
/**
* Try place the block by taking resource from given IItemSource.
2022-08-30 23:27:18 +08:00
* <p>
2022-09-11 02:54:11 +08:00
* You might want to use {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, net.minecraft.entity.player.EntityPlayer)}
2022-08-30 23:27:18 +08:00
* or its overloads to implement this.
2022-08-08 17:40:12 +08:00
*
2022-08-30 23:27:18 +08:00
* @param trigger trigger item
2022-08-08 17:40:12 +08:00
* @param s drain resources from this place
2022-08-30 23:27:18 +08:00
* @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.
2022-09-11 02:54:11 +08:00
* @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.
2022-08-08 17:40:12 +08:00
*/
2022-09-11 02:54:11 +08:00
@Deprecated
2022-08-08 17:40:12 +08:00
default PlaceResult survivalPlaceBlock (
T t ,
World world ,
int x ,
int y ,
int z ,
ItemStack trigger ,
IItemSource s ,
EntityPlayerMP actor ,
Consumer < IChatComponent > chatter ) {
if ( PANIC_MODE ) throw new RuntimeException ( "Panic Tripwire hit" );
2022-08-30 23:27:18 +08:00
if ( StructureLibAPI . isDebugEnabled ())
2022-08-08 17:40:12 +08:00
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 . SKIP ;
}
2022-09-13 21:22:34 +08:00
/**
* A simplified version of survivalPlaceBlock. Return null if not implemented.
* <p>
* <b>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}.</b>
* <p>
* 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 ;
}
2022-09-11 02:54:11 +08:00
/**
2022-09-15 15:58:17 +08:00
* Try place the block by taking resource from given {@link IItemSource}.
2022-09-11 02:54:11 +08:00
* <p>
* 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.
2022-09-15 15:58:17 +08:00
* <p>
* The default implementation provided will
* <ol>
* <li>Get {@link BlocksToPlace} via {@link #getBlocksToPlace(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}. If not null
* <ol>
* <li>call {@link #check(Object, World, int, int, int)}. If this returns {@code true}, {@link PlaceResult#SKIP} will be returned without further action.</li>
* <li>Use the {@link BlocksToPlace} retrieved earlier and passed in {@link IItemSource} to determine an item to place</li>
* <li>Hand over control to {@link StructureUtility#survivalPlaceBlock(ItemStack, NBTMode, NBTTagCompound, boolean, World, int, int, int, IItemSource, EntityPlayer, Consumer)}</li>
* </ol>
* </li>
* <li>Call legacy {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)} if {@code env.getActor()} contains an {@link EntityPlayerMP}</li>
* <li>Emit warning under debug mode (or throw exception under panic mode), then return {@link PlaceResult#SKIP}</li>
* </ol>
* 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.
2022-09-11 02:54:11 +08:00
*
* @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 ) {
2022-09-13 21:22:34 +08:00
BlocksToPlace e = getBlocksToPlace ( t , world , x , y , z , trigger , env );
IItemSource source = env . getSource ();
2022-09-11 02:54:11 +08:00
EntityPlayer actor = env . getActor ();
2022-09-13 21:22:34 +08:00
Consumer < IChatComponent > 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 ;
}
2022-09-11 02:54:11 +08:00
if ( actor instanceof EntityPlayerMP )
2022-09-13 21:22:34 +08:00
return survivalPlaceBlock ( t , world , x , y , z , trigger , source , ( EntityPlayerMP ) actor , chatter );
2022-09-11 02:54:11 +08:00
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 . SKIP ;
}
2022-08-08 17:40:12 +08:00
/**
* Forget the messed up class dependency graph for now. this is just so convenient.
*/
default IStructureElementNoPlacement < T > noPlacement () {
return new IStructureElementNoPlacement < T > () {
@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 spawnHint ( T t , World world , int x , int y , int z , ItemStack trigger ) {
return IStructureElement . this . spawnHint ( t , world , x , y , z , trigger );
}
};
}
2022-07-08 17:14:18 +08:00
default int getStepA () {
return 1 ;
}
2021-05-17 03:02:24 +08:00
2022-07-08 17:14:18 +08:00
default int getStepB () {
return 0 ;
}
2021-05-17 03:02:24 +08:00
2022-07-08 17:14:18 +08:00
default int getStepC () {
return 0 ;
}
2021-05-17 03:02:24 +08:00
2022-07-08 17:14:18 +08:00
default boolean resetA () {
return false ;
}
2021-05-17 03:02:24 +08:00
2022-07-08 17:14:18 +08:00
default boolean resetB () {
return false ;
}
2021-05-17 03:02:24 +08:00
2022-07-08 17:14:18 +08:00
default boolean resetC () {
return false ;
}
2021-05-17 03:02:24 +08:00
2022-07-08 17:14:18 +08:00
default boolean isNavigating () {
return false ;
}
2022-08-08 17:40:12 +08:00
enum PlaceResult {
/**
* This element either exists already, or does not yet have an implementation for survivalPlaceBlock, or
* some other unforeseen situations.
2022-08-24 12:36:48 +08:00
* TODO this definition doesn't seem right. Should we separate SKIP from EXISTS, or SKIP from ERROR?
2022-08-08 17:40:12 +08:00
*/
SKIP ,
/**
* This element's space is occupied by other stuff and require player attention, or player missing required resource,
* or the block cannot be placed due to obscure mechanisms, or some other unforeseen situations.
*/
REJECT ,
/**
* 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.
2022-08-30 23:27:18 +08:00
* <p>
2022-08-08 17:40:12 +08:00
* Element placed successfully. To proceed further would require stopping the placement and wait for next round.
*/
ACCEPT_STOP ;
}
2022-09-13 21:22:34 +08:00
final class BlocksToPlace {
public static final BlocksToPlace errored = createEmpty ();
private final Predicate < ItemStack > predicate ;
private final Iterable < ItemStack > 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 < ItemStack > stacks ) {
Predicate < ItemStack > 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 < ItemStack > predicate ) {
return new BlocksToPlace ( predicate , null );
}
BlocksToPlace ( Predicate < ItemStack > predicate , Iterable < ItemStack > 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.
* <p>
* Suitable for use with {@link IItemSource#takeOne(Predicate, boolean)}
2022-09-15 15:58:17 +08:00
*
2022-09-13 21:22:34 +08:00
* @return a predicate. never null.
*/
@Nonnull
public Predicate < ItemStack > 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.
* <p>
* Suitable for use with {@link IItemSource#takeOne(ItemStack, boolean)}.
* <p>
* This should be equivalent to the predicate returned from {@link #getPredicate()}
*/
@Nullable
public Iterable < ItemStack > getStacks () {
return stacks ;
}
}
2021-05-17 03:02:24 +08:00
}