Saturday, 21 November 2015

Redstone


There are a few important concepts to keep in mind with Redstone.

In general, there are two types of redstone blocks:
  • power providers, such as redstone blocks (BlockCompressedPowered), buttons, levers, daylight sensors
  • power consumers, such as redstone lamp (BlockRedstoneLight), BlockDoor, BlockNote
Some blocks are both, such as redstone torch and redstone repeaters.  

Power providers

Power providers can provide power to other blocks in two ways:
  1. “Weak power” – every block adjacent to the power provider is powered.  For example – in the picture below, when the lever is on, all adjacent lamps (south, north, west, east, up, down) will turn on.
  2. “Strong power” – when the power provider is attached to a suitable block, it causes that block to provide weak power to all of its neighbours.  For example – the button provides strong power to the stone block, and the stone block provides weak power to the adjacent lamps.

Weak power - when activated, the lever provides weak power to the adjacent lamps (in this case north, south, east, west)


Strong power - when activated, the button provides strong power to the stone it's attached to.  The stone then provides weak power to the adjacent lamps.

The amount of power provided is from 0 (no power) to 15 (max power), using the following Block methods:
isProvidingWeakPower(IBlockAccess worldIn, BlockPos pos, IBlockState state, EnumFacing side)
in [1.8.9] this is now:
getWeakPower(IBlockAccess worldIn, BlockPos pos, IBlockState state, EnumFacing side)
For the example in the picture below:
BlockCompressedPowered.isProvidingWeakPower(
  world, 
  redstone blockpos, 
  minecraft:redstone_block, 
  EAST)
When this method is called for the redstone, it returns the weak power level (i.e. 15) it is providing to the adjacent lamp.
EAST means that the redstone is to the EAST of the lamp.
pos and state are the position and state of the redstone, not the adjacent lamp.

Redstone block provides weak power to the lamp

isProvidingStrongPower(IBlockAccess worldIn, BlockPos pos, IBlockState state, EnumFacing side)

in [1.8.9] this is now: 

getStrongPower(IBlockAccess worldIn, BlockPos pos, IBlockState state, EnumFacing side)

For the example in the picture below:
BlockButton.isProvidingStrongPower(
  world, 
  button blockpos, 
  minecraft:wooden_button[facing=east,powered=true], 
  EAST)
When this method is called for the button, it returns the strong power level (i.e. 15) it is providing to the adjacent stone block.
EAST means that the button is to the EAST of the stone
pos and state are the position and state of the button, not the adjacent stone.

Button provides strong power to the stone.  This will cause the stone to provide weak power to all of the stone's neighbours.

canProvidePower()
– return true!

Optional methods that can help you tweak your block’s behaviour

canConnectRedstone(side)
- redstone wire will connect to the given side

shouldCheckWeakPower(IBlockAccess world, BlockPos pos, EnumFacing side)
This method is called when Forge wants to know the power level of this block  Actually it is named backwards!  A block which can be strongly powered (eg by an attached lever or button) should return true here.  This lets you override the vanilla assumption that a block can be strongly powered if (and only if) it isNormalCube().

  • If you return true: Forge checks whether this block is being strongly powered by any of its neighbours.  If not, the power level is zero.
  • If you return false: Forge instead checks whether this block itself is providing weak power.

Power consumers

A power consumer is a block which reacts to nearby power providers.   The propagation of power is by “pull”, not push – i.e. The consumer is responsible for requesting information from its neighbouring providers, not the other way around.  It usually does this in one of two ways-

  1. For non-direction blocks such as Redstone Lamp-
    a. World.isBlockPowered()  for an on/off answer; or
    b. World.isBlockIndirectlyGettingPowered() if they need to know the strength [0 – 15]
  2. For directional blocks such as Redstone Torch or redstone repeater-
    a. World.isSidePowered(facing) for an on/off answer; or
    b. World.getRedstonePower(facing) if they need to know the strength [0-15]


The typical sequence is as follows, with example picture below.

  1. An event occurs which means the redstone power needs to be recalculated.  In many cases, this is caused by a change in the power provider – for example the user pushes the BlockLever -> onBlockActivated(), or the user smashes the BlockLever -> breakBlock() or similar.  The block method will typically then call world.notifyNeighborsOfStateChange().
  2. All neighbouring consumers (such as the Redstone Lamp) will then call world.isBlockPowered(BlockPos) with their own blockpos to discover whether they are being powered, either weakly or strongly.  This check usually happens in onBlockAdded, onNeighbourBlockChange, and updateTick
  3. The call to World.isBlockPowered for BlockRedstoneLight will then check its neighbours (in this case- the unattached lever, and the stone):
    a. Check if the neighbour is providing weak power (the unattached lever).
    b. Check if the neighbour can be strongly powered (yes - the stone), and if so, check if the stone has any adjacent blocks which are strongly powering it (the button attached to the stone).
  4. The BlockRedstoneLight will then change its state/ perform some action as appropriate depending on whether it is being powered or not.

The glowstone can be powered by weak power from the lever, or alternatively the strong power from the button can make the stone emit weak power to the glowstone.

Comparators

Comparators function slightly differently from other redstone.  The input to a comparator might be from a block that is two positions away with an isNormalCube() in between – for example the comparator senses the level of water in the cauldron, with a stone block inbetween.

The comparator senses the cauldron water level from two blocks away.

To create your own comparator (BlockRedstoneComparator is the only vanilla example), you should implement
getWeakChanges()
 - return true.  This ensures that your comparator will be informed of a change two blocks away, instead of only for adjacent blocks.


Blocks which can be used as the input for a redstone comparator (eg BlockCauldron) should implement the following methods·

hasComparatorInputOverride() 
return true

getComparatorInputOverride(World worldIn, BlockPos pos)
return the value [0..15] to be used by the comparator


Some notes about server/client and IBlockAccess


  • You may have noticed that the most important redstone methods (eg isProvidingWeakPower() and isProvidingStrongPower()) give you an IBlockAccess instead of a World.  This poses a few restrictions on the methods you can run in there.  Most importantly, it generally means that you must rely on stored power level information rather than trying to calculate it from adjacent block power levels.  Vanilla usually stores the power level in the metadata, for this reason.  The major exception is the comparator, which uses a TileEntity to store the power level (and retrieves this information during the call to isProvidingWeakPower()).  I think this is probably done for a good reason, eg to stop infinite loops from two adjacent blocks calling each other recursively, or perhaps to make sure that the redstone propagates the same way regardless of which order the neighbour blocks update in.
  • Most of the methods relevant to redstone run on the server only - for example updateTick(), onNeighbourBlockChange() - calculation of signal strengths and propagation of power is done on the server only.
  • If your client side needs to know about redstone signal strength for rendering your block correctly, you need to use some way of communicating it to the client.  Vanilla usually does this by storing the power level as metadata (BlockRedstoneWire), or sometimes by changing the block (eg BlockRedstoneLight).  These are automatically synchronised from server to client. 
  • You might think that you could change the block's appearance during render using Block.getActualState() or ISmartBlockModel using getExtendedState() to query the redstone power based on the block's neighbours. Unfortunately this doesn't work.  The reason is that these methods are provided with an IBlockAccess instead of a World; block rendering is multithreaded and is performed on a cached copy of the world, which only stores some of the information.  This cached copy is accessed through IBlockAccess and gives no direct access to weak power methods.  It could be done by storing the redstone power in the block metadata, which would leave no room for any other information (for example- which way the block is facing).  
  • As an alternative, you can attach a TileEntity to the block - the TileEntity is rendered every frame in the client thread - it has a reference to World which you can use to access all the redstone power methods also on the client side.  Although you could use this trick to access World during a call to Block.getActualState() or getExtendedState(), I wouldn't recommend it, because this might lead to the rendering thread accessing the same objects as the client thread without any synchronisation, which is usually a recipe for disaster (i.e. crashes and other weird glitches that are very hard to debug).

Further information

For some more useful background information about redstone devices, see here.  (NB the terminology he uses for "weak" and "strong", "powered", "indirect" doesn't really match the Forge conventions).

Sample Code
Some working examples to illustrate these concepts.







5 comments: