Skip to content

Slackow/PackScript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

77 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

What is PackScript?

PackScript, provided as a CLI (Command Line Interface), is designed to generate files for Minecraft datapacks. Datapacks can often have repetitive files or files that really should be together, that are not. There are many tools that already that solve these problems but this tool has some advantages:

Direct use of Commands

This tool has no knowledge of the syntax of any individual Minecraft command, this makes it version agnostic and means there is no need to learn a new way to write commands.

Extension of Python

If you know Python you can leverage that knowledge to use this tool, since by default, PackScript is Python.

Small Footprint

The entirety of PackScript is defined in the packscript.py file. This makes it easy to add to your path or relocate.

Simplicity

The entire language adds very few constructs (see below) that are relatively simple to understand. These constructs give you the power to add commands to functions, and generate any other kind of file (especially JSON) easily.

Syntax Highlighting

There is an associated syntax highlighter for VSCode with this project. Get it here.

packscript_ex

Constructs

Command Lines

This is the main feature behind PackScript:

Command lines start with a / and are treated as commands instead of Python.

When not inside a function, command lines do nothing.

Command lines may also contain interpolation and function definitions, which will be explained below.

Command lines can also be extended to further lines by ending them with \ the subsequent line will have its leading whitespace removed

/execute as @e \
    at @s run ...
# is the same as
/execute as @e at @s run ...

Interpolation

On command lines, the user may use ${python_expression} or $python_var to refer to previously defined things in the file. A $ that is not followed by an identifier or { is interpreted as a literal dollar sign. To always introduce a literal dollar sign, use ${'$'} This is ugly, but rarely needed.

Function Definitions

If a command line ends with a : and contains the word function, it is interpreted as a function definition. This is the feature in PackScript that allows you to combine multiple files into one and have arbitrary levels of scope within your functions.

There are three main components of function definitions, they may all be omitted.

/function main:func [tag1, tag2] with storage args:

  1. Name: main:func
  2. Tags: tag1, tag2
  3. Extra: with storage args
  • Name: Placed right after function. Specifies the function's location within the datapack. Defaults to the namespace of the source file if omitted.

  • Tags: Placed within square brackets. Automatically generates function tags. Tags default to the minecraft namespace and are commonly used to mark functions as tick or load. They can also be empty.

  • Extra: Placed after square brackets. Reserved for additional command text. To use "extra" without tags, specify as /function [] with storage args:. Note the significant leading space in " with storage args.".

Any text before the function definition will appear in the final line but is not considered part of the definition itself.

More Valid Examples

/function a:

/schedule function [] 5t:

/function send_message [] {message:"Vital Message"}:

/function minecraft:func []:

/execute as @e at @s run function:

You may use interpolation within the function definition, this is useful for creating functions in loops or shortening the "extra" part to a constant (like in the example in the syntax highlighting section)

If the name of a function is omitted, (/function:) then the name of the function is set to anon/function (within the source namespace) multiple anon/function's take up names like anon/function_1, anon/function_2 etc.

ex:

=== data/main/source/main.dps ===
/function tick [tick]:
    /data modify storage args msg set value "example"
    /execute as @a run function main:func [] with storage args:
        /$say $(msg)

outputs

=== data/main/function/tick.mcfunction ===
# Generated by PackScript 0.1.4
data modify storage args msg set value "example"
execute as @a run function main:func with storage args

=== data/main/function/func.mcfunction ===
# Generated by PackScript 0.1.4
$say $(msg)

=== data/minecraft/tags/function/tick.json ===
{
    "values": [
        "main:tick"
    ]
}

You can also create a line like:

/#function example:

in order to define a function within another function without having an actual line. It's unlikely for this to be useful though, as such a function could be pulled to the top level.

The dp Object

The dp object provides a flexible way to create, manipulate, and delete datapack files directly from your code.

Basic Usage

You can use dp with either dot notation or dictionary-style access:

# Dot notation
dp.tags.block.chests = {
    "values": [
        "minecraft:chest",
        "minecraft:trapped_chest",
        "minecraft:ender_chest"
    ]
}

# Dictionary-style access
dp["tags/block", "example:metals"] = {
    "values": ["iron_block", "gold_block"]
}

Modifying and Accessing Files

Files created with dp can be accessed and modified later:

# Add a value to existing tag
dp.tags.block.chests["values"].append("minecraft:barrel")

# Delete a file or property
del dp.tags.block.outdated_tag

Dynamic Paths

The dp object fully supports dynamic paths for programmatic file generation:

# Generate multiple related files
for material in ["iron", "gold", "diamond"]:
    dp.recipe[f"{material}_upgrade"] = {
        "type": "minecraft:crafting_shapeless",
        "ingredients": [{"item": f"minecraft:{material}_ingot"}],
        "result": {"item": f"minecraft:{material}_block"}
    }

The dp object works with all datapack file types and automatically handles namespacing based on the source file's namespace, making it a powerful tool for organizing and manipulating your datapack structure.

capture_lines() Function

The capture_lines() function diverts command lines to a list of strings instead of directly writing to a function file. This is niche, but useful for constructing 'Only One Command's.

=== data/ooc/source/main.dps ===
def only_one_command(definer):
    """ Generates "Only One Command"s given a function that defines commands """
    with capture_lines() as lines:
        /gamerule commandBlockOutput false
        definer()
        /setblock ~ ~1 ~ command_block{auto:1b,Command:"fill ~ ~ ~ ~ ~-3 ~ air"}
        /kill @e[type=command_block_minecart,distance=..1]
    def escape(ln):
        return ln.replace("\\", "\\\\").replace("'", "\\'")
    main = ','.join(f"{{id:command_block_minecart,Command:'{escape(ln)}'}}" for ln in lines)
    /summon falling_block ~ ~1 ~ {BlockState:{Name:redstone_block},Passengers:[{id:falling_block,BlockState:{Name:activator_rail}},$main]}

/function ooc:
    def say(): # Commands For Only One Command!
        /say as second lame command
        /say third lamer command lol
        /tellraw @a "look ma I'm using quotes \\/!"
        /tellraw @a[name=!"Slackow"] "say \"lol\""
    only_one_command(say)

outputs

=== data/ooc/function/ooc.mcfunction ===
# Generated by PackScript 0.1.4
summon falling_block ~ ~1 ~ {BlockState:{Name:redstone_block},Passengers:[{id:falling_block,BlockState:{Name:activator_rail}},{id:command_block_minecart,Command:'gamerule commandBlockOutput false'},{id:command_block_minecart,Command:'say as second lame command'},{id:command_block_minecart,Command:'say third lamer command lol'},{id:command_block_minecart,Command:'tellraw @a "look ma I\'m using quotes \\\\/!"'},{id:command_block_minecart,Command:'tellraw @a[name=!"Slackow"] "say \\"lol\\""'},{id:command_block_minecart,Command:'setblock ~ ~1 ~ command_block{auto:1b,Command:"fill ~ ~ ~ ~ ~-3 ~ air"}'},{id:command_block_minecart,Command:'kill @e[type=command_block_minecart,distance=..1]'}]}

ns String

The ns global variable lets you access the namespace of the dps file you are in as a string. note that ns is the namespace of the source file, not the function you are in.

=== data/example_pack/source/example.dps ===
/function say_something:
   /say something!
/function example:
   # Regular functions don't default to the namespace, so this is needed
   /function $ns:say_something

outputs

=== data/example_pack/function/say_something.mcfunction ===
# Generated by PackScript 0.1.4
say something!
=== data/example_pack/function/example.mcfunction ===
# Generated by PackScript 0.1.4
function example_pack:say_something

Example Usage + PackScript Reloader

packscript_ex1

minecraft_output

This was used with the associated packscript_reloader fabric mod. This mod works on snapshots, and will automatically compile packs under the dev directory in the world into the datapacks folder of that world.

How does it work internally?

Well it's basically just doing a bunch of very fancy find and replaces to turn your invalid Python code into valid Python code, and then executing it. You can see this when you compile in verbose mode:

packscript_ex2

turns into

__f, __extra = __function_name__(f"tick [tick]")
__line__(rf""" function {__f}{__extra} """[1:-1])
with __function__(__f):
       __f, __extra = __function_name__(f"")
    __line__(rf""" execute as @a at @s run function {__f}{__extra} """[1:-1])
    with __function__(__f):
           __line__(rf""" title @s actionbar "Hey" """[1:-1])
        # remove short grass near the player
        __line__(rf""" fill ~10 ~10 ~10 ~-10 ~-10 ~-10 air replace short_grass """[1:-1])

__f, __extra = __function_name__(f"say_stuff")
__line__(rf""" function {__f}{__extra} """[1:-1])
with __function__(__f):
        __line__(rf""" $say packscript> $(message) < actual working macro parameter """[1:-1])
    __line__(rf""" say packscript> $(message) < that is just text """[1:-1])
    __line__(rf""" say packscript> 2 + 2 = {2 + 2} < this works because it is compile time """[1:-1])

dp.tags.block.chest = {
   'values': [
      'chest',
      'trapped_chest'
   ]
}

dp.tags.block.chest['values'].append('ender_chest')

del dp.tags.block.chest

dp['tags/block', 'chest'] = {
   'values': ['chest', 'trapped_chest', 'ender_chest']
}

All the functions being called here are provided by PackScript and are just routes through which the program can add lines or create new files.

FunctionPackScript

Most files shown so far have been .dps files, standing for DataPackScript, but there's also FunctionPackScript with .fps files. These are contained in the root of the input directory instead of under a proper datapack with a namespace underneath source[1], these are meant for generating independent function files easily, usually those with repetitive lines. In these files you cannot use create statements, but you can generate additional functions. All the generated function files will have their namespace ignored and be generated in the same directory as the main generated function.

[1]: In older versions of minecraft (pre 1.21) this folder is sources instead. PackScript will automatically figure out which folder name to use based on your pack_format value in your pack.mcmeta.

The CLI

This tool has two main actions it can perform: compiling packs and initializing templates.

  • packscript c (you can also use compile or comp)
  • packscript init

More actions:

  • packscript --help list general help
  • packscript --version print the version of packscript
  • packscript c --help print the help for compiling
  • packscript init --help print the help for initializing a datapack
  • packscript update update the tool to the latest version (if installed via pip, use that instead)
  • packscript pf edit and view the pack_format(s) supported by your datapack, you can use recognized version numbers directly

Compile Options

  • -i/--input <dir> specify the directory of the pack you are compiling defaults to current dir.
  • -o/--output <dir/zip> specify the output of the pack (can output zip too) defaults to output
  • -s/--source output the source files into the resulting pack, by default they get deleted
  • -v/--verbose print out all the generated Python code with line numbers. Very good for debugging.

Init Options

When init is called missing any options, it will prompt you to interactively fill them, this is the recommended way of using this action.

You can also specify options using flags

ex: packscript init --output "Datapack" --name "Datapack" --namespace "main" --description "Datapack for version 1.20.4" --pack-format 26

it's preferable to do just call the following instead:

packscript init

There is also the packscript init --modded command, it lets you add config files for compiling to fabric, forge, and neoforge.

It is recommended that you take a look at the files it generates to add any additional metadata you may want.

Compile Action

When a PackScript file is being compiled it will first print its location to the console, this is so if an error is encountered you can easily tell which file was last being run, and ensure that your .dps files are being executed.

The input directory should be a directory that contains a data and pack.mcmeta and optional pack.png and overlays or any directory that contains .fps files

The output will either be a directory, a zip file, or a jar file (jar file will make a mod that applies your pack)

When compiling to a jar, an assets folder can also be included to include a resourcepack.

ex: packscript c -o output, packscript c -o datapack.zip, packscript c -o mod.jar

Debugging

When there is an error in your PackScript file, it will print out the Python version of your PackScript code, this lets you pinpoint exactly where your error is. You should see a line that looks like:

 File "<string>", line 41, in <module>

This is what an error in the generated Python code looks like. This line number will not line up with your .dps file, but it will line up with the generated Python code.

The line with the error will get printed like this:

38:         'ender_chest'
39:     ]
40: }
41: a/say some text

In this case it's because there's a letter before the command line, making it get interpreted as regular Python.

Get Started

  1. Install Python. Get Python3 and make sure it's in your path. (You can check by running python3 -V in your terminal)
  2. Download/Install PackScript. run pip3 install packscript, or directly use the packscript.py file in releases, and use python3 packscript.py instead of packscript
  3. Create a Datapack. Run packscript init in order to create a datapack with PackScript. You'll be prompted for information about the datapack. You should have a new datapack, you can put files in there as usual for them to be outputted, files in <pack>/data/*/source/*.dps and <pack>/*.fps will be interpreted as PackScript on compilation. By default, you will find a main.dps file there.
  4. Compile Datapack. To compile, run packscript compile -i <datapack directory> -o <output>

About

A datapack compiler for Minecraft

Resources

License

Stars

Watchers

Forks

Packages

No packages published