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



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Hi all,  I have the following issue: I'd like to parse some json using gson and rely on the (somewhat new) java record types. This is supported in gson 2.10+. Gson is already a dependency used by minecraft, however it's 2.8.x for 1.19.2 which I'm targeting at the moment. My idea was to include the newer version of the library in my mod and then shadow it so it gets used inside the scope of my mod instead of the older 2.8. This works fine for building the jar: If I decompile my mod.jar, I can see that it's correctly using the shadowed classes. However obviously when using the runClient intellj config, the shadowing doesn't get invoked. Is there any way of invoking shadow when using runClient, or am I on the wrong track and there's a better way of doing this entirely? Thanks in advance!
    • Yep I did upgrade just because it showed me a new version available.  I'll redownload the mod list and make sure anything works.  Thanks!
    • The latest log was taken down by pastebin for some reason. Did you try removing the mods you added? The mods you updated, was there a specific reason you updated, or just because? It's possible the updates introduced incompatibilitie, or even need a newer build of forge. If you didn't need the updates for a specific reason, you could also try downgrading those mods.
    • Please read the FAQ, and post logs as described there. https://forums.minecraftforge.net/topic/125488-rules-and-frequently-asked-questions-faq/
    • I am using forge 1.20.1 (version 47.3.0). My pc has an RTX 4080 super and an i9 14900 KF, I am on the latest Nvidia graphics driver, latest windows 10 software, I have java 23, forge 1.12.2 works and so does all vanilla versions but for some reason no version of forge 1.20.1 works and instead the game just crashes with the error code "-1." I have no mods in my mods fodler, I have deleted my options.txt and forge.cfg files in case my settings were causing a crash and have tried removing my forge version from the installations folder and reinstalling but no matter what I still crash with the same code and my log doesn't tell me anything: 18:34:53.924 game 2025-02-06 18:34:53,914 main WARN Advanced terminal features are not available in this environment 18:34:54.023 game [18:34:54] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher running: args [--username, mrmirchi, --version, 1.20.1-forge-47.3.0, --gameDir, C:\Users\aryam\AppData\Roaming\.minecraft, --assetsDir, C:\Users\aryam\AppData\Roaming\.minecraft\assets, --assetIndex, 5, --uuid, 2db00ea8d678420a8956109a85d90e9d, --accessToken, ????????, --clientId, ZWI3NThkNzMtNmNlZS00MGI5LTgyZTgtYmZkNzcwMTM5MGMx, --xuid, 2535436222989555, --userType, msa, --versionType, release, --quickPlayPath, C:\Users\aryam\AppData\Roaming\.minecraft\quickPlay\java\1738838092785.json, --launchTarget, forgeclient, --fml.forgeVersion, 47.3.0, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412] 18:34:54.027 game [18:34:54] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.8 by Microsoft; OS Windows 10 arch amd64 version 10.0 18:34:54.132 game [18:34:54] [main/INFO] [ne.mi.fm.lo.ImmediateWindowHandler/]: Loading ImmediateWindowProvider fmlearlywindow 18:34:54.191 game [18:34:54] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.6 18:34:54.303 game [18:34:54] [main/INFO] [EARLYDISPLAY/]: Requested GL version 4.6 got version 4.6 18:34:54.367 monitor Process Monitor Process crashed with exit code -1     screenshot of log: https://drive.google.com/file/d/1WdkH88H865XErvmIqAKjlg7yrmj8EYy7/view?usp=sharing
  • Topics

×
×
  • Create New...

Important Information

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