0

For example, an addon that contains custom shaped blocks like vertical slabs or stairs. Each variant (e.g. Amethyst Vertical Slabs or Diamond Stairs) is implemented using multiple geometry files (4 for slabs, 6 for stairs), and the same number of block .json files (one for each .geo.json). The same set of underlying geometry files are referenced from all the different groups of block json files for custom blocks of a given shape.

This is a lot of files to edit manually!

I had been copying and pasting them from ones I'd previously finished, in sets of 4 or 6 (so, set up the 4 Amethyst Vertical Slab files, then duplicate them, rename them, and swap out the relevant values to make a set of Obsidian Vertical Slabs). But, to implement a complete set of custom blocks this way, to correspond to the vanilla block materials, it still takes a very long time as this means opening and editing hundreds of files. Plus, each set for a given material also has a crafting recipe file, an entry in blocks.json specifying which sound to use, and an entry in en_US.lang which sets the block's display name.

Has anyone found a way of doing this faster?

1 Answer 1

1

Below is a Python script that I have used to construct a set of custom vertical slabs consisting of hundreds of files, in a matter of minutes. The only manual editing required is specifying which materials you want the slabs made from, and those materials' properties (explosion resistance, sound, etc.), in the USER_INPUT list at the top of the script.

Crafting recipes for each slab variant, as well as the sound references in blocks.json and the display names in en_US.lang, will be automatically generated as well. All that is required is a correctly structured addon pack folder (described in the code comments; the folder template can be downloaded from MediaFire here, along with the .py script).

Here it is:

# Vertical Slab JSON generator - makes adding custom vertical slabs to your Bedrock resource and behavior packs fast and easy:
# Just enter the values for the parameters in USER_INPUT -- everything else is automated!

# Generates BP block .json files and recipe .json files, as well as adding the corresponding entries to RP blocks.json and en_US.lang

# NOTE: this script allows you to choose the name of your pack folder, but assumes that within that folder, your resource pack
# goes in a folder named RP and the behavior pack in one named BP. It also assumes blocks.json, en_US.lang, and the geometry files and loot
# table for the slabs already exist. If you downloaded this script from MediaFire, you should already have a pack template which is structured
# correctly and contains all necessary files for a working addon. Just drag and drop this .py file and the pack folder (which
# is currently named vertical_slab_development; you can change this to whatever you want) to your Desktop before running the script.

# --- IMPORTANT: Before using or uploading your addon pack, you'll still have to open the two manifest.json files and provide
# new UUIDs as described here: https://learn.microsoft.com/en-us/minecraft/creator/documents/resourcepack and here:
# https://learn.microsoft.com/en-us/minecraft/creator/documents/behaviorpack

USER_INPUT = {
    # Used in specifying the directory to save the files this script will generate.
    # Directories will look like this: /Users/USERNAME/Desktop/packFolderName/ etc., where USERNAME and packFolderName
    # get replaced with the values you provide. In the example, I've used "neonninja3" and "vertical_slab_development".
    'USERNAME': "neonninja3",
    'packFolderName': "vertical_slab_development",
    
    # This will be the namespace used for your custom vertical slab blocks. Vanilla blocks have namespace "minecraft", which
    # cannot be used for custom content. In the example, I chose "slabs" for the namespace.
    'namespace': "slabs",
    
    # This is where you specify which materials you want vertical slabs to be made from. The example shows 3 different kinds
    # (2 vanilla blocks and one custom block), but you can specify as many as you want. The Minecraft Wiki (https://minecraft.wiki)
    # is a useful reference for vanilla block parameters (it gives info like explosion resistance, luminosity, and the block's
    # official name in Java and Bedrock). You can find the vanilla block sounds in the blocks.json file from the vanilla resource
    # pack downloadable here: https://github.com/Mojang/bedrock-samples/releases
    # WHEN ADDING NEW BLOCKS, BE SURE TO USE THE SAME SYNTAX AS IN THE EXAMPLE IN ORDER FOR THIS SCRIPT TO WORK AS INTENDED.
    'BLOCKS': [
        {
            'namespace': "minecraft", # This is a vanilla block
            'material': "gold",
            'secondsToDestroy': 1,
            'blastResistance': 6,
            'fullBlock': "gold_block",
            'light_emission': 0,
            'transparent': False,
            'sound': "metal",
            'slabDisplayName': "Gold Vertical Slab"
        },
        {
            'namespace': "minecraft", # This is a vanilla block
            'material': "glass",
            'secondsToDestroy': 0.3,
            'blastResistance': 0.3,
            'fullBlock': "glass",
            'light_emission': 0,
            'transparent': True,
            'sound': "glass",
            'slabDisplayName': "Glass Vertical Slab"
        },
        {
            'namespace': "addtns", # This is a custom block
            'material': "echo",
            'secondsToDestroy': 1,
            'blastResistance': 6,
            'fullBlock': "echo_block",
            'light_emission': 7,
            'transparent': False,
            'sound': "amethyst_block",
            'slabDisplayName': "Echoing Vertical Slab"
        }
    ]
}


# NOTHING BELOW THE DOTTED LINE NEEDS TO BE EDITED.
# ----------------------------------------------------------------------------------------------------------------------------------------

geometries = [
    {'id': "geometry.vertical_slab_1", 'originCoords': [-8, 0, 0], 'sizeCoords': [16, 16, 8]},
    {'id': "geometry.vertical_slab_2", 'originCoords': [0, 0, -8], 'sizeCoords': [8, 16, 16]},
    {'id': "geometry.vertical_slab_3", 'originCoords': [-8, 0, -8], 'sizeCoords': [16, 16, 8]},
    {'id': "geometry.vertical_slab_4", 'originCoords': [-8, 0, -8], 'sizeCoords': [8, 16, 16]}
]

p = []
def generateParameters(b):
    temp = []
    for i in range(1, 5):
        temp.append(
            {
                'blockNamespace': USER_INPUT['BLOCKS'][b]['namespace'],
                'variantName': USER_INPUT['BLOCKS'][b]['material'] + "_vertical_slab_" + str(i),
                'nextVariantName': USER_INPUT['BLOCKS'][b]['material'] + "_vertical_slab_" + (str(i + 1) if (i + 1 < 5) else "1"),
                'defaultVariantName': USER_INPUT['BLOCKS'][b]['material'] + "_vertical_slab_1",
                'fullBlock': USER_INPUT['BLOCKS'][b]['fullBlock'],
                'secondsToDestroy': USER_INPUT['BLOCKS'][b]['secondsToDestroy'],
                'blastResistance': USER_INPUT['BLOCKS'][b]['blastResistance'],
                'luminosity': '"minecraft:light_emission": ' + str(USER_INPUT['BLOCKS'][b]['light_emission']) + ',' if (USER_INPUT['BLOCKS'][b]['light_emission'] > 0) else "",
                
                # If True, the block will use render method "blend" instead of "alpha_test", and will also have "material_instances" property "ambient_occlusion" set to false
                'isTransparent': '"render_method": "blend",\n\t\t\t\t\t"ambient_occlusion": false' if USER_INPUT['BLOCKS'][b]['transparent'] else '"render_method": "alpha_test"',
                
                'geometry': geometries[i - 1]['id'],
                'originCoords': geometries[i - 1]['originCoords'],
                'sizeCoords': geometries[i - 1]['sizeCoords']
            }
        )
    p.append(temp)

for block in range(len(USER_INPUT['BLOCKS'])):
    generateParameters(block)


# BP blocks JSON file for the only vertical slab variant which can appear in the inventory and be dropped upon mining/explosions
# Having only one variant be obtainable in this way avoids clutter from multiple non-stacking versions of the same material vertical slab
def generateDefaultOrientation(b):
    fullPath = '/Users/' + USER_INPUT['USERNAME'] + '/Desktop/' + USER_INPUT['packFolderName'] + '/BP/blocks/' + p[b][0]['variantName'] + '.json'
    file = open(fullPath, 'w')
    contents = '''{
    "format_version": "1.20.30", // This block requires Experimental Features due to using Block Events!
    "minecraft:block": {
        "description": {
            "identifier": "%s:%s",
            "menu_category": {
                "category": "construction",
                "group": "itemGroup.name.slab"
            }
        },
        "components": {
            "minecraft:collision_box": {
                "origin": %s,
                "size": %s
            },
            "minecraft:selection_box": {
                "origin": %s,
                "size": %s
            },
            "minecraft:destructible_by_mining": {
                "seconds_to_destroy": %s
            },
            "minecraft:destructible_by_explosion": {
                "explosion_resistance": %s
            },
            "minecraft:geometry": "%s",
            "minecraft:material_instances": {
                "*": {
                    "texture": "%s",
                    %s
                }
            },
            %s
            // Rotate the targeted slab if the player is holding a Slab Rotator; OR
            // Combine two vertical slabs into a full block if the player is holding another vertical slab of the same material as the targeted slab.
            "minecraft:on_interact": {
                "event": "%s:iterate"
            }
        },
        "events": {
            "%s:iterate": {
                "sequence": [
                    {
                        "condition": "q.is_item_name_any('slot.weapon.mainhand', '%s:slab_rotator')",
                        "set_block": {
                            "block_type": "%s:%s"
                        }
                    },
                    {
                        "condition": "q.is_item_name_any('slot.weapon.mainhand', '%s:%s')",
                        "set_block": {
                            "block_type": "%s:%s"
                        }
                    }
                ]
            }
        }
    }
}''' % (USER_INPUT['namespace'], p[b][0]['variantName'], p[b][0]['originCoords'], p[b][0]['sizeCoords'], p[b][0]['originCoords'], p[b][0]['sizeCoords'],
        p[b][0]['secondsToDestroy'], p[b][0]['blastResistance'], p[b][0]['geometry'], p[b][0]['fullBlock'], p[b][0]['isTransparent'], p[b][0]['luminosity'],
        USER_INPUT['namespace'], USER_INPUT['namespace'], USER_INPUT['namespace'], USER_INPUT['namespace'], p[b][0]['nextVariantName'], USER_INPUT['namespace'],
        p[b][0]['defaultVariantName'], p[b][0]['blockNamespace'], p[b][0]['fullBlock'])
        
    file.write(contents)
    file.close()


# BP blocks JSON files for all other slab variants besides the default one
def generateNondefaultOrientations(b):
    for i in range(1, 4):
        fullPath = '/Users/' + USER_INPUT['USERNAME'] + '/Desktop/' + USER_INPUT['packFolderName'] + '/BP/blocks/' + p[b][i]['variantName'] + '.json'
        file = open(fullPath, 'w')
        contents = '''{
    "format_version": "1.20.30", // This block requires Experimental Features due to using Block Events!
    "minecraft:block": {
        "description": {
            "identifier": "%s:%s",
            "menu_category": {
                "category": "none"
            }
        },
        "components": {
            "minecraft:collision_box": {
                "origin": %s,
                "size": %s
            },
            "minecraft:selection_box": {
                "origin": %s,
                "size": %s
            },
            "minecraft:destructible_by_mining": {
                "seconds_to_destroy": %s
            },
            "minecraft:destructible_by_explosion": {
                "explosion_resistance": %s
            },
            "minecraft:geometry": "%s",
            "minecraft:material_instances": {
                "*": {
                    "texture": "%s",
                    %s
                }
            },
            %s
            // Rotate the targeted slab if the player is holding a Slab Rotator; OR
            // Combine two vertical slabs into a full block if the player is holding another vertical slab of the same material as the targeted slab.
            "minecraft:on_interact": {
                "event": "%s:iterate"
            },
            // Mining a vertical slab in a non-default orientation drops the slab in the default orientation,
            // to avoid cluttering the inventory with multiple non-stacking versions of the vertical slab for a given material.
            "minecraft:on_player_destroyed": {
                "event": "%s:replace_with_default"
            },
            // An empty loot table, since loot tables can only specify item drops, not block drops.
            // Dropping the correct block is handled by block events/commands.
            "minecraft:loot": "loot_tables/blocks/vertical_slab_nondefault.json"
        },
        "events": {
            "%s:iterate": {
                "sequence": [
                    {
                        "condition": "q.is_item_name_any('slot.weapon.mainhand', '%s:slab_rotator')",
                        "set_block": {
                            "block_type": "%s:%s"
                        }
                    },
                    {
                        "condition": "q.is_item_name_any('slot.weapon.mainhand', '%s:%s')",
                        "set_block": {
                            "block_type": "%s:%s"
                        }
                    }
                ]
            },
            "%s:replace_with_default": {
                "sequence": [
                    {
                        "set_block": {
                            "block_type": "%s:%s"
                        }
                    },
                    {
                        "run_command": {
                            "command": "setblock ~ ~ ~ air 0 destroy"
                        }
                    }
                ]
            }
        }
    }
}''' % (USER_INPUT['namespace'], p[b][i]['variantName'], p[b][i]['originCoords'], p[b][i]['sizeCoords'], p[b][i]['originCoords'], p[b][i]['sizeCoords'],
        p[b][i]['secondsToDestroy'], p[b][i]['blastResistance'], p[b][i]['geometry'], p[b][i]['fullBlock'], p[b][i]['isTransparent'], p[b][i]['luminosity'],
        USER_INPUT['namespace'], USER_INPUT['namespace'], USER_INPUT['namespace'], USER_INPUT['namespace'], USER_INPUT['namespace'], p[b][i]['nextVariantName'],
        USER_INPUT['namespace'], p[b][i]['defaultVariantName'], p[b][i]['blockNamespace'], p[b][i]['fullBlock'], USER_INPUT['namespace'], USER_INPUT['namespace'],
        p[b][i]['defaultVariantName'])
        
        file.write(contents)
        file.close()


# Recipe JSON file for the only vertical slab variant which can be crafted (the default orientation)
def generateRecipe(b):
    fullPath = '/Users/' + USER_INPUT['USERNAME'] + '/Desktop/' + USER_INPUT['packFolderName'] + '/BP/recipes/' + p[b][0]['defaultVariantName'][0:len(p[b][0]['defaultVariantName']) - 2] + '.json'
    file = open(fullPath, 'w')
    contents = '''{
    "format_version": "1.20.30",
    "minecraft:recipe_shaped": {
        "description": {
            "identifier": "%s:%s"
        },
    
        "tags": [ "crafting_table" ],
        
        "pattern": [
            "#  ",
            "#  ",
            "#  "
        ],
        "key": {
            "#": {
                "item": "%s:%s",
                "data": 0
            }
        },
        "unlock": [
            {
                "item": "%s:%s",
                "data": 0
            }
        ],
        "result": {
            "item": "%s:%s",
            "count": 6
        }
    }
}''' % (USER_INPUT['namespace'], p[b][0]['defaultVariantName'][0:len(p[b][0]['defaultVariantName']) - 2], p[b][0]['blockNamespace'], p[b][0]['fullBlock'],
        p[b][0]['blockNamespace'], p[b][0]['fullBlock'], USER_INPUT['namespace'], p[b][0]['defaultVariantName'])
        
    file.write(contents)
    file.close()


# Adds the block sound entries to blocks.json in the RP
def addSlabSounds(b):
    fullPath = '/Users/' + USER_INPUT['USERNAME'] + '/Desktop/' + USER_INPUT['packFolderName'] + '/RP/blocks.json'
    file = open(fullPath, 'r')
    stringContent = file.read()
    file.close()
    # Insert the new entries before the final closing bracket of the JSON object.
    # This assumes that the file is formatted/indented correctly!
    stringContent = stringContent[:len(stringContent) - 3]

    newContent = "}"
    for i in range(4):
        entry = ''',
    "%s:%s": {
        "sound": "%s"
    }''' % (USER_INPUT['namespace'], p[b][i]['variantName'], USER_INPUT['BLOCKS'][b]['sound'])
        
        newContent += entry

    newContent = stringContent + newContent + "\n}"
    file = open(fullPath, 'w')
    file.write(newContent)
    file.close()


# Adds the block names to en_US.lang in the RP
def addSlabNames(b):
    fullPath = '/Users/' + USER_INPUT['USERNAME'] + '/Desktop/' + USER_INPUT['packFolderName'] + '/RP/texts/en_US.lang'
    file = open(fullPath, 'a')
    entry = "\ntile.%s:%s.name=%s" % (USER_INPUT['namespace'], p[b][0]['defaultVariantName'], USER_INPUT['BLOCKS'][b]['slabDisplayName'])
    file.write(entry)
    file.close()


for block in range(len(USER_INPUT['BLOCKS'])):
    generateRecipe(block)
    generateDefaultOrientation(block)
    generateNondefaultOrientations(block)
    addSlabSounds(block)
    addSlabNames(block)

It should also be fairly straightforward to tweak this to generate sets of custom stairs instead of vertical slabs.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .