Architecture Guide - mobends/MoBends GitHub Wiki

Motivation

This is a document describing the ideal state of the Mo' Bends architecture. This is how it should all work in the end, and is the current target for the 2.0.0 release of Mo' Bends for 1.16.4.

Assets

In the new architecture, I chose to separate the assets (animations, models, state-machines) from the systems. This is done because assets should not have to change between versions of Minecraft, they should not depend on which version of Minecraft the animations are being played and applied on. The systems on the other hand work hand-in-hand with the Minecraft code in order to bring these assets to life. The code should be general, minimal and testable so that porting is easier.

These asset files can be provided by both resource-packs as well as mods, which allows for the following:

  • Mods can provide their own metadata or even animations that will show up when used alongside Mo' Bends,
  • Resource-pack creators can create animation-sets for whatever mob they want, even outside of the the default animation-set (as long as they provide mutation data),

A resource-pack that wants to add animations for mobs can contain the following:

  • Controller files - this is a file in a custom binary format holding animation data and a description of a finite-state-machine that controls when certain animations will get played. They have to specify which mutator they are basing their animators on to avoid conflicts and increase compatibility. This coupling could in theory allow users to swap between mutators and their associated animation sets on the fly.
  • Mutator files - .json files containing instructions on how a model should be mutated (what body parts should change and how) in order to then have a wider range of motions the character is able to perform. Default mutators for some mobs are shipped with Mo' Bends, so you can just base your animations on those.
  • Dictionary files - .json files which the system is going to use to translate body part names into internal code field names. This separation is useful since the names of body parts in-code change frequently from version to version, and we want to keep these resource-packs as cross-version as we can. These files are rarely going to have to get created by resource-pack creators, unless they want to animate a mob outside of vanilla.

File structure

Status: In progress 🧐

The origin of assets does not matter (mods and resource-packs are both valid), but Mo' Bends looks for assets in specific places. To make assets visible, the following directory structure has to be implemented:

assets/
    <mod_id>/
	    ...(other assets related to the <mod_id> mod)
        bends/
            types/
                creature_a.json
                creature_b__variant_1.json
                creature_b__variant_2.json
                ...
			<unique_extension_name>/
	            ...(best place to put assets related to Mo' Bends)

The <mod_id> snippet can be replaced by:

  • The mod id of the mod that is being animated. Useful for grouping textures and animations together.
  • "mobends" if creating alterations to vanilla mobs.

If there are seperate assets with the same filepath, that would usually result in an asset collision, and one would override the other, but declarations in assets/<mod id>/bends/types go around this issue, so do not worry about collisions!

Apart from declarations in assets/<mod_id>/bends/types, there are assets like the Controllers, Mutators and Dictionaries that have to get linked together. The advised solution is to put them in a directory with a unique snake_case string, shown as <unique_extension_name> above. Can be your name, or the name of your resource-pack.

Make sure to all asset names and paths are in snake_case, otherwise the declarations can have a hard time looking for assets.

Below is an example file structure, which the rest of the asset linking is going to refer to:

assets/
	mobends/
		textures/
		models/
		blockstates/
		bends/
			types/
				wolf.json
				player_alex.json
				player_steve.json
			example_pack/
				wolf/
					anim/
						idle.bendsanim
						run.bendsanim
						sitting.bendsanim
						sitting_down.bendsanim
						standing_up.bendsanim
						breathing.bendsanim
					controller.bends
					dictionary.json
					mutator.json
				player/
					anim/
						idle.bendsanim
						run.bendsanim
						sneak_idle.bendsanim
						walk.bendsanim
					controller.bends
					dictionary.json
					mutator_standard.json
					mutator_slim.json

Type definitions

{
	"id": "example_pack/minecraft:player/alex",
	"meta": {
		"name": "ExamplePack: Alex",
		"author": "ExampleDude",
		"description": "An example animation set (alex skin variant)."
	},
    "dictionary": "mobends:bends/example_pack/player/dictionary",
    "mutator": "mobends:bends/example_pack/player/mutator_slim",
    "controller": "mobends:bends/example_pack/player/controller",
    "selector": {
        "type": "core:and",
        "conditions": [
            {
                "type": "core:entity_type",
                "entityType": "minecraft:player"
            },
            {
                "type": "mobends:skin_variant",
                "variant": "slim"
            }
        ]
    }
}

Mutator files

{
    /**
     * Describe how you'd like to change the existing parts of a model.
     */
    "mutate": {
        "bodyPart1": {
            "parent": "root", // [Optional]
            /**
             * [Optional]
             * Typically used when this part is part of a bigger whole, and the 'extentionPart1'
             * is the continuation of that bigger whole. Used for example by arms and item placement,
             * where we expect the end of the "arm" to be at the hand, instead of mid-way through.
             */
            "extendedBy": "extentionPart1",
            "position": [0, 1.5, 2], // The pivot position of this body part
            "boxes": [
                {
                    "position":     [-3, -3.5, -8], // The offset position of this box
                    "dimensions":   [6, 6, 9], // The dimensions (size)
                    /**
                     * [Optional] Each inner field (recursive) is also optional. Used for offsetting/rotating faces to alter
                     * their UV coordinates relative to the standard cube mapping.
                     */
                    "faces": {
                        "TOP":    { "offsetX": 9,   "offsetY": 6 },
                        "BOTTOM": { "offsetX": -8,  "offsetY": 6, "rotate": "HALF_TURN" },
                        "LEFT":   { "offsetX": -3,  "offsetY": -3, "rotate": "CLOCKWISE" },
                        "RIGHT":  { "offsetX": 0,   "offsetY": -3, "rotate": "COUNTER_CLOCKWISE" },
                        "FRONT":  { "offsetX": 1 },
                        "BACK":   { "offsetY": -9 }
                    }
                }
                // ...
            ]
        }
        // ...
    },
    /**
     * Describe what new parts you'd like to add to the model.
     */
    "add": {
        "root": {
            "position": [0, 0, 0]
        },
        "extentionPart1": {
            "parent": "bodyPart1", // [Optional]
            "position": [0, 2, -4], // The pivot position of this body part
            "textureOffset": [0, 12], // The top-left origin of where cube mapping should start.
            "boxes": [
                {
                    "position": [-1.5, 0, -4], // The offset position of this box
                    "dimensions": [3, 1, 4], // The dimensions (size)
                    /**
                     * [Optional] (Same as the previous body part)
                     */
                    "faces": { /* Same as the previous body part */}
                }
                // ...
            ]
        }
    }
}

Dictionary files

If you find the need to create one, see if there is any other resource pack mutating the same mob as you plan to, and agree on using the same mapping as in their metadata files.

{
    "parts": {
        "body": { "fieldName": "field_abcde_f" },
        "head": { "fieldName": "field_fabcd_e" },
        "mane": { "fieldName": "field_efabc_d" },
        "leg1": { "fieldName": "field_defab_c" },
        "leg2": { "fieldName": "field_cdefa_b" },
        "leg3": { "fieldName": "field_bcdef_a" },
        "leg4": { "fieldName": "field_abcde_f" },
        "tail": { "fieldName": "field_fabcd_e" }
        // ...
    }
}

Finite State Machines (KUMO)

Finite state machines allow us to describe how characters should react to their current state and to the state of their world. The reactions have to be purely visual, as in animations (so no additional interaction and/or functionality from mobs). These finite state machines are described in binary form, contained within animator files.

Which mutators are applied and which are not?

Motivation

Only one mutator per model can be applied at a time, and we assume there's mostly a one-to-one correspondance between an entity type and a model instance. We can therefore limit the number of applied mutators with the same target to at most one.

Possible solutions

  1. Picking an atribtrary mutator, unless a mutator id has been specified in the config. (solution for the first implementation)
  2. ... (?)

UI Proposals (not MVP)

We need a way to change which mutators are applied through the UI.

  • A list of loaded mutators categories by their target, each labeled by the resource packs they come from (maybe the filename). The user can then click on a "Set default" button to use that particular mutator for that target by default.

In what order are the animation sets applied?

Motivation

The whole idea of BendsPacks is to extend the default animation set of Mo' Bends. That means that resource pack embedded animators shouldn't have to reinvent the wheel and create each and every animation. They could for example create just one, which plays at a specific condition but falls back to the default animator otherwise. This could be achieved by creating a "Fall-Through" node, which does nothing but letting the previous layer take the spotlight. That means that if an animation set doesn't have any "Fall-Through" nodes and is non-empty, it totally overrides the previously applied animators (which means none of the previous animators have to be evaluated). Let's call this type of animator "overriding".

How do we pick which overriding animator should be the one to get evaluated? And what about the other animators, in what order should they get applied?

Possible solutions

  1. For each mutator, choose an arbitrary overriding animator as the primary one, and keep the non-overriding animators in an arbitrary order. This shouldn't be a problem for the first versions of the system as we don't expect many overriding animators to be made. (solution for the first implementation)
  2. ... (?)

How to disable certain animations?

Motivation

A user could want to disable certain animations if they don't like their look or if it breaks some other functionality. Right now we only allow disabling animations on the level of whole targets.

⚠️ **GitHub.com Fallback** ⚠️