diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java index 6de73dc0..703714e3 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java +++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java @@ -9,6 +9,7 @@ import com.gtnewhorizon.structurelib.proxy.CommonProxy; import com.gtnewhorizon.structurelib.util.XSTR; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.SidedProxy; +import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.network.NetworkRegistry; import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper; @@ -29,6 +30,7 @@ import org.apache.logging.log4j.Logger; @Mod(modid = StructureLibAPI.MOD_ID, name = "StructureLib", version = "GRADLETOKEN_VERSION", acceptableRemoteVersions = "*", guiFactory = "com.gtnewhorizon.structurelib.GuiFactory") public class StructureLib { public static boolean DEBUG_MODE = Boolean.getBoolean("structurelib.debug"); + public static boolean PANIC_MODE = Boolean.getBoolean("structurelib.panic"); public static Logger LOGGER = LogManager.getLogger("StructureLib"); @SidedProxy(serverSide = "com.gtnewhorizon.structurelib.proxy.CommonProxy", clientSide = "com.gtnewhorizon.structurelib.proxy.ClientProxy") @@ -64,6 +66,11 @@ public class StructureLib { proxy.preInit(e); } + @Mod.EventHandler + public void init(FMLInitializationEvent e) { + // TODO register stackExtractors and inventoryExtractors for other mods + } + public static void addClientSideChatMessages(String... messages) { proxy.addClientSideChatMessages(messages); } @@ -75,4 +82,8 @@ public class StructureLib { public static boolean isCurrentPlayer(EntityPlayer player) { return proxy.isCurrentPlayer(player); } + + public static long getOverworldTime() { + return proxy.getOverworldTime(); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java index f876e5d4..2e054859 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java +++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java @@ -3,6 +3,7 @@ package com.gtnewhorizon.structurelib; import com.gtnewhorizon.structurelib.net.AlignmentMessage; import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.structure.IItemSource; import cpw.mods.fml.common.network.NetworkRegistry; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; @@ -12,6 +13,8 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.IIcon; import net.minecraft.world.World; +import java.util.UUID; + import static com.gtnewhorizon.structurelib.StructureLib.proxy; /** @@ -70,6 +73,14 @@ public class StructureLibAPI { proxy.hintParticle(w, x, y, z, block, meta); } + /** + * Update the tint of given hint particle. Do nothing if particle not found. + * @return false if nothing updated. true if updated or on server side. + */ + public static boolean updateHintParticleTint(World w, int x, int y, int z, short[] RGBa) { + return proxy.updateHintParticleTint(w, x, y, z, RGBa); + } + /** * Query the ExtendedFacing of this tile entity. * The ExtendedFacing will later be set onto given tile entity via {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} @@ -133,4 +144,15 @@ public class StructureLibAPI { public static void setDebugEnabled(boolean enabled) { StructureLib.DEBUG_MODE = enabled; } + + /** + * Determines if given block can be replaced without much effort. The exact predicate clauses is not stable and + * will be changed, but the general idea will always stay the same. + * + * Use this in your {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, UUID)} + */ + public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, UUID actorProfile) { + // TODO extend this function a bit + return w.getBlock(x, y, z).isReplaceable(w, x, y, z); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java index f881f88b..d5d5dbc5 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java @@ -4,15 +4,25 @@ import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.alignment.IAlignment; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.structure.IItemSource; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ChatComponentTranslation; import net.minecraft.world.World; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.common.util.ForgeDirection; +import java.util.UUID; + public class ConstructableUtility { + + // TODO make these configurable and expose this to external world + private static final int COOLDOWN = 5; + private static final int BUDGET = 25; + private ConstructableUtility() { } @@ -30,25 +40,35 @@ public class ConstructableUtility { return aPlayer instanceof EntityPlayerMP; } if (aPlayer instanceof EntityPlayerMP) { - //struct gen - if (aPlayer.isSneaking() && aPlayer.capabilities.isCreativeMode) { - if (tTileEntity instanceof IConstructableProvider) { - IConstructable constructable = ((IConstructableProvider) tTileEntity).getConstructable(); - if (constructable != null) { - constructable.construct(aStack, false); - } - } else if (tTileEntity instanceof IConstructable) { - ((IConstructable) tTileEntity).construct(aStack, false); - } else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) { - IMultiblockInfoContainer iMultiblockInfoContainer = IMultiblockInfoContainer.get(tTileEntity.getClass()); - if (tTileEntity instanceof IAlignment) { - iMultiblockInfoContainer.construct(aStack, false, tTileEntity, - ((IAlignment) tTileEntity).getExtendedFacing()); - } else { - iMultiblockInfoContainer.construct(aStack, false, tTileEntity, - ExtendedFacing.of(ForgeDirection.getOrientation(aSide))); - } - } + // server side + // not sneaking - client side will generate hologram. nothing to do on server side + if (!aPlayer.isSneaking()) return true; + + long timePast = StructureLib.getOverworldTime() - getLastUseTick(aStack); + if (timePast < COOLDOWN) { + aPlayer.addChatComponentMessage(new ChatComponentTranslation("item.structurelib.constructableTrigger.too_fast", COOLDOWN - timePast)); + return true; + } + + IConstructable constructable = null; + if (tTileEntity instanceof IConstructableProvider) + constructable = ((IConstructableProvider) tTileEntity).getConstructable(); + else if (tTileEntity instanceof IConstructable) { + constructable = (IConstructable) tTileEntity; + } else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) { + ExtendedFacing facing = tTileEntity instanceof IAlignment ? + ((IAlignment) tTileEntity).getExtendedFacing() : + ExtendedFacing.of(ForgeDirection.getOrientation(aSide)); + constructable = IMultiblockInfoContainer.get(tTileEntity.getClass()).toConstructable(tTileEntity, facing); + } + + if (constructable == null) return false; + + if (aPlayer.capabilities.isCreativeMode) { + constructable.construct(aStack, false); + } else if (constructable instanceof ISurvivalConstructable) { + ((ISurvivalConstructable) constructable).survivalConstruct(aStack, BUDGET, IItemSource.fromPlayer((EntityPlayerMP) aPlayer), aPlayer.getGameProfile().getId()); + setLastUseTickToStackTag(aStack); } return true; } else if (StructureLib.isCurrentPlayer(aPlayer)) {//particles and text client side @@ -79,4 +99,16 @@ public class ConstructableUtility { } return false; } + + private static long getLastUseTick(ItemStack aStack) { + return aStack.hasTagCompound() ? aStack.getTagCompound().getLong("LastUse") : 0; + } + + private static void setLastUseTickToStackTag(ItemStack aStack) { + NBTTagCompound tag = aStack.stackTagCompound; + if (tag == null) + tag = aStack.stackTagCompound = new NBTTagCompound(); + // here we force use the overworld tick time, in case the current world is over + tag.setLong("LastUse", StructureLib.getOverworldTime()); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IMultiblockInfoContainer.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IMultiblockInfoContainer.java index 138ed968..3c03c47c 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IMultiblockInfoContainer.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IMultiblockInfoContainer.java @@ -1,12 +1,15 @@ package com.gtnewhorizon.structurelib.alignment.constructable; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.structure.IItemSource; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; import java.util.HashMap; +import java.util.UUID; /** * To implement IConstructable on not own TileEntities @@ -33,6 +36,52 @@ public interface IMultiblockInfoContainer { void construct(ItemStack stackSize, boolean hintsOnly, T tileEntity, ExtendedFacing aSide); + /** + * Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, UUID)} + * @return -1 if done, a helping pointer + */ + default int survivalConstruct(ItemStack stackSize, int elementBudge, IItemSource source, UUID actorProfile, T tileEntity, ExtendedFacing aSide) { + return -1; + } + @SideOnly(Side.CLIENT) String[] getDescription(ItemStack stackSize); + + /** + * Override this and return {@link #toConstructable(IMultiblockInfoContainer, Object, ExtendedFacing)} if + * {@link #survivalConstruct(ItemStack, int, IItemSource, UUID, Object, ExtendedFacing)} isn't a stub + */ + default IConstructable toConstructable(T tileEntity, ExtendedFacing aSide) { + return new IConstructable() { + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + IMultiblockInfoContainer.this.construct(stackSize, hintsOnly, tileEntity, aSide); + } + + @Override + public String[] getStructureDescription(ItemStack stackSize) { + return IMultiblockInfoContainer.this.getDescription(stackSize); + } + }; + } + + + static ISurvivalConstructable toConstructable(IMultiblockInfoContainer thiz, T tileEntity, ExtendedFacing aSide) { + return new ISurvivalConstructable() { + @Override + public int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source, UUID actorProfile) { + return thiz.survivalConstruct(stackSize, elementBudget, source, actorProfile, tileEntity, aSide); + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + thiz.construct(stackSize, hintsOnly, tileEntity, aSide); + } + + @Override + public String[] getStructureDescription(ItemStack stackSize) { + return thiz.getDescription(stackSize); + } + }; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java new file mode 100644 index 00000000..58e18b32 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java @@ -0,0 +1,16 @@ +package com.gtnewhorizon.structurelib.alignment.constructable; + +import com.gtnewhorizon.structurelib.structure.IItemSource; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +import java.util.UUID; + +public interface ISurvivalConstructable extends IConstructable { + /** + * Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, UUID)} + * + * @return -1 if done, otherwise number of elements placed this round + */ + int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source, UUID actorProfile); +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/item/ItemConstructableTrigger.java b/src/main/java/com/gtnewhorizon/structurelib/item/ItemConstructableTrigger.java index 3ff5e22e..4ce36f8b 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/item/ItemConstructableTrigger.java +++ b/src/main/java/com/gtnewhorizon/structurelib/item/ItemConstructableTrigger.java @@ -2,9 +2,11 @@ package com.gtnewhorizon.structurelib.item; import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility; +import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.World; import java.util.List; @@ -34,4 +36,14 @@ public class ItemConstructableTrigger extends Item { aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.3"));//(Sneak Use in creative to build) aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.4"));//Quantity affects tier/mode/type } + + @Override + public void onUpdate(ItemStack p_77663_1_, World p_77663_2_, Entity p_77663_3_, int p_77663_4_, boolean p_77663_5_) { + if (p_77663_1_.hasTagCompound() && p_77663_1_.getTagCompound().getLong("LastUse") + 5 < StructureLib.getOverworldTime()) { + NBTTagCompound tag = p_77663_1_.getTagCompound(); + tag.removeTag("LastUse"); + if (tag.hasNoTags()) + p_77663_1_.setTagCompound(null); + } + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java b/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java new file mode 100644 index 00000000..39718826 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java @@ -0,0 +1,66 @@ +package com.gtnewhorizon.structurelib.net; + +import com.gtnewhorizon.structurelib.StructureLib; +import com.gtnewhorizon.structurelib.StructureLibAPI; +import cpw.mods.fml.common.network.simpleimpl.IMessage; +import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; +import cpw.mods.fml.common.network.simpleimpl.MessageContext; +import io.netty.buffer.ByteBuf; + +import static com.gtnewhorizon.structurelib.StructureLib.LOGGER; + +public class UpdateHintParticleMessage implements IMessage { + private int x; + private short y; + private int z; + private byte r; + private byte g; + private byte b; + private byte a; + + public UpdateHintParticleMessage() { + } + + public UpdateHintParticleMessage(int x, short y, int z, byte r, byte g, byte b, byte a) { + this.x = x; + this.y = y; + this.z = z; + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + @Override + public void fromBytes(ByteBuf buf) { + x = buf.readInt(); + y = buf.readShort(); + z = buf.readInt(); + r = buf.readByte(); + g = buf.readByte(); + b = buf.readByte(); + a = buf.readByte(); + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(x); + buf.writeShort(y); + buf.writeInt(z); + buf.writeByte(r); + buf.writeByte(g); + buf.writeByte(b); + buf.writeByte(a); + } + + public static class Handler implements IMessageHandler { + @Override + public IMessage onMessage(UpdateHintParticleMessage msg, MessageContext ctx) { + boolean updateResult = StructureLibAPI.updateHintParticleTint(StructureLib.getCurrentPlayer().worldObj, + msg.x, msg.y, msg.z, new short[]{msg.r, msg.g, msg.b, msg.a,}); + if (!updateResult) + LOGGER.debug("Server instructed to update hint particle at ({}, {}, {}) but there is nothing there!", msg.x, msg.y, msg.z); + return null; + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/proxy/ClientProxy.java b/src/main/java/com/gtnewhorizon/structurelib/proxy/ClientProxy.java index 8457391c..5a935cb0 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/proxy/ClientProxy.java +++ b/src/main/java/com/gtnewhorizon/structurelib/proxy/ClientProxy.java @@ -4,14 +4,13 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.gtnewhorizon.structurelib.ConfigurationHandler; import com.gtnewhorizon.structurelib.entity.fx.EntityFXBlockHint; -import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import net.minecraft.block.Block; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiNewChat; -import net.minecraft.client.particle.EntityFX; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.server.MinecraftServer; import net.minecraft.util.ChatComponentText; import net.minecraft.util.IIcon; import net.minecraft.world.World; @@ -23,8 +22,6 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import static com.gtnewhorizon.structurelib.StructureLib.RANDOM; - public class ClientProxy extends CommonProxy { private static final short[] NO_TINT = {255, 255, 255, 0}; @@ -66,6 +63,17 @@ public class ClientProxy extends CommonProxy { hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), NO_TINT); } + @Override + public boolean updateHintParticleTint(World w, int x, int y, int z, short[] rgBa) { + HintParticleInfo info = new HintParticleInfo(x, y, z, null); + EntityFXBlockHint existing = allHints.inverse().get(info); + if (existing != null) { + existing.withColorTint(rgBa); + return true; + } + return false; + } + private static IIcon[] createIIconFromBlock(Block block, int meta) { IIcon[] ret = new IIcon[6]; for (int i = 0; i < 6; i++) { @@ -142,6 +150,12 @@ public class ClientProxy extends CommonProxy { MinecraftForge.EVENT_BUS.register(new ForgeEventHandler()); } + @Override + public long getOverworldTime() { + // there is no overworld, better just hope current world time is ok... + return Minecraft.getMinecraft().theWorld.getTotalWorldTime(); + } + private static class HintParticleInfo { private final int x, y, z; private final List owner; diff --git a/src/main/java/com/gtnewhorizon/structurelib/proxy/CommonProxy.java b/src/main/java/com/gtnewhorizon/structurelib/proxy/CommonProxy.java index dae0878e..93aa718a 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/proxy/CommonProxy.java +++ b/src/main/java/com/gtnewhorizon/structurelib/proxy/CommonProxy.java @@ -3,6 +3,7 @@ package com.gtnewhorizon.structurelib.proxy; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.server.MinecraftServer; import net.minecraft.util.IIcon; import net.minecraft.world.World; @@ -19,6 +20,10 @@ public class CommonProxy { public void hintParticle(World w, int x, int y, int z, Block block, int meta) { } + public boolean updateHintParticleTint(World w, int x, int y, int z, short[] rgBa) { + return true; + } + public EntityPlayer getCurrentPlayer() { return null; } @@ -38,4 +43,8 @@ public class CommonProxy { public void preInit(FMLPreInitializationEvent e) { } + + public long getOverworldTime() { + return MinecraftServer.getServer().getEntityWorld().getTotalWorldTime(); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java new file mode 100644 index 00000000..24326163 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java @@ -0,0 +1,38 @@ +package com.gtnewhorizon.structurelib.structure; + +import com.gtnewhorizon.structurelib.util.InventoryIterable; +import com.gtnewhorizon.structurelib.util.InventoryUtility; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +import java.util.function.Predicate; + +public interface IItemSource { + int take(Predicate predicate, boolean simulate, int count); + + default boolean takeOne(Predicate predicate, boolean simulate) { + return take(predicate, simulate, 1) == 1; + } + + default boolean takeAll(Predicate predicate, boolean simulate, int count) { + // fast path for 1 item take requests + if (count == 1) return takeOne(predicate, simulate); + int have = take(predicate, true, count); + if (have < count) return false; + take(predicate, simulate, count); + return true; + } + + static IItemSource fromPlayer(EntityPlayerMP player) { + return (p, s, c) -> InventoryUtility.takeFromInventory(player, p, s, c); + } + + static IItemSource fromPlayer(EntityPlayerMP player, int except) { + return (p, s, c) -> InventoryUtility.takeFromInventory(player, p, s, c); + } + + static IItemSource fromInventory(IInventory inv) { + return (p, s, c) -> InventoryUtility.takeFromInventory(new InventoryIterable(inv), p, s, c); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/ISpecialItemBlock.java b/src/main/java/com/gtnewhorizon/structurelib/structure/ISpecialItemBlock.java new file mode 100644 index 00000000..d7ca064f --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/ISpecialItemBlock.java @@ -0,0 +1,7 @@ +package com.gtnewhorizon.structurelib.structure; + +import net.minecraft.block.Block; + +public interface ISpecialItemBlock { + int getItemMetaFromBlockMeta(Block block, int blockMeta); +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java index 6b9082ff..5d63b3c3 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java @@ -6,6 +6,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.world.World; import java.util.Arrays; +import java.util.UUID; import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; @@ -51,6 +52,26 @@ public interface IStructureDefinition { basePositionA, basePositionB, basePositionC, hintsOnly, null); } + /** + * @param elementBudget build up to this many elements + * @param source from where to drain resource + * @param actorProfile source of action + * @param check whether {@link IStructureElement#check(Object, World, int, int, int)} should be called if anything + * would be placed. use with caution. + * if in doubt, call {@link #check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)} + * after this call and set this parameter to false + * @return number of elements built + * @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int) + */ + default int survivalBuild(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing, + int basePositionX, int basePositionY, int basePositionZ, + int basePositionA, int basePositionB, int basePositionC, int elementBudget, IItemSource source, UUID actorProfile, boolean check) { + SurvivalBuildStructureWalker walker = new SurvivalBuildStructureWalker<>(object, trigger, source, actorProfile, elementBudget, check); + StructureUtility.iterateV2(getStructureFor(piece), world, extendedFacing, basePositionX, basePositionY, basePositionZ, + basePositionA, basePositionB, basePositionC, walker, "iterateV2"); + return walker.getBuilt(); + } + static boolean iterate(T object, ItemStack trigger, IStructureElement[] elements, World world, ExtendedFacing extendedFacing, int basePositionX, int basePositionY, int basePositionZ, int basePositionA, int basePositionB, int basePositionC, diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java index 22764437..0d6e9ea8 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java @@ -1,8 +1,15 @@ package com.gtnewhorizon.structurelib.structure; +import com.gtnewhorizon.structurelib.StructureLibAPI; import net.minecraft.item.ItemStack; import net.minecraft.world.World; +import java.util.UUID; + +import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; +import static com.gtnewhorizon.structurelib.StructureLib.LOGGER; +import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE; + /** * Use StructureUtility to instantiate */ @@ -13,6 +20,22 @@ public interface IStructureElement { boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger); + /** + * Try place the block by taking resource from given IItemSource. + * + * @param s drain resources from this place + * @param actorProfile source of action. + */ + default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (PANIC_MODE) + throw new RuntimeException("Panic Tripwire hit"); + if (DEBUG_MODE) + 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, actorProfile)) + return PlaceResult.REJECT; + return PlaceResult.SKIP; + } + default int getStepA() { return 1; } @@ -40,4 +63,31 @@ public interface IStructureElement { default boolean isNavigating() { return false; } + + enum PlaceResult { + /** + * This element either exists already, or does not yet have an implementation for survivalPlaceBlock, or + * some other unforeseen situations. + */ + 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. + * + * Element placed successfully. To proceed further would require stopping the placement and wait for next round. + */ + ACCEPT_STOP; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java index 52ccc487..5bef5487 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java @@ -3,6 +3,8 @@ package com.gtnewhorizon.structurelib.structure; import net.minecraft.item.ItemStack; import net.minecraft.world.World; +import java.util.UUID; + /** * Use StructureUtility to instantiate */ @@ -38,4 +40,14 @@ public interface IStructureElementChain extends IStructureElement { } return false; } + + @Override + default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + for (IStructureElement fallback : fallbacks()) { + PlaceResult result = fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + if (result != PlaceResult.SKIP) + return result; + } + return PlaceResult.SKIP; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java index 3ba0c6ca..0ce33cbb 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java @@ -3,6 +3,8 @@ package com.gtnewhorizon.structurelib.structure; import net.minecraft.item.ItemStack; import net.minecraft.world.World; +import java.util.UUID; + public interface IStructureElementCheckOnly extends IStructureElement { @Override default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { @@ -13,4 +15,9 @@ public interface IStructureElementCheckOnly extends IStructureElement { default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return false; } + + @Override + default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return PlaceResult.SKIP; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java index e80d23ed..2192429f 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java @@ -3,9 +3,16 @@ package com.gtnewhorizon.structurelib.structure; import net.minecraft.item.ItemStack; import net.minecraft.world.World; +import java.util.UUID; + public interface IStructureElementNoPlacement extends IStructureElement { @Override default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { return false; } + + @Override + default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return PlaceResult.SKIP; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java index be0c7b08..271f596d 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java @@ -3,6 +3,8 @@ package com.gtnewhorizon.structurelib.structure; import net.minecraft.item.ItemStack; import net.minecraft.world.World; +import java.util.UUID; + /** * Use StructureUtility to instantiate */ @@ -22,6 +24,11 @@ interface IStructureNavigate extends IStructureElement { return true; } + @Override + default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return PlaceResult.SKIP; + } + default boolean isNavigating() { return true; } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java new file mode 100644 index 00000000..a6710169 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java @@ -0,0 +1,7 @@ +package com.gtnewhorizon.structurelib.structure; + +import net.minecraft.world.World; + +public interface IStructureWalker { + boolean visit(IStructureElement element, World world, int x, int y, int z); +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java index a1f326d7..951402b5 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java @@ -3,6 +3,7 @@ package com.gtnewhorizon.structurelib.structure; import net.minecraft.item.ItemStack; import net.minecraft.world.World; +import java.util.UUID; import java.util.function.Function; class LazyStructureElement implements IStructureElementDeferred { @@ -35,4 +36,9 @@ class LazyStructureElement implements IStructureElementDeferred { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return get(t).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index 2f7d7615..519a6f80 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -1,13 +1,17 @@ package com.gtnewhorizon.structurelib.structure; +import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.StructureLibAPI; 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.Vec3Impl; import cpw.mods.fml.common.registry.GameRegistry; import net.minecraft.block.Block; import net.minecraft.init.Blocks; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.IIcon; @@ -16,19 +20,10 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nullable; -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.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; +import java.util.*; import java.util.function.*; +import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; import static java.lang.Integer.MIN_VALUE; /** @@ -58,7 +53,17 @@ public class StructureUtility { world.setBlock(x, y, z, Blocks.air, 0, 2); return false; } + + @Override + public PlaceResult survivalPlaceBlock(Object o, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(o, world, x, y, z)) return PlaceResult.ACCEPT; + if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actorProfile)) + return PlaceResult.REJECT; + world.setBlock(x, y, z, Blocks.air, 0, 2); + return PlaceResult.ACCEPT; + } }; + @SuppressWarnings("rawtypes") private static final IStructureElement NOT_AIR = new IStructureElement() { @Override @@ -77,7 +82,19 @@ public class StructureUtility { world.setBlock(x, y, z, StructureLibAPI.getBlockHint(), 14, 2); return true; } + + @Override + public PlaceResult survivalPlaceBlock(Object o, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(o, world, x, y, z)) return PlaceResult.ACCEPT; + // user should place anything here. + // maybe make this configurable, but for now we try to take some cobble from user + if (s.take(is -> is.getItem() == Item.getItemFromBlock(Blocks.cobblestone), false, 1) == 1) { + world.setBlock(x, y, z, Blocks.cobblestone, 0, 2); + } + return PlaceResult.REJECT; + } }; + @SuppressWarnings("rawtypes") private static final IStructureElement ERROR = new IStructureElement() { @Override @@ -95,12 +112,38 @@ public class StructureUtility { 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, IItemSource s, UUID actorProfile) { + return PlaceResult.REJECT; + } }; private StructureUtility() { } + /** + * This method assume block at given coord is NOT acceptable, but may or may not be trivially replaceable + */ + static PlaceResult survivalPlaceBlock(Block block, int meta, World world, int x, int y, int z, IItemSource s, UUID actorProfile) { + if (block == null) + throw new NullPointerException(); + if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actorProfile)) + return PlaceResult.REJECT; + Item itemBlock = Item.getItemFromBlock(block); + int itemMeta = itemBlock instanceof ISpecialItemBlock ? ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta) : meta; + if (!s.takeOne(ItemStackPredicate.from(itemBlock).withMeta(itemMeta), false)) + return PlaceResult.REJECT; + if (block instanceof ICustomBlockSetting) { + ICustomBlockSetting block2 = (ICustomBlockSetting) block; + block2.setBlock(world, x, y, z, meta); + } else { + world.setBlock(x, y, z, block, meta, 2); + } + return PlaceResult.ACCEPT; + } + @SuppressWarnings("unchecked") public static IStructureElement isAir() { return AIR; @@ -256,6 +299,15 @@ public class StructureUtility { } return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + Pair hint = getHint(trigger); + if (hint == null) + return PlaceResult.REJECT; // TODO or SKIP? + return StructureUtility.survivalPlaceBlock(hint.getKey(), hint.getValue(), world, x, z, z, s, actorProfile); + } }; } @@ -295,6 +347,14 @@ public class StructureUtility { world.setBlock(x, y, z, getBlock(), meta, 2); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + if (getBlock() == null) + return PlaceResult.REJECT; // TODO or SKIP? + return StructureUtility.survivalPlaceBlock(getBlock(), meta, world, x, z, z, s, actorProfile); + } }; } @@ -348,6 +408,14 @@ public class StructureUtility { } else return fallback.placeBlock(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + if (init()) + return StructureUtility.survivalPlaceBlock(block, meta, world, x, z, z, s, actorProfile); + return fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -468,6 +536,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } else { return new IStructureElement() { @@ -488,6 +562,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } } @@ -523,6 +603,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } else { return new IStructureElement() { @@ -543,6 +629,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } } @@ -570,6 +662,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } else { return new IStructureElement() { @@ -590,6 +688,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } } @@ -619,6 +723,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } else { return new IStructureElement() { @@ -638,6 +748,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (check(t, world, x, y, z)) return PlaceResult.ACCEPT; + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } } @@ -687,6 +803,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + //TODO should we call block adder to check???? + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } else { return new IStructureElement() { @@ -707,6 +829,12 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + //TODO should we call block adder to check???? + return StructureUtility.survivalPlaceBlock(defaultBlock, defaultMeta, world, x, z, z, s, actorProfile); + } }; } } @@ -780,6 +908,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return element.spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return element.survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -803,6 +936,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return element.spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return element.survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -812,6 +950,13 @@ public class StructureUtility { * Enable this structure element only if given predicate returns true. */ public static IStructureElement onlyIf(Predicate predicate, IStructureElement downstream) { + return onlyIf(predicate, downstream, PlaceResult.SKIP); + } + + /** + * Enable this structure element only if given predicate returns true. + */ + public static IStructureElement onlyIf(Predicate predicate, IStructureElement downstream, PlaceResult placeResultWhenDisabled) { return new IStructureElement() { @Override public boolean check(T t, World world, int x, int y, int z) { @@ -827,6 +972,13 @@ public class StructureUtility { 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); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + if (predicate.test(t)) + return downstream.survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + return placeResultWhenDisabled; + } }; } @@ -873,6 +1025,11 @@ public class StructureUtility { 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); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return elem.survivalPlaceBlock(t.getCurrentContext(), world, x, y, z, trigger, s, actorProfile); + } }; } //endregion @@ -920,6 +1077,11 @@ public class StructureUtility { 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); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return to.get().survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -942,6 +1104,11 @@ public class StructureUtility { 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); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return to.apply(t).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -964,6 +1131,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.get(keyExtractor.apply(t)).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return map.get(keyExtractor.apply(t)).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -986,6 +1158,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.getOrDefault(keyExtractor.apply(t), defaultElem).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return map.getOrDefault(keyExtractor.apply(t), defaultElem).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1009,6 +1186,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return array[keyExtractor.apply(t)].spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return array[keyExtractor.apply(t)].survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1036,6 +1218,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return to.apply(t, trigger).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return to.apply(t, trigger).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1058,6 +1245,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.get(keyExtractor.apply(t, trigger)).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return map.get(keyExtractor.apply(t, trigger)).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1080,6 +1272,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1103,6 +1300,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return array[keyExtractor.apply(t, trigger)].spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return array[keyExtractor.apply(t, trigger)].survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1130,6 +1332,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return to.apply(t, trigger).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return to.apply(t, trigger).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1152,6 +1359,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.get(keyExtractor.apply(t, trigger)).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return map.get(keyExtractor.apply(t, trigger)).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1174,6 +1386,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem).survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1197,6 +1414,11 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return array[keyExtractor.apply(t, trigger)].spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s, UUID actorProfile) { + return array[keyExtractor.apply(t, trigger)].survivalPlaceBlock(t, world, x, y, z, trigger, s, actorProfile); + } }; } @@ -1450,6 +1672,47 @@ public class StructureUtility { return (builder.toString().replaceAll("\"\"", "E")); } + static boolean iterateV2(IStructureElement[] elements, World world, ExtendedFacing extendedFacing, + int basePositionX, int basePositionY, int basePositionZ, + int basePositionA, int basePositionB, int basePositionC, IStructureWalker predicate, String iterateType) { + //change base position to base offset + basePositionA = -basePositionA; + basePositionB = -basePositionB; + basePositionC = -basePositionC; + + int[] abc = new int[]{basePositionA, basePositionB, basePositionC}; + int[] xyz = new int[3]; + + for (IStructureElement element : elements) { + if (element.isNavigating()) { + abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA(); + abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB(); + abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC(); + } else { + extendedFacing.getWorldOffset(abc, xyz); + xyz[0] += basePositionX; + xyz[1] += basePositionY; + xyz[2] += basePositionZ; + + if (world.blockExists(xyz[0], xyz[1], xyz[2])) { + if (!predicate.visit(element, world, xyz[0], xyz[1], xyz[2])) { + if (DEBUG_MODE) { + StructureLib.LOGGER.info("Multi [{}, {}, {}] {} stop @ {} {}", basePositionX, basePositionY, basePositionZ, iterateType, Arrays.toString(xyz), Arrays.toString(abc)); + } + return false; + } + } else { + if (DEBUG_MODE) { + StructureLib.LOGGER.info("Multi [{}, {}, {}] {} !blockExists @ {} {}", basePositionX, basePositionY, basePositionZ, iterateType, Arrays.toString(xyz), Arrays.toString(abc)); + } + return false; + } + abc[0] += 1; + } + } + return true; + } + public static void iterate(World world, ExtendedFacing extendedFacing, int basePositionX, int basePositionY, int basePositionZ, int basePositionA, int basePositionB, int basePositionC, diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java b/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java new file mode 100644 index 00000000..164be05c --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java @@ -0,0 +1,55 @@ +package com.gtnewhorizon.structurelib.structure; + +import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +import java.util.UUID; + +class SurvivalBuildStructureWalker implements IStructureWalker { + private final T object; + private final ItemStack trigger; + private final IItemSource source; + private final UUID actorProfile; + private final int elementBudget; + private final boolean check; + private int built; + + public SurvivalBuildStructureWalker(T object, ItemStack trigger, IItemSource source, UUID actorProfile, int elementBudget, boolean check) { + this.object = object; + this.trigger = trigger; + this.source = source; + this.actorProfile = actorProfile; + this.elementBudget = elementBudget; + this.check = check; + built = 0; + } + + @Override + public boolean visit(IStructureElement element, World world, int x, int y, int z) { + PlaceResult placeResult = element.survivalPlaceBlock(object, world, x, y, z, trigger, source, actorProfile); + switch (placeResult) { + case SKIP: + return true; + case ACCEPT: + if (check) + element.check(object, world, x, y, z); + return ++built < elementBudget; + case REJECT: + return false; + case ACCEPT_STOP: + if (check) + element.check(object, world, x, y, z); + built += 1; + // intentional fallthrough + case STOP: + return false; + default: + throw new NullPointerException(); + } + } + + public int getBuilt() { + return built; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/InventoryIterable.java b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryIterable.java new file mode 100644 index 00000000..97aa35d1 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryIterable.java @@ -0,0 +1,36 @@ +package com.gtnewhorizon.structurelib.util; + +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +import java.util.Iterator; + +public class InventoryIterable implements Iterable { + private final IInventory inv; + + public InventoryIterable(IInventory inv) { + this.inv = inv; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private int ptr = 0; + + @Override + public boolean hasNext() { + return ptr >= inv.getSizeInventory(); + } + + @Override + public ItemStack next() { + return inv.getStackInSlot(ptr++); + } + + @Override + public void remove() { + inv.setInventorySlotContents(ptr - 1, null); + } + }; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java new file mode 100644 index 00000000..ef03bf1f --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2022, glee8e + * This file is part of StructureLib. + *

+ * StructureLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + *

+ * StructureLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with Foobar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.gtnewhorizon.structurelib.util; + +import com.gtnewhorizon.structurelib.util.ItemStackArrayIterable; +import com.gtnewhorizon.structurelib.util.SortedRegistry; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; + +import java.util.Iterator; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.function.Predicate; + +public class InventoryUtility { + private static final SortedRegistry>> stackExtractors = new SortedRegistry<>(); + /** + * The remove() of the Iterable returned must be implemented! + */ + private static final SortedRegistry>> inventoryProviders = new SortedRegistry<>(); + private static final NavigableMap>> inventoryProviderRegistry = new TreeMap<>(); + + static { + inventoryProviders.register("5000-main-inventory", player -> new ItemStackArrayIterable(player.inventory.mainInventory)); + } + + /** + * take count amount of stacks that matches given filter. might take partial amount of stuff, so simulation is suggested + * if you ever need to take more than one. + * + * @param player source of stacks + * @param predicate item stack filter + * @param simulate whether to do removal + * @param count let's hope int size is enough... + * @return amount taken. never negative nor bigger than count... + */ + public static int takeFromInventory(EntityPlayerMP player, Predicate predicate, boolean simulate, int count) { + int found = 0; + for (Function> provider : inventoryProviders) { + found += takeFromInventory(provider.apply(player), predicate, simulate, count - found); + if (found >= count) + return found; + } + return found; + } + + /** + * take count amount of stacks that matches given filter. Will do 1 level of recursion to try find more stacks.... + * e.g. from backpacks... + * + * @param inv source of stacks + * @param predicate item stack filter + * @param simulate whether to do removal + * @param count let's hope int size is enough... + * @return amount taken. never negative nor bigger than count... + */ + public static int takeFromInventory(Iterable inv, Predicate predicate, boolean simulate, int count) { + return takeFromInventory(inv, predicate, simulate, count, true); + } + + private static int takeFromInventory(Iterable inv, Predicate predicate, boolean simulate, int count, boolean recurse) { + int found = 0; + for (Iterator iterator = inv.iterator(); iterator.hasNext(); ) { + ItemStack stack = iterator.next(); + // invalid stack + if (stack == null || stack.getItem() == null || stack.stackSize <= 0) continue; + + if (predicate.test(stack)) { + found += stack.stackSize; + if (found > count) { + if (!simulate) { + // leave the surplus in its place + stack.stackSize = found - count; + } + return count; + } + if (!simulate) + iterator.remove(); + if (found == count) + return count; + } + if (!recurse) continue; + for (Function> f : stackExtractors) { + Iterable stacks = f.apply(stack); + if (stacks == null) continue; + found += takeFromInventory(inv, predicate, simulate, count - found, false); + if (found > count) return found; + } + } + return found; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackArrayIterable.java b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackArrayIterable.java new file mode 100644 index 00000000..c6277aac --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackArrayIterable.java @@ -0,0 +1,35 @@ +package com.gtnewhorizon.structurelib.util; + +import net.minecraft.item.ItemStack; + +import java.util.Iterator; + +public class ItemStackArrayIterable implements Iterable { + private final ItemStack[] stacks; + + public ItemStackArrayIterable(ItemStack[] stacks) { + this.stacks = stacks; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private int ptr = 0; + + @Override + public boolean hasNext() { + return ptr >= stacks.length; + } + + @Override + public ItemStack next() { + return stacks[ptr++]; + } + + @Override + public void remove() { + stacks[ptr - 1] = null; + } + }; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackPredicate.java b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackPredicate.java new file mode 100644 index 00000000..f280a26e --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackPredicate.java @@ -0,0 +1,101 @@ +package com.gtnewhorizon.structurelib.util; + +import net.minecraft.block.Block; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; + +import java.util.Objects; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +public final class ItemStackPredicate implements Predicate { + public static ItemStackPredicate from(Item item) { + return new ItemStackPredicate(item); + } + + public static ItemStackPredicate from(Block block) { + return new ItemStackPredicate(Item.getItemFromBlock(block)); + } + + private final Item item; + private int meta; + + private NBTTagCompound tag; + private NBTMode mode; + + private ItemStackPredicate(Item item) { + this.item = item; + } + + public ItemStackPredicate withMeta(int meta) { + this.meta = meta; + return this; + } + + public ItemStackPredicate withTag(NBTMode mode, NBTTagCompound tag) { + this.mode = mode; + this.tag = tag; + return this; + } + + @Override + public boolean test(ItemStack itemStack) { + if (item != null) if (itemStack.getItem() != item) return false; + if (meta != -1) if (Items.feather.getDamage(itemStack) != meta) return false; + return mode.test(tag, itemStack.stackTagCompound); + } + + public enum NBTMode implements BiPredicate { + IGNORE { + @Override + public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) { + return true; + } + }, + IN { + @Override + public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) { + if (lhs == null || lhs.hasNoTags()) return true; + if (rhs == null || rhs.hasNoTags()) return false; + for (String key : MiscUtils.getTagKeys(lhs)) { + if (!rhs.hasKey(key, lhs.func_150299_b(key))) + return false; + NBTBase tag = lhs.getTag(key); + if (tag instanceof NBTTagCompound) { + if (!test((NBTTagCompound) tag, rhs.getCompoundTag(key))) + return false; + } else { + if (!tag.equals(rhs.getTag(key))) + return false; + } + } + return true; + } + }, + EXACT { + @Override + public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) { + if (lhs != null && lhs.hasNoTags()) lhs = null; + if (rhs != null && rhs.hasNoTags()) rhs = null; + return Objects.equals(lhs, rhs); + } + }, + IGNORE_KNOWN_INSIGNIFICANT_TAGS { + @Override + public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) { + // fast path for empty tags + if (rhs == null || rhs.hasNoTags()) return lhs == null || lhs.hasNoTags(); + // TODO make an implementation without copying a potentially huge tag + rhs = (NBTTagCompound) rhs.copy(); + for (String s : KNOWN_INSIGNIFICANT_TAGS) rhs.removeTag(s); + return EXACT.test(lhs, rhs); + } + }; + private static final String[] KNOWN_INSIGNIFICANT_TAGS = { + "display", // TODO expand/refine this + }; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/MiscUtils.java b/src/main/java/com/gtnewhorizon/structurelib/util/MiscUtils.java new file mode 100644 index 00000000..973b6a78 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/MiscUtils.java @@ -0,0 +1,17 @@ +package com.gtnewhorizon.structurelib.util; + +import net.minecraft.nbt.NBTTagCompound; + +import java.util.Set; + +/** + * not related to a certain mod! + */ +public class MiscUtils { + private MiscUtils() {} + + @SuppressWarnings("unchecked") + public static Set getTagKeys(NBTTagCompound tag) { + return tag.func_150296_c(); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/SortedRegistry.java b/src/main/java/com/gtnewhorizon/structurelib/util/SortedRegistry.java new file mode 100644 index 00000000..a75855ed --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/SortedRegistry.java @@ -0,0 +1,58 @@ +package com.gtnewhorizon.structurelib.util; + +import java.util.*; +import java.util.function.Consumer; + +public class SortedRegistry implements Iterable { + private final NavigableMap store = new TreeMap<>(); + /** + * Store stuff in an array for faster iteration access than a red black tree + */ + private ArrayList baked; + + public void register(String key, V val) { + if (key == null || val == null) + throw new NullPointerException(); + V old = store.putIfAbsent(key, val); + if (old != null) { + throw new IllegalArgumentException("Duplicate key: " + key); + } + // TODO allow a user to edit the order with a configuration file + baked = new ArrayList<>(store.values()); + } + + public int size() { + return store.size(); + } + + public boolean isEmpty() { + return store.isEmpty(); + } + + public boolean containsKey(String key) { + return store.containsKey(key); + } + + public boolean containsValue(V value) { + return store.containsValue(value); + } + + public V get(String key) { + return store.get(key); + } + + @Override + public Iterator iterator() { + return baked.iterator(); + } + + @Override + public void forEach(Consumer action) { + baked.forEach(action); + } + + @Override + public Spliterator spliterator() { + return baked.spliterator(); + } +} diff --git a/src/main/resources/assets/structurelib/lang/en_US.lang b/src/main/resources/assets/structurelib/lang/en_US.lang index 3d303adc..fc9a776d 100644 --- a/src/main/resources/assets/structurelib/lang/en_US.lang +++ b/src/main/resources/assets/structurelib/lang/en_US.lang @@ -24,8 +24,9 @@ item.structurelib.constructableTrigger.name=Multiblock Structure Hologram Projec item.structurelib.constructableTrigger.desc.0=Triggers Constructable Interface item.structurelib.constructableTrigger.desc.1=Shows multiblock construction schematic, item.structurelib.constructableTrigger.desc.2=use on the multiblock controller. -item.structurelib.constructableTrigger.desc.3=(Sneak use on controller in creative to build) +item.structurelib.constructableTrigger.desc.3=(Sneak use on controller to build) item.structurelib.constructableTrigger.desc.4=Quantity of schematics in item stack affects tier/mode/type +item.structurelib.constructableTrigger.too_fast=Please wait %d ticks before another autoplace... structurelib.blockhint.desc.0=Helps while building structurelib.blockhint.0.name=Hint 1 dot diff --git a/src/main/resources/assets/structurelib/lang/zh_CN.lang b/src/main/resources/assets/structurelib/lang/zh_CN.lang index 3637adc2..75dde0be 100644 --- a/src/main/resources/assets/structurelib/lang/zh_CN.lang +++ b/src/main/resources/assets/structurelib/lang/zh_CN.lang @@ -26,6 +26,7 @@ item.structurelib.constructableTrigger.desc.1=只能在多方块主机上使用 item.structurelib.constructableTrigger.desc.2=结构的示意图。 (译者注:请搭高使用) item.structurelib.constructableTrigger.desc.3= (创造模式下潜行右键可以直接完成搭建) item.structurelib.constructableTrigger.desc.4=此物品堆叠数量会影响显示的机器等级/模式/类型 +item.structurelib.constructableTrigger.too_fast=Please wait %d ticks before another autoplace... structurelib.blockhint.desc.0=帮助你搭建机器 structurelib.blockhint.0.name=提示方块:1号