Skip to content

Instantly share code, notes, and snippets.

@pollend
Last active December 2, 2017 17:45
Show Gist options
  • Save pollend/6f106889f199d1a30736c3beddaa9e26 to your computer and use it in GitHub Desktop.
Save pollend/6f106889f199d1a30736c3beddaa9e26 to your computer and use it in GitHub Desktop.

Description

BlockFamilies was built as a way to organize blocks of a certain type together. For instance, a fence block can be connected to nothing or up to four cardinal directions and every other combination in between. A block family is defined in the .block file using the rotation flag and the value is the associated family that will be used to represent the block family. Some default families that come with the base engine includes: freeform, horizontal,attachedToSurface, and fullRotation

Usage

*.block

{
    "rotation": "attachedToSurface",
    "sides": {
        "shape": "TorchWall"
    },
    "top": {
        "shape": "TorchGrounded"
    },
    "penetrable": true,
    "attachmentAllowed": false,
    "shadowCasting": false,
    "luminance": 15,
    "hardness": 1,
    "inventory": {
        "directPickup": true
    },
    "entity": {
        "prefab": "Core:Torch"
    },
    "mass": 10
}

A general block family is defined in the JSON form above. The relevant elements are the rotation entry and the top and sides entry. This is an attachedToSurface family which defines both sections types and multisection types. Multisection types will group the same block for multiple BlockSections. MultiSections help reduces the amount of boilerplate JSON. The same can be done without the multisection type but this requires four entries vs the single entry. These types can be found in the annotations above a block family.

"left": {
    "shape": "TorchWall"
},
"front": {
    "shape": "TorchWall"
},
"back": {
    "shape": "TorchWall"
},
"right": {
    "shape": "TorchWall"
},

These properties can be found in the block familyfactory which can be found in the engine under org.terasology.world.block.family. The functions getSectionNames and getMultiSections define the section types of that associated family.

HorizontalBlockFamilyFactory.java

private static final ImmutableSet<String> BLOCK_NAMES = ImmutableSet.of("front", "left", "right", "back", "top", "bottom");
    private static final ImmutableList<MultiSection> MULTI_SECTIONS = ImmutableList.of(
            new MultiSection("all", "front", "left", "right", "back", "top", "bottom"),
            new MultiSection("topBottom", "top", "bottom"),
            new MultiSection("sides", "front", "left", "right", "back"));

...
 @Override
    public Set<String> getSectionNames() {
        return BLOCK_NAMES;
    }

    @Override
    public ImmutableList<MultiSection> getMultiSections() {
        return MULTI_SECTIONS;
    }

    @Override
    public boolean isFreeformSupported() {
        return false;
}

Old Implementation

The old usage of the block family requires both a BlockFamilyFactory and a BlockFamily. A new Family type would require an entirely new factor and family instance that represents that set of blocks. This is fairly cumbersome to build and is also limited to what is provided by the factory and what is passed down from the factor to the family itself.

New Implementation

This whole process is hopefully simplified with these changes to Block Families. The main problem with BlockFamilies at the moment is they tend not to be easy to use. The entire factory pattern with block families was replaced with an inheritance scheme that takes advantage of dependency injection. Any additional systems that are needed by the family are passed in by injection. Any additional factors that would be covered by the family are handled through annotation. For instance, a family might need access to a secondary system when the actual block is placed. This avoids all the fixed limitations of the factory pattern. A family has only access to what is available to the factory or what is handled in a given function. Access to anything outside of this scope requires adding the variable to the family and tweaking the call throughout the engine. I think this is problematic for the long-term use of a family.

This also reduces the additional boilerplate needed to get started with block families. In the old scheme, you had to create a factory along with a family that returns that family. Depending on the scheme and what is passind in through the family. It might be necessary to reimplement the entire factory itself. For instance, UpdatesWithNeighboursFamily has this connectionCondition interface that is used to determine if a block has a connection to another block, but this structure only allows what is provided by the nested function inside. This is more of a limitation of the interface itself, but the family iself is constrained by the parent factory class.

A new family that wants to take advantage of features of an existing family can inherit the associated family and also create a label marking out the family.

New Usage

*.java

@RegisterBlockFamily("attachedToSurface")
@BlockSections({"front", "left", "right", "back", "top", "bottom"})
@MultiSections({
        @MultiSection(name = "all", coversSection = "front", appliesToSections = {"front", "left", "right", "back", "top", "bottom"}),
        @MultiSection(name = "topBottom", coversSection = "top", appliesToSections = {"top", "bottom"}),
        @MultiSection(name = "sides", coversSection = "front", appliesToSections = {"front", "left", "right", "back"})})
public class AttachedToSurfaceFamily extends AbstractBlockFamily {

*.block

{
..
 "family": "attachedToSurface",
..
}

The sections that defined the blocks are now defined in the annotation. rotation has been exchanged for family for consistency. But the rest of the JSON, for the most part, is unchanged. So a block family that defines the BlockSections type will have multiple subtype blocks that define blocks of that class. so this can be used to define the connection types or all the possible orientations. This is most apparent in the MultiConnect family where the connection types are defined. The relevant logic that describes how the JSON is deserialized can be found here. The annotations on the class match the static fields within the class.

public static final String NO_CONNECTIONS = "no_connections";
public static final String ONE_CONNECTION = "one_connection";
public static final String TWO_CONNECTIONS_LINE = "line_connection";
public static final String TWO_CONNECTIONS_CORNER = "2d_corner";
public static final String THREE_CONNECTIONS_CORNER = "3d_corner";
public static final String THREE_CONNECTIONS_T = "2d_t";
public static final String FOUR_CONNECTIONS_CROSS = "cross";
public static final String FOUR_CONNECTIONS_SIDE = "3d_side";
public static final String FIVE_CONNECTIONS = "five_connections";
public static final String SIX_CONNECTIONS = "all";

Example Use Case

There is currently an example use case in Signalling that shows this and also a fix that resolves this for the old implementation and also the broken implementation beforehand. The main problem is that the ConnectionCondition does not have access to the archetype black so the entire implementation was copied in from the UpdateBlockFamily. This creates a lot of redundent code that is hopefully resolved with the new changes to blockfamily.

Reasoning

The Reasoning behind these changes is both to improve the usability of block families and reduce the amount of boilerplate code that is needed to be written to define a new family. Existing families will still work but will require just exchaning rotation for family in the *.block file. This makes it easier to define new block families by inheriting an existing family and adding the RegisterBlockFamily annotation.

Examples

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment