feat(gui): add in-game minimal configuration GUI, keybind, and ModMenu integration
This commit is contained in:
parent
86f3640982
commit
c5dba5d65d
9 changed files with 267 additions and 3 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()));
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue