diff --git a/src/main/java/com/hbm/blocks/ModBlocks.java b/src/main/java/com/hbm/blocks/ModBlocks.java index e25ea9a61..745872741 100644 --- a/src/main/java/com/hbm/blocks/ModBlocks.java +++ b/src/main/java/com/hbm/blocks/ModBlocks.java @@ -1261,6 +1261,7 @@ public class ModBlocks { public static Block wand_jigsaw; public static Block wand_logic; public static Block wand_tandem; + public static Block wand_structure; public static Block logic_block; @@ -2066,7 +2067,7 @@ public class ModBlocks { fusion_boiler = new MachineFusionBoiler().setBlockName("fusion_boiler").setHardness(5.0F).setResistance(60.0F).setCreativeTab(MainRegistry.machineTab).setBlockTextureName(RefStrings.MODID + ":block_steel"); fusion_mhdt = new MachineFusionMHDT().setBlockName("fusion_mhdt").setHardness(5.0F).setResistance(60.0F).setCreativeTab(MainRegistry.machineTab).setBlockTextureName(RefStrings.MODID + ":block_steel"); fusion_coupler = new MachineFusionCoupler().setBlockName("fusion_coupler").setHardness(5.0F).setResistance(60.0F).setCreativeTab(MainRegistry.machineTab).setBlockTextureName(RefStrings.MODID + ":block_steel"); - + machine_icf_press = new MachineICFPress().setBlockName("machine_icf_press").setHardness(5.0F).setResistance(60.0F).setCreativeTab(MainRegistry.machineTab).setBlockTextureName(RefStrings.MODID + ":block_steel"); icf = new MachineICF().setBlockName("icf").setHardness(5.0F).setResistance(60.0F).setCreativeTab(MainRegistry.machineTab).setBlockTextureName(RefStrings.MODID + ":block_steel"); icf_component = new BlockICFComponent().setBlockName("icf_component").setHardness(5.0F).setResistance(60.0F).setCreativeTab(MainRegistry.machineTab).setBlockTextureName(RefStrings.MODID + ":icf_component"); @@ -2430,6 +2431,7 @@ public class ModBlocks { wand_jigsaw = new BlockWandJigsaw().setBlockName("wand_jigsaw").setBlockTextureName(RefStrings.MODID + ":wand_jigsaw"); wand_logic = new BlockWandLogic().setBlockName("wand_logic").setBlockTextureName(RefStrings.MODID + ":wand_logic"); wand_tandem = new BlockWandTandem().setBlockName("wand_tandem").setBlockTextureName(RefStrings.MODID + ":wand_tandem"); + wand_structure = new BlockWandStructure().setBlockName("wand_structure"); logic_block = new LogicBlock().setBlockName("logic_block").setBlockTextureName(RefStrings.MODID + ":logic_block"); @@ -3448,7 +3450,7 @@ public class ModBlocks { GameRegistry.registerBlock(plasma, ItemBlockLore.class, plasma.getUnlocalizedName()); GameRegistry.registerBlock(iter, iter.getUnlocalizedName()); GameRegistry.registerBlock(plasma_heater, plasma_heater.getUnlocalizedName()); - + register(fusion_component); register(fusion_torus); register(fusion_klystron); @@ -3603,6 +3605,7 @@ public class ModBlocks { register(wand_jigsaw); register(wand_logic); register(wand_tandem); + register(wand_structure); register(logic_block); } @@ -3635,4 +3638,24 @@ public class ModBlocks { return ret; } + + public static Block getBlockFromStack(ItemStack stack) { + if(stack == null) return null; + if(!(stack.getItem() instanceof ItemBlock)) return null; + + return ((ItemBlock) stack.getItem()).field_150939_a; + } + + // Is this block a special structure handling block, so we can ignore it for blacklist selection, etc. + public static boolean isStructureBlock(Block block, boolean includeAir) { + if(block == null) return false; + if(block == wand_air) return includeAir; + if(block == wand_structure) return true; + if(block == wand_jigsaw) return true; + if(block == wand_logic) return true; + if(block == wand_tandem) return true; + if(block == wand_loot) return true; + return false; + } + } diff --git a/src/main/java/com/hbm/blocks/generic/BlockWandJigsaw.java b/src/main/java/com/hbm/blocks/generic/BlockWandJigsaw.java index d64470934..2c927fac1 100644 --- a/src/main/java/com/hbm/blocks/generic/BlockWandJigsaw.java +++ b/src/main/java/com/hbm/blocks/generic/BlockWandJigsaw.java @@ -37,7 +37,6 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.inventory.Container; -import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; @@ -135,10 +134,10 @@ public class BlockWandJigsaw extends BlockContainer implements IBlockSideRotatio TileEntityWandJigsaw jigsaw = (TileEntityWandJigsaw) te; if(!player.isSneaking()) { - Block block = getBlock(world, player.getHeldItem()); + Block block = ModBlocks.getBlockFromStack(player.getHeldItem()); if(block == ModBlocks.wand_air) block = Blocks.air; - if(block != null && block != ModBlocks.wand_jigsaw && block != ModBlocks.wand_loot) { + if(block != null && !ModBlocks.isStructureBlock(block, false)) { jigsaw.replaceBlock = block; jigsaw.replaceMeta = player.getHeldItem().getItemDamage(); @@ -155,13 +154,6 @@ public class BlockWandJigsaw extends BlockContainer implements IBlockSideRotatio return false; } - private Block getBlock(World world, ItemStack stack) { - if(stack == null) return null; - if(!(stack.getItem() instanceof ItemBlock)) return null; - - return ((ItemBlock) stack.getItem()).field_150939_a; - } - @Override @SideOnly(Side.CLIENT) public Object provideGUI(int ID, EntityPlayer player, World world, int x, int y, int z) { diff --git a/src/main/java/com/hbm/blocks/generic/BlockWandLoot.java b/src/main/java/com/hbm/blocks/generic/BlockWandLoot.java index cf5cebce0..715da81a5 100644 --- a/src/main/java/com/hbm/blocks/generic/BlockWandLoot.java +++ b/src/main/java/com/hbm/blocks/generic/BlockWandLoot.java @@ -33,7 +33,6 @@ import net.minecraft.client.renderer.texture.IIconRegister; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.IInventory; -import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; @@ -82,7 +81,7 @@ public class BlockWandLoot extends BlockContainer implements ILookOverlay, ITool @Override public void onBlockPlacedBy(World world, int x, int y, int z, EntityLivingBase player, ItemStack itemStack) { - int i = MathHelper.floor_double(player.rotationYaw * 4.0F / 360.0F + 0.5D) & 3; + int i = MathHelper.floor_double(player.rotationYaw * 4.0F / 360.0F + 0.5D) & 3; if(i == 0) world.setBlockMetadataWithNotify(x, y, z, 3, 2); if(i == 1) world.setBlockMetadataWithNotify(x, y, z, 2, 2); @@ -153,17 +152,14 @@ public class BlockWandLoot extends BlockContainer implements ILookOverlay, ITool } private Block getLootableBlock(World world, ItemStack stack) { - if(stack == null) return null; + Block block = ModBlocks.getBlockFromStack(stack); + if(block == null) return null; - if(stack.getItem() instanceof ItemBlock) { - Block block = ((ItemBlock) stack.getItem()).field_150939_a; + if(block == ModBlocks.deco_loot) return block; - if(block == ModBlocks.deco_loot) return block; - - if(block instanceof ITileEntityProvider) { - TileEntity te = ((ITileEntityProvider) block).createNewTileEntity(world, 12); - if(te instanceof IInventory) return block; - } + if(block instanceof ITileEntityProvider) { + TileEntity te = ((ITileEntityProvider) block).createNewTileEntity(world, 12); + if(te instanceof IInventory) return block; } return null; diff --git a/src/main/java/com/hbm/blocks/generic/BlockWandStructure.java b/src/main/java/com/hbm/blocks/generic/BlockWandStructure.java new file mode 100644 index 000000000..07d68da44 --- /dev/null +++ b/src/main/java/com/hbm/blocks/generic/BlockWandStructure.java @@ -0,0 +1,573 @@ +package com.hbm.blocks.generic; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.lwjgl.input.Keyboard; + +import com.hbm.blocks.IBlockMulti; +import com.hbm.blocks.ILookOverlay; +import com.hbm.blocks.ModBlocks; +import com.hbm.config.StructureConfig; +import com.hbm.interfaces.IControlReceiver; +import com.hbm.inventory.gui.element.GuiFileList; +import com.hbm.lib.RefStrings; +import com.hbm.main.MainRegistry; +import com.hbm.packet.PacketDispatcher; +import com.hbm.packet.toserver.NBTControlPacket; +import com.hbm.tileentity.IGUIProvider; +import com.hbm.tileentity.TileEntityLoadedBase; +import com.hbm.util.BufferUtil; +import com.hbm.util.Tuple.Pair; +import com.hbm.util.i18n.I18nUtil; +import com.hbm.world.gen.nbt.NBTStructure; + +import cpw.mods.fml.common.network.internal.FMLNetworkHandler; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import net.minecraft.block.Block; +import net.minecraft.block.BlockContainer; +import net.minecraft.block.material.Material; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.event.ClickEvent; +import net.minecraft.init.Blocks; +import net.minecraft.inventory.Container; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IIcon; +import net.minecraft.world.World; +import net.minecraftforge.client.event.RenderGameOverlayEvent.Pre; + +public class BlockWandStructure extends BlockContainer implements IBlockMulti, IGUIProvider, ILookOverlay { + + private IIcon saveIcon; + private IIcon loadIcon; + + public BlockWandStructure() { + super(Material.iron); + } + + @Override + public TileEntity createNewTileEntity(World worldIn, int meta) { + return new TileEntityWandStructure(); + } + + @Override + @SideOnly(Side.CLIENT) + public void registerBlockIcons(IIconRegister iconRegister) { + saveIcon = iconRegister.registerIcon(RefStrings.MODID + ":wand_structure_save"); + loadIcon = iconRegister.registerIcon(RefStrings.MODID + ":wand_structure_load"); + } + + @Override + public IIcon getIcon(int side, int meta) { + if(meta == 1) return loadIcon; + return saveIcon; + } + + @Override + public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX, float hitY, float hitZ) { + TileEntity te = world.getTileEntity(x, y, z); + + if(!(te instanceof TileEntityWandStructure)) return false; + + TileEntityWandStructure structure = (TileEntityWandStructure) te; + + if(!player.isSneaking()) { + Block block = ModBlocks.getBlockFromStack(player.getHeldItem()); + if(block != null && !ModBlocks.isStructureBlock(block, true)) { + Pair bm = new Pair(block, player.getHeldItem().getItemDamage()); + + if(structure.blacklist.contains(bm)) { + structure.blacklist.remove(bm); + } else { + structure.blacklist.add(bm); + } + + return true; + } + + if(world.isRemote) FMLNetworkHandler.openGui(player, MainRegistry.instance, 0, world, x, y, z); + + return true; + } + + return false; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + @SideOnly(Side.CLIENT) + public void getSubBlocks(Item itemIn, CreativeTabs tab, List list) { + list.add(new ItemStack(itemIn, 1, 0)); + list.add(new ItemStack(itemIn, 1, 1)); + } + + @Override + public int getSubCount() { + return 2; + } + + @Override + public String getUnlocalizedName(ItemStack stack) { + int meta = stack.getItemDamage(); + if(meta == 1) return getUnlocalizedName() + ".load"; + return getUnlocalizedName() + ".save"; + } + + @Override + public int damageDropped(int meta) { + return meta; + } + + @Override + public Container provideContainer(int ID, EntityPlayer player, World world, int x, int y, int z) { + return null; + } + + @Override + public Object provideGUI(int ID, EntityPlayer player, World world, int x, int y, int z) { + int meta = world.getBlockMetadata(x, y, z); + TileEntityWandStructure structure = (TileEntityWandStructure) world.getTileEntity(x, y, z); + if(meta == 1) return new GuiStructureLoad(structure); + return new GuiStructureSave(structure); + } + + @Override + public void printHook(Pre event, World world, int x, int y, int z) { + if(world.getBlockMetadata(x, y, z) != 0) return; + + TileEntity te = world.getTileEntity(x, y, z); + if(!(te instanceof TileEntityWandStructure)) return; + TileEntityWandStructure structure = (TileEntityWandStructure) te; + + List text = new ArrayList(); + + text.add(EnumChatFormatting.GRAY + "Name: " + EnumChatFormatting.RESET + structure.name); + + text.add(EnumChatFormatting.GRAY + "Blacklist:"); + for (Pair bm : structure.blacklist) { + text.add(EnumChatFormatting.RED + "- " + bm.getKey().getUnlocalizedName() + " : " + bm.getValue()); + } + + ILookOverlay.printGeneric(event, I18nUtil.resolveKey(getUnlocalizedName() + ".save.name"), 0xffff00, 0x404000, text); + } + + public static class TileEntityWandStructure extends TileEntityLoadedBase implements IControlReceiver { + + public String name = ""; + + public int sizeX = 1; + public int sizeY = 1; + public int sizeZ = 1; + + public Set> blacklist = new HashSet<>(); + + @Override + public void updateEntity() { + if(!worldObj.isRemote) { + networkPackNT(256); + } + } + + public void saveStructure(EntityPlayer player) { + if(name.isEmpty()) { + player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Could not save: invalid name")); + return; + } + + if(sizeX <= 0 || sizeY <= 0 || sizeZ <= 0) { + player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Could not save: invalid dimensions")); + return; + } + + Pair air = new Pair(Blocks.air, 0); + blacklist.add(air); + + File file = NBTStructure.quickSaveArea(name + ".nbt", worldObj, xCoord, yCoord + 1, zCoord, xCoord + sizeX - 1, yCoord + sizeY, zCoord + sizeZ - 1, blacklist); + + blacklist.remove(air); + + if(file == null) { + player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Failed to save structure")); + return; + } + + ChatComponentText fileText = new ChatComponentText(file.getName()); + fileText.getChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, file.getParentFile().getAbsolutePath())); + fileText.getChatStyle().setUnderlined(true); + + player.addChatMessage(new ChatComponentText("Saved structure as ").appendSibling(fileText)); + } + + public void loadStructure(EntityPlayer player) { + if(name.isEmpty()) { + player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Could not load: no filename specified")); + return; + } + + File structureDirectory = new File(Minecraft.getMinecraft().mcDataDir, "structures"); + structureDirectory.mkdir(); + + File structureFile = new File(structureDirectory, name + ".nbt"); + + boolean previousDebug = StructureConfig.debugStructures; + StructureConfig.debugStructures = true; + + try { + NBTStructure structure = new NBTStructure(structureFile); + + sizeX = structure.getSizeX(); + sizeY = structure.getSizeY(); + sizeZ = structure.getSizeZ(); + + structure.build(worldObj, xCoord, yCoord + 1, zCoord, 0, false, true); + + worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, 0, 3); + + player.addChatMessage(new ChatComponentText("Structure loaded")); + + } catch (FileNotFoundException ex) { + player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "Could not load: file not found")); + } finally { + StructureConfig.debugStructures = previousDebug; + } + } + + @Override + public void serialize(ByteBuf buf) { + BufferUtil.writeString(buf, name); + + buf.writeInt(sizeX); + buf.writeInt(sizeY); + buf.writeInt(sizeZ); + + buf.writeInt(blacklist.size()); + for(Pair bm : blacklist) { + buf.writeInt(Block.getIdFromBlock(bm.getKey())); + buf.writeInt(bm.getValue()); + } + } + + @Override + public void deserialize(ByteBuf buf) { + name = BufferUtil.readString(buf); + + sizeX = buf.readInt(); + sizeY = buf.readInt(); + sizeZ = buf.readInt(); + + int count = buf.readInt(); + blacklist = new HashSet<>(); + for(int i = 0; i < count; i++) { + Block block = Block.getBlockById(buf.readInt()); + int meta = buf.readInt(); + blacklist.add(new Pair(block, meta)); + } + } + + @Override + public void readFromNBT(NBTTagCompound nbt) { + super.readFromNBT(nbt); + + name = nbt.getString("name"); + + sizeX = nbt.getInteger("sizeX"); + sizeY = nbt.getInteger("sizeY"); + sizeZ = nbt.getInteger("sizeZ"); + + int[] blocks = nbt.getIntArray("blocks"); + int[] metas = nbt.getIntArray("metas"); + + blacklist = new HashSet<>(); + for (int i = 0; i < blocks.length; i++) { + blacklist.add(new Pair(Block.getBlockById(blocks[i]), metas[i])); + } + } + + @Override + public void writeToNBT(NBTTagCompound nbt) { + super.writeToNBT(nbt); + + nbt.setString("name", name); + + nbt.setInteger("sizeX", sizeX); + nbt.setInteger("sizeY", sizeY); + nbt.setInteger("sizeZ", sizeZ); + + nbt.setIntArray("blocks", blacklist.stream().mapToInt(b -> Block.getIdFromBlock(b.getKey())).toArray()); + nbt.setIntArray("metas", blacklist.stream().mapToInt(b -> b.getValue()).toArray()); + } + + @Override + public boolean hasPermission(EntityPlayer player) { + return true; + } + + public void receiveControl(NBTTagCompound data) {} + + @Override + public void receiveControl(EntityPlayer player, NBTTagCompound nbt) { + readFromNBT(nbt); + markDirty(); + + if(nbt.getBoolean("save")) { + saveStructure(player); + } + + if(nbt.getBoolean("load")) { + loadStructure(player); + } + } + + @Override + @SideOnly(Side.CLIENT) + public AxisAlignedBB getRenderBoundingBox() { + return INFINITE_EXTENT_AABB; + } + + @Override + @SideOnly(Side.CLIENT) + public double getMaxRenderDistanceSquared() { + return 65536.0D; + } + + } + + @SideOnly(Side.CLIENT) + public static class GuiStructureSave extends GuiScreen { + + private final TileEntityWandStructure tile; + + private GuiTextField textName; + + private GuiTextField textSizeX; + private GuiTextField textSizeY; + private GuiTextField textSizeZ; + + private GuiButton performAction; + + private boolean saveOnClose = false; + + public GuiStructureSave(TileEntityWandStructure tile) { + this.tile = tile; + } + + @Override + public void initGui() { + Keyboard.enableRepeatEvents(true); + + textName = new GuiTextField(fontRendererObj, width / 2 - 150, 50, 300, 20); + textName.setText(tile.name); + + textSizeX = new GuiTextField(fontRendererObj, width / 2 - 150, 100, 50, 20); + textSizeX.setText("" + tile.sizeX); + textSizeY = new GuiTextField(fontRendererObj, width / 2 - 100, 100, 50, 20); + textSizeY.setText("" + tile.sizeY); + textSizeZ = new GuiTextField(fontRendererObj, width / 2 - 50, 100, 50, 20); + textSizeZ.setText("" + tile.sizeZ); + + performAction = new GuiButton(0, width / 2 - 150, 150, 300, 20, "SAVE"); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + drawDefaultBackground(); + + textName.drawTextBox(); + + textSizeX.drawTextBox(); + textSizeY.drawTextBox(); + textSizeZ.drawTextBox(); + + performAction.drawButton(mc, mouseX, mouseY); + + super.drawScreen(mouseX, mouseY, partialTicks); + } + + @Override + public void onGuiClosed() { + Keyboard.enableRepeatEvents(false); + + NBTTagCompound data = new NBTTagCompound(); + tile.writeToNBT(data); + + data.setString("name", textName.getText()); + + try { data.setInteger("sizeX", Integer.parseInt(textSizeX.getText())); } catch (Exception ex) {} + try { data.setInteger("sizeY", Integer.parseInt(textSizeY.getText())); } catch (Exception ex) {} + try { data.setInteger("sizeZ", Integer.parseInt(textSizeZ.getText())); } catch (Exception ex) {} + + if(saveOnClose) data.setBoolean("save", true); + + PacketDispatcher.wrapper.sendToServer(new NBTControlPacket(data, tile.xCoord, tile.yCoord, tile.zCoord)); + } + + @Override + protected void keyTyped(char typedChar, int keyCode) { + super.keyTyped(typedChar, keyCode); + + textName.textboxKeyTyped(typedChar, keyCode); + + textSizeX.textboxKeyTyped(typedChar, keyCode); + textSizeY.textboxKeyTyped(typedChar, keyCode); + textSizeZ.textboxKeyTyped(typedChar, keyCode); + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) { + super.mouseClicked(mouseX, mouseY, mouseButton); + textName.mouseClicked(mouseX, mouseY, mouseButton); + + textSizeX.mouseClicked(mouseX, mouseY, mouseButton); + textSizeY.mouseClicked(mouseX, mouseY, mouseButton); + textSizeZ.mouseClicked(mouseX, mouseY, mouseButton); + + if(performAction.mousePressed(mc, mouseX, mouseY)) { + saveOnClose = true; + + mc.displayGuiScreen(null); + mc.setIngameFocus(); + } + } + + @Override + public boolean doesGuiPauseGame() { + return false; + } + + } + + @SideOnly(Side.CLIENT) + public static class GuiStructureLoad extends GuiScreen { + + private final TileEntityWandStructure tile; + + private GuiTextField textName; + + private GuiFileList fileList; + + private GuiButton performAction; + + private boolean loadOnClose = false; + + private static File structureDirectory = new File(Minecraft.getMinecraft().mcDataDir, "structures"); + private static String nameFilter = ""; + private static final FileFilter structureFilter = new FileFilter() { + + public boolean accept(File file) { + if(!file.isFile() || !file.getName().endsWith(".nbt")) return false; + return nameFilter.isEmpty() || file.getName().contains(nameFilter); + } + + }; + + public GuiStructureLoad(TileEntityWandStructure tile) { + this.tile = tile; + } + + @Override + public void initGui() { + Keyboard.enableRepeatEvents(true); + + textName = new GuiTextField(fontRendererObj, width / 2 - 150, 50, 300, 20); + textName.setText(tile.name); + nameFilter = tile.name; + + structureDirectory.mkdir(); + + fileList = new GuiFileList(mc, structureDirectory.listFiles(structureFilter), this::selectFile, nameFilter, width, height, 70, height - 90, 16); + + performAction = new GuiButton(0, width / 2 - 150, height - 70, 300, 20, "LOAD"); + } + + public void selectFile(File file) { + String fileName = file.getName(); + textName.setText(fileName.substring(0, fileName.length() - 4)); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + fileList.drawScreen(mouseX, mouseY, partialTicks); + + textName.drawTextBox(); + + performAction.drawButton(mc, mouseX, mouseY); + + super.drawScreen(mouseX, mouseY, partialTicks); + } + + @Override + public void onGuiClosed() { + Keyboard.enableRepeatEvents(false); + + NBTTagCompound data = new NBTTagCompound(); + tile.writeToNBT(data); + + data.setString("name", textName.getText()); + + if(loadOnClose) data.setBoolean("load", true); + + PacketDispatcher.wrapper.sendToServer(new NBTControlPacket(data, tile.xCoord, tile.yCoord, tile.zCoord)); + } + + @Override + protected void keyTyped(char typedChar, int keyCode) { + super.keyTyped(typedChar, keyCode); + + textName.textboxKeyTyped(typedChar, keyCode); + + if(!nameFilter.equals(textName.getText())) { + nameFilter = textName.getText(); + fileList = new GuiFileList(mc, structureDirectory.listFiles(structureFilter), this::selectFile, nameFilter, width, height, 70, height - 90, 16); + } + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) { + super.mouseClicked(mouseX, mouseY, mouseButton); + textName.mouseClicked(mouseX, mouseY, mouseButton); + + fileList.func_148179_a(mouseX, mouseY, mouseButton); + + fileList.select(textName.getText()); + + if(performAction.mousePressed(mc, mouseX, mouseY)) { + loadOnClose = true; + + mc.displayGuiScreen(null); + mc.setIngameFocus(); + } + } + + @Override + protected void mouseMovedOrUp(int mouseX, int mouseY, int state) { + super.mouseMovedOrUp(mouseX, mouseY, state); + fileList.func_148181_b(mouseX, mouseY, state); + } + + @Override + public boolean doesGuiPauseGame() { + return false; + } + + } + +} diff --git a/src/main/java/com/hbm/blocks/generic/BlockWandTandem.java b/src/main/java/com/hbm/blocks/generic/BlockWandTandem.java index 1ad176e63..2a9342454 100644 --- a/src/main/java/com/hbm/blocks/generic/BlockWandTandem.java +++ b/src/main/java/com/hbm/blocks/generic/BlockWandTandem.java @@ -44,7 +44,6 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.init.Items; import net.minecraft.inventory.Container; -import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; @@ -175,10 +174,10 @@ public class BlockWandTandem extends BlockContainer implements IBlockSideRotatio } if(!player.isSneaking()) { - Block block = getBlock(world, player.getHeldItem()); + Block block = ModBlocks.getBlockFromStack(player.getHeldItem()); if(block == ModBlocks.wand_air) block = Blocks.air; - if(block != null && block != ModBlocks.wand_jigsaw && block != ModBlocks.wand_loot) { + if(block != null && !ModBlocks.isStructureBlock(block, false)) { jigsaw.replaceBlock = block; jigsaw.replaceMeta = player.getHeldItem().getItemDamage(); jigsaw.markDirty(); @@ -196,13 +195,6 @@ public class BlockWandTandem extends BlockContainer implements IBlockSideRotatio return false; } - private Block getBlock(World world, ItemStack stack) { - if(stack == null) return null; - if(!(stack.getItem() instanceof ItemBlock)) return null; - - return ((ItemBlock) stack.getItem()).field_150939_a; - } - @Override @SideOnly(Side.CLIENT) public Object provideGUI(int ID, EntityPlayer player, World world, int x, int y, int z) { diff --git a/src/main/java/com/hbm/commands/CommandLocate.java b/src/main/java/com/hbm/commands/CommandLocate.java index ec646c817..9c9c225f5 100644 --- a/src/main/java/com/hbm/commands/CommandLocate.java +++ b/src/main/java/com/hbm/commands/CommandLocate.java @@ -13,6 +13,7 @@ import net.minecraft.command.ICommandSender; import net.minecraft.command.PlayerNotFoundException; import net.minecraft.command.WrongUsageException; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ChatComponentText; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.MathHelper; @@ -71,6 +72,8 @@ public class CommandLocate extends CommandBase { ChatComponentTranslation message = new ChatComponentTranslation("commands.locate.success.coordinates", structure.name, pos.chunkXPos * 16, pos.chunkZPos * 16); message.getChatStyle().setColor(EnumChatFormatting.GREEN); sender.addChatMessage(message); + } else if (args[0].equals("list")) { + sender.addChatMessage(new ChatComponentText(String.join(", ", NBTStructure.listStructures()))); } else { throw new WrongUsageException(getCommandUsage(sender), new Object[0]); } @@ -105,9 +108,9 @@ public class CommandLocate extends CommandBase { return Collections.emptyList(); if(args.length == 1) - return getListOfStringsMatchingLastWord(args, "structure"); + return getListOfStringsMatchingLastWord(args, "structure", "list"); - if(args.length == 2) { + if(args.length == 2 && args[0].equals("structure")) { List structures = NBTStructure.listStructures(); return getListOfStringsMatchingLastWord(args, structures.toArray(new String[structures.size()])); } diff --git a/src/main/java/com/hbm/inventory/gui/element/GuiFileList.java b/src/main/java/com/hbm/inventory/gui/element/GuiFileList.java new file mode 100644 index 000000000..63364f3d4 --- /dev/null +++ b/src/main/java/com/hbm/inventory/gui/element/GuiFileList.java @@ -0,0 +1,99 @@ +package com.hbm.inventory.gui.element; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiListExtended; +import net.minecraft.client.renderer.Tessellator; + +@SideOnly(Side.CLIENT) +public class GuiFileList extends GuiListExtended { + + private List rows = new ArrayList<>(); + private int selectedId = -1; + + public GuiFileList(Minecraft mc, File[] files, Consumer onSelect, String nameFilter, int width, int height, int top, int bottom, int slotHeight) { + super(mc, width, height, top, bottom, slotHeight); + + for(File file : files) { + if(file.getName().equals(nameFilter + ".nbt")) { + selectedId = rows.size(); + } + rows.add(new Row(file, onSelect, width, slotHeight)); + } + } + + @Override + public IGuiListEntry getListEntry(int id) { + return rows.get(id); + } + + @Override + protected int getSize() { + return rows.size(); + } + + @Override + protected boolean isSelected(int id) { + return id == selectedId; + } + + public void select(String nameFilter) { + for(int i = 0; i < rows.size(); i++) { + Row row = rows.get(i); + if(row.file.getName().equals(nameFilter + ".nbt")) { + selectedId = i; + return; + } + } + } + + @SideOnly(Side.CLIENT) + public static class Row implements IGuiListEntry { + + private final Minecraft mc; + private final File file; + + private final int width; + private final int height; + + private final Consumer onSelect; + + public Row(File file, Consumer onSelect, int width, int height) { + this.mc = Minecraft.getMinecraft(); + this.file = file; + + this.width = width; + this.height = height; + + this.onSelect = onSelect; + } + + @Override + public void drawEntry(int id, int x, int y, int width, int height, Tessellator tess, int mouseX, int mouseY, boolean isVisible) { + mc.fontRenderer.drawString(file.getName(), x + 20, y + 1, 0xFFFFFF); + } + + @Override + public boolean mousePressed(int id, int mouseX, int mouseY, int button, int hoverX, int hoverY) { + if(hoverX < 0 || hoverX > width) return false; + if(hoverY < 0 || hoverY > height) return false; + + onSelect.accept(file); + + return true; + } + + @Override + public void mouseReleased(int id, int mouseX, int mouseY, int button, int hoverX, int hoverY) { + + } + + } + +} diff --git a/src/main/java/com/hbm/items/tool/ItemWandS.java b/src/main/java/com/hbm/items/tool/ItemWandS.java index 55de41e9a..0a98cde19 100644 --- a/src/main/java/com/hbm/items/tool/ItemWandS.java +++ b/src/main/java/com/hbm/items/tool/ItemWandS.java @@ -1,6 +1,5 @@ package com.hbm.items.tool; -import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -8,23 +7,23 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import com.hbm.blocks.ModBlocks; +import com.hbm.blocks.generic.BlockWandStructure.TileEntityWandStructure; import com.hbm.util.BobMathUtil; import com.hbm.util.Tuple.Pair; -import com.hbm.world.gen.nbt.NBTStructure; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import net.minecraft.world.World; public class ItemWandS extends Item { - private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss"); + private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss"); @Override public void addInformation(ItemStack stack, EntityPlayer player, List list, boolean bool) { @@ -34,106 +33,124 @@ public class ItemWandS extends Item { list.add("adds a block to the blacklist by crouch right-clicking!)"); if(stack.stackTagCompound != null) { - int px = stack.stackTagCompound.getInteger("x"); - int py = stack.stackTagCompound.getInteger("y"); - int pz = stack.stackTagCompound.getInteger("z"); + int px = stack.stackTagCompound.getInteger("x"); + int py = stack.stackTagCompound.getInteger("y"); + int pz = stack.stackTagCompound.getInteger("z"); if(px != 0 || py != 0 || pz != 0) { - list.add(EnumChatFormatting.AQUA + "From: " + px + ", " + py + ", " + pz); - } else { - list.add(EnumChatFormatting.AQUA + "No start position set"); - } + list.add(EnumChatFormatting.AQUA + "From: " + px + ", " + py + ", " + pz); + } else { + list.add(EnumChatFormatting.AQUA + "No start position set"); + } - Set> blocks = getBlocks(stack); + Set> blocks = getBlocks(stack); - if(blocks.size() > 0) { - list.add("Blacklist:"); - for(Pair block : blocks) { - list.add(EnumChatFormatting.RED + "- " + block.key.getUnlocalizedName()); - } - } - } + if(blocks.size() > 0) { + list.add("Blacklist:"); + for(Pair block : blocks) { + list.add(EnumChatFormatting.RED + "- " + block.key.getUnlocalizedName()); + } + } + } } - // why the fuck ye'd leave this whole thing obfuscated is beyond me - @Override - public boolean onItemUse(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int side, float fx, float fy, float fz) { + // why the fuck ye'd leave this whole thing obfuscated is beyond me + @Override + public boolean onItemUse(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int side, float fx, float fy, float fz) { if(stack.stackTagCompound == null) { stack.stackTagCompound = new NBTTagCompound(); } if(player.isSneaking()) { - Pair target = new Pair(world.getBlock(x, y, z), world.getBlockMetadata(x, y, z)); - Set> blocks = getBlocks(stack); + Pair target = new Pair(world.getBlock(x, y, z), world.getBlockMetadata(x, y, z)); + Set> blocks = getBlocks(stack); - if(blocks.contains(target)) { - blocks.remove(target); - if(world.isRemote) player.addChatMessage(new ChatComponentText("Removed from blacklist " + target.key.getUnlocalizedName())); - } else { - blocks.add(target); - if(world.isRemote) player.addChatMessage(new ChatComponentText("Added to blacklist " + target.key.getUnlocalizedName())); - } + if(blocks.contains(target)) { + blocks.remove(target); + if(world.isRemote) player.addChatMessage(new ChatComponentText("Removed from blacklist " + target.key.getUnlocalizedName())); + } else { + blocks.add(target); + if(world.isRemote) player.addChatMessage(new ChatComponentText("Added to blacklist " + target.key.getUnlocalizedName())); + } - setBlocks(stack, blocks); + setBlocks(stack, blocks); } else { - int px = stack.stackTagCompound.getInteger("x"); - int py = stack.stackTagCompound.getInteger("y"); - int pz = stack.stackTagCompound.getInteger("z"); + int px = stack.stackTagCompound.getInteger("x"); + int py = stack.stackTagCompound.getInteger("y"); + int pz = stack.stackTagCompound.getInteger("z"); if(px == 0 && py == 0 && pz == 0) { - setPosition(stack, x, y, z); + setPosition(stack, x, y, z); if(world.isRemote) player.addChatMessage(new ChatComponentText("First position set!")); } else { - setPosition(stack, 0, 0, 0); + setPosition(stack, 0, 0, 0); - Set> blocks = getBlocks(stack); - blocks.add(new Pair(Blocks.air, 0)); - blocks.add(new Pair(ModBlocks.spotlight_beam, 0)); + int minX = Math.min(x, px); + int minY = Math.min(y, py) - 1; + int minZ = Math.min(z, pz); - String filename = "structure_" + dateFormat.format(new Date()).toString() + ".nbt"; + int sizeX = Math.abs(x - px) + 1; + int sizeY = Math.abs(y - py) + 1; + int sizeZ = Math.abs(z - pz) + 1; - NBTStructure.saveArea(filename, world, x, y, z, px, py, pz, blocks); + world.setBlock(minX, minY, minZ, ModBlocks.wand_structure); - if(world.isRemote) player.addChatMessage(new ChatComponentText("Structure saved to: .minecraft/structures/" + filename)); + TileEntity te = world.getTileEntity(minX, minY, minZ); + if (te instanceof TileEntityWandStructure) { + TileEntityWandStructure structure = (TileEntityWandStructure) te; + + structure.sizeX = sizeX; + structure.sizeY = sizeY; + structure.sizeZ = sizeZ; + + structure.blacklist = getBlocks(stack); + } else { + if (world.isRemote) + player.addChatMessage(new ChatComponentText("Could not add a structure block!")); + return true; + } + + if (world.isRemote) + player.addChatMessage(new ChatComponentText("Structure block configured and added at: " + minX + ", " + minY + ", " + minZ)); } } - return true; - } + return true; + } - private void setPosition(ItemStack stack, int x, int y, int z) { - stack.stackTagCompound.setInteger("x", x); - stack.stackTagCompound.setInteger("y", y); - stack.stackTagCompound.setInteger("z", z); - } + private void setPosition(ItemStack stack, int x, int y, int z) { + stack.stackTagCompound.setInteger("x", x); + stack.stackTagCompound.setInteger("y", y); + stack.stackTagCompound.setInteger("z", z); + } - private Set> getBlocks(ItemStack stack) { - if(stack.stackTagCompound == null) { - return new HashSet<>(); - } + private Set> getBlocks(ItemStack stack) { + if(stack.stackTagCompound == null) { + return new HashSet<>(); + } - int[] blockIds = stack.stackTagCompound.getIntArray("blocks"); - int[] metas = stack.stackTagCompound.getIntArray("metas"); - Set> blocks = new HashSet<>(blockIds.length); + int[] blockIds = stack.stackTagCompound.getIntArray("blocks"); + int[] metas = stack.stackTagCompound.getIntArray("metas"); + Set> blocks = new HashSet<>(blockIds.length); - for(int i = 0; i < blockIds.length; i++) { - blocks.add(new Pair(Block.getBlockById(blockIds[i]), metas[i])); - } + for(int i = 0; i < blockIds.length; i++) { + blocks.add(new Pair(Block.getBlockById(blockIds[i]), metas[i])); + } - return blocks; - } + return blocks; + } @SuppressWarnings("unchecked") - private void setBlocks(ItemStack stack, Set> blocks) { - if(stack.stackTagCompound == null) { - stack.stackTagCompound = new NBTTagCompound(); - } + private void setBlocks(ItemStack stack, Set> blocks) { + if(stack.stackTagCompound == null) { + stack.stackTagCompound = new NBTTagCompound(); + } - stack.stackTagCompound.setIntArray("blocks", BobMathUtil.collectionToIntArray(blocks, i -> Block.getIdFromBlock(((Pair)i).getKey()))); - stack.stackTagCompound.setIntArray("metas", BobMathUtil.collectionToIntArray(blocks, i -> ((Pair)i).getValue())); - } + stack.stackTagCompound.setIntArray("blocks", BobMathUtil.collectionToIntArray(blocks, i -> Block.getIdFromBlock(((Pair)i).getKey()))); + stack.stackTagCompound.setIntArray("metas", BobMathUtil.collectionToIntArray(blocks, i -> ((Pair)i).getValue())); + } @Override public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) { @@ -147,7 +164,7 @@ public class ItemWandS extends Item { if(world.isRemote) { player.addChatMessage(new ChatComponentText("Cleared blacklist")); - } + } } return stack; diff --git a/src/main/java/com/hbm/main/ClientProxy.java b/src/main/java/com/hbm/main/ClientProxy.java index b57d0becf..2f2d7f2f4 100644 --- a/src/main/java/com/hbm/main/ClientProxy.java +++ b/src/main/java/com/hbm/main/ClientProxy.java @@ -8,6 +8,7 @@ import com.hbm.blocks.generic.BlockPedestal.TileEntityPedestal; import com.hbm.blocks.generic.BlockPlushie.TileEntityPlushie; import com.hbm.blocks.generic.BlockSkeletonHolder.TileEntitySkeletonHolder; import com.hbm.blocks.generic.BlockSnowglobe.TileEntitySnowglobe; +import com.hbm.blocks.generic.BlockWandStructure.TileEntityWandStructure; import com.hbm.blocks.machine.Floodlight.TileEntityFloodlight; import com.hbm.blocks.machine.MachineFan.TileEntityFan; import com.hbm.blocks.machine.PistonInserter.TileEntityPistonInserter; @@ -130,18 +131,18 @@ import java.util.*; import java.util.Map.Entry; public class ClientProxy extends ServerProxy { - + private static final I18nClient I18N = new I18nClient(); public RenderInfoSystem theInfoSystem = new RenderInfoSystem(); - + public ITranslate getI18n() { return I18N; } /** Runs just before item an block init */ @Override public void registerPreRenderInfo() { AdvancedModelLoader.registerModelHandler(new HmfModelLoader()); - + QMAWLoader.registerModFileURL(FMLCommonHandler.instance().findContainerFor(RefStrings.MODID).getSource()); } @@ -438,6 +439,8 @@ public class ClientProxy extends ServerProxy { ClientRegistry.bindTileEntitySpecialRenderer(TileEntityVaultDoor.class, new RenderVaultDoor()); ClientRegistry.bindTileEntitySpecialRenderer(TileEntityBlastDoor.class, new RenderBlastDoor()); ClientRegistry.bindTileEntitySpecialRenderer(TileEntityDoorGeneric.class, new RenderDoorGeneric()); + //NBTStructure + ClientRegistry.bindTileEntitySpecialRenderer(TileEntityWandStructure.class, new RenderWandStructure()); } @Override @@ -459,7 +462,7 @@ public class ClientProxy extends ServerProxy { } } } - + // same crap but for items directly because why invent a new solution when this shit works just fine Iterator itItems = Item.itemRegistry.iterator(); while(itItems.hasNext()) { @@ -787,7 +790,7 @@ public class ClientProxy extends ServerProxy { public void registerBlockRenderer() { RenderingRegistry.registerBlockHandler(new RenderISBRHUniversal()); - + /// STOP DOING THIS /// RenderingRegistry.registerBlockHandler(new RenderScaffoldBlock()); RenderingRegistry.registerBlockHandler(new RenderTapeBlock()); diff --git a/src/main/java/com/hbm/render/tileentity/RenderWandStructure.java b/src/main/java/com/hbm/render/tileentity/RenderWandStructure.java new file mode 100644 index 000000000..fd6b0dced --- /dev/null +++ b/src/main/java/com/hbm/render/tileentity/RenderWandStructure.java @@ -0,0 +1,92 @@ +package com.hbm.render.tileentity; + +import org.lwjgl.opengl.GL11; + +import com.hbm.blocks.generic.BlockWandStructure.TileEntityWandStructure; + +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; +import net.minecraft.tileentity.TileEntity; + +public class RenderWandStructure extends TileEntitySpecialRenderer { + + @Override + public void renderTileEntityAt(TileEntity tile, double x, double y, double z, float interp) { + if(!(tile instanceof TileEntityWandStructure)) return; + TileEntityWandStructure structure = (TileEntityWandStructure) tile; + + if(tile.getBlockMetadata() != 0) return; + + GL11.glPushMatrix(); + { + + double x1 = x; + double y1 = y + 1; + double z1 = z; + + double x2 = x + structure.sizeX; + double y2 = y + structure.sizeY + 1; + double z2 = z + structure.sizeZ; + + + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glColor3f(1F, 1F, 1F); + + + + Tessellator tess = Tessellator.instance; + tess.startDrawing(GL11.GL_LINES); + tess.setBrightness(240); + tess.setColorRGBA_F(1F, 1F, 1F, 1F); + + // top + tess.addVertex(x1, y2, z1); + tess.addVertex(x1, y2, z2); + + tess.addVertex(x1, y2, z2); + tess.addVertex(x2, y2, z2); + + tess.addVertex(x2, y2, z2); + tess.addVertex(x2, y2, z1); + + tess.addVertex(x2, y2, z1); + tess.addVertex(x1, y2, z1); + + // bottom + tess.addVertex(x1, y1, z1); + tess.addVertex(x1, y1, z2); + + tess.addVertex(x1, y1, z2); + tess.addVertex(x2, y1, z2); + + tess.addVertex(x2, y1, z2); + tess.addVertex(x2, y1, z1); + + tess.addVertex(x2, y1, z1); + tess.addVertex(x1, y1, z1); + + // sides + tess.addVertex(x1, y1, z1); + tess.addVertex(x1, y2, z1); + + tess.addVertex(x2, y1, z1); + tess.addVertex(x2, y2, z1); + + tess.addVertex(x2, y1, z2); + tess.addVertex(x2, y2, z2); + + tess.addVertex(x1, y1, z2); + tess.addVertex(x1, y2, z2); + + tess.draw(); + + + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glDisable(GL11.GL_LIGHTING); + + } + GL11.glPopMatrix(); + } + +} diff --git a/src/main/java/com/hbm/tileentity/TileMappings.java b/src/main/java/com/hbm/tileentity/TileMappings.java index caf2588aa..f663451d1 100644 --- a/src/main/java/com/hbm/tileentity/TileMappings.java +++ b/src/main/java/com/hbm/tileentity/TileMappings.java @@ -20,6 +20,7 @@ import com.hbm.blocks.generic.BlockSnowglobe.TileEntitySnowglobe; import com.hbm.blocks.generic.BlockSupplyCrate.TileEntitySupplyCrate; import com.hbm.blocks.generic.BlockWandJigsaw.TileEntityWandJigsaw; import com.hbm.blocks.generic.BlockWandLoot.TileEntityWandLoot; +import com.hbm.blocks.generic.BlockWandStructure.TileEntityWandStructure; import com.hbm.blocks.generic.BlockWandTandem.TileEntityWandTandem; import com.hbm.blocks.generic.BlockWandLogic.TileEntityWandLogic; import com.hbm.blocks.generic.DungeonSpawner.TileEntityDungeonSpawner; @@ -245,6 +246,7 @@ public class TileMappings { put(TileEntityWandJigsaw.class, "tileentity_wand_jigsaw"); put(TileEntityWandLogic.class, "tileentity_wand_spawner"); put(TileEntityWandTandem.class, "tileentity_wand_tandem"); + put(TileEntityWandStructure.class, "tileentity_wand_structure"); putNetwork(); putBombs(); @@ -442,7 +444,7 @@ public class TileMappings { put(TileEntityPipeExhaustPaintable.class, "tileentity_pipe_exhaust_paintable"); put(TileEntityFluidValve.class, "tileentity_pipe_valve"); put(TileEntityFluidPump.class, "tileentity_pipe_pump"); - + put(TileEntityPipeAnchor.class, "tileentity_pioe_anchor"); put(TileEntityCraneInserter.class, "tileentity_inserter"); @@ -496,7 +498,7 @@ public class TileMappings { if(IConfigurableMachine.class.isAssignableFrom(clazz)) { configurables.add((Class) clazz); } - + /** * Causes problems with most machines where two independently acting tiles work together (TU machines, RBMKs, fluid transfer) * Also breaks due to some sort of buffer leak in the threaded packets, if a boiler is involved (which uses a ByteBuf instead of the usual serializing) it crashes diff --git a/src/main/java/com/hbm/world/gen/nbt/JigsawPiece.java b/src/main/java/com/hbm/world/gen/nbt/JigsawPiece.java index 039f8d154..6db8b75d1 100644 --- a/src/main/java/com/hbm/world/gen/nbt/JigsawPiece.java +++ b/src/main/java/com/hbm/world/gen/nbt/JigsawPiece.java @@ -9,17 +9,47 @@ import net.minecraft.world.gen.structure.StructureComponent.BlockSelector; // Assigned to a Component to build public class JigsawPiece { - // Translates a given name into a jigsaw piece, for serialization - protected static Map jigsawMap = new HashMap<>(); + // Translates a given name into a jigsaw piece, for serialization + protected static Map jigsawMap = new HashMap<>(); public final String name; public final NBTStructure structure; // Block modifiers, for randomization and terrain matching + /** + * Replaces matching blocks using the result of a BlockSelector + */ public Map blockTable; - public boolean conformToTerrain = false; // moves every single column to the terrain (digging out trenches, natural formations) - public boolean alignToTerrain = false; // aligns this component y-level individually, without moving individual columns (village houses) - public int heightOffset = 0; // individual offset for the structure + + /** + * Moves every single column to the terrain (for digging out trenches, natural formations, etc) + */ + public boolean conformToTerrain = false; + + /** + * Aligns this component y-level individually, without moving individual columns (like village houses) + */ + public boolean alignToTerrain = false; + + /** + * Height offset for this structure piece, -1 will sink the floor flush into the ground + */ + public int heightOffset = 0; + + /** + * Will fill any air gap beneath the jigsaw piece using the given selector + */ + public BlockSelector platform; + + /** + * If greater than 0, will limit the number of times this piece can spawn in a structure + */ + public int instanceLimit = 0; + + /** + * If set, will continue generation beyond the defined limits until this piece exists at least once + */ + public boolean required = false; public JigsawPiece(String name, NBTStructure structure) { this(name, structure, 0); diff --git a/src/main/java/com/hbm/world/gen/nbt/JigsawPool.java b/src/main/java/com/hbm/world/gen/nbt/JigsawPool.java index aaf0e70d2..8fbc0c533 100644 --- a/src/main/java/com/hbm/world/gen/nbt/JigsawPool.java +++ b/src/main/java/com/hbm/world/gen/nbt/JigsawPool.java @@ -23,6 +23,11 @@ public class JigsawPool { totalWeight += weight; } + public int getAverageWeight() { + if(pieces.size() == 0) return 1; + return totalWeight / pieces.size(); + } + protected JigsawPool clone() { JigsawPool clone = new JigsawPool(); clone.pieces = new ArrayList<>(this.pieces); diff --git a/src/main/java/com/hbm/world/gen/nbt/NBTStructure.java b/src/main/java/com/hbm/world/gen/nbt/NBTStructure.java index 4109338cc..04a7dda65 100644 --- a/src/main/java/com/hbm/world/gen/nbt/NBTStructure.java +++ b/src/main/java/com/hbm/world/gen/nbt/NBTStructure.java @@ -1,12 +1,16 @@ package com.hbm.world.gen.nbt; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.function.Predicate; +import org.apache.commons.io.IOUtils; + import com.hbm.blocks.ModBlocks; import com.hbm.blocks.generic.BlockWand; import com.hbm.blocks.generic.BlockWandTandem.TileEntityWandTandem; @@ -18,6 +22,7 @@ import com.hbm.util.Tuple.Pair; import com.hbm.util.Tuple.Quartet; import com.hbm.util.fauxpointtwelve.BlockPos; import com.hbm.world.gen.nbt.SpawnCondition.WorldCoordinate; +import com.hbm.world.gen.nbt.selector.BiomeBlockSelector; import cpw.mods.fml.common.registry.GameRegistry; import net.minecraft.block.*; @@ -57,9 +62,11 @@ public class NBTStructure { private static Map namedMap = new HashMap<>(); - protected static Map> weightedMap = new HashMap<>(); + protected static Map> spawnMap = new HashMap<>(); protected static Map> customSpawnMap = new HashMap<>(); + private static Map> validBiomeCache = new HashMap<>(); + private String name; private boolean isLoaded; @@ -84,6 +91,34 @@ public class NBTStructure { } } + public NBTStructure(String name, InputStream stream) { + this.name = name; + loadStructure(stream); + } + + public NBTStructure(File file) throws FileNotFoundException { + this.name = file.getName(); + InputStream stream = new FileInputStream(file); + loadStructure(stream); + IOUtils.closeQuietly(stream); + } + + public String getName() { + return name.substring(0, name.length() - 4); // trim .nbt + } + + public int getSizeX() { + return size.x; + } + + public int getSizeY() { + return size.y; + } + + public int getSizeZ() { + return size.z; + } + public static void register() { MapGenStructureIO.registerStructure(Start.class, "NBTStructures"); MapGenStructureIO.func_143031_a(Component.class, "NBTComponents"); @@ -102,10 +137,8 @@ public class NBTStructure { return; } - List weightedList = weightedMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); - for(int i = 0; i < spawn.spawnWeight; i++) { - weightedList.add(spawn); - } + List spawnList = spawnMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); + spawnList.add(spawn); } public static void registerStructure(SpawnCondition spawn, int[] dimensionIds) { @@ -122,10 +155,8 @@ public class NBTStructure { public static void registerNullWeight(int dimensionId, int weight, Predicate predicate) { SpawnCondition spawn = new SpawnCondition(weight, predicate); - List weightedList = weightedMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); - for(int i = 0; i < spawn.spawnWeight; i++) { - weightedList.add(spawn); - } + List spawnList = spawnMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); + spawnList.add(spawn); } // Presents a list of all structures registered (so far) @@ -142,7 +173,7 @@ public class NBTStructure { } // Saves a selected area into an NBT structure (+ some of our non-standard stuff to support 1.7.10) - public static void saveArea(String filename, World world, int x1, int y1, int z1, int x2, int y2, int z2, Set> exclude) { + public static NBTTagCompound saveArea(World world, int x1, int y1, int z1, int x2, int y2, int z2, Set> exclude) { NBTTagCompound structure = new NBTTagCompound(); NBTTagList nbtBlocks = new NBTTagList(); NBTTagList nbtPalette = new NBTTagList(); @@ -249,6 +280,13 @@ public class NBTStructure { structure.setTag("entities", new NBTTagList()); + return structure; + } + + // Writes out a specified area to an .nbt file with a given name + public static File quickSaveArea(String filename, World world, int x1, int y1, int z1, int x2, int y2, int z2, Set> exclude) { + NBTTagCompound structure = saveArea(world, x1, y1, z1, x2, y2, z2, exclude); + try { File structureDirectory = new File(Minecraft.getMinecraft().mcDataDir, "structures"); structureDirectory.mkdir(); @@ -256,8 +294,12 @@ public class NBTStructure { File structureFile = new File(structureDirectory, filename); CompressedStreamTools.writeCompressed(structure, new FileOutputStream(structureFile)); + + return structureFile; } catch (Exception ex) { MainRegistry.logger.warn("Failed to save NBT structure", ex); + + return null; } } @@ -441,10 +483,14 @@ public class NBTStructure { } public void build(World world, int x, int y, int z) { - build(world, x, y, z, 0); + build(world, x, y, z, 0, true, false); } public void build(World world, int x, int y, int z, int coordBaseMode) { + build(world, x, y, z, coordBaseMode, true, false); + } + + public void build(World world, int x, int y, int z, int coordBaseMode, boolean center, boolean wipeExisting) { if(!isLoaded) { MainRegistry.logger.info("NBTStructure is invalid"); return; @@ -452,9 +498,11 @@ public class NBTStructure { HashMap worldItemPalette = getWorldItemPalette(); - boolean swizzle = coordBaseMode == 1 || coordBaseMode == 3; - x -= (swizzle ? size.z : size.x) / 2; - z -= (swizzle ? size.x : size.z) / 2; + if(center) { + boolean swizzle = coordBaseMode == 1 || coordBaseMode == 3; + x -= (swizzle ? size.z : size.x) / 2; + z -= (swizzle ? size.x : size.z) / 2; + } int maxX = size.x; int maxZ = size.z; @@ -466,16 +514,17 @@ public class NBTStructure { for(int by = 0; by < size.y; by++) { BlockState state = blockArray[bx][by][bz]; - if(state == null) continue; + if(state == null) { + if(wipeExisting) world.setBlock(rx, by + y, rz, Blocks.air, 0, 2); + continue; + } int ry = by + y; Block block = transformBlock(state.definition, null, world.rand); int meta = transformMeta(state.definition, null, coordBaseMode); - if(ry < 0 || ry >= world.getHeight()) continue; - Block existing = world.getBlock(rx, ry, rz); - if(existing == Blocks.bedrock) continue; + if(ry < 1) continue; world.setBlock(rx, ry, rz, block, meta, 2); @@ -535,12 +584,30 @@ public class NBTStructure { int minZ = Math.min(rotMinZ, rotMaxZ); int maxZ = Math.max(rotMinZ, rotMaxZ); + if(piece.blockTable != null || piece.platform != null) { + BiomeGenBase biome = world.getWorldChunkManager().getBiomeGenAt(generatingBounds.getCenterX(), generatingBounds.getCenterZ()); + + if(piece.blockTable != null) { + for(BlockSelector selector : piece.blockTable.values()) { + if(selector instanceof BiomeBlockSelector) { + ((BiomeBlockSelector) selector).nextBiome = biome; + } + } + } + + if(piece.platform instanceof BiomeBlockSelector) { + ((BiomeBlockSelector) piece.platform).nextBiome = biome; + } + } + for(int bx = minX; bx <= maxX; bx++) { for(int bz = minZ; bz <= maxZ; bz++) { int rx = rotateX(bx, bz, coordBaseMode) + totalBounds.minX; int rz = rotateZ(bx, bz, coordBaseMode) + totalBounds.minZ; int oy = piece.conformToTerrain ? world.getTopSolidOrLiquidBlock(rx, rz) + piece.heightOffset : totalBounds.minY; + boolean hasBase = false; + for(int by = 0; by < size.y; by++) { BlockState state = blockArray[bx][by][bz]; if(state == null) continue; @@ -550,9 +617,7 @@ public class NBTStructure { Block block = transformBlock(state.definition, piece.blockTable, world.rand); int meta = transformMeta(state.definition, piece.blockTable, coordBaseMode); - if(ry < 0 || ry >= world.getHeight()) continue; - Block existing = world.getBlock(rx, ry, rz); - if(existing == Blocks.bedrock) continue; + if(ry < 1) continue; world.setBlock(rx, ry, rz, block, meta, 2); @@ -560,6 +625,16 @@ public class NBTStructure { TileEntity te = buildTileEntity(world, block, worldItemPalette, state.nbt, coordBaseMode, structureName); world.setTileEntity(rx, ry, rz, te); } + + if(by == 0 && piece.platform != null && !block.getMaterial().isReplaceable()) hasBase = true; + } + + if(hasBase && !piece.conformToTerrain) { + for(int y = oy - 1; y > 0; y--) { + if(!world.getBlock(rx, y, rz).isReplaceable(world, rx, y, rz)) break; + piece.platform.selectBlocks(world.rand, 0, 0, 0, false); + world.setBlock(rx, y, rz, piece.platform.func_151561_a(), piece.platform.getSelectedBlockMetaData(), 2); + } } } } @@ -602,7 +677,7 @@ public class NBTStructure { for(int i = 0; i < items.tagCount(); i++) { NBTTagCompound item = items.getCompoundTagAt(i); - item.setShort("id", palette.get(item.getShort("id"))); + item.setShort("id", palette.getOrDefault(item.getShort("id"), (short)0)); } } @@ -940,6 +1015,8 @@ public class NBTStructure { List queuedComponents = new ArrayList<>(); if(spawn.structure == null) queuedComponents.add(startComponent); + Set requiredPieces = findRequiredPieces(spawn); + // Iterate through and build out all the components we intend to spawn while(!queuedComponents.isEmpty()) { queuedComponents.sort((a, b) -> b.priority - a.priority); // sort by placement priority descending @@ -956,7 +1033,10 @@ public class NBTStructure { if(fromComponent.piece.structure.fromConnections == null) continue; int distance = getDistanceTo(fromComponent.getBoundingBox()); - boolean fallbacksOnly = this.components.size() >= spawn.sizeLimit || distance >= spawn.rangeLimit; + + // Only generate fallback pieces once we hit our size limit, unless we have a required component + // Note that there is a HARD limit of 1024 pieces to prevent infinite generation + boolean fallbacksOnly = requiredPieces.size() == 0 && (components.size() >= spawn.sizeLimit || distance >= spawn.rangeLimit) || components.size() > 1024; for(List unshuffledList : fromComponent.piece.structure.fromConnections) { List connectionList = new ArrayList<>(unshuffledList); @@ -994,6 +1074,8 @@ public class NBTStructure { if(nextComponent != null) { addComponent(nextComponent, fromConnection.placementPriority); queuedComponents.add(nextComponent); + + requiredPieces.remove(nextComponent.piece); } else { // If we failed to fit anything in, grab something from the fallback pool, ignoring bounds check // unless we are perfectly abutting another piece, so grid layouts can work! @@ -1043,6 +1125,22 @@ public class NBTStructure { return new BlockPos(x, y, z); } + private Set findRequiredPieces(SpawnCondition spawn) { + Set requiredPieces = new HashSet<>(); + + if(spawn.pools == null) return requiredPieces; + + for(JigsawPool pool : spawn.pools.values()) { + for(Pair weight : pool.pieces) { + if(weight.getKey().required) { + requiredPieces.add(weight.getKey()); + } + } + } + + return requiredPieces; + } + private Component buildNextComponent(Random rand, SpawnCondition spawn, JigsawPool pool, Component fromComponent, JigsawConnection fromConnection) { JigsawPiece nextPiece = pool.get(rand); if(nextPiece == null) { @@ -1050,6 +1148,17 @@ public class NBTStructure { return null; } + if(nextPiece.instanceLimit > 0) { + int instances = 0; + for(Object component : components) { + if(component instanceof Component && ((Component) component).piece == nextPiece) { + instances++; + + if(instances >= nextPiece.instanceLimit) return null; + } + } + } + List connectionPool = nextPiece.structure.getConnectionPool(fromConnection.dir, fromConnection.targetName); if(connectionPool == null || connectionPool.isEmpty()) { MainRegistry.logger.warn("[Jigsaw] No valid connections for: " + fromConnection.targetName + " - in piece: " + nextPiece.name); @@ -1153,7 +1262,7 @@ public class NBTStructure { } } - if (!weightedMap.containsKey(worldObj.provider.dimensionId)) + if (!spawnMap.containsKey(worldObj.provider.dimensionId)) return null; int x = chunkX; @@ -1189,11 +1298,35 @@ public class NBTStructure { } private SpawnCondition findSpawn(BiomeGenBase biome) { - List spawnList = weightedMap.get(worldObj.provider.dimensionId); + Map dimensionCache = validBiomeCache.computeIfAbsent(worldObj.provider.dimensionId, integer -> new HashMap<>()); - for(int i = 0; i < 64; i++) { - SpawnCondition spawn = spawnList.get(rand.nextInt(spawnList.size())); - if(spawn.isValid(biome)) return spawn; + WeightedSpawnList filteredList; + if(!dimensionCache.containsKey(biome.biomeID)) { + List spawnList = spawnMap.get(worldObj.provider.dimensionId); + + filteredList = new WeightedSpawnList(); + for(SpawnCondition spawn : spawnList) { + if(spawn.isValid(biome)) { + filteredList.add(spawn); + filteredList.totalWeight += spawn.spawnWeight; + } + } + + dimensionCache.put(biome.biomeID, filteredList); + } else { + filteredList = dimensionCache.get(biome.biomeID); + } + + if(filteredList.totalWeight == 0) return null; + + int weight = rand.nextInt(filteredList.totalWeight); + + for(SpawnCondition spawn : filteredList) { + weight -= spawn.spawnWeight; + + if(weight < 0) { + return spawn; + } } return null; @@ -1201,4 +1334,10 @@ public class NBTStructure { } + private static class WeightedSpawnList extends ArrayList { + + public int totalWeight = 0; + + } + } diff --git a/src/main/java/com/hbm/world/gen/nbt/SpawnCondition.java b/src/main/java/com/hbm/world/gen/nbt/SpawnCondition.java index f84d3aad8..0c385e51e 100644 --- a/src/main/java/com/hbm/world/gen/nbt/SpawnCondition.java +++ b/src/main/java/com/hbm/world/gen/nbt/SpawnCondition.java @@ -18,34 +18,70 @@ public class SpawnCondition { public final String name; - // If defined, will spawn a single jigsaw piece, for single nbt structures + /** + * If defined, will spawn a single jigsaw piece, for single nbt structures + */ public JigsawPiece structure; - // If defined, will spawn in a non-nbt structure component + /** + * If defined, will spawn in a non-nbt structure component + */ public Function, StructureStart> start; - // If defined, will override regular spawn location checking, for placing at specific coordinates or with special rules + /** + * If defined, will override regular spawn location checking, for placing at specific coordinates or with special rules + */ public Predicate checkCoordinates; - // Our regular spawning mechanics, based on biome, you should generally use these + /** + * Defines whether the current biome is valid for spawning this structure + */ public Predicate canSpawn; + + /** + * The chance of this structure spawning relative to others, + * higher weights will spawn more often. + */ public int spawnWeight = 1; - // Named jigsaw pools that are referenced within the structure + /** + * Named jigsaw pools that are referenced by jigsaw blocks within the structure + */ public Map pools; + + /** + * The name of the "core" pool, which the structure starts generation from, + * must be a name of a pool defined within `pool` + */ public String startPool; - // Maximum amount of components in this structure + /** + * Maximum amount of components in this structure. + * Once the structure reaches this many components, + * it will only generate fallback pieces and stop + * + * Note: there is a hard limit of 1024 pieces to prevent infinite generation, + * even if some pieces are marked as required! + */ public int sizeLimit = 8; - // How far the structure can extend horizontally from the center, maximum of 128 - // This could be increased by changing GenStructure:range from 8, but this is already quite reasonably large + // This could be increased by changing GenStructure:range from 8, but this is + // already quite reasonably large + /** + * How far the structure can extend horizontally from the center, maximum of 128 + */ public int rangeLimit = 128; - // Height modifiers, will clamp height that the start generates at, allowing for: - // * Submarines that must spawn under the ocean surface - // * Bunkers that sit underneath the ground + /** + * Height modifiers, will clamp height that the start generates at, allowing for: + * * Submarines that must spawn under the ocean surface + * * Bunkers that sit underneath the ground + */ public int minHeight = 1; + + /** + * @see minHeight + */ public int maxHeight = 128; protected SpawnCondition(int weight, Predicate predicate) { diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/BiomeBlockSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeBlockSelector.java new file mode 100644 index 000000000..cf700c1a0 --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeBlockSelector.java @@ -0,0 +1,10 @@ +package com.hbm.world.gen.nbt.selector; + +import net.minecraft.world.biome.BiomeGenBase; +import net.minecraft.world.gen.structure.StructureComponent.BlockSelector; + +public abstract class BiomeBlockSelector extends BlockSelector { + + public BiomeGenBase nextBiome; + +} diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/BiomeFillerSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeFillerSelector.java new file mode 100644 index 000000000..73fcf9c9b --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeFillerSelector.java @@ -0,0 +1,12 @@ +package com.hbm.world.gen.nbt.selector; + +import java.util.Random; + +public class BiomeFillerSelector extends BiomeBlockSelector { + + @Override + public void selectBlocks(Random rand, int x, int y, int z, boolean notInterior) { + field_151562_a = nextBiome.fillerBlock; + } + +} diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/BiomeTopSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeTopSelector.java new file mode 100644 index 000000000..862f17ae1 --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeTopSelector.java @@ -0,0 +1,12 @@ +package com.hbm.world.gen.nbt.selector; + +import java.util.Random; + +public class BiomeTopSelector extends BiomeBlockSelector { + + @Override + public void selectBlocks(Random rand, int x, int y, int z, boolean notInterior) { + field_151562_a = nextBiome.topBlock; + } + +} diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/BrickSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/BrickSelector.java new file mode 100644 index 000000000..98852290d --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/BrickSelector.java @@ -0,0 +1,15 @@ +package com.hbm.world.gen.nbt.selector; + +import java.util.Random; + +import net.minecraft.init.Blocks; +import net.minecraft.world.gen.structure.StructureComponent.BlockSelector; + +public class BrickSelector extends BlockSelector { + + @Override + public void selectBlocks(Random rand, int x, int y, int z, boolean notInterior) { + field_151562_a = Blocks.brick_block; + } + +} diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/StoneBrickSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/StoneBrickSelector.java new file mode 100644 index 000000000..ff841d08b --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/StoneBrickSelector.java @@ -0,0 +1,24 @@ +package com.hbm.world.gen.nbt.selector; + +import java.util.Random; + +import net.minecraft.init.Blocks; +import net.minecraft.world.gen.structure.StructureComponent.BlockSelector; + +public class StoneBrickSelector extends BlockSelector { + + @Override + public void selectBlocks(Random rand, int x, int y, int z, boolean notInterior) { + field_151562_a = Blocks.stonebrick; + float f = rand.nextFloat(); + + if (f < 0.2F) { + this.selectedBlockMetaData = 2; + } else if (f < 0.5F) { + this.selectedBlockMetaData = 1; + } else { + this.selectedBlockMetaData = 0; + } + } + +} diff --git a/src/main/resources/assets/hbm/lang/en_US.lang b/src/main/resources/assets/hbm/lang/en_US.lang index e89380a4b..879e27666 100644 --- a/src/main/resources/assets/hbm/lang/en_US.lang +++ b/src/main/resources/assets/hbm/lang/en_US.lang @@ -6354,6 +6354,8 @@ tile.wand_air.name=Structure Wand Block (Air) tile.wand_loot.name=Structure Wand Block (Lootable) tile.wand_jigsaw.name=Structure Wand Block (Jigsaw) tile.wand_logic.name=Structure Wand Block (Logic) +tile.wand_structure.load.name=Structure Loading Block +tile.wand_structure.save.name=Structure Saving Block tile.waste_earth.name=Dead Grass tile.waste_leaves.name=Dead Leaves tile.waste_log.name=Charred Log diff --git a/src/main/resources/assets/hbm/textures/blocks/wand_structure_load.png b/src/main/resources/assets/hbm/textures/blocks/wand_structure_load.png new file mode 100644 index 000000000..fbebc58e9 Binary files /dev/null and b/src/main/resources/assets/hbm/textures/blocks/wand_structure_load.png differ diff --git a/src/main/resources/assets/hbm/textures/blocks/wand_structure_save.png b/src/main/resources/assets/hbm/textures/blocks/wand_structure_save.png new file mode 100644 index 000000000..9773d70e4 Binary files /dev/null and b/src/main/resources/assets/hbm/textures/blocks/wand_structure_save.png differ