Skip to content

Commit 369dfaf

Browse files
committed
Add Create Block Edits and refine behaviours docs
Add new Create Block Edits reference and update documentation across the site. Improve Getting Started with recommended setup and examples, add Goggle API and Create Block Edits entries to the index, and refine wording and examples in Super Behaviours and RenderedBehaviourExtension (clarify lifecycle, Flywheel visual setup, examples, and helper usage). Minor formatting and API clarity improvements throughout docs/Super Behaviours/* and docs/Getting Started.md.
1 parent 164dbbf commit 369dfaf

File tree

5 files changed

+310
-71
lines changed

5 files changed

+310
-71
lines changed

docs/Create Block Edits.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Create Block Edits
2+
3+
`CreateBlockEdits` lets a mod adjust Create's own block registrations while `AllBlocks` is still being built. That makes it possible to soft-mod existing Create blocks, add extra builder transforms, or swap the generated `BlockItem` class without replacing the block itself.
4+
5+
## Registering edits
6+
7+
Declare a public static no-arg method, annotate it with `@CreateBlockEdits.Registrator`, and register edits inside it:
8+
9+
```java
10+
public class MyCreateBlockEdits {
11+
12+
public static final BooleanProperty GLOWING = BooleanProperty.create("glowing");
13+
14+
@CreateBlockEdits.Registrator
15+
public static void register() {
16+
CreateBlockEdits.forBlock("belt", builder ->
17+
builder.properties(p -> p.emissiveRendering(
18+
(state, level, pos) -> state.hasProperty(GLOWING) && state.getValue(GLOWING)
19+
))
20+
);
21+
22+
CreateBlockEdits.forBlockItem("fluid_pipe", MyDyeablePipeBlockItem::new);
23+
}
24+
}
25+
```
26+
27+
The string id is the original Create registration name, such as `belt` or `fluid_pipe`.
28+
29+
## API overview
30+
31+
| API | Description |
32+
| --- | --- |
33+
| `@CreateBlockEdits.Registrator` | Marks a `public static void register()` method for automatic discovery during Create block bootstrap. |
34+
| `CreateBlockEdits.forBlock(id, edit)` | Applies an extra transform to Create's original `BlockBuilder` for that id. |
35+
| `CreateBlockEdits.forBlockItem(id, factory)` | Replaces the generated `BlockItem` factory for that id. |
36+
37+
## `@CreateBlockEdits.Registrator`
38+
39+
Registrator methods are discovered automatically from NeoForge scan data when Create's `AllBlocks` class starts bootstrapping. No manual bootstrap call is required from mod setup.
40+
41+
The method signature is strict:
42+
43+
```java
44+
@CreateBlockEdits.Registrator
45+
public static void register() {
46+
// edits go here
47+
}
48+
```
49+
50+
Anything else, such as private methods, instance methods, parameters, or a non-`void` return type, throws an `IllegalStateException` during discovery.
51+
52+
## `forBlock(...)`
53+
54+
`forBlock(...)` exposes the original `BlockBuilder<?, CreateRegistrate>` so extra registration transforms can be layered onto Create's own block definition.
55+
56+
```java
57+
CreateBlockEdits.forBlock("belt", builder ->
58+
builder.properties(p -> p.noOcclusion())
59+
);
60+
```
61+
62+
Multiple edits for the same id are merged and run in registration order. This makes it practical for separate compat modules to contribute independent builder changes to the same Create block.
63+
64+
## `forBlockItem(...)`
65+
66+
`forBlockItem(...)` replaces the generated item factory for a Create block, generally for when blocks dont have an explicit item themselves.
67+
68+
```java
69+
CreateBlockEdits.forBlockItem("fluid_pipe", MyDyeablePipeBlockItem::new);
70+
```
71+
72+
Only one item override may exist for a given block id. Registering a second one for the same id throws an `IllegalStateException`. This makes block items mutually exclusive to use with multiple edits on the same block, so should only be used when item classes dont exist and cannot have mixins.
73+
74+
## Timing rules
75+
76+
> `forBlock(...)` and `forBlockItem(...)` only work while a `@CreateBlockEdits.Registrator` method is running. Calling them later throws an `IllegalStateException` because the Create registration window is already closed.
77+
78+
Azimuth opens that window automatically from a mixin on Create's `AllBlocks` bootstrap, then applies the collected block edits and item overrides as the original Create registrations are created.
79+
80+
## Case study use cases
81+
82+
`Create: Bits 'n' Bobs` uses `CreateBlockEdits` in two practical ways:
83+
84+
- `forBlock("belt", ...)` adds emissive rendering to Create's belt block when the custom `glowing` property is enabled, which then gets external handling to add the interaction check.
85+
- `forBlockItem("fluid_pipe", ...)` swaps the normal pipe item for `DyeablePipeBlockItem`, which carries the extra placement behavior used by the dyeable pipe system, since pipes dont have their own item class to mixin into.
86+
87+
That is the intended pattern when the base Create block should stay intact but its registration-time properties or generated item class need to change.

docs/Getting Started.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
# Getting Started
1+
# Getting Started
22

3-
Azimuth is a Create addon library focused on making it easier to extend the functionality of Create.
3+
Azimuth is a Create addon library focused on making it easier to extend Create without forking large chunks of block entity or registration code.
44

55
## Adding Azimuth to your project
66

7-
Add the following to your `build.gradle` dependencies block, replacing `<version>` with the version you want to target, [the latest production-ready version is available here](https://modrinth.com/project/azimuth-api/settings/versions):
7+
Add the following to the `build.gradle` dependencies block, replacing `<version>` with the version to target. [The latest production-ready version is available here](https://modrinth.com/project/azimuth-api/settings/versions):
88

99
```groovy
1010
dependencies {
1111
implementation "com.cake.azimuth:azimuth:<version>"
1212
}
1313
```
1414

15-
You may also need to declare Azimuth as a required dependency in your `neoforge.mods.toml`:
15+
It may also be necessary to declare Azimuth as a required dependency in `neoforge.mods.toml`:
1616

1717
```toml
1818
[[dependencies.yourmodid]]
@@ -23,22 +23,52 @@ You may also need to declare Azimuth as a required dependency in your `neoforge.
2323
side = "BOTH"
2424
```
2525

26-
Azimuth doesn't require any explicit initialisation on your part just depend on it and start using the APIs.
26+
Most Azimuth APIs are ready as soon as the dependency is present. The one setup pattern worth knowing is behaviour and visual registration.
27+
28+
## Recommended common setup
29+
30+
If a mod registers type-specific behaviour applicators or Flywheel visual interest predicates, resolve them during common setup once the registries exist:
31+
32+
```java
33+
public MyMod(final IEventBus modEventBus) {
34+
MyBehaviourApplicators.register();
35+
modEventBus.addListener(MyMod::commonSetup);
36+
}
37+
38+
private static void commonSetup(final FMLCommonSetupEvent event) {
39+
BehaviourApplicators.resolveRegisteredTypes();
40+
VisualWrapperInterest.resolve();
41+
}
42+
```
43+
44+
`BehaviourApplicators.resolveRegisteredTypes()` is relevant for `registerForType(...)` suppliers. `VisualWrapperInterest.resolve()` is only relevant for `RenderedBehaviourExtension` visuals created through `getVisualFactory()`. Plain BER rendering does not need it.
2745

2846
## What's available
2947

3048
### Super Block Entity Behaviours
3149

32-
An expanded version of Create's `BlockEntityBehaviour` that can do significantly more, in effort to replicate features that would typically take new block entity types. All of which is composable on a single `SmartBlockEntity`. You can also *inject* behaviours onto block entities you don't own, without touching their source, making simple soft-compatability easy.
50+
An expanded version of Create's `BlockEntityBehaviour` with full tick lifecycle support, extra interaction hooks, typed lookup helpers, and extension interfaces for rendering, kinetics, and schematic requirements. Behaviours can also be injected onto block entities that are not owned by the mod.
3351

3452
[Super Block Entity Behaviours](./Super%20Behaviours/Super%20Behaviours.md)
3553

54+
### Create Block Edits
55+
56+
A registration-time API for soft-modding Create's own blocks. Use it to apply extra `BlockBuilder` transforms or replace the generated `BlockItem` for an existing Create block id.
57+
58+
[Create Block Edits](./Create%20Block%20Edits.md)
59+
3660
### Advancements
3761

3862
A thin wrapper around Create's internal advancement machinery. Define advancements in the same style Create uses, generate the required data, and award them from anywhere including from a block entity via `AzimuthAdvancementBehaviour`.
3963

4064
[Advancements](./Advancements/Advancements.md)
4165

66+
### Goggle API
67+
68+
A declarative builder for Create goggle tooltips, including labels, statistics, preset styles, and language key collection for datagen.
69+
70+
[Goggle API](./Goggle%20API/Goggle%20API.md)
71+
4272
### Outlines
4373

4474
Extra outline types built on top of Catnip's outliner. Particularly handy for ponders.
Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
# RenderedBehaviourExtension
1+
# RenderedBehaviourExtension
22

3-
`RenderedBehaviourExtension` lets a behaviour contribute rendering output alongside the block entity it's attached to. There are two rendering paths available: a standard Block Entity Renderer (BER) that works unconditionally, and an optional Flywheel visual for hardware-accelerated rendering. You can use either path or both together.
3+
`RenderedBehaviourExtension` lets a behaviour contribute rendering output alongside the block entity it is attached to. Two rendering paths are available: a standard Block Entity Renderer (BER) that always works, and an optional Flywheel visual for hardware-accelerated rendering.
44

55
For an overview of all extensions, see [Super Block Entity Behaviours](../Super%20Behaviours.md#extensions).
66

77
## Implementing
88

9+
To use a renderer, it is suggested you create a public static final supplier instance, which functions as a singleton for the renderer factory. This is not strictly required, but it is more efficient to reuse the same supplier instance across all behaviour instances than to create a new one for each:
10+
911
```java
1012
public class MyRenderedBehaviour extends SuperBlockEntityBehaviour
1113
implements RenderedBehaviourExtension {
1214

1315
public static final BehaviourType<MyRenderedBehaviour> TYPE = new BehaviourType<>();
16+
public static final BehaviourRenderSupplier RENDERER = () -> MyBehaviourRenderer::new;
1417

15-
public MyRenderedBehaviour(SmartBlockEntity be) {
18+
public MyRenderedBehaviour(final SmartBlockEntity be) {
1619
super(be);
1720
}
1821

@@ -23,97 +26,102 @@ public class MyRenderedBehaviour extends SuperBlockEntityBehaviour
2326

2427
@Override
2528
public BehaviourRenderSupplier getRenderer() {
26-
return () -> () -> new MyBehaviourRenderer();
29+
return RENDERER;
2730
}
2831
}
2932
```
3033

31-
3234
## Block Entity Renderer (BER)
3335

3436
### `getRenderer()`
3537

36-
**Required.** Returns a `BehaviourRenderSupplier` a double-supplier that produces a new `BlockEntityBehaviourRenderer` instance. The renderer's `renderSafe` method is called each frame while the block entity is in view.
38+
Required. Returns a `BehaviourRenderSupplier`, which produces a new `BlockEntityBehaviourRenderer` instance when Azimuth needs one.
3739

3840
```java
3941
public class MyBehaviourRenderer extends BlockEntityBehaviourRenderer<MyBlockEntity> {
4042
@Override
41-
public void renderSafe(SuperBlockEntityBehaviour behaviour, MyBlockEntity blockEntity,
42-
float partialTicks, PoseStack ms, MultiBufferSource buffer,
43-
int light, int overlay) {
44-
// your rendering here
43+
public void renderSafe(final SuperBlockEntityBehaviour behaviour, final MyBlockEntity blockEntity,
44+
final float partialTicks, final PoseStack ms, final MultiBufferSource buffer,
45+
final int light, final int overlay) {
46+
// render here
4547
}
4648
}
4749
```
4850

49-
The type parameter `T` determines what the `blockEntity` argument is cast to. If the cast fails at runtime, an informative `ClassCastException` is thrown.
51+
The type parameter `T` controls the type of the `blockEntity` argument. If the cast fails at runtime, Azimuth throws an informative `ClassCastException`.
5052

5153
### `rendersWhenVisualizationAvailable()`
5254

53-
Controls whether the BER runs even when a Flywheel visual is also active for this block entity. Defaults to `true`. Override to return `false` if your BER and Flywheel visual would produce duplicate output and you only want the visual to run.
55+
Controls whether the BER still runs when a Flywheel visual is active for the same block entity. The default is `true`. Return `false` when the visual fully replaces the BER output.
5456

5557
### `getRenderBoundingBox()`
5658

57-
Optional. Return an `AABB` to expand the culling bounding box for this block entity. Returning `null` (default) leaves it untouched.
58-
59-
> If you add a `RenderedBehaviourExtension` *after* the block entity has already been rendered (i.e. deferred), call `((CachedRenderBBBlockEntity) blockEntity).invalidateRenderBoundingBox()` to force a client-side refresh.
59+
Optional. Return an `AABB` to expand the culling bounds for the owning block entity. Returning `null` leaves the bounds unchanged.
6060

61+
> If a `RenderedBehaviourExtension` is attached after the block entity has already been rendered, call `((CachedRenderBBBlockEntity) blockEntity).invalidateRenderBoundingBox()` to force a client-side refresh.
6162
6263
## Flywheel Visual Interest
6364

64-
Flywheel visuals from `RenderedBehaviourExtension` are injected via a wrapping visualizer. To keep the overhead minimal, you must explicitly declare which block entity types need the wrapper types that aren't registered are left untouched.
65-
66-
Call this once during client setup for any type that will host a behaviour using `getVisualFactory()`:
65+
Flywheel visuals are injected through a wrapping visualizer. Register interest only for the block entity types that might actually create a behaviour visual:
6766

6867
```java
69-
VisualWrapperInterest.registerInterest(type ->
70-
type == MyBlockEntityType.MY_TYPE.get()
71-
);
72-
```
73-
74-
This is only required for the Flywheel visual path. Plain BER rendering works without it.
68+
public static void register() {
69+
VisualWrapperInterest.registerInterest(type ->
70+
type == MyBlockEntityType.MY_TYPE.get()
71+
);
72+
}
7573

74+
private static void commonSetup(final FMLCommonSetupEvent event) {
75+
VisualWrapperInterest.resolve();
76+
}
77+
```
7678

7779
## Flywheel Visual
7880

7981
### `getVisualFactory()`
8082

81-
Optional. Return a `BehaviourVisualFactory` to attach a Flywheel visual to this behaviour. Return `null` (default) to skip the visual path entirely.
83+
Optional. Return a `BehaviourVisualFactory` to attach a Flywheel visual to this behaviour. Return `null` to skip the visual path entirely.
8284

8385
```java
8486
@Override
8587
public BehaviourVisualFactory getVisualFactory() {
86-
return (context, behaviour, blockEntity, parentVisual, partialTick) ->
87-
new MyBehaviourVisual(parentVisual);
88+
return (context, behaviour, blockEntity, parentVisual, partialTick) -> {
89+
if (!(blockEntity instanceof final MyKineticBlockEntity kineticBlockEntity) || behaviour != this) {
90+
return null;
91+
}
92+
return new MyBehaviourVisual(parentVisual);
93+
};
8894
}
8995
```
9096

97+
That guard pattern is useful when only some block entities attached to a behaviour can actually build the visual.
98+
9199
### Implementing `BehaviourVisual`
92100

93-
Extend `RenderedBehaviourExtension.BehaviourVisual` and implement the required `delete()` method. The other lifecycle hooks are optional:
101+
Extend `RenderedBehaviourExtension.BehaviourVisual` and implement `delete()`. The other lifecycle hooks are optional:
94102

95103
```java
96104
public class MyBehaviourVisual extends RenderedBehaviourExtension.BehaviourVisual {
97105

98-
// your Flywheel instances here
106+
// Flywheel instances here
99107

100-
public MyBehaviourVisual(AbstractBlockEntityVisual<?> parentVisual) {
108+
public MyBehaviourVisual(final AbstractBlockEntityVisual<?> parentVisual) {
101109
super(parentVisual);
102110
}
103111

104112
@Override
105-
public void update(float partialTick) {
113+
public void update(final float partialTick) {
106114
// update instance transforms
107115
}
108116

109117
@Override
110-
public void updateLight(float partialTick) {
118+
public void updateLight(final float partialTick) {
111119
// update instance lighting
112120
}
113121

114122
@Override
115-
public void collectCrumblingInstances(Consumer<Instance> consumer) {
116-
// feed your instances to the consumer for block-breaking animation
123+
public void collectCrumblingInstances(final Consumer<Instance> consumer) {
124+
// feed instances into the block-breaking animation
117125
}
118126

119127
@Override
@@ -123,4 +131,4 @@ public class MyBehaviourVisual extends RenderedBehaviourExtension.BehaviourVisua
123131
}
124132
```
125133

126-
Use `getVisualPosition()` (inherited from `BehaviourVisual`) to get the block position for placing instances.
134+
Use `getVisualPosition()` from `BehaviourVisual` to place instances at the owning block entity's visual position.

0 commit comments

Comments
 (0)