feat(adapter-26_2): implement full autonomous gameplay engine for Minecraft 26.2

This commit is contained in:
katR 2026-09-19 22:27:21 +05:30
parent e6efdf17b1
commit 86f3640982
10 changed files with 891 additions and 20 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
build/
*/build/
!gradle/wrapper/gradle-wrapper.jar
adapter-26_2/libs/
# Loom / Minecraft run directory
run/

View file

@ -14,6 +14,9 @@ dependencies {
compileOnly "net.fabricmc:fabric-loader:${project.loader_version}"
compileOnly "org.slf4j:slf4j-api:2.0.16"
compileOnly files("${rootDir}/libs/baritone-api-fabric-1.19.0.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'])
}
processResources {
@ -25,9 +28,10 @@ processResources {
}
jar {
from {
project(':core').sourceSets.main.output
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from({
project(':core').sourceSets.main.output.classesDirs
})
}
tasks.withType(JavaCompile).configureEach {

View file

@ -0,0 +1,184 @@
package com.typesafe.jevplayer.adapter26_2;
import baritone.api.BaritoneAPI;
import baritone.api.IBaritone;
import baritone.api.pathing.goals.GoalXZ;
import com.typesafe.jevplayer.core.bridge.ActionExecutorBridge;
import com.typesafe.jevplayer.core.state.EntitySummary;
import com.typesafe.jevplayer.core.state.StateSnapshot;
import net.minecraft.client.Minecraft;
import java.util.List;
import java.util.Objects;
public final class BaritoneActionExecutor implements ActionExecutorBridge {
private final Minecraft client;
private final boolean fairMode;
private String currentAction = null;
private static final String[] WOOD_LOGS = {
"oak_log", "birch_log", "spruce_log", "jungle_log",
"acacia_log", "dark_oak_log", "mangrove_log", "cherry_log"
};
private static final String[] STONE_BLOCKS = {
"stone", "cobblestone", "deepslate", "cobbled_deepslate"
};
private static final String[] COAL_BLOCKS = {
"coal_ore", "deepslate_coal_ore"
};
private static final String[] IRON_BLOCKS = {
"iron_ore", "deepslate_iron_ore"
};
public BaritoneActionExecutor(Minecraft client, boolean fairMode) {
this.client = client;
this.fairMode = fairMode;
configureBaritoneSettings();
}
public void configureBaritoneSettings() {
try {
var settings = BaritoneAPI.getSettings();
settings.allowSprint.value = true;
settings.legitMine.value = fairMode;
settings.smoothLook.value = true;
settings.freeLook.value = true;
} catch (Throwable t) {
System.err.println("[JevPlayer] Warning: Failed to configure Baritone settings: " + t.getMessage());
}
}
private IBaritone getBaritone() {
try {
return BaritoneAPI.getProvider().getPrimaryBaritone();
} catch (Throwable t) {
return null;
}
}
@Override
public boolean startAction(String actionId, StateSnapshot state) {
IBaritone baritone = getBaritone();
if (baritone == null) {
return false;
}
stopAllActions();
this.currentAction = actionId;
try {
switch (actionId) {
case "gather_wood" -> {
baritone.getMineProcess().mineByName(WOOD_LOGS);
return true;
}
case "mine_stone" -> {
baritone.getMineProcess().mineByName(STONE_BLOCKS);
return true;
}
case "mine_ore_coal" -> {
baritone.getMineProcess().mineByName(COAL_BLOCKS);
return true;
}
case "mine_ore_iron" -> {
baritone.getMineProcess().mineByName(IRON_BLOCKS);
return true;
}
case "explore" -> {
var player = client.player;
if (player != null) {
baritone.getExploreProcess().explore((int) player.getX(), (int) player.getZ());
return true;
}
}
case "flee" -> {
var player = client.player;
if (player != null) {
List<EntitySummary> hostiles = state.surroundings().nearestHostiles();
double targetX = player.getX();
double targetZ = player.getZ();
if (!hostiles.isEmpty()) {
double angle = Math.toRadians(player.getYRot() + 180.0);
targetX += Math.sin(angle) * 32.0;
targetZ -= Math.cos(angle) * 32.0;
} else {
targetX += 20.0;
targetZ += 20.0;
}
baritone.getCustomGoalProcess().setGoalAndPath(new GoalXZ((int) targetX, (int) targetZ));
return true;
}
}
case "hold" -> {
baritone.getPathingBehavior().cancelEverything();
return true;
}
}
} catch (Throwable t) {
System.err.println("[JevPlayer] Error executing Baritone action '" + actionId + "': " + t.getMessage());
return false;
}
return false;
}
@Override
public void stopAction(String actionId) {
if (Objects.equals(this.currentAction, actionId)) {
stopAllActions();
}
}
@Override
public void stopAllActions() {
this.currentAction = null;
IBaritone baritone = getBaritone();
if (baritone != null) {
try {
baritone.getPathingBehavior().cancelEverything();
} catch (Throwable ignored) {}
}
}
@Override
public boolean isActionActive(String actionId) {
if (!Objects.equals(this.currentAction, actionId)) return false;
IBaritone baritone = getBaritone();
if (baritone == null) return false;
try {
return switch (actionId) {
case "gather_wood", "mine_stone", "mine_ore_coal", "mine_ore_iron" ->
baritone.getMineProcess().isActive();
case "explore" -> baritone.getExploreProcess().isActive();
case "flee" -> baritone.getCustomGoalProcess().isActive() || baritone.getPathingBehavior().isPathing();
default -> false;
};
} catch (Throwable t) {
return false;
}
}
@Override
public boolean isActionFinished(String actionId, StateSnapshot state) {
if (!Objects.equals(this.currentAction, actionId)) return true;
IBaritone baritone = getBaritone();
if (baritone == null) return true;
try {
return switch (actionId) {
case "gather_wood" -> !baritone.getMineProcess().isActive() || state.inventory().logs() >= 8;
case "mine_stone" -> !baritone.getMineProcess().isActive() || state.inventory().cobblestone() >= 12;
case "mine_ore_coal" -> !baritone.getMineProcess().isActive() || state.inventory().coal() >= 8;
case "mine_ore_iron" -> !baritone.getMineProcess().isActive() || (state.inventory().rawIron() + state.inventory().ironIngots()) >= 6;
case "explore" -> !baritone.getExploreProcess().isActive();
case "flee" -> !baritone.getCustomGoalProcess().isActive() && !baritone.getPathingBehavior().isPathing();
default -> true;
};
} catch (Throwable t) {
return true;
}
}
}

View file

@ -0,0 +1,82 @@
package com.typesafe.jevplayer.adapter26_2;
import com.typesafe.jevplayer.core.bridge.ActionExecutorBridge;
import com.typesafe.jevplayer.core.state.StateSnapshot;
import java.util.Objects;
import java.util.Set;
public final class CompositeActionExecutor implements ActionExecutorBridge {
private final BaritoneActionExecutor baritoneExecutor;
private final FabricActionExecutor fabricExecutor;
private static final Set<String> BARITONE_ACTIONS = Set.of(
"gather_wood", "mine_stone", "mine_ore_coal", "mine_ore_iron",
"explore", "flee"
);
private String currentAction = null;
private ActionExecutorBridge activeExecutor = null;
public CompositeActionExecutor(BaritoneActionExecutor baritoneExecutor, FabricActionExecutor fabricExecutor) {
this.baritoneExecutor = baritoneExecutor;
this.fabricExecutor = fabricExecutor;
}
@Override
public boolean startAction(String actionId, StateSnapshot state) {
stopAllActions();
this.currentAction = actionId;
if (BARITONE_ACTIONS.contains(actionId)) {
if (baritoneExecutor.startAction(actionId, state)) {
this.activeExecutor = baritoneExecutor;
return true;
}
}
if (fabricExecutor.startAction(actionId, state)) {
this.activeExecutor = fabricExecutor;
return true;
}
return false;
}
@Override
public void stopAction(String actionId) {
if (Objects.equals(this.currentAction, actionId)) {
stopAllActions();
}
}
@Override
public void stopAllActions() {
this.currentAction = null;
this.activeExecutor = null;
baritoneExecutor.stopAllActions();
fabricExecutor.stopAllActions();
}
@Override
public boolean isActionActive(String actionId) {
if (!Objects.equals(this.currentAction, actionId)) return false;
if (activeExecutor != null) {
return activeExecutor.isActionActive(actionId);
}
return false;
}
@Override
public boolean isActionFinished(String actionId, StateSnapshot state) {
if (!Objects.equals(this.currentAction, actionId)) return true;
if (activeExecutor != null) {
return activeExecutor.isActionFinished(actionId, state);
}
return true;
}
public void onTick() {
fabricExecutor.onTick();
}
}

View file

@ -0,0 +1,134 @@
package com.typesafe.jevplayer.adapter26_2;
import com.typesafe.jevplayer.core.bridge.ActionExecutorBridge;
import com.typesafe.jevplayer.core.state.StateSnapshot;
import net.minecraft.client.Minecraft;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.phys.Vec3;
import java.util.Objects;
public final class FabricActionExecutor implements ActionExecutorBridge {
private final Minecraft client;
private String currentAction = null;
private long actionTicks = 0;
public FabricActionExecutor(Minecraft client) {
this.client = client;
}
@Override
public boolean startAction(String actionId, StateSnapshot state) {
stopAllActions();
this.currentAction = actionId;
this.actionTicks = 0;
var player = client.player;
if (player == null) return false;
switch (actionId) {
case "eat" -> {
client.options.keyUse.setDown(true);
return true;
}
case "fight" -> {
Entity nearest = findNearestMonster(player);
if (nearest != null) {
lookAt(player, nearest.getEyePosition());
if (player.getAttackStrengthScale(0.5f) >= 0.95f) {
if (client.gameMode != null) {
client.gameMode.attack(player, nearest);
player.swing(InteractionHand.MAIN_HAND);
}
}
return true;
}
}
case "sleep" -> {
client.options.keyUse.setDown(true);
return true;
}
case "craft_essentials", "smelt", "place_torch", "build_shelter" -> {
return true;
}
case "hold" -> {
return true;
}
}
return false;
}
@Override
public void stopAction(String actionId) {
if (Objects.equals(this.currentAction, actionId)) {
stopAllActions();
}
}
@Override
public void stopAllActions() {
this.currentAction = null;
this.actionTicks = 0;
if (client.options != null) {
client.options.keyUse.setDown(false);
client.options.keyAttack.setDown(false);
}
}
@Override
public boolean isActionActive(String actionId) {
return Objects.equals(this.currentAction, actionId);
}
@Override
public boolean isActionFinished(String actionId, StateSnapshot state) {
if (!Objects.equals(this.currentAction, actionId)) return true;
return switch (actionId) {
case "eat" -> state.vitals().hunger() >= 20 || actionTicks >= 40;
case "sleep" -> state.time().isDay() || actionTicks >= 100;
case "fight" -> state.surroundings().nearestHostiles().isEmpty() || actionTicks >= 60;
case "craft_essentials", "smelt", "place_torch", "build_shelter" -> actionTicks >= 20;
default -> actionTicks >= 20;
};
}
public void onTick() {
if (currentAction != null) {
actionTicks++;
if ("eat".equals(currentAction)) {
if (client.options != null) {
client.options.keyUse.setDown(true);
}
}
}
}
private Entity findNearestMonster(Entity player) {
if (client.level == null) return null;
Entity nearest = null;
double minDistSq = 16.0;
for (Entity e : client.level.entitiesForRendering()) {
if (e instanceof Monster && e.isAlive()) {
double distSq = player.distanceToSqr(e);
if (distSq < minDistSq) {
minDistSq = distSq;
nearest = e;
}
}
}
return nearest;
}
private void lookAt(Entity player, Vec3 target) {
double dx = target.x - player.getX();
double dy = target.y - player.getEyeY();
double dz = target.z - player.getZ();
double r = Math.sqrt(dx * dx + dz * dz);
float yaw = (float) (Math.toDegrees(Math.atan2(-dx, dz)));
float pitch = (float) (Math.toDegrees(-Math.atan2(dy, r)));
player.setYRot(yaw);
player.setXRot(pitch);
}
}

View file

@ -0,0 +1,23 @@
package com.typesafe.jevplayer.adapter26_2;
import com.typesafe.jevplayer.core.bridge.HudRendererBridge;
import com.typesafe.jevplayer.core.hud.HudState;
import net.minecraft.client.Minecraft;
public final class FabricHudRenderer implements HudRendererBridge {
private final Minecraft client;
private HudState currentState;
public FabricHudRenderer(Minecraft client) {
this.client = client;
}
@Override
public void updateHudState(HudState state) {
this.currentState = state;
}
public HudState getCurrentState() {
return currentState;
}
}

View file

@ -0,0 +1,62 @@
package com.typesafe.jevplayer.adapter26_2;
import com.typesafe.jevplayer.core.bridge.InputBridge;
import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import org.lwjgl.glfw.GLFW;
public final class FabricInputBridge implements InputBridge {
private final Minecraft client;
private final KeyMapping killSwitchKey;
private final KeyMapping hudToggleKey;
public FabricInputBridge(Minecraft client) {
this.client = client;
this.killSwitchKey = KeyMappingHelper.registerKeyMapping(new KeyMapping(
"key.jevplayer.kill_switch",
GLFW.GLFW_KEY_F8,
KeyMapping.Category.MISC
));
this.hudToggleKey = KeyMappingHelper.registerKeyMapping(new KeyMapping(
"key.jevplayer.toggle_hud",
GLFW.GLFW_KEY_F9,
KeyMapping.Category.MISC
));
}
public boolean wasKillSwitchPressed() {
return killSwitchKey.consumeClick();
}
public boolean wasHudTogglePressed() {
return hudToggleKey.consumeClick();
}
@Override
public void releaseAllInputs() {
if (client.options == null) return;
client.options.keyUp.setDown(false);
client.options.keyDown.setDown(false);
client.options.keyLeft.setDown(false);
client.options.keyRight.setDown(false);
client.options.keyJump.setDown(false);
client.options.keyShift.setDown(false);
client.options.keySprint.setDown(false);
client.options.keyAttack.setDown(false);
client.options.keyUse.setDown(false);
}
@Override
public void sendChatMessage(String message) {
if (client.player != null) {
client.player.sendSystemMessage(Component.literal(message));
}
}
@Override
public boolean isRemoteServer() {
return client.getCurrentServer() != null || !client.isLocalServer();
}
}

View file

@ -0,0 +1,279 @@
package com.typesafe.jevplayer.adapter26_2;
import com.typesafe.jevplayer.core.bridge.WorldSensor;
import com.typesafe.jevplayer.core.state.*;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import java.util.*;
public final class FabricWorldSensor implements WorldSensor {
private final Minecraft client;
private final boolean fairMode;
public FabricWorldSensor(Minecraft client, boolean fairMode) {
this.client = client;
this.fairMode = fairMode;
}
@Override
public StateSnapshot captureSnapshot(long nowMs) {
var player = client.player;
var level = client.level;
if (player == null || level == null) {
return StateSnapshot.initial(nowMs);
}
// 1. Vitals
float health = player.getHealth();
float maxHealth = player.getMaxHealth();
int hunger = player.getFoodData().getFoodLevel();
boolean saturation = player.getFoodData().getSaturationLevel() > 0.0f;
int air = player.getAirSupply();
boolean onFire = player.isOnFire();
boolean falling = player.fallDistance > 2.0f;
boolean inLava = player.isInLava();
boolean inWater = player.isInWater();
Vitals vitals = new Vitals(health, maxHealth, hunger, saturation, air, onFire, falling, inLava, inWater);
// 2. Time
long timeOfDay = level.getLevelData().getGameTime() % 24000;
boolean isDay = timeOfDay < 12000;
long ticksToSunset = isDay ? (12000 - timeOfDay) : 0;
boolean isRaining = level.isRaining();
boolean isThundering = level.isThundering();
long dayCount = level.getLevelData().getGameTime() / 24000 + 1;
TimeInfo time = new TimeInfo(isDay, ticksToSunset, isRaining, isThundering, dayCount);
// 3. Inventory
InventorySummary inventory = scanInventory(player);
// 4. Surroundings
Surroundings surroundings = scanSurroundings(player, level);
return new StateSnapshot(
nowMs,
vitals,
time,
inventory,
surroundings,
GoalInfo.defaultGoal(),
List.of()
);
}
private InventorySummary scanInventory(net.minecraft.world.entity.player.Player player) {
int logs = 0;
int planks = 0;
int sticks = 0;
int cobblestone = 0;
int coal = 0;
int rawIron = 0;
int ironIngots = 0;
int foodCount = 0;
int torches = 0;
int beds = 0;
int dirt = 0;
int freeSlots = 0;
String bestPickaxeTier = "none";
String bestAxeTier = "none";
String bestSwordTier = "none";
List<String> durabilityWarnings = new ArrayList<>();
var inv = player.getInventory();
int size = inv.getContainerSize();
for (int i = 0; i < size; i++) {
ItemStack stack = inv.getItem(i);
if (stack.isEmpty()) {
if (i < 36) freeSlots++;
continue;
}
int count = stack.getCount();
String path = BuiltInRegistries.ITEM.getKey(stack.getItem()).getPath();
if (path.endsWith("_log")) logs += count;
else if (path.endsWith("_planks")) planks += count;
else if ("stick".equals(path)) sticks += count;
else if ("cobblestone".equals(path) || "cobbled_deepslate".equals(path)) cobblestone += count;
else if ("coal".equals(path) || "charcoal".equals(path)) coal += count;
else if ("raw_iron".equals(path)) rawIron += count;
else if ("iron_ingot".equals(path)) ironIngots += count;
else if ("torch".equals(path)) torches += count;
else if (path.endsWith("_bed")) beds += count;
else if ("dirt".equals(path)) dirt += count;
if (stack.has(net.minecraft.core.component.DataComponents.FOOD)) {
foodCount += count;
}
if (path.endsWith("_pickaxe")) {
String tier = extractTier(path);
if (tierRank(tier) > tierRank(bestPickaxeTier)) bestPickaxeTier = tier;
} else if (path.endsWith("_axe")) {
String tier = extractTier(path);
if (tierRank(tier) > tierRank(bestAxeTier)) bestAxeTier = tier;
} else if (path.endsWith("_sword")) {
String tier = extractTier(path);
if (tierRank(tier) > tierRank(bestSwordTier)) bestSwordTier = tier;
}
}
return new InventorySummary(
logs, planks, sticks, cobblestone, coal, rawIron, ironIngots,
foodCount, torches, beds, dirt, freeSlots,
bestPickaxeTier, bestAxeTier, bestSwordTier, durabilityWarnings
);
}
private Surroundings scanSurroundings(net.minecraft.world.entity.player.Player player, net.minecraft.client.multiplayer.ClientLevel level) {
int px = (int) player.getX();
int py = (int) player.getY();
int pz = (int) player.getZ();
Map<String, Integer> visibleResources = new HashMap<>();
int lightLevel = level.getMaxLocalRawBrightness(player.blockPosition());
String biome = level.getBiome(player.blockPosition()).unwrapKey()
.map(k -> k.identifier().getPath()).orElse("plains");
int radius = 12;
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
double nearestWaterDist = -1.0;
double nearestLavaDist = -1.0;
for (int dx = -radius; dx <= radius; dx += 2) {
for (int dy = -4; dy <= 4; dy += 2) {
for (int dz = -radius; dz <= radius; dz += 2) {
pos.set(px + dx, py + dy, pz + dz);
BlockState state = level.getBlockState(pos);
if (state.isAir()) continue;
String blockId = BuiltInRegistries.BLOCK.getKey(state.getBlock()).getPath();
if (blockId.endsWith("_log") || blockId.contains("ore")) {
visibleResources.merge(blockId, 1, Integer::sum);
}
if (blockId.contains("water") && nearestWaterDist < 0) {
nearestWaterDist = Math.sqrt(dx * dx + dy * dy + dz * dz);
}
if (blockId.contains("lava") && nearestLavaDist < 0) {
nearestLavaDist = Math.sqrt(dx * dx + dy * dy + dz * dz);
}
}
}
}
boolean isUnderground = py < 50;
List<EntitySummary> hostiles = new ArrayList<>();
EntitySummary nearestPassive = null;
double minPassiveDist = Double.MAX_VALUE;
for (Entity e : level.entitiesForRendering()) {
if (e == player || !e.isAlive()) continue;
double dist = player.distanceTo(e);
if (dist > 32.0) continue;
String typeId = BuiltInRegistries.ENTITY_TYPE.getKey(e.getType()).getPath();
boolean isHostile = (e instanceof Monster);
boolean isAnimal = (e instanceof Animal);
double dx = e.getX() - player.getX();
double dz = e.getZ() - player.getZ();
String direction = getDirection(dx, dz);
EntitySummary summary = new EntitySummary(
typeId,
dist,
direction,
isHostile,
true,
true
);
if (isHostile) {
hostiles.add(summary);
} else if (isAnimal && dist < minPassiveDist) {
minPassiveDist = dist;
nearestPassive = summary;
}
}
hostiles.sort(Comparator.comparingDouble(EntitySummary::distance));
if (hostiles.size() > 5) {
hostiles = hostiles.subList(0, 5);
}
return new Surroundings(
biome,
py,
isUnderground,
lightLevel,
hostiles,
nearestPassive,
nearestWaterDist,
nearestLavaDist,
visibleResources
);
}
private String getDirection(double dx, double dz) {
double angle = Math.toDegrees(Math.atan2(dz, dx));
if (angle < 0) angle += 360;
if (angle >= 337.5 || angle < 22.5) return "E";
if (angle < 67.5) return "SE";
if (angle < 112.5) return "S";
if (angle < 157.5) return "SW";
if (angle < 202.5) return "W";
if (angle < 247.5) return "NW";
if (angle < 292.5) return "N";
return "NE";
}
private String extractTier(String itemName) {
if (itemName.startsWith("wooden_")) return "wooden";
if (itemName.startsWith("stone_")) return "stone";
if (itemName.startsWith("iron_")) return "iron";
if (itemName.startsWith("golden_")) return "golden";
if (itemName.startsWith("diamond_")) return "diamond";
if (itemName.startsWith("netherite_")) return "netherite";
return "none";
}
private int tierRank(String tier) {
return switch (tier) {
case "wooden", "golden" -> 1;
case "stone" -> 2;
case "iron" -> 3;
case "diamond" -> 4;
case "netherite" -> 5;
default -> 0;
};
}
@Override
public double getPlayerX() {
return client.player != null ? client.player.getX() : 0.0;
}
@Override
public double getPlayerY() {
return client.player != null ? client.player.getY() : 0.0;
}
@Override
public double getPlayerZ() {
return client.player != null ? client.player.getZ() : 0.0;
}
}

View file

@ -1,10 +1,15 @@
package com.typesafe.jevplayer.adapter26_2;
import baritone.api.BaritoneAPI;
import com.typesafe.jevplayer.core.JevPlayerCore;
import com.typesafe.jevplayer.core.config.JevPlayerConfig;
import com.typesafe.jevplayer.core.engine.JevPlayerEngine;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.ClientCommands;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -19,26 +24,48 @@ public class JevPlayerClientMod implements ClientModInitializer {
private static JevPlayerClientMod instance;
private JevPlayerConfig config;
private Path configPath;
private Path logDir;
private JevPlayerEngine engine;
private FabricWorldSensor worldSensor;
private BaritoneActionExecutor baritoneExecutor;
private FabricActionExecutor fabricExecutor;
private CompositeActionExecutor compositeExecutor;
private FabricInputBridge inputBridge;
private FabricHudRenderer hudRenderer;
@Override
public void onInitializeClient() {
instance = this;
LOGGER.info("[{}] Initialized client adapter for Minecraft 26.2 (Core v{})",
LOGGER.info("[{}] Initializing JevPlayer client mod v{} for Minecraft 26.2...",
JevPlayerCore.MOD_NAME, JevPlayerCore.VERSION);
Path configDir = FabricLoader.getInstance().getConfigDir();
this.configPath = configDir.resolve("jevplayer.json");
this.logDir = FabricLoader.getInstance().getGameDir().resolve("logs").resolve("jevplayer");
this.logDir.toFile().mkdirs();
loadConfig();
try {
var settings = BaritoneAPI.getSettings();
settings.allowSprint.value = true;
settings.legitMine.value = config.fairMode;
settings.smoothLook.value = true;
LOGGER.info("[{}] Baritone v1.19.0 configured successfully for 26.2!", JevPlayerCore.MOD_NAME);
} catch (Throwable t) {
LOGGER.warn("[{}] Baritone API not detected: {}", JevPlayerCore.MOD_NAME, t.getMessage());
Minecraft client = Minecraft.getInstance();
this.inputBridge = new FabricInputBridge(client);
this.worldSensor = new FabricWorldSensor(client, config.fairMode);
this.baritoneExecutor = new BaritoneActionExecutor(client, config.fairMode);
this.fabricExecutor = new FabricActionExecutor(client);
this.compositeExecutor = new CompositeActionExecutor(baritoneExecutor, fabricExecutor);
this.hudRenderer = new FabricHudRenderer(client);
this.engine = new JevPlayerEngine(config, worldSensor, compositeExecutor, inputBridge, hudRenderer, logDir);
registerCommands();
registerTickHandler();
LOGGER.info("[{}] Initialized successfully for 26.2! Provider: {}, Fair perception: {}",
JevPlayerCore.MOD_NAME, config.provider, config.fairMode);
}
public static JevPlayerClientMod getInstance() {
return instance;
}
public void loadConfig() {
@ -46,18 +73,93 @@ public class JevPlayerClientMod implements ClientModInitializer {
if (file.exists()) {
try (FileReader fr = new FileReader(file)) {
this.config = JevPlayerConfig.fromJson(fr);
LOGGER.info("[{}] Loaded config from {}", JevPlayerCore.MOD_NAME, file.getAbsolutePath());
} catch (Exception e) {
LOGGER.error("[{}] Error reading config, using defaults: {}", JevPlayerCore.MOD_NAME, e.getMessage());
this.config = new JevPlayerConfig();
}
} else {
this.config = new JevPlayerConfig();
try (FileWriter fw = new FileWriter(file)) {
config.toJson(fw);
} catch (Exception ignored) {}
saveConfig();
}
}
public static JevPlayerClientMod getInstance() {
return instance;
public void saveConfig() {
try (FileWriter fw = new FileWriter(configPath.toFile())) {
config.toJson(fw);
} catch (Exception e) {
LOGGER.error("[{}] Error writing default config: {}", JevPlayerCore.MOD_NAME, e.getMessage());
}
}
private void registerTickHandler() {
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (client.player == null || client.level == null) {
return;
}
// Check F8 kill switch
if (inputBridge.wasKillSwitchPressed()) {
if (engine.isEnabled()) {
engine.triggerKillSwitch();
inputBridge.sendChatMessage("§c[JevPlayer] Kill switch triggered! Autopilot stopped.");
} else {
engine.setEnabled(true);
inputBridge.sendChatMessage("§a[JevPlayer] Autopilot resumed via hotkey.");
}
}
// Check F9 HUD toggle
if (inputBridge.wasHudTogglePressed()) {
config.hudEnabled = !config.hudEnabled;
inputBridge.sendChatMessage("§b[JevPlayer] HUD overlay " + (config.hudEnabled ? "§aEnabled" : "§cDisabled"));
}
// Advance ticks
compositeExecutor.onTick();
engine.tick();
});
}
private void registerCommands() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
dispatcher.register(ClientCommands.literal("jevplayer")
.then(ClientCommands.literal("status").executes(ctx -> {
boolean enabled = engine.isEnabled();
String statusMsg = String.format("§b[JevPlayer] Status: %s §7| Provider: §f%s §7| Milestone: §f%s §7| Spend: §e$%.4f",
enabled ? "§aRUNNING" : "§cSTOPPED",
engine.getDecisionProvider().getName(),
engine.getEpisodeDirector().getCurrentMilestone().name(),
engine.getDecisionProvider().getTotalSpendUsd()
);
ctx.getSource().sendFeedback(Component.literal(statusMsg));
return 1;
}))
.then(ClientCommands.literal("start").executes(ctx -> {
engine.setEnabled(true);
ctx.getSource().sendFeedback(Component.literal("§a[JevPlayer] Autonomous play activated!"));
return 1;
}))
.then(ClientCommands.literal("stop").executes(ctx -> {
engine.setEnabled(false);
ctx.getSource().sendFeedback(Component.literal("§c[JevPlayer] Autonomous play stopped."));
return 1;
}))
.then(ClientCommands.literal("reload").executes(ctx -> {
loadConfig();
ctx.getSource().sendFeedback(Component.literal("§b[JevPlayer] Configuration reloaded."));
return 1;
}))
.then(ClientCommands.literal("test_gather_wood").executes(ctx -> {
ctx.getSource().sendFeedback(Component.literal("§e[JevPlayer] Testing Baritone wood gathering..."));
baritoneExecutor.startAction("gather_wood", worldSensor.captureSnapshot(System.currentTimeMillis()));
return 1;
}))
);
});
}
public JevPlayerEngine getEngine() {
return engine;
}
}

View file

@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"id": "jevplayer",
"version": "${version}",
"version": "1.0.0",
"name": "JevPlayer",
"description": "Fabric mod where TypeSafe's Jev plays Minecraft autonomously through Baritone",
"authors": [