Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# IDE files
.vscode
.idea
.eclipse
.vscode/
.idea/
.eclipse/

# build
build
bin
.gradle
build/
bin/
.gradle/

# run
run/
123 changes: 48 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ This README explains the core vocabulary (Roles, Properties, Compositions) and w
- [Data Pack Files](#data-pack-files)
- [Composition](#composition)
- [Material](#material)
- [Role](#role)
- [Resource Pack Files](#resource-pack-files)
- [Shape](#shape)
- [Palette](#palette)
Expand All @@ -30,17 +29,17 @@ Masterworks is built on a flexible, data-driven system. Most “content” is ex

Roles act as the “type system” for Masterworks constructs. A role describes what something *is allowed to be used as* in a composition. In practice, roles define the valid “slots” that inputs may occupy and the “interfaces” that a composition can fulfill.

When authoring compositions, you’ll use roles to declare what kind of input each named component expects (e.g., a `material` vs a `component/handle/tool`). When consuming a composition (crafting/building a construct), those role constraints are what make the system consistent and predictable.
When authoring compositions, you define the roles a composition can fulfill in its `roles` block. That block maps the input components to specific sub-roles or textures depending on the role type (e.g., a `materializer` vs a `compositor`). When consuming a composition (crafting/building a construct), those role mappings are what make the visual rendering and system consistent and predictable.

- **Material (`masterworks:material`)**: The fundamental building block (e.g., Iron, Wood). Materials are leaf nodes in a construct tree: they contribute base properties (like durability) and a visual style (palette + color), and they cannot be decomposed further.
- **Component (`masterworks:component/...`)**: An intermediate part of a construct (e.g., `masterworks:component/pickaxe_head`). Components are produced by compositions that consume one or more materials/components as inputs.
- **Item (`masterworks:item`)**: The root role. This represents a complete, usable item stack in the player’s inventory—typically the “final output” role of a composition.

### Properties

Properties define the behavior, stats, and appearance of a construct. Properties are always associated with a role within a composition: the same composition can output different properties depending on which role you’re asking it to fulfill.
Properties define the behavior and stats of a composition. Properties apply to the entire composition natively: the identical formula pipeline calculates the final stats irrespective of which target role is requested.

Currently, properties fall into three categories: numeric expressions (stats), providers (bridges into vanilla systems), and rendering (how the item is visually assembled).
Currently, properties fall into two categories: numeric expressions (stats) and providers (bridges into vanilla systems). Visual assembly and rendering are driven within the `roles` declarations rather than `properties`.

#### Expression Properties

Expand All @@ -58,86 +57,86 @@ These are bridge properties that connect Masterworks’ computed values to vanil
- **Tool Rule Provider:** Generates Minecraft tool rules (mining speed and correct tool checks) based on Masterworks properties.
- **Data Component Provider:** A less specialized form attaching any generic data component to the item stack.

#### Render Property

The `masterworks:render` property controls how the item is visually assembled. It defines which shape textures to use for each component role and how those layers are combined into the final icon.

## Data-Driven Behavior

### Data Pack Files

Masterworks uses data packs to define compositions, materials, and roles. These drive the entire system, allowing for clear extension and customization.
Masterworks uses data packs to define compositions and materials. These drive the entire system, allowing for clear extension and customization.

#### Composition

Compositions are rich JSON files defining how constructs can be formed. They act as a bridge, mapping a set of input **components** (named slots with role constraints) to a set of output **roles** (the things the composition can “be”).

Think of a composition as both a recipe and a stat pipeline: it specifies what goes in, and for each role it can fulfill, it specifies the properties (stats, providers, render rules) of the resulting output.
Think of a composition as both a recipe and a stat pipeline: it specifies what goes in, handles routing those inputs to specific functions based on the fulfilled role, and specifies global properties (stats, providers) scaling off those inputs.

**Directory:** [src/main/resources/data/masterworks/masterworks/composition/](src/main/resources/data/masterworks/masterworks/composition/)

**Structure:**

- `components`: A map defining the required inputs. Keys are local names (e.g., "head", "main") used in formulas. Values are the **Role** that the input component must fulfill.
- `properties`: A map defining the output behavior. Keys are the **Role** this composition fulfills. Values are maps of properties (like stats or rendering instructions) for that role.
- `components`: A list defining the required inputs by their local names (e.g., `"head"`, `"main"`). These names are used in formulas and role definitions.
- `roles`: A map defining the output behavior contexts. Keys are the **Role** this composition can fulfill. Values set the `type` of the role (e.g., `"masterworks:compositor"` or `"masterworks:materializer"`) and map the component names to their specific target roles or expected sub-components.
- `properties`: A map defining the stats and functional behavior of the composition. These apply universally to the composition.

**Key Features:**

- **Formulas:** Property values can be mathematical expressions referencing the input components by their local name (e.g., `$head * 0.5`).
- **Multi-Role:** A single composition can fulfill multiple roles. For example, a "Rod" can exist as a standalone item, but it can also be used as multiple handle-like component roles, each with its own properties and render configuration.
- **Multi-Role:** A single composition can fulfill multiple roles. For example, a "Rod" can exist as a standalone item, but it can also be used as multiple handle-like component roles, each with its own rendering configurations mapped within the `roles` block.

**Example:** [rod.json](src/main/resources/data/masterworks/masterworks/composition/rod.json) takes a `material` as input (`main`). It outputs properties for three roles: `masterworks:item` (to exist as an item), `masterworks:component/handle/tool` (to be used as a tool handle), and `masterworks:component/handle/short`.
**Example:** [rod.json](src/main/resources/data/masterworks/masterworks/composition/rod.json) takes a single input (`main`). It fulfills roles for three endpoints: `masterworks:item` (to exist as an item), `masterworks:component/pickaxe/handle`, and `masterworks:component/sword/broad/handle`, mapping the main material into each specific sub-component. Its properties are defined globally for the composition.

```json
{
"components": {
"main": "masterworks:material"
},
"properties": {
"components": [
"main"
],
"roles": {
"masterworks:item": {
"masterworks:render_item": "masterworks:rod",
"masterworks:durability": "$main * 0.2",
...
"type": "masterworks:materializer",
"main": "masterworks:rod"
},
"masterworks:component/handle/tool": {
"masterworks:render_item": "masterworks:rod/tool_handle",
"masterworks:durability": "$main * 0.2",
...
"masterworks:component/pickaxe/handle": {
"type": "masterworks:materializer",
"main": "masterworks:pickaxe/handle"
},
"masterworks:component/handle/short": {
"masterworks:render_item": "masterworks:rod/short_handle",
"masterworks:durability": "$main * 0.15",
...
"masterworks:component/sword/broad/handle": {
"type": "masterworks:materializer",
"main": "masterworks:sword/broad/handle"
}
},
"properties": {
"masterworks:durability": "$main * 0.2",
"masterworks:mining_speed": "$main",
"masterworks:attack_speed": "$main"
}
}
```

**Example:** [pickaxe.json](src/main/resources/data/masterworks/masterworks/composition/pickaxe.json) takes three components as input: a pickaxe head, a binding, and a handle. It outputs properties for the `masterworks:item` role, calculating stats based on all three inputs. The set of blocks it can mine is determined by two factors: the head component, which inherits its material's denied blocks tag, and the vanilla pickaxe mining tag defined for its mining speed property.
**Example:** [pickaxe.json](src/main/resources/data/masterworks/masterworks/composition/pickaxe.json) takes three components as input: a pickaxe head, a binding, and a handle. It outputs properties configured under the `roles` block for the `masterworks:item` role as a "compositor", mapping those components to their final visual targets. The set of blocks it can mine is defined in the `properties` block along with the rest of the calculated stats.

```json
{
"components": {
"head": "masterworks:component/pickaxe_head",
"binding": "masterworks:component/binder/tool",
"handle": "masterworks:component/handle/tool"
},
"properties": {
"components": [
"head",
"binding",
"handle"
],
"roles": {
"masterworks:item": {
"masterworks:render_item": [
"head",
"handle",
"binding"
],
"masterworks:durability": "$head + $handle + $binding",
"masterworks:mining_denied": "$head",
"masterworks:mining_speed": {
"block_tag": "minecraft:mineable/pickaxe",
"value": "$head * 0.4 + $handle * 0.4 + $binding * 0.2"
},
"masterworks:attack_damage": "$head",
"masterworks:attack_speed": "$head * 0.4 + $handle * 0.4 + $binding * 0.2"
"type": "masterworks:compositor",
"head": "masterworks:component/pickaxe/head",
"binding": "masterworks:component/pickaxe/binding",
"handle": "masterworks:component/pickaxe/handle"
}
},
"properties": {
"masterworks:durability": "$head + $handle + $binding",
"masterworks:mining_denied": "$head",
"masterworks:mining_speed": {
"block_tag": "minecraft:mineable/pickaxe",
"value": "$head * 0.4 + $handle * 0.4 + $binding * 0.2"
},
"masterworks:attack_damage": "$head",
"masterworks:attack_speed": "$head * 0.4 + $handle * 0.4 + $binding * 0.2"
}
}
```
Expand Down Expand Up @@ -171,32 +170,6 @@ Materials are JSON files defining the base properties and visual style of a raw
}
```

#### Role

Roles are JSON files defining the “interfaces” or “slots” that constructs and components fit into. Role definitions are used both for validation (what is allowed where) and for presentation (example shapes used for display/preview).

**Directory:** [src/main/resources/data/masterworks/masterworks/role/](src/main/resources/data/masterworks/masterworks/role/)

**Types:**

- **Material (`masterworks:material`):** The fundamental input. Its render logic takes a **Shape** argument and applies the material's palette to it.
- **Component (`masterworks:component/...`):** Intermediate parts (e.g., tool binding, handle). Its render logic typically takes a boolean flag to pass through the underlying construct's rendering.
- **Item (`masterworks:item`):** The final output for a usable item.

**Structure:**

- `type`: The role type registry name.
- `examples`: A list of **Shape** textures valid for this role (often used for display in creative tabs or guides).

**Example:** [component/handle/tool.json](src/main/resources/data/masterworks/masterworks/role/component/handle/tool.json) defines the `masterworks:component/handle/tool` role, which represents a tool handle component. It provides an example shape (`masterworks:rod`) that can be used for display. Note that this shape is actually representative of a rod's item form, not of this specific handle shape.

```json
{
"type": "masterworks:component",
"examples": "masterworks:rod"
}
```

### Resource Pack Files

Texture assets in Masterworks are organized into three categories: shapes, palettes, and tiers. These assets are dynamically combined to render constructs and templates.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.masterworks.masterworks;

import com.masterworks.masterworks.block.entity.ConstructForgeBlockEntity;
import java.util.Arrays;
import java.util.function.Supplier;
import java.util.stream.Collectors;
Expand All @@ -14,14 +13,14 @@
import net.neoforged.neoforge.registries.DeferredRegister;

public class MasterworksBlockEntityTypes {
static final DeferredRegister<BlockEntityType<?>> REGISTRAR =
private static final DeferredRegister<BlockEntityType<?>> REGISTRAR =
DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, MasterworksMod.ID);

public static void register(IEventBus bus) {
REGISTRAR.register(bus);
}

static <T extends BlockEntity> Supplier<BlockEntityType<T>> register(
private static <T extends BlockEntity> Supplier<BlockEntityType<T>> register(
String name, BlockEntitySupplier<T> factory, Stream<? extends Supplier<? extends Block>> validBlocks) {
return REGISTRAR.register(
name,
Expand All @@ -30,11 +29,9 @@ static <T extends BlockEntity> Supplier<BlockEntityType<T>> register(
}

@SafeVarargs
static <T extends BlockEntity> Supplier<BlockEntityType<T>> register(
@SuppressWarnings("unused")
private static <T extends BlockEntity> Supplier<BlockEntityType<T>> register(
String name, BlockEntitySupplier<T> factory, Supplier<? extends Block>... validBlocks) {
return register(name, factory, Arrays.stream(validBlocks));
}

public static final Supplier<BlockEntityType<ConstructForgeBlockEntity>> CONSTRUCT_FORGE =
register("construct_forge", ConstructForgeBlockEntity::new, MasterworksBlocks.CONSTRUCT_FORGE);
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
package com.masterworks.masterworks;

import com.masterworks.masterworks.block.ConstructForgeBlock;
import java.util.function.Function;
import net.minecraft.world.level.block.Block;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.registries.DeferredBlock;
import net.neoforged.neoforge.registries.DeferredRegister;

public class MasterworksBlocks {
static final DeferredRegister.Blocks REGISTRAR = DeferredRegister.createBlocks(MasterworksMod.ID);
private static final DeferredRegister.Blocks REGISTRAR = DeferredRegister.createBlocks(MasterworksMod.ID);

public static void register(IEventBus bus) {
REGISTRAR.register(bus);
}

static <T extends Block> DeferredBlock<T> register(String name, Function<Block.Properties, T> factory) {
@SuppressWarnings("unused")
private static <T extends Block> DeferredBlock<T> register(String name, Function<Block.Properties, T> factory) {
return REGISTRAR.registerBlock(name, factory);
}

public static final DeferredBlock<ConstructForgeBlock> CONSTRUCT_FORGE =
register("construct_forge", ConstructForgeBlock::new);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.masterworks.masterworks;

import com.masterworks.masterworks.block.entity.ConstructForgeBlockEntity;
import com.masterworks.masterworks.util.registrar.CapabilitiesRegistrar;
import com.mojang.datafixers.util.Unit;
import java.util.function.Supplier;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.capabilities.BlockCapability;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.capabilities.ICapabilityProvider;

public class MasterworksCapabilities {
Expand All @@ -18,15 +16,11 @@ public static void register(IEventBus bus) {
REGISTRAR.register(bus);
}

static <T, C, BE extends BlockEntity> Unit register(
@SuppressWarnings("unused")
private static <T, C, BE extends BlockEntity> Unit register(
BlockCapability<T, C> capability,
Supplier<BlockEntityType<BE>> blockEntityType,
ICapabilityProvider<BE, C, T> provider) {
return REGISTRAR.registerBlockEntity(capability, blockEntityType, provider);
}

public static final Unit CONSTRUCT_FORGE_INVENTORY = register(
Capabilities.Item.BLOCK,
MasterworksBlockEntityTypes.CONSTRUCT_FORGE,
ConstructForgeBlockEntity::inventoryCapability);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.registries.DeferredRegister;

public class MasterworksCreativeTabs {
static final DeferredRegister<CreativeModeTab> REGISTRAR =
public class MasterworksCreativeModeTabs {
private static final DeferredRegister<CreativeModeTab> REGISTRAR =
DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MasterworksMod.ID);

public static void register(IEventBus bus) {
REGISTRAR.register(bus);
}

static Supplier<CreativeModeTab> register(
private static Supplier<CreativeModeTab> register(
String name, Supplier<? extends ItemLike> icon, CreativeModeTab.DisplayItemsGenerator generator) {
return REGISTRAR.register(name, key -> CreativeModeTab.builder()
.title(Component.translatable(String.join(".", "itemGroup", key.getNamespace(), name)))
Expand All @@ -32,21 +32,19 @@ static Supplier<CreativeModeTab> register(
}

@SafeVarargs
static Supplier<CreativeModeTab> register(
@SuppressWarnings("unused")
private static Supplier<CreativeModeTab> register(
String name, Supplier<? extends ItemLike> icon, Supplier<? extends ItemLike>... items) {
return register(name, icon, new CollectionDisplayItemsGenerator(Arrays.asList(items)));
}

public static final Supplier<CreativeModeTab> TEMPLATES =
register("templates", MasterworksItems.TEMPLATE, new TemplateItem.DisplayItemsGenerator());
register("templates", MasterworksItems.ROD_TEMPLATE, new TemplateItem.DisplayItemsGenerator());

public static final Supplier<CreativeModeTab> CONSTRUCTS =
register("constructs", MasterworksItems.CONSTRUCT, new ConstructItem.DisplayItemsGenerator());

public static final Supplier<CreativeModeTab> OTHER =
register("other", MasterworksItems.CONSTRUCT_FORGE, MasterworksItems.CONSTRUCT_FORGE);

record CollectionDisplayItemsGenerator(Collection<? extends Supplier<? extends ItemLike>> items)
private record CollectionDisplayItemsGenerator(Collection<? extends Supplier<? extends ItemLike>> items)
implements CreativeModeTab.DisplayItemsGenerator {
@Override
public void accept(
Expand Down
Loading