Skip to content

Commit 4cd54f7

Browse files
committed
Rewrite config in Java
AbstractConfigScreen.renderHearts: fix Heart.previousAngle not being used ConfigManager.finalize(): rename to finish() to avoid overriding JDK method
1 parent 1daeed5 commit 4cd54f7

File tree

18 files changed

+492
-515
lines changed

18 files changed

+492
-515
lines changed

build-logic/src/main/java/juuxel/adorn/gradle/action/InlineServiceLoader.java

+1-64
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package juuxel.adorn.gradle.action;
22

33
import juuxel.adorn.gradle.asm.Asm;
4-
import juuxel.adorn.gradle.asm.InsnPattern;
5-
import juuxel.adorn.gradle.asm.MethodBodyPattern;
64
import org.gradle.api.Action;
75
import org.gradle.api.Task;
86
import org.objectweb.asm.ConstantDynamic;
@@ -12,9 +10,7 @@
1210
import org.objectweb.asm.tree.AnnotationNode;
1311
import org.objectweb.asm.tree.InsnNode;
1412
import org.objectweb.asm.tree.LdcInsnNode;
15-
import org.objectweb.asm.tree.MethodInsnNode;
1613
import org.objectweb.asm.tree.MethodNode;
17-
import org.objectweb.asm.tree.TypeInsnNode;
1814

1915
import java.io.IOException;
2016
import java.io.UncheckedIOException;
@@ -23,26 +19,6 @@
2319
import java.util.Objects;
2420

2521
public final class InlineServiceLoader implements Action<Task> {
26-
private static final MethodBodyPattern INLINE_PATTERN = new MethodBodyPattern(
27-
List.of(
28-
new InsnPattern<>(LdcInsnNode.class, ldc -> ldc.cst instanceof Type),
29-
new InsnPattern<>(
30-
MethodInsnNode.class,
31-
method -> method.getOpcode() == Opcodes.INVOKESTATIC
32-
&& "java/util/ServiceLoader".equals(method.owner)
33-
&& "load".equals(method.name)
34-
&& "(Ljava/lang/Class;)Ljava/util/ServiceLoader;".equals(method.desc)
35-
),
36-
new InsnPattern<>(
37-
MethodInsnNode.class,
38-
method -> method.getOpcode() == Opcodes.INVOKEVIRTUAL
39-
&& "java/util/ServiceLoader".equals(method.owner)
40-
&& "findFirst".equals(method.name)
41-
&& "()Ljava/util/Optional;".equals(method.desc)
42-
)
43-
)
44-
);
45-
4622
@Override
4723
public void execute(Task task) {
4824
try {
@@ -61,39 +37,6 @@ private void run(Task task) throws IOException {
6137
}
6238

6339
for (MethodNode method : classNode.methods) {
64-
boolean success = INLINE_PATTERN.match(method, ctx -> {
65-
var ldc = (LdcInsnNode) ctx.instructions().get(0);
66-
var type = ((Type) ldc.cst).getInternalName().replace('/', '.');
67-
var serviceFile = filer.apply("META-INF/services/" + type);
68-
if (Files.exists(serviceFile)) {
69-
try {
70-
var serviceImpls = Files.readAllLines(serviceFile);
71-
if (serviceImpls.size() != 1) throw new IllegalArgumentException("Service file " + type + " must have exactly one provider");
72-
var implType = serviceImpls.get(0).replace('.', '/');
73-
ctx.replaceWith(
74-
List.of(
75-
new TypeInsnNode(Opcodes.NEW, implType),
76-
new InsnNode(Opcodes.DUP),
77-
new MethodInsnNode(Opcodes.INVOKESPECIAL, implType, "<init>", "()V"),
78-
new MethodInsnNode(
79-
Opcodes.INVOKESTATIC,
80-
"java/util/Optional",
81-
"of",
82-
"(Ljava/lang/Object;)Ljava/util/Optional;"
83-
)
84-
)
85-
);
86-
} catch (IOException e) {
87-
throw new UncheckedIOException(e);
88-
}
89-
}
90-
});
91-
92-
if (success) {
93-
method.maxStack++;
94-
return true;
95-
}
96-
9740
if (method.invisibleAnnotations == null) continue;
9841
for (AnnotationNode annotation : method.invisibleAnnotations) {
9942
if ("Ljuuxel/adorn/util/InlineServices$Getter;".equals(annotation.desc)) {
@@ -125,13 +68,7 @@ private void run(Task task) throws IOException {
12568
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/Object;",
12669
false
12770
),
128-
new Handle(
129-
Opcodes.H_NEWINVOKESPECIAL,
130-
implType,
131-
"<init>",
132-
"()V",
133-
false
134-
)
71+
new Handle(Opcodes.H_NEWINVOKESPECIAL, implType, "<init>", "()V", false)
13572
)));
13673
method.instructions.add(new InsnNode(Opcodes.ARETURN));
13774
return true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package juuxel.adorn.client.gui.screen;
2+
3+
import com.mojang.blaze3d.systems.RenderSystem;
4+
import juuxel.adorn.AdornCommon;
5+
import juuxel.adorn.client.gui.widget.ConfigScreenHeading;
6+
import juuxel.adorn.config.ConfigManager;
7+
import juuxel.adorn.util.Colors;
8+
import juuxel.adorn.util.Displayable;
9+
import juuxel.adorn.util.PropertyRef;
10+
import juuxel.adorn.util.animation.AnimationEngine;
11+
import juuxel.adorn.util.animation.AnimationTask;
12+
import net.minecraft.client.gui.DrawContext;
13+
import net.minecraft.client.gui.screen.NoticeScreen;
14+
import net.minecraft.client.gui.screen.Screen;
15+
import net.minecraft.client.gui.tooltip.Tooltip;
16+
import net.minecraft.client.gui.widget.CyclingButtonWidget;
17+
import net.minecraft.text.Text;
18+
import net.minecraft.util.Formatting;
19+
import net.minecraft.util.Identifier;
20+
import net.minecraft.util.math.MathHelper;
21+
import net.minecraft.util.math.RotationAxis;
22+
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.util.Random;
26+
27+
public abstract class AbstractConfigScreen extends Screen {
28+
private static final int CONFIG_BUTTON_START_Y = 40;
29+
public static final int BUTTON_HEIGHT = 20;
30+
public static final int BUTTON_GAP = 4;
31+
public static final int BUTTON_SPACING = BUTTON_HEIGHT + BUTTON_GAP;
32+
private static final int HEART_SIZE = 12;
33+
private static final int[] HEART_COLORS = new int[] {
34+
0xFF_FF0000, // Red
35+
0xFF_FC8702, // Orange
36+
0xFF_FFFF00, // Yellow
37+
0xFF_A7FC58, // Green
38+
0xFF_2D61FC, // Blue
39+
0xFF_A002FC, // Purple
40+
0xFF_58E9FC, // Light blue
41+
0xFF_FCA1DF, // Pink
42+
};
43+
private static final Identifier HEART_TEXTURE = AdornCommon.id("textures/gui/heart.png");
44+
private static final double MIN_HEART_SPEED = 0.05;
45+
private static final double MAX_HEART_SPEED = 1.5;
46+
private static final double MAX_HEART_ANGULAR_SPEED = 0.07;
47+
private static final int HEART_CHANCE = 65;
48+
49+
private final Screen parent;
50+
private final Random random = new Random();
51+
private final List<Heart> hearts = new ArrayList<>();
52+
private boolean restartRequired = false;
53+
private final AnimationEngine animationEngine = new AnimationEngine();
54+
55+
/** The Y-coordinate of the next config option or heading to be added. */
56+
protected int nextChildY = CONFIG_BUTTON_START_Y;
57+
58+
protected AbstractConfigScreen(Text title, Screen parent) {
59+
super(title);
60+
this.parent = parent;
61+
animationEngine.add(new HeartAnimationTask());
62+
}
63+
64+
@Override
65+
protected void init() {
66+
nextChildY = CONFIG_BUTTON_START_Y;
67+
animationEngine.start();
68+
}
69+
70+
@Override
71+
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
72+
renderBackground(context, mouseX, mouseY, delta);
73+
synchronized (hearts) {
74+
renderHearts(context, delta);
75+
}
76+
context.drawCenteredTextWithShadow(textRenderer, title, width / 2, 20, Colors.WHITE);
77+
super.render(context, mouseX, mouseY, delta);
78+
}
79+
80+
private void renderHearts(DrawContext context, float delta) {
81+
for (var heart : hearts) {
82+
RenderSystem.setShaderColor(Colors.redOf(heart.color), Colors.greenOf(heart.color), Colors.blueOf(heart.color), 1f);
83+
var matrices = context.getMatrices();
84+
matrices.push();
85+
matrices.translate(heart.x, MathHelper.lerp(delta, heart.previousY, heart.y), 0.0);
86+
matrices.translate(0.5 * HEART_SIZE, 0.5 * HEART_SIZE, 0.0);
87+
var angle = MathHelper.lerp(delta, heart.previousAngle, heart.angle);
88+
matrices.multiply(RotationAxis.POSITIVE_Z.rotation((float) angle));
89+
matrices.translate(-0.5 * HEART_SIZE, -0.5 * HEART_SIZE, 0.0);
90+
context.drawTexture(HEART_TEXTURE, 0, 0, HEART_SIZE, HEART_SIZE, 0f, 0f, 8, 8, 8, 8);
91+
matrices.pop();
92+
}
93+
94+
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
95+
}
96+
97+
@Override
98+
public void close() {
99+
client.setScreen(restartRequired ? new NoticeScreen(
100+
() -> client.setScreen(parent),
101+
Text.translatable("gui.adorn.config.restart_required.title"),
102+
Text.translatable("gui.adorn.config.restart_required.message"),
103+
Text.translatable("gui.ok"),
104+
true
105+
) : parent);
106+
}
107+
108+
@Override
109+
public void removed() {
110+
animationEngine.stop();
111+
}
112+
113+
private void tickHearts() {
114+
var iter = hearts.iterator();
115+
while (iter.hasNext()) {
116+
var heart = iter.next();
117+
118+
if (heart.y - HEART_SIZE > height) {
119+
iter.remove();
120+
} else {
121+
heart.move();
122+
}
123+
}
124+
125+
if (random.nextInt(HEART_CHANCE) == 0) {
126+
int x = random.nextInt(width);
127+
int color = HEART_COLORS[random.nextInt(HEART_COLORS.length)];
128+
double speed = random.nextDouble(MIN_HEART_SPEED, MAX_HEART_SPEED);
129+
double angularSpeed = random.nextDouble(-MAX_HEART_ANGULAR_SPEED, MAX_HEART_ANGULAR_SPEED);
130+
hearts.add(new Heart(x, -HEART_SIZE, color, speed, angularSpeed));
131+
}
132+
}
133+
134+
private <T> CyclingButtonWidget<T> createConfigButton(CyclingButtonWidget.Builder<T> builder, int x, int y, int width, PropertyRef<T> property, boolean restartRequired) {
135+
return builder
136+
.tooltip(value -> {
137+
var text = Text.translatable(getTooltipTranslationKey(property.getName()));
138+
if (restartRequired) {
139+
text.append(Text.literal("\n"))
140+
.append(Text.translatable("gui.adorn.config.requires_restart").formatted(Formatting.ITALIC, Formatting.GOLD));
141+
}
142+
return Tooltip.of(text);
143+
})
144+
.build(x, y, width, BUTTON_HEIGHT, Text.translatable(getOptionTranslationKey(property.getName())), (button, value) -> {
145+
property.set(value);
146+
ConfigManager.get().save();
147+
148+
if (restartRequired) {
149+
this.restartRequired = true;
150+
}
151+
});
152+
}
153+
154+
protected void addConfigToggle(int width, PropertyRef<Boolean> property) {
155+
addConfigToggle(width, property, false);
156+
}
157+
158+
protected void addConfigToggle(int width, PropertyRef<Boolean> property, boolean restartRequired) {
159+
var button = createConfigButton(
160+
CyclingButtonWidget.onOffBuilder(property.get()),
161+
(this.width - width) / 2, nextChildY, width, property, restartRequired
162+
);
163+
addDrawableChild(button);
164+
nextChildY += BUTTON_SPACING;
165+
}
166+
167+
protected <T extends Displayable> void addConfigButton(int width, PropertyRef<T> property, List<T> values) {
168+
addConfigButton(width, property, values, false);
169+
}
170+
171+
protected <T extends Displayable> void addConfigButton(int width, PropertyRef<T> property, List<T> values, boolean restartRequired) {
172+
var button = createConfigButton(
173+
CyclingButtonWidget.<T>builder(Displayable::getDisplayName).values(values).initially(property.get()),
174+
(this.width - width) / 2, nextChildY, width, property, restartRequired
175+
);
176+
addDrawableChild(button);
177+
nextChildY += BUTTON_SPACING;
178+
}
179+
180+
protected void addHeading(Text text, int width) {
181+
addDrawable(new ConfigScreenHeading(text, (this.width - width) / 2, nextChildY, width));
182+
nextChildY += ConfigScreenHeading.HEIGHT;
183+
}
184+
185+
protected String getOptionTranslationKey(String name) {
186+
return "gui.adorn.config.option." + name;
187+
}
188+
189+
private String getTooltipTranslationKey(String name) {
190+
return getOptionTranslationKey(name) + ".description";
191+
}
192+
193+
private static final class Heart {
194+
private final int x;
195+
private double y;
196+
private final int color;
197+
private final double speed;
198+
private final double angularSpeed;
199+
private double previousY;
200+
private double previousAngle = 0.0;
201+
private double angle = 0.0;
202+
203+
private Heart(int x, double y, int color, double speed, double angularSpeed) {
204+
this.x = x;
205+
this.y = y;
206+
this.color = color;
207+
this.speed = speed;
208+
this.angularSpeed = angularSpeed;
209+
previousY = y;
210+
}
211+
212+
private void move() {
213+
previousY = y;
214+
y += speed;
215+
previousAngle = angle;
216+
angle = (angle + angularSpeed) % MathHelper.TAU;
217+
}
218+
}
219+
220+
private final class HeartAnimationTask implements AnimationTask {
221+
@Override
222+
public boolean isAlive() {
223+
return true;
224+
}
225+
226+
@Override
227+
public void tick() {
228+
synchronized (hearts) {
229+
tickHearts();
230+
}
231+
}
232+
}
233+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package juuxel.adorn.client.gui.screen;
2+
3+
import juuxel.adorn.config.ConfigManager;
4+
import juuxel.adorn.util.PropertyRef;
5+
import net.minecraft.client.gui.screen.Screen;
6+
import net.minecraft.client.gui.widget.ButtonWidget;
7+
import net.minecraft.screen.ScreenTexts;
8+
import net.minecraft.text.Text;
9+
10+
public final class GameRuleDefaultsScreen extends AbstractConfigScreen {
11+
private static final int BUTTON_WIDTH = 250;
12+
13+
public GameRuleDefaultsScreen(Screen parent) {
14+
super(Text.translatable("gui.adorn.config.game_rule_defaults"), parent);
15+
}
16+
17+
@Override
18+
protected void init() {
19+
super.init();
20+
var config = ConfigManager.config();
21+
addConfigToggle(BUTTON_WIDTH, PropertyRef.ofField(config.gameRuleDefaults, "skipNightOnSofas"));
22+
addConfigToggle(BUTTON_WIDTH, PropertyRef.ofField(config.gameRuleDefaults, "infiniteKitchenSinks"));
23+
addConfigToggle(BUTTON_WIDTH, PropertyRef.ofField(config.gameRuleDefaults, "dropLockedTradingStations"));
24+
addDrawableChild(
25+
ButtonWidget.builder(ScreenTexts.BACK, button -> close())
26+
.position(this.width / 2 - 100, this.height - 27)
27+
.size(200, 20)
28+
.build()
29+
);
30+
}
31+
32+
@Override
33+
protected String getOptionTranslationKey(String name) {
34+
return "gamerule.adorn:" + name;
35+
}
36+
}

0 commit comments

Comments
 (0)