feat(adapter-26_2): implement full autonomous gameplay engine for Minecraft 26.2
This commit is contained in:
parent
e6efdf17b1
commit
86f3640982
10 changed files with 891 additions and 20 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,6 +3,7 @@
|
|||
build/
|
||||
*/build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
adapter-26_2/libs/
|
||||
|
||||
# Loom / Minecraft run directory
|
||||
run/
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
Reference in a new issue