diff --git a/.gitignore b/.gitignore index 85a0e3d..ad1653f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build/ */build/ !gradle/wrapper/gradle-wrapper.jar +adapter-26_2/libs/ # Loom / Minecraft run directory run/ diff --git a/adapter-26_2/build.gradle b/adapter-26_2/build.gradle index 2a11098..bd1ac85 100644 --- a/adapter-26_2/build.gradle +++ b/adapter-26_2/build.gradle @@ -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 { diff --git a/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/BaritoneActionExecutor.java b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/BaritoneActionExecutor.java new file mode 100644 index 0000000..439346a --- /dev/null +++ b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/BaritoneActionExecutor.java @@ -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 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; + } + } +} diff --git a/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/CompositeActionExecutor.java b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/CompositeActionExecutor.java new file mode 100644 index 0000000..488fb91 --- /dev/null +++ b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/CompositeActionExecutor.java @@ -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 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(); + } +} diff --git a/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricActionExecutor.java b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricActionExecutor.java new file mode 100644 index 0000000..02e97f4 --- /dev/null +++ b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricActionExecutor.java @@ -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); + } +} diff --git a/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricHudRenderer.java b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricHudRenderer.java new file mode 100644 index 0000000..4d63939 --- /dev/null +++ b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricHudRenderer.java @@ -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; + } +} diff --git a/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricInputBridge.java b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricInputBridge.java new file mode 100644 index 0000000..1febaff --- /dev/null +++ b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricInputBridge.java @@ -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(); + } +} diff --git a/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricWorldSensor.java b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricWorldSensor.java new file mode 100644 index 0000000..0196bd9 --- /dev/null +++ b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/FabricWorldSensor.java @@ -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 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 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 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; + } +} diff --git a/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/JevPlayerClientMod.java b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/JevPlayerClientMod.java index d04884c..a70de0a 100644 --- a/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/JevPlayerClientMod.java +++ b/adapter-26_2/src/main/java/com/typesafe/jevplayer/adapter26_2/JevPlayerClientMod.java @@ -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; } } diff --git a/adapter-26_2/src/main/resources/fabric.mod.json b/adapter-26_2/src/main/resources/fabric.mod.json index 2339f04..bb6c864 100644 --- a/adapter-26_2/src/main/resources/fabric.mod.json +++ b/adapter-26_2/src/main/resources/fabric.mod.json @@ -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": [