Load Tile Entities from schematic in Forge 1.8 Mod

Discussion in 'Programming Help' started by gummby8, Mar 11, 2016.

  1. gummby8

    gummby8 New Member

    I have a mod where I have an entity that flies around and generates structures based on schematics. In a few of the schematics resides a custom block with a tile entity. I really only need to grab one NBT tag out of the tile entity and make sure it gets placed into the new tile entity that is pasted.

    Code (Text):
    message = compound.getString("message");
    here is my schematic loader / placer
    schematic

    Code (Text):

    package com.Splosions.ModularBosses.util;
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.util.List;
    import java.util.Map;

    import com.google.common.primitives.UnsignedBytes;

    import net.minecraft.block.Block;
    import net.minecraft.block.state.IBlockState;
    import net.minecraft.init.Blocks;
    import net.minecraft.nbt.CompressedStreamTools;
    import net.minecraft.nbt.NBTTagCompound;
    import net.minecraft.util.BlockPos;
    import net.minecraft.world.World;

    public class Schematic {
       
       private short width;
       private short height;
       private short length;
       private int size;
       private BlockObject[] blockObjects;
       
       public Schematic(String fileName) {
          try {
             InputStream is = Schematic.class.getResourceAsStream("/assets/mb/schematics/2.schematic");
             NBTTagCompound nbtdata = CompressedStreamTools.readCompressed(is);
         
             is.close();
             System.out.println("Schematic?");
             width = nbtdata.getShort("Width");
             height = nbtdata.getShort("Height");
             length = nbtdata.getShort("Length");
             size = width * height * length;
             blockObjects = new BlockObject[size];
             
             byte[] blockIDs = nbtdata.getByteArray("Blocks");
             byte[] metadata = nbtdata.getByteArray("Data");

             
           
             
             int counter = 0;
             for(int i = 0; i < height; i++) {
                for(int j = 0; j < length; j++) {
                   for(int k = 0; k < width; k++) {
                       int blockId = UnsignedBytes.toInt(blockIDs[counter]);
                      BlockPos pos = new BlockPos(k, i, j);
                      IBlockState state = Block.getBlockById(blockId).getStateFromMeta(metadata[counter]);
                      blockObjects[counter] = new BlockObject(pos, state);
                      counter++;
                   }
                }
             }
             
          } catch(Exception e) {
             e.printStackTrace();
          }
       }
       
       
       
       public void generate(World world, double x, double y, double z) {
          for(BlockObject obj : blockObjects) {
             world.setBlockState(obj.getPosWithOffset((int) x, (int) y, (int) z), obj.getState());
          }
       }
       

    }


     

    blockobject

    Code (Text):

    package com.Splosions.ModularBosses.util;

    import net.minecraft.block.state.IBlockState;
    import net.minecraft.util.BlockPos;

    public class BlockObject {
       
       private BlockPos pos;
       private IBlockState state;
       
       public BlockObject(BlockPos pos, IBlockState state) {
          this.pos = pos;
          this.state = state;
       }

       public BlockPos getPos() {
          return pos;
       }

       public IBlockState getState() {
          return state;
       }
       
       public BlockPos getPosWithOffset(int x, int y, int z) {
          return new BlockPos(x + pos.getX(), y + pos.getY(), z + pos.getZ());
       }

    }
     


    I saw in the souce of the 1.8 world edit you don't actually place a tile entity with the block, rather you detect if there is supposed ot be a tile entity and first remove then replace the tile entity like so.

    Code (Text):

    if (block.hasNbtData()) {
                    // Kill the old TileEntity
                    world.removeTileEntity(pos);
                    NBTTagCompound nativeTag = NBTConverter.toNative(block.getNbtData());
                    nativeTag.setString("id", block.getNbtId());
                    TileEntityUtils.setTileEntity(world, position, nativeTag);
                }
     

    How can I get this working in my mod?

    Thank you
  2. wizjany

    wizjany Administrator Developer

    is the custom block yours? if so, pass the data to the tile entity after it loads.

    worldedit relies on some really hacky methods because we need to be able to basically duplicate any TE from any mod.
    our process basically calls the method that writes the entire TE to NBT (as if the chunk was being unloaded and saving to disk) and then taking that entire nbt tag and placing it into the new block (as if a chunk was being loaded from disk).

    it's not possible to copy just a single tag like this without saving the entire TE to an NBT tag, changing the one tag that you want to modify, and then loading the TE back up from your NBT tag. this is really hacky and if you have access to the tileentity class/object at all, there should be a better way of getting the data to it.

    i'd really need to know more about what you were doing to say for sure though.
  3. gummby8

    gummby8 New Member

    Yes the custom block is mine.

    The custom entity flies around and spawns "Rooms" from a scematic. Each room has a "ControlBlock". It is a custom spawn block that has a tile entity that can spawn any mob in the global entity list, even mobs from other mods. It saves its config that is set via in game gui to a single string in csv format. That's why I said I only needed 1 tag. But it sounds like I get all or nothing, which is fine.

    So I want my entity to be able to fly around and spawn the rooms from the schematics with the custom ControlBlocks config intact.

    The problem I am having is figuring out how to "Pass the data to the tile entity after it loads" I am having a hard time getting the data in the first place.

    When I look at how WE saves / loads the schematics it is doing it with all sorts of CompoundTags, ListTags, Vectors, CuboidClipBoards and BaseBlocks. I am not looking flip or rotate or do anything special with the schematic. I just want to get the Block IDs Metadata and tileentities.

    Thank you
  4. wizjany

    wizjany Administrator Developer

    the tile entity stores that string in a field somewhere right? just add a method that sets the field that will be accessible once the tile entity is constructed into an object.
    block states don't store nbt anyway, only tile entities do, so you will have to wait for after the generate method for at least your special block to do it.
  5. gummby8

    gummby8 New Member

    I'm confused.

    I have a set message(String message) method in the tile entity. But I need to get the message string from out of the stored tile entity in the schematic. How do I do that?
  6. wizjany

    wizjany Administrator Developer

    depends on the the nbt code you have. i'm not completely familiar with minecraft's native nbt code, but there should be a way to grab the tag by name. you might also want to open the schematic up in an nbt editor to get a better idea of where tags are and how it's formatted.
  7. gummby8

    gummby8 New Member


    Ok but don't I have to get the tileentity first before I can get its nbt data? How do I get the tile entities?

    All I know how to do is get the blockID and Data. How do i get the tile entities out of the schematic?
  8. wizjany

    wizjany Administrator Developer

    the schematic file has a list of tags for each tile entity.
    that's why i said to take a look at the format.
  9. gummby8

    gummby8 New Member

    SWEET GIBBLY-GIBBLETS!!!!

    I DID IT
    I did done lrnd somethin

    Schematic loader
    Code (Text):

    package com.Splosions.ModularBosses.util;
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.util.List;
    import java.util.Map;

    import com.Splosions.ModularBosses.blocks.tileentity.TileEntityControlBlock;
    import com.google.common.primitives.UnsignedBytes;

    import net.minecraft.block.Block;
    import net.minecraft.block.state.IBlockState;
    import net.minecraft.init.Blocks;
    import net.minecraft.nbt.CompressedStreamTools;
    import net.minecraft.nbt.NBTTagCompound;
    import net.minecraft.nbt.NBTTagList;
    import net.minecraft.tileentity.TileEntity;
    import net.minecraft.util.BlockPos;
    import net.minecraft.util.EnumParticleTypes;
    import net.minecraft.world.World;
    import net.minecraftforge.common.util.Constants;

    public class Schematic {
       
       private short width;
       private short height;
       private short length;
       private int size;
       private BlockObject[] blockObjects;

       
       public Schematic(String fileName) {
          try {
             InputStream is = Schematic.class.getResourceAsStream("/assets/mb/schematics/2.schematic");
             NBTTagCompound nbtdata = CompressedStreamTools.readCompressed(is);
         
             is.close();
             //System.out.println("Schematic?");
             width = nbtdata.getShort("Width");
             height = nbtdata.getShort("Height");
             length = nbtdata.getShort("Length");
             size = width * height * length;
             blockObjects = new BlockObject[size];
             
             byte[] blockIDs = nbtdata.getByteArray("Blocks");
             byte[] metadata = nbtdata.getByteArray("Data");



             
             
             int counter = 0;
             for(int Y = 0; Y < height; Y++) {
                for(int Z = 0; Z < length; Z++) {
                   for(int X = 0; X < width; X++) {
                      int blockId = UnsignedBytes.toInt(blockIDs[counter]);
                      BlockPos pos = new BlockPos(X, Y, Z);
                      IBlockState state = Block.getBlockById(blockId).getStateFromMeta(metadata[counter]);
                     
                      String message = null;
                     
                      NBTTagList tagList = nbtdata.getTagList("TileEntities", Constants.NBT.TAG_COMPOUND);
                     
                      for(int i = 0; i < tagList.tagCount(); i++) {
                     NBTTagCompound tag = tagList.getCompoundTagAt(i);
                     String gotMessage = tag.getString("message");
                     int x = tag.getInteger("x");
                     int y = tag.getInteger("y");
                     int z = tag.getInteger("z");
                     
                     if (x == X && y == Y && z == Z){
                         message = gotMessage;
                     }

                     
                    }
                     
                      blockObjects[counter] = new BlockObject(pos, state, message);
                      counter++;
                   }
                }
             }
             
          } catch(Exception e) {
             e.printStackTrace();
          }
       }
       
       
       
       public void generate(World world, double x, double y, double z) {
          for(BlockObject obj : blockObjects) {
             world.setBlockState(obj.getPosWithOffset((int) x, (int) y, (int) z), obj.getState());
             if(obj.getMessage() != null){
                TileEntity te = world.getTileEntity(obj.getPosWithOffset((int) x, (int) y, (int) z));
                if (te instanceof TileEntityControlBlock) {
                    ((TileEntityControlBlock) te).setMessage(obj.getMessage());
                }
             }

          }
       }
       

    }
     

    BlockObject
    Code (Text):

    package com.Splosions.ModularBosses.util;

    import com.Splosions.ModularBosses.blocks.BlockControlBlock;

    import net.minecraft.block.Block;
    import net.minecraft.block.state.IBlockState;
    import net.minecraft.util.BlockPos;

    public class BlockObject {
       
       private BlockPos pos;
       private IBlockState state;
       private String message;
       
       public BlockObject(BlockPos pos, IBlockState state, String message) {
          this.pos = pos;
          this.state = state;
          this.message = message;
        }

       public BlockPos getPos() {
          return pos;
       }

       public IBlockState getState() {
          return state;
       }
       
       public String getMessage() {
             return message;
          }
       
       public BlockPos getPosWithOffset(int x, int y, int z) {
          return new BlockPos(x + pos.getX(), y + pos.getY(), z + pos.getZ());
       }

    }
     
  10. gummby8

    gummby8 New Member

    So I have noticed that the ID's seem to change from world to world, resulting in air blocks being placed where the mod blocks should be.

    To resolve this I added
    Code (Text):

                      if (message != null){
                         state = Block.getBlockFromName("mb:control_block").getStateFromMeta(metadata[counter]);
                      }
     
    Is there a better way around this to better incorporate other mod blocks, or will I have to to specific work arounds for every non-vanilla block?
  11. wizjany

    wizjany Administrator Developer

    not yet unfortunately. different users of the schematic format have attacked this problem in different ways and worldedit is currently just stuck without it. you can check out this PR https://github.com/sk89q/WorldEdit/pull/325 but it's stale right now.