Finish custom block tutorial

This commit is contained in:
ZXMushroom63 2024-12-08 18:01:34 +08:00
parent 3d3de34e26
commit cd10aa65ce
5 changed files with 294 additions and 24 deletions

View File

@ -27,16 +27,14 @@ Store this at the beginning of the function using a constant. Also use that cons
```
Let's start work on the part of registering the custom block that occurs on both the server and the client: making a class and registering it as both a `Block` and a `BlockItem`, and adding it to the creative inventory.
```javascript
function GlobalBlockRegistrationCode() {
function BlockRegistrationFunction() {
//future code here
}
ModAPI.dedicatedServer.appendCode(GlobalBlockRegistrationCode);
GlobalBlockRegistrationCode();
```
In the `GlobalBlockRegistrationCode()` function, add this nested function, which we'll use later to fixup the blockmap after we register new blocks.
In the `BlockRegistrationFunction()` function, add this nested function, which we'll use later to fixup the blockmap after we register new blocks.
```javascript
function GlobalBlockRegistrationCode() {
function BlockRegistrationFunction() {
function fixupBlockIds() { //function to correct ids for block states after registering a new item
var blockRegistry = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.blockRegistry).getCorrective(); //get the blockregistry, corrected for weird property suffixes
var BLOCK_STATE_IDS = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.BLOCK_STATE_IDS).getCorrective(); //get the BLOCK_STATE_IDS variable, also corrected for weird teavm suffixes
@ -57,25 +55,18 @@ function GlobalBlockRegistrationCode() {
To make our own block, we'll need the following data:
- The `Item` class
- The `Block` class
- The `IProperty` class
- The `BlockState` constructor
- The `super()` function for the `Block` class;
- A `CreativeBlock` tab. I'll use `tabBlock`
Since I'll be doing the equivalent of `@Override`ing the `blockBreak` method in java (to make the block explode when broken), i'll need to get the `blockBreak` method. (java equivalent of `super.blockBreak()` in the overrided method.)
```javascript
function GlobalBlockRegistrationCode() {
function BlockRegistrationFunction() {
function fixupBlockIds() {
//...
}
var ItemClass = ModAPI.reflect.getClassById("net.minecraft.item.Item");
var BlockClass = ModAPI.reflect.getClassById("net.minecraft.block.Block");
var IPropertyClass = ModAPI.reflect.getClassById("net.minecraft.block.properties.IProperty");
var blockStateConstructor = ModAPI.reflect.getClassById("net.minecraft.block.state.BlockState")
.constructors
.find(x => x.length === 2);
var blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2); //Get the super function for the block
@ -85,6 +76,156 @@ function GlobalBlockRegistrationCode() {
var breakBlockMethod = blockClass.methods.breakBlock.method; //Get the break block method
//new code will go here
}
```
```
Next, we're going to define a regular javascript class using the [constructor function](https://www.w3schools.com/js/js_object_constructors.asp) syntax. Inside the function, we'll call the `blockSuper` function retrieved from `ModAPI.reflect.getSuper()`. We'll also need to manually set the default block state, and put our block into the correct creative tab. We also need to call a ModAPI function called `ModAPI.reflect.prototypeStack`, which emulates extending classes in Java/TeaVM.\
\
For this custom block, we're also going to override the `breakBlock` method for our custom block's class, and make it spawn an explosion when broken.
```javascript
function BlockRegistrationFunction() {
// ...
function MyCustomBlock() { //Define constructor function for our custom block
blockSuper(this, ModAPI.materials.rock.getRef()); //Call block super function with the current MyCustomBlock instance, and the material we want to use.
this.$defaultBlockState = this.$blockState.$getBaseState(); //Set the default block state
this.$setCreativeTab(creativeBlockTab); //Set the creative tab to the creativeBlockTab variable from earlier.
}
ModAPI.reflect.prototypeStack(BlockClass, MyCustomBlock); //The equivalent of `MyCustomBlock extends Block` in Java.
//Override the breakBlock function in the custom block's prototype
//We are using a $ prefix because the method needs to be useable by TeaVM without ModAPI's intervention. The process is fairly standard, just put a $ before the actual method's name.
//As for the $ in the arguments, I use that to represent a raw TeaVM object.
MyCustomBlock.prototype.$breakBlock = function ($world, $blockpos, $blockstate) {
var world = ModAPI.util.wrap($world); //Wrap the $world argument to make it easy to use without stupid $ prefixes
var blockpos = ModAPI.util.wrap($blockpos); //Wrap the $blockpos argument to make it easy to use without stupid $ prefixes
world.newExplosion(
null, //Exploding entity. This would usually be a primed TNT or a creeper, but null is used when those aren't applicable.
blockpos.getX() + 0.5, //The X position of the explosion.
blockpos.getY() + 0.5, //The Y position of the explosion.
blockpos.getZ() + 0.5, //The Z position of the explosion.
9, //The explosion strength. For reference, 3=creeper, 6=charged creeper, 5=bed in nether/end
1, //Should the ground be set on fire after the explosion. 1=yes, 0=no
0 //Should the explosion have smoke particles. 1=yes, 0=no
); //Call the newExplosion method on the world.
return breakBlockMethod(this, $world, $blockpos, $blockstate); //Call the original breakBlock method. ( Equivalent of `super.breakBlock(world, blockpos, blockstate);` )
}
// We'll add an internal registration function here
}
```
That's the block class done! Let's start writing the internal registration function, which will contain the code that actually registers our block with IDs and other important things. This is going to be a nested function, so we'll be defining it inside of `BlockRegistrationFunction`.
This function will do the following steps:
- Make an instance of our block.
- Register it as a Block using `ModAPI.keygen.block()` to get the block ID.
- Register it as a BlockItem.
- Use the `fixupBlockIds` function from earlier to clean up the block registry.
- Define it on the `ModAPI.blocks` global, so other mods can interact with the custom block.
- Return the block's instance, for future use.
After the function, we'll check if `ModAPI.materials` has been initialised. If it hasn't, we are on the server and we have to wait for the bootstrap event. Otherwise, we'll go ahead and register our block.
```javascript
function BlockRegistrationFunction() {
// ...
function MyCustomBlock() {
//...
}
ModAPI.reflect.prototypeStack(BlockClass, MyCustomBlock);
MyCustomBlock.prototype.$breakBlock = function ($world, $blockpos, $blockstate) {
//...
}
function internalRegistration() {
//Make an instance of the custom block
var custom_block = (new MyCustomBlock())
.$setHardness(3.0) //Set the block hardness. -1 is unbreakable, 0 is instant, 0.5 is dirt, 1.5 is stone.
.$setStepSound(BlockClass.staticVariables.soundTypePiston) //Set the step sound. For some reason, the stone sounds are named `soundTypePiston`
.$setUnlocalizedName(
ModAPI.util.str("custom_block") //Set the translation ID. ModAPI.util.str() is used to convert it into a Java string
);
BlockClass.staticMethods.registerBlock0.method( //Use the registerBlock0 method to register the block.
ModAPI.keygen.block("custom_block"), //Get a working numeric ID from a text block ID
ModAPI.util.str("custom_block"), //The text block id
custom_block //The custom block instance
);
ItemClass.staticMethods.registerItemBlock0.method(custom_block); //Register the block as a valid item in the inventory.
fixupBlockIds(); //Call the fix up block IDs function to clean up the block state registry.
ModAPI.blocks["custom_block"] = custom_block; //Define it onto the ModAPI.blocks global.
return custom_block; //Return the function
}
if (ModAPI.materials) { // Check if ModAPI.materials has been initialised. If it isn't, we are on the server which loads after mods.
return internalRegistration(); //We are on the client. Register our block and return the block's instance for texturing and model registration.
} else {
ModAPI.addEventListener("bootstrap", internalRegistration); //We are on the server. Attatch the internalRegistration function to the bootstrap event
}
}
```
Let's finish off. We'll append the `BlockRegistrationFunction` to the server, and call it on the client. Then, we'll add a [library listener](../apidoc/events.md#using-library-load-events) to wait until the [AsyncSink](../../examplemods/AsyncSink.js) library loads.
When it's loaded, we'll:
- Add a AsyncSink reload listener that registers our block to `RenderItem`.
- Set the translated name
- Make an in-memory resource pack for the block model and textures
```javascript
(function CustomBlock() {
const texture = "...";
//...
function BlockRegistrationFunction() {
//...
}
ModAPI.dedicatedServer.appendCode(BlockRegistrationFunction); //Run the code on the server
var custom_block = BlockRegistrationFunction(); //Get the registered block instance
ModAPI.addEventListener("lib:asyncsink", async () => { //Add an asyncronous listener to AsyncSink loading.
ModAPI.addEventListener("custom:asyncsink_reloaded", ()=>{
ModAPI.mc.renderItem.registerBlock(custom_block, ModAPI.util.str("custom_block"));
});
AsyncSink.L10N.set("tile.custom_block.name", "My Custom Block"); //Set the name of the block
//Required boilerplate for block and item models.
AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/block/custom_block.json", JSON.stringify(
{
"parent": "block/cube_all",
"textures": {
"all": "blocks/custom_block"
}
}
));
AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/item/custom_block.json", JSON.stringify(
{
"parent": "block/custom_block",
"display": {
"thirdperson": {
"rotation": [10, -45, 170],
"translation": [0, 1.5, -2.75],
"scale": [0.375, 0.375, 0.375]
}
}
}
));
AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/blockstates/custom_block.json", JSON.stringify(
{
"variants": {
"normal": [
{ "model": "custom_block" },
]
}
}
));
//Finally, fetch the texture and store it.
AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/blocks/custom_block.png", await (await fetch(
texture
)).arrayBuffer());
});
})();
```
And with that, we've finished the arduos process of registering a block with ModAPI! Load the complete mod into an EaglerForgeInjector build, along with [AsyncSink](../../examplemods/AsyncSink.js).
[Find the completed code here](../../examplemods/Tutorial_Custom_Block.js)

View File

@ -33,6 +33,6 @@ Prerequisites:
- Your EaglerForgeInjector processed.html opened in an editor (optional)
Tutorials:
- [Custom Blocks](comingsoon)
- [Custom Blocks](custom_block.md)
- [Custom Items](comingsoon)
- [Timescale Command](comingsoon)

File diff suppressed because one or more lines are too long

View File

@ -23,7 +23,7 @@ function makeSteveBlock() {
var makeBlockState = ModAPI.reflect.getClassById("net.minecraft.block.state.BlockState").constructors.find(x => x.length === 2);
var blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2);
var creativeBlockTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.tabBlock;
var nmb_BlockSteve = function nmb_BlockSteve() {
function nmb_BlockSteve() {
blockSuper(this, ModAPI.materials.rock.getRef());
this.$defaultBlockState = this.$blockState.$getBaseState();
this.$setCreativeTab(creativeBlockTab);

View File

@ -29,18 +29,12 @@
var blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2); //Get super function from the block class with a target length of two. ($this (mandatory), material (optional))
var creativeBlockTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.tabBlock;
var breakBlockMethod = blockClass.methods.breakBlock.method;
var nmb_BlockUnlucky = function nmb_BlockUnlucky() {
function nmb_BlockUnlucky() {
blockSuper(this, ModAPI.materials.rock.getRef()); //Use super function to get block properties on this class.
this.$defaultBlockState = this.$blockState.$getBaseState();
this.$setCreativeTab(creativeBlockTab);
}
ModAPI.reflect.prototypeStack(blockClass, nmb_BlockUnlucky);
nmb_BlockUnlucky.prototype.$isOpaqueCube = function () {
return 1;
}
nmb_BlockUnlucky.prototype.$createBlockState = function () {
return makeBlockState(this, ModAPI.array.object(iproperty, 0));
}
nmb_BlockUnlucky.prototype.$breakBlock = function ($world, $blockpos, $blockstate) {
var world = ModAPI.util.wrap($world);
var blockpos = ModAPI.util.wrap($blockpos);