Skip to content

Commit 5c91489

Browse files
Allow rendering other modules from placeholders (#3900)
1 parent 53dc2bf commit 5c91489

22 files changed

Lines changed: 709 additions & 73 deletions
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
title: The Central Monitor & Placeholder System
3+
---
4+
5+
### The Central Monitor
6+
7+
The Central Monitor is a multiblock that allows you to insert modules into it to render images and text.<br>
8+
Images update every 120 seconds, text update rate depends on the voltage provided to the multiblock.
9+
Guide on how to use the central monitor:
10+
11+
1. Right-click the controller
12+
2. In the UI, you will see a grid of monitors, the controller, energy hatch and (optionally) a data hatch
13+
3. Select some of the monitors (in any configuration) by left-clicking on them
14+
4. Click the "Create group" button
15+
5. You should see a group appear on the left of the UI, click on it to select all monitors in that group, click again to unselect
16+
6. Click on the gear icon next to the name of the group you want to edit
17+
7. A UI with a single slot should open, put a module into that slot (while it is possible to put a stack of modules in, that does literally nothing)
18+
8. If it's a text/image module a new field should appear, where you can enter some text (for image it'll be a single line for a URL)
19+
9. Once you've entered your text, click on the green checkmark below the slot, that will save the text you entered
20+
10. Click on the gear icon next to the group you're editing to go back to the main menu
21+
11. You should see the text/image on the Central Monitor
22+
23+
To remove a group, select it and click "Remove from group". To remove a single monitor from a group select only it and click "Remove from group".
24+
You cannot add monitors to a group after it has been created. Image dimensions are determined by the left-up corner of the group and the right-down corner,
25+
the blocks between them have to be in the same group. The text module will only display text on monitors of its group.
26+
27+
!!! warning "The image module is a bit buggy, so the image may not appear immediately"
28+
29+
### Text Module
30+
31+
You may have noticed that the text module has a number input in its UI. It is the text scale, where 1 represents a line height of 1/16th of a block.
32+
You may have also noticed that the text module has some additional slots on the left.
33+
Those are referenced by placeholders, you can put any item in them. Most placeholders also need a target block to work. To select a target for your monitor group,
34+
in the main UI of the controller select the group, right-click the block you want to target and click "Set target". You may want to target a block that is not in the
35+
central monitor, to do that you have to use a Wireless Transmitter Cover. Place it on the block you want to target and right-click it with a data stick. Then put that
36+
data stick into a data hatch in the Central Monitor multiblock. If you select the data hatch as a target, you will see a new number field appear. Enter the number of the
37+
slot your data stick is in and click "Set target". The target will be set to the block the Wireless Transmitter Cover is on. It can work cross-dimensionally.
38+
39+
!!! note "For the Computer Monitor Cover, the targeted block is always the block it's placed on."
40+
41+
### Placeholders
42+
Placeholders can be used by players in the monitor text module, or in the computer monitor cover (though a bit more limited).
43+
For example, a player may write something like this in a text module:
44+
```
45+
Hello on day {calc {tick} / 20000}!
46+
Current energy buffer: {formatInt {energy}}/{formatInt {energyCapacity}} EU\
47+
{if {cmp {energy} < 5000000} {color red "\nLOW ENERGY!"}}
48+
Here's some random stuff:
49+
{repeat 5 {repeat {random 2 10} {block}}
50+
```
51+
And something like this would be displayed:
52+
```
53+
Hello on day 420!
54+
Current energy buffer: 4.2M/6.9M EU
55+
LOW ENERGY!
56+
Here's some random stuff:
57+
███████
58+
██
59+
█████
60+
████
61+
██████████
62+
```
63+
This system is turing-complete (i.e. if the player really wanted to play Doom on the Central Monitor, they could).<br>
64+
All placeholders work on strings (or, more specifically, `Component`s to allow text formatting), so when you write `{calc {calc 2 + 4} * 3}`,
65+
first `{calc 2 + 4}` will be evaluated into `6`, then it will be converted to a string and back to an int, and then it will be passed into the second placeholder
66+
to evaluate `{calc 6 * 3}` into `18`, which will be turned into a string again. That also allows for things like `{calc 3 + 1}2`, which will evaluate into `42`,
67+
since outside of placeholders text is simply concatenated. Placeholder arguments are separated by spaces, which may be a bit annoying, when wanting to pass a string
68+
with a space into a placeholder, for example `{if 1 string with spaces}`, which will cause an error. In these cases, double quotes can be used: `{if 1 "string with spaces"}`
69+
will work perfectly fine. There are placeholders that need reference items, to achieve that, there are 8 slots in the text module's UI on the left.
70+
Items can be inserted/extracted from these slots automatically using the `ender` placeholder by interacting with Ender Item Links.<br>
71+
72+
!!! tip "The full list of placeholders with explanations on what they do and usage examples can be found in-game in the text module or computer monitor UI on the left."
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
title: Central Monitor & Placeholder System
3+
---
4+
5+
### Custom monitor modules
6+
If you want to add a monitor module, simply attach a component that implements `IMonitorModuleItem` to your `ComponentItem`.
7+
Modules can have a custom UI, can be ticked (in a placeholder or not) and, most importantly, rendered.
8+
??? example "Example of a custom module in Java"
9+
```java
10+
public class ExampleModuleBehaviour implements IMonitorModuleItem {
11+
@Override
12+
public String getType() {
13+
// can be any string, this is currently only used for CC: Tweaked compat
14+
return "example";
15+
}
16+
17+
@Override
18+
public void tick(ItemStack stack, CentralMonitorMachine machine, MonitorGroup group) {
19+
// this is only called on the logical server
20+
// put all of your module's logic here instead of in getRenderer(stack)
21+
// can also be left completely empty (like in the image module)
22+
}
23+
24+
@Override
25+
public void tickInPlaceholder(ItemStack stack, PlaceholderContext context) {
26+
// this is also only called on the logical server, but only when a placeholder accesses this module and wants to render it
27+
// this *isn't* called on each tick
28+
// you can even put the same code here as in the tick() method, like the text module does
29+
}
30+
31+
@Override
32+
public IMonitorRenderer getRenderer(ItemStack stack) {
33+
// this is only called on the logical client
34+
// should return a new instance of the renderer for this module (not null)
35+
// for examples of renderer code look in the GTCEu Modern github:
36+
// https://github.com/GregTechCEu/GregTech-Modern/tree/1.20.1/src/main/java/com/gregtechceu/gtceu/client/renderer
37+
return new MonitorTextRenderer(MultiLineComponent.of("this text is displayed on the monitor"), 1.0);
38+
}
39+
40+
@Override
41+
public Widget createUIWidget(ItemStack stack, CentralMonitorMachine machine, MonitorGroup group) {
42+
// should create the UI for your module and return it
43+
// if the module doesn't need a UI just return new WidgetGroup()
44+
return new WidgetGroup();
45+
}
46+
}
47+
```
48+
49+
!!! info "For info on the placeholder system itself, see [the gameplay wiki page](../../Gameplay/Central-Monitor.md)"
50+
51+
### Adding custom placeholders
52+
53+
Placeholders can be added by calling `PlaceholderHandler.addPlaceholder(...)` at any point during runtime (preferably at mod init time).
54+
They can take any number of arguments in the form of a `List<MultiLineComponent>`. They also take an instance of `PlaceholderContext` and
55+
must return a `MultiLineComponent`. Placeholders can also render literally anything, not only text, using `MultiLineComponent.addRenderer()`,
56+
`GraphicsComponent` and an `IPlaceholderRenderer` (that has to be registered separately using `PlaceholderHandler.addRenderer(...)`)
57+
58+
??? example "Example of a `sum` placeholder in Java"
59+
```java
60+
public class Example {
61+
// you should call this function at mod initialization
62+
public static void addPlaceholders() {
63+
int priority = 1; // by default the priority of all placeholders is 0 (you don't have to specify it)
64+
PlaceholderHandler.addPlaceholder(new Placeholder("sum", priority) {
65+
@Override
66+
public MultiLineComponent apply(PlaceholderContext ctx, List<MultiLineComponent> args) throws PlaceholderException {
67+
PlaceholderUtils.checkArgs(args, 2); // check that there are exactly 2 arguments
68+
double a = PlaceholderUtils.toDouble(args.get(0));
69+
double b = PlaceholderUtils.toDouble(args.get(1));
70+
return MultiLineComponent.literal(a + b);
71+
}
72+
});
73+
// you can call addPlaceholder as many times as you need
74+
// if you want to override an existing placeholder, simply add a new one with the same name and a higher or equal priority
75+
}
76+
}
77+
```
78+
79+
!!! tip "Placeholder exceptions"
80+
Any runtime exception that occurs while processing a placeholder will be caught and even displayed to the player.
81+
Instead of relying on runtime exceptions though, you should throw any subclass of `PlaceholderException`, for example
82+
`InvalidNumberException` or `MissingItemException`. All the `PlaceholderUtils` methods throw these, so you should use them
83+
instead of calling `parseDouble` yourself, for example.
84+
85+
!!! note "Placeholder data"
86+
If your placeholder needs to save any data specific to the placeholder caller, you can use `getData(ctx)` at any point in
87+
a placeholder. It will return a `CompoundTag` that is automatically saved, and you're free to modify it in whatever way you want.
88+
89+
### Placeholder graphics
90+
91+
You may have noticed, that some placeholders output graphics instead of text, for example `rect` or `quad`.
92+
To achieve that you have to write your own class that implements `IPlaceholderRenderer`, or use an existing one.
93+
They work similarly to normal renderers, except you can pass a `CompoundTag` into them from your placeholder.
94+
To register one, call `PlaceholderHandler.addRenderer("put_id_here", new YourRendererClassHere())`.
95+
After that, you can reference it from any placeholder by calling `output.addGraphics(new GraphicsComponent(x, y, "put_id_here", renderData)`
96+
on the object that your placeholder will return. `renderData` is the same `CompoundTag` that will be passed into your renderer as an argument.
97+
This is done to avoid calling rendering code on the server side, as all placeholders are processed server-side only. A neat side effect of that
98+
is that all players will (almost always) see the same thing on the monitor.
99+
100+
!!! warning "Graphics do not work on the Computer Monitor Cover"
101+
102+
### Placeholder parsing
103+
104+
You may want to add something that needs to parse a string containing placeholders. To achieve that, you can use
105+
`PlaceholderHandler.processPlaceholders(string, context)`. You can also use `PlaceholderHandler.placeholderExists(name)`
106+
to check if a placeholder exists, or `PlaceholderHandler.getAllPlaceholderNames()` to get all placeholders.
107+
To get a `PlaceholderContext`, you just have to call its constructor (it takes in basic parameters like `Level`, `BlockPos`, etc., most of which can be `null`).

src/main/java/com/gregtechceu/gtceu/api/item/component/IMonitorModuleItem.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.gregtechceu.gtceu.api.item.component;
22

3+
import com.gregtechceu.gtceu.api.placeholder.PlaceholderContext;
34
import com.gregtechceu.gtceu.client.renderer.monitor.IMonitorRenderer;
45
import com.gregtechceu.gtceu.common.machine.multiblock.electric.CentralMonitorMachine;
56
import com.gregtechceu.gtceu.common.machine.multiblock.electric.monitor.MonitorGroup;
@@ -12,7 +13,9 @@ public interface IMonitorModuleItem extends IItemComponent {
1213

1314
default void tick(ItemStack stack, CentralMonitorMachine machine, MonitorGroup group) {}
1415

15-
IMonitorRenderer getRenderer(ItemStack stack, CentralMonitorMachine machine, MonitorGroup group);
16+
default void tickInPlaceholder(ItemStack stack, PlaceholderContext context) {}
17+
18+
IMonitorRenderer getRenderer(ItemStack stack);
1619

1720
Widget createUIWidget(ItemStack stack, CentralMonitorMachine machine, MonitorGroup group);
1821

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.gregtechceu.gtceu.api.placeholder;
2+
3+
import com.gregtechceu.gtceu.GTCEu;
4+
import com.gregtechceu.gtceu.client.renderer.monitor.IMonitorRenderer;
5+
import com.gregtechceu.gtceu.common.machine.multiblock.electric.CentralMonitorMachine;
6+
import com.gregtechceu.gtceu.common.machine.multiblock.electric.monitor.MonitorGroup;
7+
8+
import net.minecraft.client.renderer.MultiBufferSource;
9+
import net.minecraft.nbt.CompoundTag;
10+
import net.minecraft.nbt.NbtOps;
11+
import net.minecraft.nbt.Tag;
12+
13+
import com.mojang.blaze3d.vertex.PoseStack;
14+
import com.mojang.serialization.Codec;
15+
import com.mojang.serialization.codecs.RecordCodecBuilder;
16+
17+
import java.util.function.Supplier;
18+
19+
public record GraphicsComponent(float x, float y, float x2, float y2, String rendererId, CompoundTag renderData)
20+
implements Supplier<IMonitorRenderer> {
21+
22+
public GraphicsComponent(double x, double y, double x2, double y2, String rendererId, CompoundTag renderData) {
23+
this((float) x, (float) y, (float) x2, (float) y2, rendererId, renderData);
24+
}
25+
26+
public static final Codec<GraphicsComponent> CODEC = RecordCodecBuilder.create(instance -> instance.group(
27+
Codec.FLOAT.fieldOf("x").forGetter(GraphicsComponent::x),
28+
Codec.FLOAT.fieldOf("y").forGetter(GraphicsComponent::y),
29+
Codec.FLOAT.fieldOf("x2").forGetter(GraphicsComponent::x2),
30+
Codec.FLOAT.fieldOf("y2").forGetter(GraphicsComponent::y2),
31+
Codec.STRING.fieldOf("rendererId").forGetter(GraphicsComponent::rendererId),
32+
CompoundTag.CODEC.fieldOf("renderData").forGetter(GraphicsComponent::renderData))
33+
.apply(instance, GraphicsComponent::new));
34+
35+
@Override
36+
public IMonitorRenderer get() {
37+
return new IMonitorRenderer() {
38+
39+
private final IMonitorRenderer renderer = PlaceholderHandler.getRenderer(rendererId, renderData);
40+
41+
@Override
42+
public void render(CentralMonitorMachine machine, MonitorGroup group, float partialTick,
43+
PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) {
44+
poseStack.pushPose();
45+
poseStack.translate(x, y, 0);
46+
assert this.renderer != null;
47+
this.renderer.render(machine, group, partialTick, poseStack, buffer, packedLight, packedOverlay);
48+
poseStack.popPose();
49+
}
50+
};
51+
}
52+
53+
public Tag toTag() {
54+
return CODEC.encodeStart(NbtOps.INSTANCE, this).getOrThrow(false, GTCEu.LOGGER::error);
55+
}
56+
57+
public static GraphicsComponent fromTag(Tag tag) {
58+
return CODEC.decode(NbtOps.INSTANCE, tag).getOrThrow(false, GTCEu.LOGGER::error).getFirst();
59+
}
60+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.gregtechceu.gtceu.api.placeholder;
2+
3+
import com.gregtechceu.gtceu.common.machine.multiblock.electric.CentralMonitorMachine;
4+
import com.gregtechceu.gtceu.common.machine.multiblock.electric.monitor.MonitorGroup;
5+
6+
import net.minecraft.client.renderer.MultiBufferSource;
7+
import net.minecraft.nbt.CompoundTag;
8+
9+
import com.mojang.blaze3d.vertex.PoseStack;
10+
11+
public interface IPlaceholderRenderer {
12+
13+
void render(CentralMonitorMachine machine, MonitorGroup group, float partialTick, PoseStack poseStack,
14+
MultiBufferSource buffer, int packedLight, int packedOverlay, CompoundTag tag);
15+
}

0 commit comments

Comments
 (0)