feat(gui): add in-game minimal configuration GUI, keybind, and ModMenu integration

This commit is contained in:
katR 2026-09-19 22:48:18 +05:30
parent 86f3640982
commit c5dba5d65d
9 changed files with 267 additions and 3 deletions

View file

@ -17,6 +17,7 @@ dependencies {
compileOnly fileTree(dir: 'libs/fabric-api', include: ['*.jar']) compileOnly fileTree(dir: 'libs/fabric-api', include: ['*.jar'])
compileOnly fileTree(dir: "${System.getProperty('user.home')}/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries", include: ['**/*.jar']) compileOnly fileTree(dir: "${System.getProperty('user.home')}/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries", include: ['**/*.jar'])
compileOnly fileTree(dir: "${System.getProperty('user.home')}/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/Performium/minecraft/mods", include: ['modmenu-*.jar'])
} }
processResources { processResources {

View file

@ -13,7 +13,7 @@ import java.util.Objects;
public final class BaritoneActionExecutor implements ActionExecutorBridge { public final class BaritoneActionExecutor implements ActionExecutorBridge {
private final Minecraft client; private final Minecraft client;
private final boolean fairMode; private boolean fairMode;
private String currentAction = null; private String currentAction = null;
private static final String[] WOOD_LOGS = { private static final String[] WOOD_LOGS = {
@ -36,6 +36,11 @@ public final class BaritoneActionExecutor implements ActionExecutorBridge {
configureBaritoneSettings(); configureBaritoneSettings();
} }
public void setFairMode(boolean fairMode) {
this.fairMode = fairMode;
configureBaritoneSettings();
}
public void configureBaritoneSettings() { public void configureBaritoneSettings() {
try { try {
var settings = BaritoneAPI.getSettings(); var settings = BaritoneAPI.getSettings();

View file

@ -11,6 +11,7 @@ public final class FabricInputBridge implements InputBridge {
private final Minecraft client; private final Minecraft client;
private final KeyMapping killSwitchKey; private final KeyMapping killSwitchKey;
private final KeyMapping hudToggleKey; private final KeyMapping hudToggleKey;
private final KeyMapping configGuiKey;
public FabricInputBridge(Minecraft client) { public FabricInputBridge(Minecraft client) {
this.client = client; this.client = client;
@ -24,6 +25,11 @@ public final class FabricInputBridge implements InputBridge {
GLFW.GLFW_KEY_F9, GLFW.GLFW_KEY_F9,
KeyMapping.Category.MISC KeyMapping.Category.MISC
)); ));
this.configGuiKey = KeyMappingHelper.registerKeyMapping(new KeyMapping(
"key.jevplayer.config_gui",
GLFW.GLFW_KEY_F10,
KeyMapping.Category.MISC
));
} }
public boolean wasKillSwitchPressed() { public boolean wasKillSwitchPressed() {
@ -34,6 +40,10 @@ public final class FabricInputBridge implements InputBridge {
return hudToggleKey.consumeClick(); return hudToggleKey.consumeClick();
} }
public boolean wasConfigGuiPressed() {
return configGuiKey.consumeClick();
}
@Override @Override
public void releaseAllInputs() { public void releaseAllInputs() {
if (client.options == null) return; if (client.options == null) return;

View file

@ -15,13 +15,17 @@ import java.util.*;
public final class FabricWorldSensor implements WorldSensor { public final class FabricWorldSensor implements WorldSensor {
private final Minecraft client; private final Minecraft client;
private final boolean fairMode; private boolean fairMode;
public FabricWorldSensor(Minecraft client, boolean fairMode) { public FabricWorldSensor(Minecraft client, boolean fairMode) {
this.client = client; this.client = client;
this.fairMode = fairMode; this.fairMode = fairMode;
} }
public void setFairMode(boolean fairMode) {
this.fairMode = fairMode;
}
@Override @Override
public StateSnapshot captureSnapshot(long nowMs) { public StateSnapshot captureSnapshot(long nowMs) {
var player = client.player; var player = client.player;

View file

@ -68,6 +68,23 @@ public class JevPlayerClientMod implements ClientModInitializer {
return instance; return instance;
} }
public JevPlayerConfig getConfig() {
return config;
}
public void reloadConfig() {
loadConfig();
if (engine != null) {
engine.reloadConfig(this.config);
}
if (worldSensor != null) {
worldSensor.setFairMode(this.config.fairMode);
}
if (baritoneExecutor != null) {
baritoneExecutor.setFairMode(this.config.fairMode);
}
}
public void loadConfig() { public void loadConfig() {
File file = configPath.toFile(); File file = configPath.toFile();
if (file.exists()) { if (file.exists()) {
@ -115,6 +132,11 @@ public class JevPlayerClientMod implements ClientModInitializer {
inputBridge.sendChatMessage("§b[JevPlayer] HUD overlay " + (config.hudEnabled ? "§aEnabled" : "§cDisabled")); inputBridge.sendChatMessage("§b[JevPlayer] HUD overlay " + (config.hudEnabled ? "§aEnabled" : "§cDisabled"));
} }
// Check F10 Config GUI toggle
if (inputBridge.wasConfigGuiPressed()) {
client.setScreenAndShow(new JevPlayerConfigScreen(null));
}
// Advance ticks // Advance ticks
compositeExecutor.onTick(); compositeExecutor.onTick();
engine.tick(); engine.tick();
@ -146,10 +168,22 @@ public class JevPlayerClientMod implements ClientModInitializer {
return 1; return 1;
})) }))
.then(ClientCommands.literal("reload").executes(ctx -> { .then(ClientCommands.literal("reload").executes(ctx -> {
loadConfig(); reloadConfig();
ctx.getSource().sendFeedback(Component.literal("§b[JevPlayer] Configuration reloaded.")); ctx.getSource().sendFeedback(Component.literal("§b[JevPlayer] Configuration reloaded."));
return 1; return 1;
})) }))
.then(ClientCommands.literal("config").executes(ctx -> {
Minecraft.getInstance().execute(() -> {
Minecraft.getInstance().setScreenAndShow(new JevPlayerConfigScreen(null));
});
return 1;
}))
.then(ClientCommands.literal("gui").executes(ctx -> {
Minecraft.getInstance().execute(() -> {
Minecraft.getInstance().setScreenAndShow(new JevPlayerConfigScreen(null));
});
return 1;
}))
.then(ClientCommands.literal("test_gather_wood").executes(ctx -> { .then(ClientCommands.literal("test_gather_wood").executes(ctx -> {
ctx.getSource().sendFeedback(Component.literal("§e[JevPlayer] Testing Baritone wood gathering...")); ctx.getSource().sendFeedback(Component.literal("§e[JevPlayer] Testing Baritone wood gathering..."));
baritoneExecutor.startAction("gather_wood", worldSensor.captureSnapshot(System.currentTimeMillis())); baritoneExecutor.startAction("gather_wood", worldSensor.captureSnapshot(System.currentTimeMillis()));

View file

@ -0,0 +1,172 @@
package com.typesafe.jevplayer.adapter26_2;
import com.typesafe.jevplayer.core.config.JevPlayerConfig;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
public class JevPlayerConfigScreen extends Screen {
private final Screen parentScreen;
private final JevPlayerConfig config;
private boolean isTypeSafe;
private boolean fairMode;
private EditBox apiKeyEdit;
private EditBox modelEdit;
private EditBox baseUrlEdit;
private EditBox intervalEdit;
private EditBox maxSpendEdit;
private Button providerButton;
private Button fairModeButton;
private String statusMessage = null;
private int statusColor = 0xFF55FF55;
public JevPlayerConfigScreen(Screen parentScreen) {
super(Component.literal("JevPlayer Configuration"));
this.parentScreen = parentScreen;
this.config = JevPlayerClientMod.getInstance().getConfig();
this.isTypeSafe = "typesafe".equalsIgnoreCase(config.provider);
this.fairMode = config.fairMode;
}
private Component getProviderComponent() {
return Component.literal("Provider: " + (isTypeSafe ? "§bTypeSafe AI (Live)" : "§eMock (Simulated)"));
}
private Component getFairModeComponent() {
return Component.literal("Fair Perception: " + (fairMode ? "§aON (Player View Only)" : "§cOFF (All-Seeing)"));
}
@Override
protected void init() {
int centerX = this.width / 2;
int fieldW = 240;
int leftX = centerX - fieldW / 2;
int startY = 32;
// Provider Button
this.providerButton = addRenderableWidget(
Button.builder(getProviderComponent(), btn -> {
isTypeSafe = !isTypeSafe;
btn.setMessage(getProviderComponent());
}).bounds(leftX, startY, fieldW, 20).build()
);
// API Key Field
this.apiKeyEdit = new EditBox(this.font, leftX, startY + 36, fieldW, 20, Component.literal("API Key"));
this.apiKeyEdit.setMaxLength(256);
this.apiKeyEdit.setValue(config.apiKey != null ? config.apiKey : "");
addRenderableWidget(this.apiKeyEdit);
// Model Field
this.modelEdit = new EditBox(this.font, leftX, startY + 72, fieldW, 20, Component.literal("Model"));
this.modelEdit.setMaxLength(64);
this.modelEdit.setValue(config.model != null ? config.model : "jev-latest");
addRenderableWidget(this.modelEdit);
// Base URL Field
this.baseUrlEdit = new EditBox(this.font, leftX, startY + 108, fieldW, 20, Component.literal("Base URL"));
this.baseUrlEdit.setMaxLength(256);
this.baseUrlEdit.setValue(config.baseUrl != null ? config.baseUrl : "https://api.typesafe.ai/v1");
addRenderableWidget(this.baseUrlEdit);
// Fair Mode Button
this.fairModeButton = addRenderableWidget(
Button.builder(getFairModeComponent(), btn -> {
fairMode = !fairMode;
btn.setMessage(getFairModeComponent());
}).bounds(leftX, startY + 144, fieldW, 20).build()
);
// Interval and Max Spend (Two columns)
int halfW = (fieldW - 10) / 2;
this.intervalEdit = new EditBox(this.font, leftX, startY + 180, halfW, 20, Component.literal("Interval"));
this.intervalEdit.setMaxLength(10);
this.intervalEdit.setValue(String.valueOf(config.minDecisionIntervalMs));
addRenderableWidget(this.intervalEdit);
this.maxSpendEdit = new EditBox(this.font, leftX + halfW + 10, startY + 180, halfW, 20, Component.literal("Max Spend"));
this.maxSpendEdit.setMaxLength(10);
this.maxSpendEdit.setValue(String.valueOf(config.maxSpendUsd));
addRenderableWidget(this.maxSpendEdit);
// Save & Cancel Buttons
int btnY = Math.max(startY + 214, this.height - 28);
addRenderableWidget(
Button.builder(Component.literal("§aSave & Apply"), btn -> saveAndClose())
.bounds(leftX, btnY, halfW, 20).build()
);
addRenderableWidget(
Button.builder(Component.literal("§cCancel"), btn -> onClose())
.bounds(leftX + halfW + 10, btnY, halfW, 20).build()
);
}
@Override
public void extractRenderState(GuiGraphicsExtractor extractor, int mouseX, int mouseY, float delta) {
super.extractRenderState(extractor, mouseX, mouseY, delta);
int centerX = this.width / 2;
int fieldW = 240;
int leftX = centerX - fieldW / 2;
int startY = 32;
extractor.centeredText(this.font, Component.literal("§6§lJevPlayer Settings"), centerX, 12, 0xFFFFFFFF);
extractor.text(this.font, Component.literal("§7API Key (or env TYPESAFE_AI_API_KEY):"), leftX, startY + 25, 0xFFAAAAAA);
extractor.text(this.font, Component.literal("§7Model:"), leftX, startY + 61, 0xFFAAAAAA);
extractor.text(this.font, Component.literal("§7Base URL:"), leftX, startY + 97, 0xFFAAAAAA);
extractor.text(this.font, Component.literal("§7Min Interval (ms):"), leftX, startY + 169, 0xFFAAAAAA);
extractor.text(this.font, Component.literal("§7Max Spend ($):"), leftX + (fieldW - 10) / 2 + 10, startY + 169, 0xFFAAAAAA);
if (statusMessage != null) {
extractor.centeredText(this.font, Component.literal(statusMessage), centerX, this.height - 46, statusColor);
}
}
@Override
public void onClose() {
if (this.minecraft != null) {
this.minecraft.setScreenAndShow(this.parentScreen);
}
}
private void saveAndClose() {
try {
config.provider = isTypeSafe ? "typesafe" : "mock";
config.apiKey = apiKeyEdit.getValue().trim();
config.model = modelEdit.getValue().trim();
config.baseUrl = baseUrlEdit.getValue().trim();
config.fairMode = fairMode;
try {
config.minDecisionIntervalMs = Long.parseLong(intervalEdit.getValue().trim());
} catch (NumberFormatException ignored) {}
try {
config.maxSpendUsd = Double.parseDouble(maxSpendEdit.getValue().trim());
} catch (NumberFormatException ignored) {}
config.validate();
JevPlayerClientMod.getInstance().saveConfig();
JevPlayerClientMod.getInstance().reloadConfig();
if (this.minecraft != null && this.minecraft.player != null) {
this.minecraft.player.sendSystemMessage(
Component.literal("§a[JevPlayer] Settings saved! Active Provider: §f" + config.provider +
(isTypeSafe ? " §7(Key: §e" + config.getRedactedApiKey() + "§7)" : ""))
);
}
onClose();
} catch (Exception e) {
this.statusMessage = "§cFailed to save settings: " + e.getMessage();
this.statusColor = 0xFFFF5555;
}
}
}

View file

@ -0,0 +1,11 @@
package com.typesafe.jevplayer.adapter26_2;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
public class JevPlayerModMenu implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return JevPlayerConfigScreen::new;
}
}

View file

@ -17,6 +17,9 @@
"entrypoints": { "entrypoints": {
"client": [ "client": [
"com.typesafe.jevplayer.adapter26_2.JevPlayerClientMod" "com.typesafe.jevplayer.adapter26_2.JevPlayerClientMod"
],
"modmenu": [
"com.typesafe.jevplayer.adapter26_2.JevPlayerModMenu"
] ]
}, },
"depends": { "depends": {

View file

@ -100,6 +100,30 @@ public final class JevPlayerEngine implements AutoCloseable {
} }
} }
public void reloadConfig(JevPlayerConfig newConfig) {
if (newConfig != null) {
this.config.apiKey = newConfig.apiKey;
this.config.apiKeyEnvVar = newConfig.apiKeyEnvVar;
this.config.provider = newConfig.provider;
this.config.model = newConfig.model;
this.config.baseUrl = newConfig.baseUrl;
this.config.minDecisionIntervalMs = newConfig.minDecisionIntervalMs;
this.config.maxActionAgeMs = newConfig.maxActionAgeMs;
this.config.requestTimeoutMs = newConfig.requestTimeoutMs;
this.config.maxSpendUsd = newConfig.maxSpendUsd;
this.config.fairMode = newConfig.fairMode;
this.config.allowMultiplayer = newConfig.allowMultiplayer;
this.config.hudEnabled = newConfig.hudEnabled;
this.config.hudDebugPage = newConfig.hudDebugPage;
this.config.logLevel = newConfig.logLevel;
if (newConfig.confidenceThresholds != null) {
this.config.confidenceThresholds = new HashMap<>(newConfig.confidenceThresholds);
}
this.config.safeDefaultAction = newConfig.safeDefaultAction;
}
initProvider();
}
public void setDecisionProvider(DecisionProvider provider) { public void setDecisionProvider(DecisionProvider provider) {
this.decisionProvider = provider; this.decisionProvider = provider;
} }