Monday 22 December 2014

Blocks [1.8]


Key concepts relating to blocks:

  • The minecraft world is composed of a three-dimensional grid of blocks.  The world grid is only 256 blocks high but extends indefinitely along the two horizontal axes.
  • The blocks are grouped into 'Chunks', which comprise a cuboid of 16x256x16 blocks.
  • Chunks are then grouped into regions; each region is comprised of 32 x 32 chunks.  Regions are stored on disk, one per file.
  • The point of all this grouping is firstly to reduce storage space (Sections which are completely empty are not saved to disk) and secondly to allow chunks to be created and loaded into memory individually as necessary, without having to store areas where the player hasn't explored.
  • The coordinate system used in minecraft is shown below:


See the EnumFacing class for a number of constants and utility functions related to the six directions (eg getOpposite(), rotateX(), getFrontOffsetX(), etc). 


At a minimum, each grid location [x,y,z] contains the following information:
  1. An IBlockState, which specifies the type of block at that location (eg a BlockDoor) as well as its current state (eg OPEN or CLOSED).
  2. An integer "SkyLight" value from 0 to 15, which specifies the amount of light this block is receiving from the sky.
  3. An integer "BlockLight" value from 0 to 15, which specifies the amount of light this block is receiving from nearby blocks.
These data are not stored in the Block class.   They are stored in an array inside ExtendedBlockStorage objects, inside Chunk objects.  They are accessed through various World methods such as
IBlockState blockAtXYZ = world.getBlockState( {x,y,z});
int lightFromSky = world.getLightFor(SKY, {x,y,z});
int lightFromBlocks = world.getLightFor(BLOCK, {x,y,z});

The most important aspect for the modder to understand is the IBlockState.  An IBlockState is comprised of two components, the Block and its Properties (its state)
  1. The Block class and derived classes do not store unique information about every location in the world, they only store information related to the Block type.  The Block classes hence consist mostly of methods to determine the behaviour of the block and how it is drawn on the screen. These are generally unique but not always (for example: different types of wood share the same BlockOldLog instance).  The vanilla Block instances can be found in the Blocks class; for example Blocks.dirt, Blocks.sand, Blocks.water.
  2. Each Block may also have one or more Properties which describe the different states that the block can be in. For example- BlockLadder has a property called FACING which has four allowable values (NORTH, SOUTH, EAST, or WEST) depending on which direction it is facing.  BlockOldLog has a property called VARIANT with allowable values OAK, SPRUCE, BIRCH, JUNGLE.  Some Blocks have multiple properties, such as BlockBed which has
  •   FACING, with possible values NORTH, SOUTH, EAST, WEST;
  •   PART, with possible values HEAD or FOOT to define which part of the bed this is;
  •   OCCUPIED, possible values TRUE or FALSE;
The combination of a Block and its current state is represented by an IBlockState.  For example, one possible state of BlockBed is
block_bed [facing=east, part=head, occupied=false];
The state can be manipulated using
1.    IBlockState.getValue(property) to get the value of a particular property, eg 
    bedFacingDirection = currentBedState.getValue(FACING); to return EAST.
2.   IBlockState.withProperty(property, new value) to return a new state with one of the properties changed to the new target value, eg 
    IBlockState newBedState = oldBedState.withProperty(FACING, NORTH);

If you want to set the state to a particular value, you need to start from the base(default) state, for example
IBlockState bedFacingNorth = Blocks.bed.getDefaultState().withProperty(FACING, NORTH);
It's a bit verbose but that's how vanilla does it.

A number of other data structures can be used to store more information about a particular grid location:
  • TileEntity, which stores custom information such as the text on signs, the list of items in a chest, etc.  Most grid locations do not have a corresponding TileEntity; each chunk contains a list of TileEntities, with an entry for each TileEntity and its [x,y,z] location within the chunk.  The TileEntity at a particular location is accessed using
    TileEntity tileEntityAtXYZ = world.getTileEntity({x,y,z});
  • Entity, used for some special "blocks" such falling blocks (sand, gravel), paintings, item frames.

Further details

The main IProperties used by vanilla are
  • PropertyEnum such as VARIANT (OAK, SPRUCE, BIRCH, JUNGLE) for BlockOldLogs
  • PropertyDirection such as FACING (N, S, E, W) for BlockDoor
  • PropertyInteger such as AGE (0 – 7) for BlockCrops (eg wheat growth stage)
  • PropertyBool – such as OCCUPIED (true/false) for BlockBed.
The vanilla code contains helpers to create your own properties with custom names and values; see the vanilla blocks mentioned above for inspiration.  

For each given Block, a BlockState instance is used to define the valid states that the corresponding Block can have.  (The name is misleading because BlockState does not implement IBlockState!).  The BlockState can be accessed using Block.getBlockState().  For example, the BlockState for BlockBed contains three properties
-        FACING, with possible values NORTH, SOUTH, EAST, WEST.
-        PART, with possible values HEAD or FOOT.
-        OCCUPIED, possible values TRUE or FALSE.
The BlockState for your custom Block is created during Block construction by overriding Block.createBlockState().  You need to be careful to set the default state in your block's constructor - see BlockBed for an example.

As for versions 1.7 and earlier, minecraft only uses a total of 16 bits for IBlockState - 12 bits for a blockID plus 4 bits for the state information (previously called metadata)).  In general, this means that a block must have no more than 16 unique states.  Although some vanilla blocks have more than 16 combinations of properties (for example BlockDoor has 64), at most 16 of these are actually stored per grid location.  In the example of BlockBed, although there are 16 possible combinations of values, only 12 are used– see the table below.

PART
HEAD
FOOT

OCCUPIED
TRUE
FALSE
TRUE
FALSE
FACING
NORTH
14
10
not used
2
SOUTH
12
8
not used
0
EAST
15
11
not used
3
WEST
13
9
not used
1

Block.getMetaFromState() and Block.getStateFromMeta() are used to interconvert between the IBlockState value and the corresponding integer metadata value (0-15 inclusive).  When implementing Blocks with Properties, you must be careful to only return metadata in the range 0 – 15, otherwise you will overwrite the mappings for other blocks.  Likewise, you must make sure that your getMetaFromState() and getStateFromMeta() match each other.

Some multiblock structures, such as BlockDoor or BlockBed, use a third method called Block.getActualState() which checks the IBlockState of the other blocks in the multiblock - this allows the multiblock to
  • synchronise the state of all blocks in the multiblock (eg BlockBed - OCCUPIED); and/or
  • store more than 4 bits' worth information- for example BlockDoor:
    - the upper block of the door stores the HINGE position (LEFT/RIGHT), and whether the door is POWERED or not.
    - the lower block of the door stores the FACING (NORTH, SOUTH, EAST, WEST) and whether the door is OPEN or not.
The same method Block.getActualState() is also used by blocks which change appearance depending on their neighbours - for example BlockFence, BlockRedstoneWire.
Block.getActualState() is generally called just before a render, to make sure the block has the correct appearance.

During the game initialisation phases, a unique instance is created for each block, see Block.registerBlocks(), and the Blocks class.  This means that you can test for equality using the == operator instead of having to use .equals(), for example
boolean thisBlockIsDirt = (blockAtCursor == Blocks.dirt);
Likewise, there is a unique instance for each IBlockState, allowing for example
boolean stateHasChanged = (newIBlockState != currentIBlockState);
instead of
boolean stateHasChanged = !newIBlockState.equals(currentIBlockState);





13 comments:

  1. Hi Grey Ghost,

    It's great to see you back and posting again! It's fantastic that you are bringing us your great information on 1.8.

    Thank you for all your previous posts. Of any person out there, you have been the most helpful in Minecraft modding for me.

    Cheers!

    ReplyDelete
  2. Keen thanks :)
    There's a lot to learn in 1.8, it's pretty interesting. I'm working on block rendering at the moment, more in a few days....

    ReplyDelete
    Replies
    1. I'm learning the core code myself too, with the aim to contribute to M3L (Magic Mojo Mod Loader) - maybe you can check it out sometime, it might interest you ;) I'm only new to Minecraft but I'm no stranger to Java heh.

      Merry xmas and all the best to you this season, lets hope it's a cool one this year (I'm also Aussie). Looking forward to your future posts!

      Delete
  3. getActualState() - exactly what I am looking for. Great post, thank you!

    ReplyDelete
  4. Please explain the redstone for FML 1.8

    ReplyDelete
  5. Hi. I have plans for a redstone tutorial once I have finished updating a mod from 1.7 to 1.8. I think that will take around a month at the rate I'm going :(.... -TGG

    ReplyDelete
  6. Thanks for the excellent, in-depth information.

    ReplyDelete
  7. Hey TGG, I have a clarity question to ask. You said "As for versions 1.7 and earlier, minecraft only uses a total of 16 bits for IBlockState - 12 bits for a blockID plus 4 bits for the state information (previously called metadata))". Does this mean that in 1.8, you can use more than 4 bits of metadata on a block? Such as you can have more than 4 blockstate properties? I understand that before we only had 0-15 to do extra stuff (excluding using tile entities), but now we may have then that? What is the theoretical property limit we can use?

    ReplyDelete
  8. Hi
    You can still only store 0 - 15 in metadata. You can do blockstates with more than that, if you are tricky. For example, the door does this, by storing some info in the bottom door block metadata, and some more in the upper door block metadata. You can also calculate some of the properties instead of storing them (getActualState()) - for example, BlockWall changes its blockstates by checking whether there are other walls to the north, south, east, west, and up - which is five properties instead of four, on top of the VARIANT property it stores in metadata)

    But in general - 0 - 15 only. If you need to store more than that, you need to use a TileEntity.

    -TGG

    ReplyDelete
    Replies
    1. I think I may have worded my question badly (or I might have some terminology mixed up). I'm going to use the Anvil to try to help out. The Anvil has two properties (DAMAGE, and FACING) and those two properties have multiple values, such as DAMAGE either having a value of 0, 1, or 2. I want to know how many values the property can have (can DAMAGE hold values of 4, 5, 6, 7, etc) and how many properties a block can have (say the anvil had a POWERED property, with two values of either true or false)?

      I am confused if you meant that there can only be 0 - 15 values per property (DAMAGE can only have 0 - 15 number of values), or if you mean there can only be 0 - 15 properties per block (the block can only have 0 - 15 properties such as DAMAGE, FACING, POWERED, ROTATION, etc). Sorry for being tedious, but I hope that this post will help you to understand what I am asking, so that you can help me! :)

      Delete
  9. Ah, ok. For each block, you can store a single number from 0 to 15. But you have the choice of how many properties you cram into that.

    So for example, if you have a FACING (north, south, east, west), that is four values.
    If you also have a property DAMAGE (0, 1, 2) you now have a total of twelve combinations;
    i.e.
    metadata facing damage
    0 north, damage=0
    1 north, damage=1
    2 north, damage=2
    3 south, damage=0
    4 south, damage=1
    5 south, damage=2
    6 east, damage=0
    7 east, damage=1
    8 east, damage=2
    9 west, damage=0
    10 west, damage=1
    11 west, damage=2

    Another example -
    you could have four YES/NO properties, = 2 * 2 * 2 * 2 combinations = 16
    or you could have one single blockproperty with 16 values all by itself.

    -TGG

    ReplyDelete
    Replies
    1. Perfect! Makes so much more sense now! Thanks alot TGG! :D

      Delete