Jump to content

Recommended Posts

Posted

When RedPower 2 Frames first came out in I believe 1.2.5, it was the first thing to ever hit minecraft that could move blocks since pistons. And it did it in such a cool way! But making such functionality is a difficult task, and there's huge mod compatibility problems involved.

 

I've been working on an API for Forge that would make the movement and motion of blocks much easier, and would also allow mod devs to declare how they want their blocks to interact with motion. You can find it on my github. It needs a lot of work, specifically some in the way of rendering the motion rather than just making it happen logically. But it's fairly simple in concept.

 

First, it should be noted that there's a new class called ForgePosition because passing around x,y,z,world for absolutely everything makes method declarations so much worse than they need to be, and in this API, I found myself needing access to the specific coordinates much less than the simple idea of the position. ForgePosition is a wrapper class for x,y,z,world coordinates. It has some convenience methods for positions such as getting the metadata from that position.

 

There are two main parts to this motion API. There's IForgeMotionHandler, and BlockSnapshot. Motion handlers are supposed to be asked about a blocks motion capabilities whenever motion is attempted. There are canMove(position) and canRotate(position, direction) methods which ask if the block at that position can be moved, or rotated to the attempted rotation. Motion handlers are registered per block ID, and it is acceptable to register one handler to multiple IDs, such as one handler for all the stair blocks.

 

To move or rotate a block, your mod will create a snapshot of the block you want to modify. BlockSnapshot is a class that saves the block to a state in memory and is meant to restore that state to a new location later. That time will likely, but not necessarily always, be immediately after the snapshot is taken. To save the TileEntity, it is asked to be written to NBT and that NBT is read from when the snapshot is restored.

 

public class BlockSnapshot {
public int blockID, metadata;
public NBTTagCompound nbt;

/*
 * Alert the TileEntity of being snapshotted, allowing it to prepare
 */
public void willSnapshotPosition(ForgePosition pos) { ... }

/*
 * Copy the block at position @pos into the snapshot
 * Override this to add custom state
 * Be sure to call super.popBlock
 * 
 * Stores blockID and metadata of the block
 * Saves the TileEntity in an NBT tag for easy restoration
 */
public void snapshotPosition(ForgePosition pos) { ... }

/*
 * Put the block back into the world at position @pos
 * Override this to restore custom state
 * Be sure to call super.pushBlock()
 * 
 * Changes the coordinates in NBT for the TileEntity to create a change in position
 */
public void restoreSnapshot(ForgePosition pos) { ... }

/*
 * Rotation occurs after snapshotting a position
 * Therefore, modify the data in the snapshot from this method
 * 
 * When looking at the @dir face of a block, turn 90 degrees clockwise
 */
public void rotate(ForgeDirection dir) {} // no default behavior. Most blocks are the same texture on all sides,
								even if the ones that aren't are the important ones.
}

 

In order to support motion more elegantly, you are encouraged to subclass BlockSnapshot. This allows you to store custom state and alert

the TileEntity in ways other than just willSnapshotPosition(). Your motion handler will allow you to return an instance of your snapshot class instead of the base version of the class.

 

For rotation, you must first create the snapshot, then you do the rotation logic on the snapshotted data. Modifying the tile entity NBT, the metadata, etc. No modifications are done while the block exists in world.

 

Rotation was an interesting problem. I had to decide to either rotate dynamically by defining an axis and telling the block to rotate around that 90 degrees, or I had to figure out a way to store the state of the blocks rotation statically. Doing it statically made for some bigger problems. For example, when rotating a block, a mod with no knowledge of said block would have to know how the block was currently rotated in order to do a relative rotation, meaning there'd need to be a method it could call which translated the block state into very specific rotation data. With something like a wood log, that's impossible. The metadata in the log only accounts for three possible rotations. There are many more possibilities than that.

 

So instead, relativity is the way the rotation method works. The reason the rotate() method has a ForgeDirection argument isn't to define a direction to turn to. That wouldn't allow for stairs to have an upside down rotation because there'd be one NORTH rotation for right side up, and a second NORTH rotation for upside down. Therefore passing NORTH as dir would be ambiguous. Instead, imagine looking at the block from the face that's in the direction of dir, and rotating the block 90 degrees from that perspective. It's dynamic, and much easier to handle when using less metadata to handle rotations. Plus, I anticipate mods wanting to rotate blocks from their current rotations much more than wanting to rotate blocks to an arbitrary rotation that has no meaning to the mod performing the rotation.

 

It's also nice to be notified that a block is about to be snapshotted. The base BlockSnapshot class asks the tile entity at the position (if there is one) to prepare itself for snapshotting if it's an instance of IMovingTileEntity (whose only method right now is willSnapshotPosition()).

 

To make a block support motion, create an IForgeMotionHandler class, and write the methods to describe your blocks' motion. Motion handlers are instantiated and registered to a block ID.

 

public interface IForgeMotionHandler {
/*
 * Can the block currently at position @pos be moved?
 */
public boolean canMove(ForgePosition pos);

/*
 * Can the block currently at position @pos be rotated?
 * Look at the BlockSnapshot.rotate method for details on how @dir is defined
 */
public boolean canRotate(ForgePosition pos, ForgeDirection dir);

/*
 * Create an instance of BlockSnapshot
 * Use this to return instances of subclasses instead of the base class 
 */
public BlockSnapshot createBlockSnapshot(ForgePosition pos);
}

 

With this interface, you are asked three things. First, is the block in some position allowed to move? Next, can that block rotate 90 degrees around this axis? And finally, return an instance of BlockSnapshot that will shortly be used to snapshot the block at that position.

 

Considering how relatively small the implementation of this class can be, it's nice to implement it in your Block subclass.

 

An extremely simple example of this API in action is also on my github. This mod adds two wrenches: the Rotary Wrench, and the Wrenchy Piston. The Rotary Wrench rotates the block around the face you right clicked by 90 degrees. The Wrenchy Piston pushes a block in the opposite direction of the side you right clicked on. If you shift right click, it goes the direction of the side you clicked on. The mod adds motion handlers for stairs, logs, and chests. This is only for rotation though. I couldn't find any blocks in vanilla MC that would benefit from some movement logic. It should also be noted that if a block doesn't have a registered motion handler, the API uses a default one which says the block can be moved and can be rotated. However, it performs no rotation logic. This is because the majority of blocks are the same texture on all sides and need to rotation logic.

 

Also, the example mod performs no animations yet. I'm still trying to figure out the best way to render a block at an offset while still allowing the block to exist in the world at the destination position. I'd rather not have a dead time in between where the world can close and the block can be lost forever.

 

 

 

So what I'm looking for in this post is a few things. First, what do you all think of the idea of this? Second, should it be incorporated into forge itself? If so, why? What benefits can be added by being able to modify base classes? Although I do recognize the benefit of having the API present for ALL mods to use, knowing for sure that it will work on all blocks. Also, some guidance in that rendering problem would be appreciated.

 

And finally, the most important question: How do you think the API should evolve from here? Is there a better way of moving blocks around than snapshotting them, modifying them, and restoring them?

 

Thanks for reading, input is greatly appreciated.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.