├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ ├── assets │ │ └── ai-player │ │ │ └── icon.png │ ├── ai-player.mixins.json │ ├── logback.xml │ └── fabric.mod.json │ └── java │ └── net │ └── shasankp000 │ ├── Overlay │ ├── ThinkingStateManager.class │ └── ThinkingStateManager.java │ ├── Exception │ ├── intentMisclassification.java │ └── ollamaNotReachableException.java │ ├── FunctionCaller │ ├── ToolStateUpdater.java │ ├── OutputVerifier.java │ ├── Tool.java │ ├── SharedStateUtils.java │ └── ToolVerifiers.java │ ├── AIPlayerDataGenerator.java │ ├── PlayerUtils │ ├── getHealth.java │ ├── getOffHandStack.java │ ├── getPlayerHunger.java │ ├── getPlayerOxygen.java │ ├── turnTool.java │ ├── getArmorStack.java │ ├── SelectedItemDetails.java │ ├── ToolSelector.java │ ├── hotBarUtils.java │ ├── getFrostLevel.java │ ├── ThreatDetector.java │ ├── BlockNameNormalizer.java │ ├── MiningTool.java │ ├── blockDetectionUnit.java │ ├── BlockDistanceLimitedSearch.java │ ├── armorUtils.java │ ├── InternalMap.java │ └── ResourceEvaluator.java │ ├── WorldUitls │ ├── isBlockItem.java │ ├── GetTime.java │ └── isFoodItem.java │ ├── ChatUtils │ ├── ClarificationState.java │ ├── ChatContextManager.java │ ├── LIDSNetModel │ │ ├── LIDSNetTranslator.java │ │ └── LIDSNetModelManager.java │ ├── BERTModel │ │ └── BertTranslator.java │ ├── PreProcessing │ │ ├── NLPModelSetup.java │ │ └── OpenNLPProcessor.java │ ├── CART │ │ ├── TreeNodeDeserializer.java │ │ └── CartClassifier.java │ └── Helper │ │ └── JsonUtils.java │ ├── ServiceLLMClients │ ├── ModelFetcher.java │ ├── LLMClient.java │ ├── GrokModelFetcher.java │ ├── ClaudeModelFetcher.java │ ├── OpenAIModelFetcher.java │ ├── GeminiModelFetcher.java │ ├── GenericOpenAIModelFetcher.java │ ├── GeminiClient.java │ ├── GrokClient.java │ ├── AnthropicClient.java │ ├── OpenAIClient.java │ └── GenericOpenAIClient.java │ ├── mixin │ └── ExampleMixin.java │ ├── Network │ ├── StringCodec.java │ ├── OpenConfigPayload.java │ ├── SaveConfigPayload.java │ ├── SaveAPIKeyPayload.java │ ├── SaveCustomProviderPayload.java │ ├── ConfigJsonUtil.java │ └── configNetworkManager.java │ ├── GameAI │ └── StateActions.java │ ├── Database │ ├── QEntry.java │ ├── QTable.java │ ├── StateActionPair.java │ ├── QTableExporter.java │ ├── OldSQLiteDB.java │ └── StateActionTransition.java │ ├── Entity │ ├── RespawnHandler.java │ ├── FaceClosestEntity.java │ ├── EntityDetails.java │ ├── LookController.java │ └── RayCasting.java │ ├── PathFinding │ ├── Segment.java │ ├── ChartPathToBlock.java │ └── GoTo.java │ ├── DangerZoneDetector │ ├── DangerZoneDetector.java │ ├── CliffDetector.java │ └── LavaDetector.java │ ├── Commands │ └── configCommand.java │ ├── GraphicalUserInterface │ ├── ReasoningLogScreen.java │ └── Widgets │ │ └── DropdownMenuWidget.java │ ├── FilingSystem │ ├── ServerConfigUtil.java │ ├── getLanguageModels.java │ └── LLMClientFactory.java │ ├── WebSearch │ └── AISearchConfig.java │ └── AIPlayer.java ├── settings.gradle ├── .gitattributes ├── .gitignore ├── gradle.properties ├── LICENSE ├── CUSTOM_PROVIDERS.md └── gradlew.bat /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shasankp000/AI-Player/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/assets/ai-player/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shasankp000/AI-Player/HEAD/src/main/resources/assets/ai-player/icon.png -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Overlay/ThinkingStateManager.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shasankp000/AI-Player/HEAD/src/main/java/net/shasankp000/Overlay/ThinkingStateManager.class -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | mavenCentral() 8 | gradlePluginPortal() 9 | } 10 | } -------------------------------------------------------------------------------- /src/main/resources/ai-player.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": false, 3 | "package": "net.shasankp000.mixin", 4 | "compatibilityLevel": "JAVA_17", 5 | "mixins": [ 6 | "ExampleMixin" 7 | ], 8 | "injectors": { 9 | "defaultRequire": 1 10 | } 11 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # Linux start script should use lf 5 | /gradlew text eol=lf 6 | 7 | # These are Windows script files and should use crlf 8 | *.bat text eol=crlf 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Exception/intentMisclassification.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Exception; 2 | 3 | public class intentMisclassification extends RuntimeException { 4 | public intentMisclassification(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Exception/ollamaNotReachableException.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Exception; 2 | 3 | public class ollamaNotReachableException extends Exception{ 4 | 5 | public ollamaNotReachableException(String message) { 6 | 7 | super(message); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/FunctionCaller/ToolStateUpdater.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.FunctionCaller; 2 | 3 | import java.util.Map; 4 | 5 | @FunctionalInterface 6 | public interface ToolStateUpdater { 7 | void update(Map sharedState, Map paramMap, Object functionResult); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/AIPlayerDataGenerator.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000; 2 | 3 | import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; 4 | import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; 5 | 6 | public class AIPlayerDataGenerator implements DataGeneratorEntrypoint { 7 | @Override 8 | public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | 8 | # eclipse 9 | 10 | *.launch 11 | 12 | # idea 13 | 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | # vscode 20 | 21 | .settings/ 22 | .vscode/ 23 | bin/ 24 | .classpath 25 | .project 26 | 27 | # macos 28 | 29 | *.DS_Store 30 | 31 | # fabric 32 | 33 | run/ 34 | 35 | # java 36 | 37 | hs_err_*.log 38 | replay_*.log 39 | *.hprof 40 | *.jfr 41 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/getHealth.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.server.network.ServerPlayerEntity; 4 | 5 | public class getHealth { 6 | 7 | public static float getBotHealthLevel(ServerPlayerEntity bot) { 8 | if (bot != null) { 9 | return bot.getHealth(); 10 | } 11 | return 0.0f; // Default to 0 if bot is null or hunger cannot be retrieved 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/getOffHandStack.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | 4 | import net.minecraft.item.ItemStack; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | 7 | public class getOffHandStack { 8 | 9 | public static ItemStack getOffhandItem(ServerPlayerEntity bot) { 10 | // The offhand slot is a specific slot in the bot's inventory 11 | return bot.getOffHandStack(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/getPlayerHunger.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.server.network.ServerPlayerEntity; 4 | 5 | public class getPlayerHunger { 6 | 7 | public static int getBotHungerLevel(ServerPlayerEntity bot) { 8 | if (bot != null) { 9 | return bot.getHungerManager().getFoodLevel(); 10 | } 11 | return 0; // Default to 0 if bot is null or hunger cannot be retrieved 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/WorldUitls/isBlockItem.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.WorldUitls; 2 | 3 | import net.minecraft.item.BlockItem; 4 | import net.minecraft.item.ItemStack; 5 | 6 | public class isBlockItem { 7 | 8 | // Check if the given item is a food item 9 | public static boolean checkBlockItem(ItemStack selectedItemStack) { 10 | // Get the Item instance from the item registry 11 | 12 | return (selectedItemStack.getItem() instanceof BlockItem); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/getPlayerOxygen.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.server.network.ServerPlayerEntity; 4 | 5 | public class getPlayerOxygen { 6 | 7 | public static int getBotOxygenLevel(ServerPlayerEntity bot) { 8 | if (bot != null) { 9 | return bot.getAir(); // Returns the bot's current oxygen level 10 | } 11 | return 0; // Default to 0 if bot is null or oxygen level cannot be retrieved 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/WorldUitls/GetTime.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.WorldUitls; 2 | 3 | import net.minecraft.server.network.ServerPlayerEntity; 4 | import net.minecraft.world.World; 5 | 6 | public class GetTime { 7 | public static int getTimeOfWorld(ServerPlayerEntity bot) { 8 | 9 | World GameWorld = bot.getServerWorld(); 10 | 11 | long timeOfDay = GameWorld.getTimeOfDay() % 24000; // Normalize to one day cycle 12 | 13 | return (int) timeOfDay; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/ClarificationState.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils; 2 | 3 | public class ClarificationState { 4 | public final String botName; 5 | public final String originalMessage; 6 | public final String clarifyingQuestion; 7 | 8 | public ClarificationState(String originalMessage, String clarifyingQuestion, String botName) { 9 | this.originalMessage = originalMessage; 10 | this.clarifyingQuestion = clarifyingQuestion; 11 | this.botName = botName; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/FunctionCaller/OutputVerifier.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.FunctionCaller; 2 | 3 | import java.util.Map; 4 | 5 | public interface OutputVerifier { 6 | /** 7 | * Verifies whether the function output matches expected conditions. 8 | * @param parameters - parameters passed to the function 9 | * @param functionOutput - raw output from the function 10 | * @return true if valid, false if output is unexpected or invalid 11 | */ 12 | boolean verify(Map parameters, String functionOutput); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/turnTool.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.server.MinecraftServer; 4 | import net.minecraft.server.command.ServerCommandSource; 5 | 6 | 7 | public class turnTool { 8 | public static void turn(ServerCommandSource botSource, String direction) { 9 | 10 | MinecraftServer server = botSource.getServer(); 11 | String botName = botSource.getName(); 12 | server.getCommandManager().executeWithPrefix(botSource, "/player " + botName + " turn " + direction); 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/ModelFetcher.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Interface for fetching a list of available models from a language model provider. 7 | */ 8 | public interface ModelFetcher { 9 | 10 | /** 11 | * Fetches a list of available model identifiers from the provider's API. 12 | * @param apiKey The API key required to authenticate with the service. 13 | * @return A list of model identifiers as strings. 14 | */ 15 | List fetchModels(String apiKey); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx3G 3 | org.gradle.parallel=true 4 | 5 | # Fabric Properties 6 | # check these on https://fabricmc.net/develop 7 | # Updated versions for better Java 21 compatibility 8 | minecraft_version=1.21.1 9 | yarn_mappings=1.21.1+build.3 10 | loader_version=0.16.14 11 | loom_version=1.11-SNAPSHOT 12 | 13 | # Mod Properties (keep your existing values) 14 | mod_version=1.0.5.2-release+1.21.1 15 | maven_group=net.shasankp000 16 | archives_base_name=ai-player 17 | 18 | # Dependencies 19 | fabric_version=0.116.6+1.21.1 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/mixin/ExampleMixin.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.mixin; 2 | 3 | import net.minecraft.server.MinecraftServer; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.injection.At; 6 | import org.spongepowered.asm.mixin.injection.Inject; 7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 8 | 9 | @Mixin(MinecraftServer.class) 10 | public class ExampleMixin { 11 | @Inject(at = @At("HEAD"), method = "loadWorld") 12 | private void init(CallbackInfo info) { 13 | // This code is injected into the start of MinecraftServer.loadWorld()V 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Network/StringCodec.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Network; 2 | 3 | 4 | import net.minecraft.network.PacketByteBuf; 5 | import net.minecraft.network.codec.PacketCodec; 6 | 7 | 8 | public class StringCodec implements PacketCodec { 9 | private final int maxLength; 10 | 11 | public StringCodec(int maxLength) { 12 | this.maxLength = maxLength; 13 | } 14 | 15 | 16 | @Override 17 | public String decode(PacketByteBuf buf) { 18 | return buf.readString(maxLength); 19 | } 20 | 21 | @Override 22 | public void encode(PacketByteBuf buf, String value) { 23 | buf.writeString(value, maxLength); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/WorldUitls/isFoodItem.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.WorldUitls; 2 | 3 | import net.minecraft.component.ComponentMap; 4 | import net.minecraft.component.DataComponentTypes; 5 | import net.minecraft.item.ItemStack; 6 | 7 | 8 | public class isFoodItem { 9 | 10 | // Check if the given item is a food item 11 | public static boolean checkFoodItem(ItemStack selectedItemStack) { 12 | // 1.20.6, get the Item's component map, run it against DataComponentTypes to check if it's a food 13 | 14 | ComponentMap componentMap = selectedItemStack.getComponents(); 15 | 16 | return componentMap.contains(DataComponentTypes.FOOD); 17 | 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/GameAI/StateActions.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.GameAI; 2 | 3 | public class StateActions { 4 | 5 | public enum Action { 6 | MOVE_FORWARD, 7 | MOVE_BACKWARD, 8 | TURN_LEFT, 9 | TURN_RIGHT, 10 | JUMP, 11 | STAY, 12 | SNEAK, 13 | SPRINT, 14 | STOP_MOVING, 15 | STOP_SNEAKING, 16 | STOP_SPRINTING, 17 | USE_ITEM, 18 | ATTACK, 19 | EQUIP_ARMOR, 20 | HOTBAR_1, 21 | HOTBAR_2, 22 | HOTBAR_3, 23 | HOTBAR_4, 24 | HOTBAR_5, 25 | HOTBAR_6, 26 | HOTBAR_7, 27 | HOTBAR_8, 28 | HOTBAR_9, 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Database/QEntry.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Database; 2 | 3 | import net.shasankp000.GameAI.State; 4 | 5 | import java.io.Serializable; 6 | 7 | public class QEntry implements Serializable { 8 | private final double qValue; 9 | private final State nextState; 10 | 11 | public QEntry(double qValue, State nextState) { 12 | this.qValue = qValue; 13 | this.nextState = nextState; 14 | } 15 | 16 | public double getQValue() { 17 | return qValue; 18 | } 19 | 20 | public State getNextState() { 21 | return nextState; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return String.format("QValue: %.2f, NextState: %s", qValue, nextState); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Entity/RespawnHandler.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Entity; 2 | 3 | import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | 6 | public class RespawnHandler { 7 | public static void registerRespawnListener(ServerPlayerEntity bot) { 8 | 9 | String botName = bot.getName().getString(); 10 | 11 | ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> { 12 | if (newPlayer.getName().getString().equals(botName)) { // Replace "Steve" with your bot's name 13 | System.out.println("Detected bot respawn for " + newPlayer.getName().getString()); 14 | AutoFaceEntity.handleBotRespawn(newPlayer); // Pass the new bot instance 15 | } 16 | }); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/getArmorStack.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.entity.EquipmentSlot; 4 | import net.minecraft.item.ItemStack; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class getArmorStack { 11 | 12 | public static Map getArmorItems(ServerPlayerEntity bot) { 13 | Map armorItems = new HashMap<>(); 14 | armorItems.put("helmet", bot.getEquippedStack(EquipmentSlot.HEAD)); 15 | armorItems.put("chestplate", bot.getEquippedStack(EquipmentSlot.CHEST)); 16 | armorItems.put("leggings", bot.getEquippedStack(EquipmentSlot.LEGS)); 17 | armorItems.put("boots", bot.getEquippedStack(EquipmentSlot.FEET)); 18 | return armorItems; 19 | } 20 | 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Database/QTable.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Database; 2 | 3 | import net.shasankp000.GameAI.State; 4 | import net.shasankp000.GameAI.StateActions; 5 | 6 | import java.io.Serializable; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class QTable implements Serializable { 11 | private final Map qTable; 12 | 13 | public QTable() { 14 | this.qTable = new HashMap<>(); 15 | } 16 | 17 | public void addEntry(State state, StateActions.Action action, double qValue, State nextState) { 18 | StateActionPair pair = new StateActionPair(state, action); 19 | qTable.put(pair, new QEntry(qValue, nextState)); 20 | } 21 | 22 | public QEntry getEntry(StateActionPair pair) { 23 | return qTable.get(pair); 24 | } 25 | 26 | public Map getTable() { 27 | return qTable; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PathFinding/Segment.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PathFinding; 2 | 3 | import net.minecraft.util.math.BlockPos; 4 | 5 | public class Segment { 6 | private final BlockPos start; 7 | private final BlockPos end; 8 | private final boolean jump; 9 | private boolean sprint; // <--- mutable! 10 | 11 | public Segment(BlockPos start, BlockPos end, boolean jump, boolean sprint) { 12 | this.start = start; 13 | this.end = end; 14 | this.jump = jump; 15 | this.sprint = sprint; 16 | } 17 | 18 | public BlockPos start() { return start; } 19 | public BlockPos end() { return end; } 20 | public boolean jump() { return jump; } 21 | public boolean sprint() { return sprint; } 22 | 23 | public void setSprint(boolean sprint) { 24 | this.sprint = sprint; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "Segment[start=" + start + ", end=" + end + ", jump=" + jump + ", sprint=" + sprint + "]"; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/ChatContextManager.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.UUID; 6 | 7 | public class ChatContextManager { 8 | private static final Map pendingClarifications = new HashMap<>(); 9 | 10 | public static void setPendingClarification(UUID playerUUID, String originalMessage, String clarifyingQuestion, String botName) { 11 | pendingClarifications.put(playerUUID, new ClarificationState(originalMessage, clarifyingQuestion, botName)); 12 | } 13 | 14 | public static ClarificationState getPendingClarification(UUID playerUUID) { 15 | return pendingClarifications.get(playerUUID); 16 | } 17 | 18 | public static void clearPendingClarification(UUID playerUUID) { 19 | pendingClarifications.remove(playerUUID); 20 | } 21 | 22 | public static boolean isAwaitingClarification(UUID playerUUID) { 23 | return pendingClarifications.containsKey(playerUUID); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "ai-player", 4 | "version": "${version}", 5 | "name": "AI-Player", 6 | "description": "A minecraft mod which aims to add a \"second player\" into the game which will actually be intelligent.", 7 | "authors": [ 8 | "shasankp000" 9 | ], 10 | "contact": { 11 | "homepage": "https://modrinth.com/mod/ai-player", 12 | "sources": "https://github.com/shasankp000/AI-player" 13 | }, 14 | "license": "MIT", 15 | "icon": "assets/ai-player/icon.png", 16 | "environment": "*", 17 | "entrypoints": { 18 | "main": [ 19 | "net.shasankp000.AIPlayer" 20 | ], 21 | "client": [ 22 | "net.shasankp000.AIPlayerClient" 23 | ], 24 | "fabric-datagen": [ 25 | "net.shasankp000.AIPlayerDataGenerator" 26 | ] 27 | }, 28 | "mixins": [ 29 | "ai-player.mixins.json" 30 | ], 31 | "depends": { 32 | "fabricloader": ">=0.16.10", 33 | "minecraft": "~1.21.1", 34 | "java": ">=21", 35 | "fabric-api": "*" 36 | }, 37 | "suggests": { 38 | "carpet": "1.4.147", 39 | "owo-lib": "0.12.15.4+1.21" 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Network/OpenConfigPayload.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Network; 2 | 3 | import net.minecraft.network.PacketByteBuf; 4 | import net.minecraft.network.codec.PacketCodec; 5 | import net.minecraft.network.packet.CustomPayload; 6 | import net.minecraft.util.Identifier; 7 | 8 | public record OpenConfigPayload(String configData) implements CustomPayload { 9 | public static final Identifier ID_IDENTIFIER = Identifier.of("ai-player", "open_config"); 10 | public static final CustomPayload.Id ID = new CustomPayload.Id<>(ID_IDENTIFIER); 11 | 12 | // Define a string codec with a max length (adjust 32767 as needed) 13 | public static final PacketCodec STRING_CODEC = new StringCodec(32767); 14 | 15 | // Use the tuple helper to create a codec for this payload 16 | public static final PacketCodec CODEC = 17 | PacketCodec.tuple(STRING_CODEC, OpenConfigPayload::configData, OpenConfigPayload::new); 18 | 19 | @Override 20 | public Id getId() { 21 | return ID; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Shasank Prasad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Network/SaveConfigPayload.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Network; 2 | 3 | import net.minecraft.network.PacketByteBuf; 4 | import net.minecraft.network.codec.PacketCodec; 5 | import net.minecraft.network.packet.CustomPayload; 6 | import net.minecraft.util.Identifier; 7 | 8 | 9 | public record SaveConfigPayload(String configData) implements CustomPayload { 10 | public static final Identifier ID_IDENTIFIER = Identifier.of("ai-player", "save_config"); 11 | public static final CustomPayload.Id ID = new CustomPayload.Id<>(ID_IDENTIFIER); 12 | 13 | 14 | // Define a string codec with a max length (adjust 32767 as needed) 15 | public static final PacketCodec STRING_CODEC = new StringCodec(32767); 16 | 17 | // Use the tuple helper to create a codec for this payload 18 | public static final PacketCodec CODEC = 19 | PacketCodec.tuple(STRING_CODEC, SaveConfigPayload::configData, SaveConfigPayload::new); 20 | 21 | @Override 22 | public Id getId() { 23 | return ID; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/FunctionCaller/Tool.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.FunctionCaller; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | public class Tool { 7 | private final String name; 8 | private final String description; 9 | private final List parameters; 10 | private final Set stateKeys; 11 | private final ToolStateUpdater stateUpdater; 12 | 13 | public Tool(String name, String description, List parameters, Set stateKeys, ToolStateUpdater stateUpdater) { 14 | this.name = name; 15 | this.description = description; 16 | this.parameters = parameters; 17 | this.stateKeys = stateKeys; 18 | this.stateUpdater = stateUpdater; 19 | } 20 | 21 | public String name() { return name; } 22 | public String description() { return description; } 23 | public List parameters() { return parameters; } 24 | public Set stateKeys() { return stateKeys; } 25 | public ToolStateUpdater stateUpdater() { return stateUpdater; } 26 | 27 | public record Parameter(String name, String description) {} 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/SelectedItemDetails.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import java.io.Serializable; 4 | 5 | public class SelectedItemDetails implements Serializable { 6 | private static final long serialVersionUID = 1L; // Ensures compatibility between serialized versions 7 | 8 | private final String name; 9 | private final boolean isFood; 10 | private final boolean isBlock; 11 | 12 | public SelectedItemDetails(String name, boolean isFood, boolean isBlock) { 13 | this.name = name; 14 | this.isFood = isFood; 15 | this.isBlock = isBlock; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public boolean isFood() { 23 | return isFood; 24 | } 25 | 26 | public boolean isBlock() { 27 | return isBlock; 28 | 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "SelectedItemDetails{" + 34 | "name = '" + name + '\'' + 35 | ", isFood = " + isFood + 36 | ", isBlock = " + isBlock + 37 | '}'; 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/ToolSelector.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.item.ItemStack; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | 7 | import java.util.List; 8 | 9 | public class ToolSelector { 10 | 11 | public static ItemStack selectBestToolForBlock(ServerPlayerEntity bot, BlockState blockState) { 12 | List hotbarItems = hotBarUtils.getHotbarItems(bot); 13 | ItemStack bestTool = ItemStack.EMPTY; 14 | float highestSpeed = 0.0f; 15 | 16 | for (ItemStack item : hotbarItems) { 17 | if (item.isEmpty()) continue; 18 | 19 | float speed = item.getMiningSpeedMultiplier(blockState); 20 | if (speed > highestSpeed) { 21 | highestSpeed = speed; 22 | bestTool = item; 23 | } 24 | } 25 | 26 | // If none has a speed > 1.0, just use whatever is selected 27 | if (highestSpeed <= 1.0f) { 28 | return hotBarUtils.getSelectedHotbarItemStack(bot); 29 | } 30 | 31 | return bestTool; 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Network/SaveAPIKeyPayload.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Network; 2 | 3 | 4 | import net.minecraft.network.PacketByteBuf; 5 | import net.minecraft.network.codec.PacketCodec; 6 | import net.minecraft.network.packet.CustomPayload; 7 | import net.minecraft.util.Identifier; 8 | 9 | public record SaveAPIKeyPayload(String provider, String key) implements CustomPayload { 10 | public static final Identifier ID_IDENTIFIER = Identifier.of("ai-player", "save_api_key"); 11 | public static final CustomPayload.Id ID = new CustomPayload.Id<>(ID_IDENTIFIER); 12 | 13 | 14 | // Define a string codec with a max length (adjust 32767 as needed) 15 | public static final PacketCodec STRING_CODEC = new StringCodec(32767); 16 | 17 | // Use the tuple helper to create a codec for this payload 18 | public static final PacketCodec CODEC = 19 | PacketCodec.tuple( 20 | STRING_CODEC, SaveAPIKeyPayload::provider, 21 | STRING_CODEC, SaveAPIKeyPayload::key, 22 | SaveAPIKeyPayload::new 23 | ); 24 | 25 | @Override 26 | public Id getId() { 27 | return ID; 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Overlay/ThinkingStateManager.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Overlay; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class ThinkingStateManager { 7 | private static boolean active = false; 8 | private static final List reasoningLines = new ArrayList<>(); 9 | private static String botName = ""; 10 | private static boolean collapsed = true; 11 | 12 | public static void start(String name) { 13 | active = true; 14 | reasoningLines.clear(); 15 | botName = name; 16 | collapsed = true; 17 | } 18 | 19 | public static void appendThoughtLine(String line) { 20 | reasoningLines.add(line.trim()); 21 | } 22 | 23 | public static void end() { 24 | active = false; 25 | } 26 | 27 | public static boolean isThinking() { 28 | return active; 29 | } 30 | 31 | public static List getReasoningLines() { 32 | return reasoningLines; 33 | } 34 | 35 | public static String getBotName() { 36 | return botName; 37 | } 38 | 39 | public static boolean isCollapsed() { 40 | return collapsed; 41 | } 42 | 43 | public static void toggleCollapsed() { 44 | collapsed = !collapsed; 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Network/SaveCustomProviderPayload.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Network; 2 | 3 | import net.minecraft.network.PacketByteBuf; 4 | import net.minecraft.network.codec.PacketCodec; 5 | import net.minecraft.network.packet.CustomPayload; 6 | import net.minecraft.util.Identifier; 7 | 8 | public record SaveCustomProviderPayload(String apiKey, String apiUrl) implements CustomPayload { 9 | public static final Identifier ID_IDENTIFIER = Identifier.of("ai-player", "save_custom_provider"); 10 | public static final CustomPayload.Id ID = new CustomPayload.Id<>(ID_IDENTIFIER); 11 | 12 | // Define a string codec with a max length (adjust 32767 as needed) 13 | public static final PacketCodec STRING_CODEC = new StringCodec(32767); 14 | 15 | // Use the tuple helper to create a codec for this payload 16 | public static final PacketCodec CODEC = 17 | PacketCodec.tuple( 18 | STRING_CODEC, SaveCustomProviderPayload::apiKey, 19 | STRING_CODEC, SaveCustomProviderPayload::apiUrl, 20 | SaveCustomProviderPayload::new 21 | ); 22 | 23 | @Override 24 | public Id getId() { 25 | return ID; 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/hotBarUtils.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.item.ItemStack; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class hotBarUtils { 10 | public static List getHotbarItems(ServerPlayerEntity bot) { 11 | List hotbarItems = new ArrayList<>(); 12 | 13 | if (bot != null) { 14 | for (int i = 0; i < 9; i++) { 15 | hotbarItems.add(bot.getInventory().getStack(i)); 16 | } 17 | } 18 | 19 | return hotbarItems; 20 | } 21 | 22 | public static ItemStack getSelectedHotbarItemStack(ServerPlayerEntity bot) { 23 | 24 | // Ensure the client and player are not null 25 | 26 | // Get the selected slot's stack 27 | int selectedSlot = bot.getInventory().selectedSlot; 28 | ItemStack selectedStack = bot.getInventory().getStack(selectedSlot); 29 | 30 | // Check if the slot is not empty 31 | if (!selectedStack.isEmpty()) { 32 | // Return the translation key of the item 33 | 34 | return selectedStack; 35 | } 36 | 37 | 38 | // Return a placeholder if there's no item in the selected slot 39 | return ItemStack.EMPTY; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Database/StateActionPair.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Database; 2 | 3 | import net.shasankp000.GameAI.State; 4 | import net.shasankp000.GameAI.StateActions; 5 | 6 | import java.io.Serializable; 7 | import java.util.Objects; /** 8 | * Wrapper class to represent a single state-action pair. 9 | */ 10 | public class StateActionPair implements Serializable { 11 | private final State state; 12 | private final StateActions.Action action; 13 | 14 | public StateActionPair(State state, StateActions.Action action) { 15 | this.state = state; 16 | this.action = action; 17 | } 18 | 19 | public State getState() { 20 | return state; 21 | } 22 | 23 | public StateActions.Action getAction() { 24 | return action; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (this == o) return true; 30 | if (o == null || getClass() != o.getClass()) return false; 31 | StateActionPair that = (StateActionPair) o; 32 | return Objects.equals(state, that.state) && action == that.action; 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(state, action); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return String.format("State: %s, Action: %s", state, action); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/LLMClient.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | // Base interface for all LLM clients 4 | public interface LLMClient { 5 | /** 6 | * Send a prompt to the LLM and receive the response. 7 | * @param systemPrompt The system prompt 8 | * @param userPrompt The input prompt 9 | * @return The LLM's response as a string 10 | */ 11 | String sendPrompt(String systemPrompt, String userPrompt); 12 | 13 | /** 14 | * Checks if the LLM service is reachable and the API key is valid. 15 | * This is a lightweight check and does not involve a full chat completion. 16 | * @return true if the service is reachable, false otherwise. 17 | */ 18 | boolean isReachable(); 19 | 20 | /** 21 | * Fetches the name of the LLM client's provider. 22 | */ 23 | String getProvider(); 24 | 25 | /** 26 | * Optional: If the provider supports streaming responses. 27 | * @param systemPrompt The system prompt 28 | * @param userPrompt The input prompt 29 | * @param callback Function to handle streaming chunks 30 | */ 31 | default void sendPromptStreaming(String systemPrompt, String userPrompt, java.util.function.Consumer callback) { 32 | // Default: not supported 33 | callback.accept(sendPrompt(systemPrompt, userPrompt)); 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/LIDSNetModel/LIDSNetTranslator.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils.LIDSNetModel; 2 | 3 | import ai.djl.translate.Translator; 4 | import ai.djl.translate.TranslatorContext; 5 | import ai.djl.ndarray.NDArray; 6 | import ai.djl.ndarray.NDList; 7 | import ai.djl.translate.Batchifier; 8 | import ai.djl.modality.Classifications; 9 | import java.util.List; 10 | 11 | public class LIDSNetTranslator implements Translator { 12 | 13 | private final List classNames; 14 | 15 | public LIDSNetTranslator(List classNames) { 16 | this.classNames = classNames; 17 | } 18 | 19 | @Override 20 | public NDList processInput(TranslatorContext ctx, float[] input) { 21 | // Shape: [1, input_dim] 22 | NDArray array = ctx.getNDManager().create(input).reshape(1, input.length); 23 | return new NDList(array); 24 | } 25 | 26 | @Override 27 | public Classifications processOutput(TranslatorContext ctx, NDList list) throws Exception { 28 | NDArray output = list.singletonOrThrow(); // Shape: [1, num_classes] 29 | NDArray prob = output.softmax(1); // Shape preserved 30 | return new Classifications(classNames, prob); 31 | } 32 | 33 | @Override 34 | public Batchifier getBatchifier() { 35 | return null; // Set to Batchifier.STACK for batch inference 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/getFrostLevel.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.entity.player.PlayerEntity; 4 | import net.minecraft.block.Blocks; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.util.math.BlockPos; 7 | 8 | public class getFrostLevel { 9 | 10 | // Method to calculate the bot's frost level 11 | public static int calculateFrostLevel(PlayerEntity bot) { 12 | BlockPos botPosition = bot.getBlockPos(); 13 | BlockState blockState = bot.getWorld().getBlockState(botPosition); 14 | 15 | // Start with a base frost level of 0 16 | int frostLevel = 0; 17 | 18 | // Check if the bot is in powdered snow 19 | if (blockState.isOf(Blocks.POWDER_SNOW)) { 20 | frostLevel += 5; // Assign a higher frost level for powdered snow 21 | } 22 | 23 | // Check for cold biomes (e.g., Snowy Tundra, Frozen Ocean) 24 | if (bot.getWorld().getBiome(botPosition).value().getTemperature() < 0.15f) { 25 | frostLevel += 2; // Assign a moderate frost level for cold biomes 26 | } 27 | 28 | // Check if the bot is wearing frost protection gear 29 | if (bot.hasStatusEffect(net.minecraft.entity.effect.StatusEffects.FIRE_RESISTANCE)) { 30 | frostLevel -= 3; // Reduce frost level due to fire resistance effect 31 | } 32 | 33 | // Ensure frost level is non-negative 34 | return Math.max(frostLevel, 0); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/FunctionCaller/SharedStateUtils.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.FunctionCaller; 2 | 3 | import java.util.Map; 4 | 5 | public class SharedStateUtils { 6 | 7 | /** 8 | * Gets a value from sharedState, attempting to parse strings to numbers if possible. 9 | * Returns null if key missing or unparseable. 10 | */ 11 | public static Object getValue(Map state, String key) { 12 | if (!state.containsKey(key)) return null; 13 | Object val = state.get(key); 14 | if (val instanceof String s) { 15 | try { 16 | return Integer.parseInt(s); 17 | } catch (NumberFormatException e1) { 18 | try { 19 | return Double.parseDouble(s); 20 | } catch (NumberFormatException e2) { 21 | return s; // Fallback to string 22 | } 23 | } 24 | } 25 | return val; // Already non-string 26 | } 27 | 28 | /** 29 | * Sets a value in sharedState (accepts any Object). 30 | */ 31 | public static void setValue(Map state, String key, Object value) { 32 | state.put(key, value); 33 | } 34 | 35 | /** 36 | * Resolves a placeholder key to a string (for params; uses toString() on non-strings). 37 | */ 38 | public static String resolveAsString(Map state, String key) { 39 | Object val = getValue(state, key); 40 | if (val == null) return "__UNRESOLVED__"; 41 | return val.toString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Network/ConfigJsonUtil.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Network; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import net.shasankp000.AIPlayer; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class ConfigJsonUtil { 11 | 12 | public static String configToJson() { 13 | // Retrieve config values from the generated CONFIG 14 | List modelList = AIPlayer.CONFIG.getModelList(); 15 | String selectedLanguageModel = AIPlayer.CONFIG.getSelectedLanguageModel(); 16 | Map botGameProfile = AIPlayer.CONFIG.getBotGameProfile(); 17 | 18 | // Build JSON using Gson's JsonObject and JsonArray 19 | JsonObject root = new JsonObject(); 20 | 21 | // Add modelList as a JSON array 22 | JsonArray modelsArray = new JsonArray(); 23 | for (String model : modelList) { 24 | modelsArray.add(model); 25 | } 26 | root.add("modelList", modelsArray); 27 | 28 | // Add selectedLanguageModel as a property 29 | root.addProperty("selectedLanguageModel", selectedLanguageModel); 30 | 31 | // Add BotGameProfile as a JSON object 32 | JsonObject profileObject = new JsonObject(); 33 | for (Map.Entry entry : botGameProfile.entrySet()) { 34 | profileObject.addProperty(entry.getKey(), entry.getValue()); 35 | } 36 | root.add("BotGameProfile", profileObject); 37 | 38 | // Return the JSON string (pretty printing optional) 39 | return root.toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/DangerZoneDetector/DangerZoneDetector.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.DangerZoneDetector; 2 | 3 | import net.minecraft.server.network.ServerPlayerEntity; 4 | 5 | public class DangerZoneDetector { 6 | 7 | private static final int BOUNDING_BOX_SIZE = 10; // Detection range in blocks 8 | 9 | /** 10 | * Detects nearby danger zones (lava or cliffs) and calculates the effective danger distance. 11 | * 12 | * @param source The bot entity. 13 | * @param lavaRange The range to check for lava blocks. 14 | * @param cliffRange The forward range to check for cliffs. 15 | * @param cliffDepth The downward range to check for solid blocks (cliff depth). 16 | * @return The effective danger distance (distance from lava + distance from cliff). 17 | */ 18 | public static double detectDangerZone(ServerPlayerEntity source, int lavaRange, int cliffRange, int cliffDepth) { 19 | // Detect nearby lava blocks 20 | double lavaDistance = LavaDetector.detectNearestLava(source, lavaRange, BOUNDING_BOX_SIZE); 21 | if (lavaDistance == Double.MAX_VALUE) { 22 | lavaDistance = 0; // Default to 0 if no lava is nearby 23 | } 24 | 25 | // Detect nearby cliffs 26 | double cliffDistance = CliffDetector.detectCliffWithBoundingBox(source, cliffRange, cliffDepth); 27 | if (cliffDistance == Double.MAX_VALUE) { 28 | cliffDistance = 0; // Default to 0 if no cliffs are nearby 29 | } 30 | 31 | // Calculate the effective danger distance 32 | return lavaDistance + cliffDistance; 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Entity/FaceClosestEntity.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Entity; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.util.math.Vec3d; 6 | 7 | import java.util.List; 8 | 9 | public class FaceClosestEntity { 10 | 11 | public static void faceClosestEntity(ServerPlayerEntity bot, List entities) { 12 | if (entities.isEmpty()) { 13 | return; 14 | } 15 | 16 | Entity closestEntity = null; 17 | double closestDistance = Double.MAX_VALUE; 18 | 19 | // Find the closest entity 20 | for (Entity entity : entities) { 21 | double distance = bot.squaredDistanceTo(entity); 22 | if (distance < closestDistance) { 23 | closestDistance = distance; 24 | closestEntity = entity; 25 | } 26 | } 27 | 28 | if (closestEntity != null) { 29 | // Calculate the direction to the closest entity 30 | Vec3d botPos = bot.getPos(); 31 | Vec3d entityPos = closestEntity.getPos(); 32 | Vec3d direction = entityPos.subtract(botPos).normalize(); 33 | 34 | // Calculate yaw and pitch 35 | double yaw = Math.toDegrees(Math.atan2(direction.z, direction.x)) - 90; 36 | double pitch = Math.toDegrees(-Math.atan2(direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z))); 37 | 38 | // Set the bot's rotation 39 | bot.setYaw((float) yaw); 40 | bot.setPitch((float) pitch); 41 | 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Entity/EntityDetails.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Entity; 2 | 3 | import java.io.Serial; 4 | import java.io.Serializable; 5 | 6 | public class EntityDetails implements Serializable { 7 | @Serial 8 | private static final long serialVersionUID = 1L; 9 | 10 | private final String name; 11 | private final double x, y, z; 12 | private final boolean isHostile; 13 | private final String directionToBot; // New parameter 14 | 15 | public EntityDetails(String name, double x, double y, double z, boolean isHostile, String directionToBot) { 16 | this.name = name; 17 | this.x = x; 18 | this.y = y; 19 | this.z = z; 20 | this.isHostile = isHostile; 21 | this.directionToBot = directionToBot; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public double getX() { 29 | return x; 30 | } 31 | 32 | public double getY() { 33 | return y; 34 | } 35 | 36 | public double getZ() { 37 | return z; 38 | } 39 | 40 | public boolean isHostile() { 41 | return isHostile; 42 | } 43 | 44 | public String getDirectionToBot() { 45 | return directionToBot; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "EntityDetails{" + 51 | "name='" + name + '\'' + 52 | ", x=" + x + 53 | ", y=" + y + 54 | ", z=" + z + 55 | ", isHostile=" + isHostile + 56 | ", directionToBot='" + directionToBot + '\'' + 57 | '}'; 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Commands/configCommand.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Commands; 2 | 3 | import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; 4 | import net.minecraft.server.command.CommandManager; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | import net.shasankp000.Network.configNetworkManager; 7 | import net.fabricmc.loader.api.FabricLoader; 8 | import net.fabricmc.api.EnvType; 9 | 10 | public class configCommand { 11 | 12 | public static void register() { 13 | CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { 14 | dispatcher.register(CommandManager.literal("configMan") 15 | .executes(context -> { 16 | // Get the player who executed the command. 17 | ServerPlayerEntity player = context.getSource().getPlayer(); 18 | 19 | // Check if we're on a dedicated server. 20 | if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) { 21 | // On a dedicated server, send a packet to the client to open the config GUI. 22 | configNetworkManager.sendOpenConfigPacket(player); 23 | } 24 | else { 25 | // we are on client, send packet to the client to open the config GUI 26 | 27 | configNetworkManager.sendOpenConfigPacket(player); 28 | 29 | } 30 | 31 | return 1; 32 | }) 33 | ); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/ThreatDetector.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.shasankp000.Entity.EntityDetails; 4 | import net.shasankp000.GameAI.State; 5 | 6 | import java.util.List; 7 | 8 | public class ThreatDetector { 9 | 10 | public static double calculateThreatLevel(State currentState) { 11 | double threatLevel = 0.0; 12 | 13 | List nearbyEntities = currentState.getNearbyEntities(); 14 | 15 | List hostileEntities = nearbyEntities.stream() 16 | .filter(EntityDetails::isHostile) 17 | .toList(); 18 | 19 | // Example threat calculations 20 | int numberOfHostiles = hostileEntities.size(); 21 | double closestHostileDistance = currentState.getDistanceToHostileEntity(); 22 | String currentDimension = currentState.getDimensionType(); 23 | 24 | // Add threat based on number of hostile entities 25 | threatLevel += Math.min(1.0, numberOfHostiles / 10.0); // Cap at 1.0 for 10+ hostiles 26 | 27 | // Add threat based on proximity to the closest hostile 28 | if (closestHostileDistance < 5.0) { 29 | threatLevel += 0.5; // Immediate danger 30 | } else if (closestHostileDistance < 15.0) { 31 | threatLevel += 0.3; // Moderate danger 32 | } 33 | 34 | // Add threat based on biome 35 | if (currentDimension.contains("Nether") || currentDimension.contains("nether") || currentDimension.contains("End") || currentDimension.contains("end")) { 36 | threatLevel += 0.4; // Dangerous biome 37 | } 38 | 39 | // Clamp threat level between 0 and 1 40 | return Math.min(1.0, threatLevel); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/BlockNameNormalizer.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.registry.Registries; 4 | import net.minecraft.util.Identifier; 5 | 6 | public class BlockNameNormalizer { 7 | 8 | public static String normalizeBlockName(String rawInput) { 9 | if (rawInput == null || rawInput.isEmpty()) return rawInput; 10 | 11 | String cleaned = rawInput.toLowerCase() 12 | .replace("minecraft:", "") 13 | .replaceAll("[\\s\\-]", "_"); 14 | 15 | Identifier bestMatch = null; 16 | int bestScore = Integer.MIN_VALUE; 17 | 18 | for (Identifier id : Registries.BLOCK.getIds()) { 19 | String path = id.getPath(); 20 | 21 | int score = getMatchScore(cleaned, path); 22 | if (score > bestScore) { 23 | bestScore = score; 24 | bestMatch = id; 25 | } 26 | } 27 | 28 | if (bestMatch != null) { 29 | System.out.println("[block-detection-unit] Best match for " + cleaned + ": " + bestMatch + " with score " + bestScore); 30 | return bestMatch.toString(); 31 | } 32 | 33 | // If nothing scores well, default to this fallback 34 | return "minecraft:" + cleaned; 35 | } 36 | 37 | private static int getMatchScore(String input, String target) { 38 | int score = 0; 39 | 40 | if (target.equals(input)) score += 1000; // Perfect match 41 | else if (target.startsWith(input)) score += 500; // Starts with 42 | else if (target.contains(input)) score += 100; // Substring match 43 | 44 | // Prefer shorter names for less ambiguity 45 | score -= target.length(); 46 | 47 | return score; 48 | } 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/BERTModel/BertTranslator.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils.BERTModel; 2 | 3 | import ai.djl.Model; 4 | import ai.djl.huggingface.tokenizers.Encoding; 5 | import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer; 6 | import ai.djl.modality.Classifications; 7 | import ai.djl.ndarray.*; 8 | import ai.djl.translate.*; 9 | 10 | import java.util.*; 11 | 12 | public class BertTranslator implements Translator { 13 | 14 | private List classes; 15 | private HuggingFaceTokenizer tokenizer; 16 | 17 | public BertTranslator(List labels) { 18 | this.classes = labels; 19 | } 20 | 21 | 22 | public void prepare(NDManager manager, Model model) throws Exception { 23 | tokenizer = HuggingFaceTokenizer.newInstance("distilbert-base-uncased"); 24 | } 25 | 26 | @Override 27 | public NDList processInput(TranslatorContext ctx, String input) throws Exception { 28 | NDManager manager = ctx.getNDManager(); 29 | 30 | // Use Encoding instead of SingleToken 31 | Encoding encoding = tokenizer.encode(input); 32 | 33 | long[] inputIds = encoding.getIds(); 34 | long[] attentionMask = encoding.getAttentionMask(); 35 | 36 | // Create NDArrays with proper data type (long) 37 | NDArray inputIdArray = manager.create(inputIds).expandDims(0); 38 | NDArray attentionMaskArray = manager.create(attentionMask).expandDims(0); 39 | 40 | return new NDList(inputIdArray, attentionMaskArray); 41 | } 42 | 43 | @Override 44 | public Classifications processOutput(TranslatorContext ctx, NDList list) { 45 | NDArray logits = list.get(0); 46 | return new Classifications(classes, logits); 47 | } 48 | 49 | @Override 50 | public Batchifier getBatchifier() { 51 | return null; // Handling single input at a time 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Entity/LookController.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Entity; 2 | 3 | import net.minecraft.entity.Entity; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.util.math.BlockPos; 6 | import net.minecraft.util.math.Vec3d; 7 | 8 | public class LookController { 9 | 10 | public static String faceBlock(ServerPlayerEntity bot, BlockPos targetPos) { 11 | Vec3d botEyePos = bot.getEyePos(); 12 | Vec3d targetVec = new Vec3d( 13 | targetPos.getX() + 0.5, 14 | targetPos.getY() + 0.5, 15 | targetPos.getZ() + 0.5 16 | ); 17 | 18 | Vec3d diff = targetVec.subtract(botEyePos); 19 | double dx = diff.x; 20 | double dy = diff.y; 21 | double dz = diff.z; 22 | 23 | double distanceXZ = Math.sqrt(dx * dx + dz * dz); 24 | float yaw = (float) (Math.atan2(-dx, dz) * (180 / Math.PI)); 25 | float pitch = (float) (-Math.atan2(dy, distanceXZ) * (180 / Math.PI)); 26 | 27 | bot.setYaw(yaw); 28 | bot.setPitch(pitch); 29 | 30 | System.out.printf("Facing block at %s with Yaw: %.2f Pitch: %.2f%n", targetPos, yaw, pitch); 31 | 32 | return "Facing block at " + targetPos + " with Yaw: " + yaw + " and Pitch: " + pitch; 33 | } 34 | 35 | public static void faceEntity(ServerPlayerEntity bot, Entity target) { 36 | Vec3d botPos = bot.getPos(); 37 | Vec3d targetPos = target.getPos(); 38 | Vec3d direction = targetPos.subtract(botPos).normalize(); 39 | 40 | double yaw = Math.toDegrees(Math.atan2(direction.z, direction.x)) - 90; 41 | double pitch = Math.toDegrees(-Math.atan2(direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z))); 42 | 43 | bot.setYaw((float) yaw); 44 | bot.setPitch((float) pitch); 45 | 46 | System.out.printf("Facing entity %s at Yaw: %.2f Pitch: %.2f%n", target.getName().getString(), yaw, pitch); 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/GraphicalUserInterface/ReasoningLogScreen.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.GraphicalUserInterface; 2 | 3 | import net.minecraft.client.gui.DrawContext; 4 | import net.minecraft.client.gui.screen.Screen; 5 | import net.minecraft.client.gui.widget.ButtonWidget; 6 | import net.minecraft.text.OrderedText; 7 | import net.minecraft.text.Text; 8 | import net.shasankp000.Overlay.ThinkingStateManager; 9 | 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | public class ReasoningLogScreen extends Screen { 14 | private final Screen parent; 15 | 16 | public ReasoningLogScreen(Screen parent) { 17 | super(Text.of("Reasoning Log")); 18 | this.parent = parent; 19 | } 20 | 21 | @Override 22 | protected void init() { 23 | ButtonWidget closeButton = ButtonWidget.builder(Text.of("Close"), (btn) -> this.close()) 24 | .dimensions(this.width - 120, 40, 100, 20) 25 | .build(); 26 | 27 | this.addDrawableChild(closeButton); 28 | } 29 | 30 | @Override 31 | public void render(DrawContext context, int mouseX, int mouseY, float delta) { 32 | super.render(context, mouseX, mouseY, delta); 33 | 34 | int x = 20, y = 40; 35 | int white = 0xFFFFFFFF; 36 | 37 | context.drawText(this.textRenderer, "Chain-of-Thought Reasoning:", x, y, white, true); 38 | 39 | int i = 1; 40 | 41 | int maxWidth = this.width - 40; 42 | 43 | for (String line : ThinkingStateManager.getReasoningLines()) { 44 | List wrappedLines = this.textRenderer.wrapLines(Text.of(line), maxWidth); 45 | for (OrderedText wrapped : wrappedLines) { 46 | context.drawText(this.textRenderer, wrapped, x + 10, y + i * 12, white, false); 47 | i++; 48 | } 49 | } 50 | } 51 | 52 | 53 | @Override 54 | public void close() { 55 | Objects.requireNonNull(this.client).setScreen(this.parent); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/FilingSystem/ServerConfigUtil.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.FilingSystem; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import net.fabricmc.loader.api.FabricLoader; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | 13 | 14 | public class ServerConfigUtil { 15 | 16 | /** 17 | * Updates the "selectedLanguageModel" field in the config file. 18 | * Adjust the file path if your config is stored in a subfolder. 19 | */ 20 | public static void updateSelectedLanguageModel(String newModelName) { 21 | // Get the config directory (this is a Path to the config folder) 22 | Path configDir = FabricLoader.getInstance().getConfigDir(); 23 | // Assuming your file is named "settings.json5" and is in the root of the config directory. 24 | // If it's in a subfolder (e.g. "ai-player/settings.json5"), adjust accordingly. 25 | Path configFile = configDir.resolve("settings.json5"); 26 | 27 | try { 28 | // Read the file content 29 | String content = Files.readString(configFile); 30 | // Parse the JSON content (assuming it’s JSON-compatible) 31 | JsonObject jsonObject = JsonParser.parseString(content).getAsJsonObject(); 32 | // Update the field 33 | jsonObject.addProperty("selectedLanguageModel", newModelName); 34 | // Convert the updated object back to a JSON string (with pretty printing) 35 | Gson gson = new GsonBuilder().setPrettyPrinting().create(); 36 | String updatedJson = gson.toJson(jsonObject); 37 | // Write the updated JSON back to the file 38 | Files.writeString(configFile, updatedJson); 39 | } catch (IOException e) { 40 | throw new RuntimeException("Failed to update config file", e); 41 | } 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /CUSTOM_PROVIDERS.md: -------------------------------------------------------------------------------- 1 | # Custom OpenAI-Compatible Provider Support 2 | 3 | This feature allows you to use alternative AI providers that are compatible with the OpenAI API standard, such as OpenRouter, TogetherAI, Perplexity, and others. 4 | 5 | ## How to Use 6 | 7 | ### 1. Enable Custom Provider Mode 8 | 9 | Set the system property when launching the game: 10 | ``` 11 | -Daiplayer.llmMode=custom 12 | ``` 13 | 14 | ### 2. Configure API Settings 15 | 16 | 1. Open the in-game API Keys configuration screen 17 | 2. Set the following fields: 18 | - **Custom API URL**: The base URL of your provider (e.g., `https://openrouter.ai/api/v1`) 19 | - **Custom API Key**: Your API key for the provider 20 | 21 | ### 3. Select a Model 22 | 23 | The system will automatically fetch available models from your provider's `/models` endpoint and display them in the model selection interface. 24 | 25 | ## Supported Providers 26 | 27 | Any provider that implements the OpenAI API standard should work. Some examples: 28 | 29 | - **OpenRouter**: `https://openrouter.ai/api/v1` 30 | - **TogetherAI**: `https://api.together.xyz/v1` 31 | - **Perplexity**: `https://api.perplexity.ai/` 32 | - **Groq**: `https://api.groq.com/openai/v1` 33 | - **Local LM Studio**: `http://localhost:1234/v1` 34 | 35 | ## API Compatibility 36 | 37 | The custom provider implementation uses the following OpenAI API endpoints: 38 | 39 | - `GET /models` - For fetching available models 40 | - `POST /chat/completions` - For sending chat completion requests 41 | 42 | Your provider must support these endpoints with the same request/response format as OpenAI's API. 43 | 44 | ## Troubleshooting 45 | 46 | - **"Custom provider selected but no API URL configured"**: Make sure you've set the Custom API URL field 47 | - **"Custom API key not set in config!"**: Make sure you've set the Custom API Key field 48 | - **Empty model list**: Check that your API key is valid and the URL is correct 49 | - **Connection errors**: Verify that the provider URL is accessible and supports the OpenAI API format -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/GrokModelFetcher.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import java.net.URI; 8 | import java.net.http.HttpClient; 9 | import java.net.http.HttpRequest; 10 | import java.net.http.HttpResponse; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class GrokModelFetcher implements ModelFetcher { 15 | 16 | private final HttpClient client; 17 | 18 | public GrokModelFetcher() { 19 | this.client = HttpClient.newHttpClient(); 20 | } 21 | 22 | @Override 23 | public List fetchModels(String apiKey) { 24 | List modelList = new ArrayList<>(); 25 | if (apiKey == null || apiKey.trim().isEmpty()) { 26 | return modelList; 27 | } 28 | 29 | try { 30 | HttpRequest request = HttpRequest.newBuilder() 31 | .uri(URI.create("https://api.x.ai/v1/models")) 32 | .header("Authorization", "Bearer " + apiKey) 33 | .GET() 34 | .build(); 35 | 36 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 37 | 38 | if (response.statusCode() == 200) { 39 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 40 | JsonArray models = jsonResponse.getAsJsonArray("data"); 41 | 42 | for (JsonElement modelElement : models) { 43 | JsonObject modelObject = modelElement.getAsJsonObject(); 44 | modelList.add(modelObject.get("id").getAsString()); 45 | } 46 | } else { 47 | System.err.println("Error fetching Grok models: " + response.statusCode() + " - " + response.body()); 48 | } 49 | } catch (Exception e) { 50 | e.printStackTrace(); 51 | } 52 | 53 | return modelList; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/ClaudeModelFetcher.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import java.net.URI; 8 | import java.net.http.HttpClient; 9 | import java.net.http.HttpRequest; 10 | import java.net.http.HttpResponse; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class ClaudeModelFetcher implements ModelFetcher { 15 | 16 | private final HttpClient client; 17 | 18 | public ClaudeModelFetcher() { 19 | this.client = HttpClient.newHttpClient(); 20 | } 21 | 22 | @Override 23 | public List fetchModels(String apiKey) { 24 | List modelList = new ArrayList<>(); 25 | if (apiKey == null || apiKey.trim().isEmpty()) { 26 | return modelList; 27 | } 28 | 29 | try { 30 | HttpRequest request = HttpRequest.newBuilder() 31 | .uri(URI.create("https://api.anthropic.com/v1/models")) 32 | .header("x-api-key", apiKey) 33 | .header("anthropic-version", "2023-06-01") // Required header 34 | .GET() 35 | .build(); 36 | 37 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 38 | 39 | if (response.statusCode() == 200) { 40 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 41 | JsonArray models = jsonResponse.getAsJsonArray("data"); 42 | 43 | for (JsonElement modelElement : models) { 44 | JsonObject modelObject = modelElement.getAsJsonObject(); 45 | modelList.add(modelObject.get("id").getAsString()); 46 | } 47 | } else { 48 | System.err.println("Error fetching Claude models: " + response.statusCode() + " - " + response.body()); 49 | } 50 | } catch (Exception e) { 51 | e.printStackTrace(); 52 | } 53 | 54 | return modelList; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/OpenAIModelFetcher.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import java.net.URI; 8 | import java.net.http.HttpClient; 9 | import java.net.http.HttpRequest; 10 | import java.net.http.HttpResponse; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class OpenAIModelFetcher implements ModelFetcher { 15 | 16 | private final HttpClient client; 17 | 18 | public OpenAIModelFetcher() { 19 | this.client = HttpClient.newHttpClient(); 20 | } 21 | 22 | @Override 23 | public List fetchModels(String apiKey) { 24 | List modelList = new ArrayList<>(); 25 | if (apiKey == null || apiKey.trim().isEmpty()) { 26 | return modelList; // Return empty list if no API key is provided 27 | } 28 | 29 | try { 30 | HttpRequest request = HttpRequest.newBuilder() 31 | .uri(URI.create("https://api.openai.com/v1/models")) 32 | .header("Authorization", "Bearer " + apiKey) 33 | .GET() 34 | .build(); 35 | 36 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 37 | 38 | if (response.statusCode() == 200) { 39 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 40 | JsonArray models = jsonResponse.getAsJsonArray("data"); 41 | 42 | for (JsonElement modelElement : models) { 43 | JsonObject modelObject = modelElement.getAsJsonObject(); 44 | String modelId = modelObject.get("id").getAsString(); 45 | 46 | // Optional: Filter for relevant models (e.g., chat models) 47 | if (modelId.startsWith("gpt-")) { 48 | modelList.add(modelId); 49 | } 50 | } 51 | } 52 | } catch (Exception e) { 53 | e.printStackTrace(); 54 | // Handle exceptions (e.g., network error, invalid key) 55 | } 56 | 57 | return modelList; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/FilingSystem/getLanguageModels.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.FilingSystem; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import net.shasankp000.Exception.ollamaNotReachableException; 8 | 9 | import java.io.IOException; 10 | import java.net.URI; 11 | import java.net.URISyntaxException; 12 | import java.net.http.HttpClient; 13 | import java.net.http.HttpRequest; 14 | import java.net.http.HttpResponse; 15 | import java.util.ArrayList; 16 | import java.util.HashSet; 17 | import java.util.List; 18 | import java.util.Set; 19 | 20 | public class getLanguageModels { 21 | 22 | public static List get() throws ollamaNotReachableException { 23 | Set modelSet = new HashSet<>(); 24 | 25 | try { 26 | HttpClient client = HttpClient.newHttpClient(); 27 | HttpRequest request = HttpRequest.newBuilder() 28 | .uri(new URI("http://localhost:11434/api/tags")) 29 | .GET() 30 | .build(); 31 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 32 | 33 | if (response.statusCode() == 200) { 34 | String responseBody = response.body(); 35 | JsonObject jsonObject = JsonParser.parseString(responseBody).getAsJsonObject(); 36 | JsonArray modelsArray = jsonObject.getAsJsonArray("models"); 37 | 38 | if (modelsArray != null) { 39 | for (JsonElement element : modelsArray) { 40 | JsonObject modelObject = element.getAsJsonObject(); 41 | String modelName = modelObject.get("name").getAsString(); 42 | modelSet.add(modelName); 43 | } 44 | } 45 | } else { 46 | throw new ollamaNotReachableException("Ollama Server returned status code: " + response.statusCode()); 47 | } 48 | 49 | } catch (URISyntaxException | IOException | InterruptedException e) { 50 | throw new ollamaNotReachableException("Error pinging Ollama Server: " + e.getMessage()); 51 | } 52 | 53 | return new ArrayList<>(modelSet); 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Entity/RayCasting.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Entity; 2 | 3 | 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.util.hit.BlockHitResult; 6 | import net.minecraft.util.hit.HitResult; 7 | import net.minecraft.util.math.Direction; 8 | import net.minecraft.util.math.Vec3d; 9 | import net.minecraft.world.RaycastContext; 10 | import net.shasankp000.ChatUtils.ChatUtils; 11 | 12 | public class RayCasting { 13 | 14 | private static String checkOutput = ""; 15 | 16 | public static String detect(ServerPlayerEntity bot) { 17 | detectBlocks(bot); 18 | return checkOutput; 19 | } 20 | 21 | private static void detectBlocks(ServerPlayerEntity bot) { 22 | 23 | Vec3d botPosition = bot.getPos(); 24 | Direction getDirection = bot.getHorizontalFacing(); 25 | Vec3d botDirection = Vec3d.of(getDirection.getVector()); 26 | double rayLength = 15.0; 27 | Vec3d rayEnd = botPosition.add(botDirection.multiply(rayLength)); 28 | 29 | RaycastContext raycastContext = new RaycastContext( 30 | botPosition, 31 | rayEnd, 32 | RaycastContext.ShapeType.COLLIDER, // Use COLLIDER for block and entity detection 33 | RaycastContext.FluidHandling.ANY, // Consider all fluids 34 | bot 35 | ); 36 | 37 | BlockHitResult hitResult = bot.getWorld().raycast(raycastContext); 38 | 39 | 40 | if (hitResult.getType() == HitResult.Type.BLOCK) { 41 | System.out.println("Block detected at: " + hitResult.getBlockPos()); 42 | checkOutput = "Block detected in front at " + hitResult.getBlockPos().getX() + ", " + hitResult.getBlockPos().getY() + ", " + hitResult.getBlockPos().getZ(); 43 | 44 | ChatUtils.sendChatMessages(bot.getCommandSource().withSilent().withMaxLevel(4), "Block detected in front at " + hitResult.getBlockPos().getX() + ", " + hitResult.getBlockPos().getY() + ", " + hitResult.getBlockPos().getZ()); 45 | 46 | } else if (hitResult.getType() == HitResult.Type.MISS) { 47 | System.out.println("Nothing detected in front by raycast"); 48 | 49 | checkOutput = "No block detected in front"; 50 | 51 | ChatUtils.sendChatMessages(bot.getCommandSource().withSilent().withMaxLevel(4), "No block detected in front"); 52 | } 53 | 54 | } 55 | 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/PreProcessing/NLPModelSetup.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils.PreProcessing; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.net.URL; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.StandardCopyOption; 12 | import java.security.MessageDigest; 13 | 14 | public class NLPModelSetup { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger("NLPModelSetup"); 17 | private static final int MAX_RETRIES = 2; 18 | 19 | public static void ensureModelDownloaded(Path targetFile, String modelURL, String expectedSha512) throws IOException { 20 | 21 | 22 | int retryCount = 0; 23 | 24 | while (retryCount <= MAX_RETRIES) { 25 | // Download 26 | try (InputStream in = new URL(modelURL).openStream()) { 27 | Files.copy(in, targetFile, StandardCopyOption.REPLACE_EXISTING); 28 | } 29 | 30 | // Validate 31 | String actualHash = sha512Hex(targetFile); 32 | if (actualHash.equalsIgnoreCase(expectedSha512)) { 33 | LOGGER.info("✅ File {} downloaded and verified successfully.", targetFile); 34 | return; 35 | } else { 36 | LOGGER.warn("⚠️ Hash mismatch for {} (attempt {}/{}). Retrying...", targetFile.getFileName(), retryCount + 1, MAX_RETRIES); 37 | Files.deleteIfExists(targetFile); 38 | retryCount++; 39 | } 40 | } 41 | 42 | throw new IOException("Max retries reached. Could not verify integrity of: " + targetFile); 43 | } 44 | 45 | // SHA-512 checksum calculator 46 | public static String sha512Hex(Path file) throws IOException { 47 | try (InputStream fis = Files.newInputStream(file)) { 48 | MessageDigest digest = MessageDigest.getInstance("SHA-512"); 49 | byte[] buf = new byte[8192]; 50 | int n; 51 | while ((n = fis.read(buf)) > 0) { 52 | digest.update(buf, 0, n); 53 | } 54 | byte[] hashBytes = digest.digest(); 55 | StringBuilder sb = new StringBuilder(); 56 | for (byte b : hashBytes) { 57 | sb.append(String.format("%02x", b)); 58 | } 59 | return sb.toString(); 60 | } catch (Exception e) { 61 | throw new IOException("Failed to calculate SHA-512: " + e.getMessage(), e); 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/DangerZoneDetector/CliffDetector.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.DangerZoneDetector; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.util.math.Box; 6 | import net.minecraft.util.math.Vec3d; 7 | import net.minecraft.util.math.BlockPos; 8 | import net.minecraft.util.shape.VoxelShape; 9 | import net.minecraft.world.World; 10 | 11 | public class CliffDetector { 12 | 13 | /** 14 | * Detects cliffs using a bounding box. 15 | * 16 | * @param source The bot entity. 17 | * @param range The forward range to scan for cliffs. 18 | * @param depth The downward range to check for solid blocks. 19 | * @return Distance to the cliff if detected, or Double.MAX_VALUE if no cliff is found. 20 | */ 21 | public static double detectCliffWithBoundingBox(ServerPlayerEntity source, int range, int depth) { 22 | Vec3d botPos = source.getPos(); 23 | World world = source.getWorld(); 24 | 25 | // Get the direction the bot is facing 26 | Vec3d facingDirection = source.getRotationVec(1.0F).normalize(); 27 | 28 | // Iterate through positions in the facing direction 29 | for (int i = 1; i <= range; i++) { 30 | // Calculate the current position in the facing direction 31 | Vec3d checkPos = botPos.add(facingDirection.multiply(i)); 32 | BlockPos blockPos = new BlockPos((int) checkPos.x, (int) checkPos.y, (int) checkPos.z); 33 | 34 | // Create a bounding box that stretches downward 35 | Box detectionBox = new Box(blockPos).stretch(0, -depth, 0); 36 | 37 | boolean hasSolidBlock = false; 38 | 39 | // Iterate through all voxel shapes within the bounding box 40 | for (VoxelShape shape : world.getBlockCollisions(source, detectionBox)) { 41 | // Get the bounding box of the current voxel shape 42 | Box voxelBox = shape.getBoundingBox(); 43 | BlockPos voxelPos = new BlockPos((int) voxelBox.minX, (int) voxelBox.minY, (int) voxelBox.minZ); 44 | 45 | // Check if the block is solid 46 | BlockState state = world.getBlockState(voxelPos); 47 | if (state.isSolidBlock(world, voxelPos)) { 48 | hasSolidBlock = true; 49 | break; // Stop checking if a solid block is found 50 | } 51 | } 52 | 53 | // If no solid blocks are found, this is a cliff 54 | if (!hasSolidBlock) { 55 | return botPos.distanceTo(checkPos); 56 | } 57 | } 58 | 59 | // No cliff detected within the specified range 60 | return Double.MAX_VALUE; 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/GeminiModelFetcher.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import java.net.URI; 8 | import java.net.http.HttpClient; 9 | import java.net.http.HttpRequest; 10 | import java.net.http.HttpResponse; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class GeminiModelFetcher implements ModelFetcher { 15 | 16 | private final HttpClient client; 17 | 18 | public GeminiModelFetcher() { 19 | this.client = HttpClient.newHttpClient(); 20 | } 21 | 22 | @Override 23 | public List fetchModels(String apiKey) { 24 | List modelList = new ArrayList<>(); 25 | if (apiKey == null || apiKey.trim().isEmpty()) { 26 | return modelList; 27 | } 28 | 29 | try { 30 | HttpRequest request = HttpRequest.newBuilder() 31 | .uri(URI.create("https://generativelanguage.googleapis.com/v1beta/models?key=" + apiKey)) 32 | .GET() 33 | .build(); 34 | 35 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 36 | 37 | if (response.statusCode() == 200) { 38 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 39 | JsonArray models = jsonResponse.getAsJsonArray("models"); 40 | 41 | for (JsonElement modelElement : models) { 42 | JsonObject modelObject = modelElement.getAsJsonObject(); 43 | String modelName = modelObject.get("name").getAsString(); 44 | // Gemini models start with "models/" 45 | String modelId = modelName.startsWith("models/") ? modelName.substring(7) : modelName; 46 | 47 | // Filter for generative models 48 | if (modelObject.has("supportedGenerationMethods")) { 49 | JsonArray methods = modelObject.getAsJsonArray("supportedGenerationMethods"); 50 | for (JsonElement method : methods) { 51 | if (method.getAsString().equals("generateContent")) { 52 | modelList.add(modelId); 53 | break; 54 | } 55 | } 56 | } 57 | } 58 | } else { 59 | // Log the error 60 | System.err.println("Error fetching Gemini models: " + response.statusCode() + " - " + response.body()); 61 | } 62 | } catch (Exception e) { 63 | e.printStackTrace(); 64 | } 65 | 66 | return modelList; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PathFinding/ChartPathToBlock.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PathFinding; 2 | 3 | import net.minecraft.server.MinecraftServer; 4 | import net.minecraft.server.command.ServerCommandSource; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | import net.minecraft.util.math.BlockPos; 7 | import net.minecraft.util.math.MathHelper; 8 | import net.minecraft.util.math.Vec3d; 9 | import net.minecraft.util.math.Vec3i; 10 | import net.shasankp000.Entity.LookController; 11 | import net.shasankp000.PlayerUtils.blockDetectionUnit; 12 | 13 | import java.util.Objects; 14 | 15 | public class ChartPathToBlock { 16 | 17 | public static String chart(ServerPlayerEntity bot, BlockPos targetBlockPos, String blockType) { 18 | ServerCommandSource botSource = bot.getCommandSource().withSilent().withMaxLevel(4); 19 | MinecraftServer server = bot.getServer(); 20 | String botName = bot.getName().getString(); 21 | 22 | 23 | // Start micro nav 24 | while (true) { 25 | // Face the block 26 | LookController.faceBlock(bot, targetBlockPos); 27 | 28 | Objects.requireNonNull(server).getCommandManager().executeWithPrefix(botSource, "/player " + botName + " move forward"); 29 | 30 | // If the bot collides with a block, stop 31 | Vec3d nextPos = bot.getPos().add(bot.getRotationVec(1.0f).multiply(0.1)); 32 | 33 | // Convert manually to Vec3i 34 | Vec3i nextPosInt = new Vec3i( 35 | MathHelper.floor(nextPos.x), 36 | MathHelper.floor(nextPos.y), 37 | MathHelper.floor(nextPos.z) 38 | ); 39 | 40 | if (bot.getWorld().getBlockState(new BlockPos(nextPosInt)).isOpaque()) { 41 | Objects.requireNonNull(server).getCommandManager().executeWithPrefix(botSource, "/player " + botName + " stop"); 42 | 43 | // Check if it’s the correct block 44 | BlockPos hitPos = blockDetectionUnit.detectBlocks(bot, blockType); // returns BlockPos instead of string 45 | if (hitPos.equals(targetBlockPos)) { 46 | System.out.println("Bot is now in front of the target block!"); 47 | return "Bot is now in front of the target block! Bot is at " + bot.getBlockPos().getX() + " " + bot.getBlockPos().getY() + " " + bot.getBlockPos().getZ(); 48 | } else { 49 | System.out.println("Hit obstacle that is not target block! Need to adjust."); 50 | return "Hit obstacle that is not target block! Need to adjust."; 51 | } 52 | } 53 | 54 | // Sleep for a short tick (pseudo) 55 | try { 56 | Thread.sleep(50); 57 | } catch (InterruptedException e) { 58 | Thread.currentThread().interrupt(); 59 | } 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/FilingSystem/LLMClientFactory.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.FilingSystem; 2 | 3 | import net.shasankp000.AIPlayer; 4 | import net.shasankp000.ServiceLLMClients.*; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | public class LLMClientFactory { 9 | 10 | private static final Logger LOGGER = LoggerFactory.getLogger("llm-client-factory"); 11 | 12 | public static LLMClient createClient(String mode) { 13 | return switch (mode) { 14 | case "openai", "gpt" -> { 15 | if (AIPlayer.CONFIG.getOpenAIKey().isEmpty()) { 16 | LOGGER.error("OpenAI API key not set in config!"); 17 | yield null; 18 | } 19 | yield new OpenAIClient(AIPlayer.CONFIG.getOpenAIKey(), AIPlayer.CONFIG.getSelectedLanguageModel()); 20 | } 21 | case "anthropic", "claude" -> { 22 | if (AIPlayer.CONFIG.getClaudeKey().isEmpty()) { 23 | LOGGER.error("Claude API key not set in config!"); 24 | yield null; 25 | } 26 | yield new AnthropicClient(AIPlayer.CONFIG.getClaudeKey(), AIPlayer.CONFIG.getSelectedLanguageModel()); 27 | } 28 | case "google", "gemini" -> { 29 | if (AIPlayer.CONFIG.getGeminiKey().isEmpty()) { 30 | LOGGER.error("Gemini API key not set in config!"); 31 | yield null; 32 | } 33 | yield new GeminiClient(AIPlayer.CONFIG.getGeminiKey(), AIPlayer.CONFIG.getSelectedLanguageModel()); 34 | } 35 | case "xAI", "xai", "grok" -> { 36 | if (AIPlayer.CONFIG.getGrokKey().isEmpty()) { 37 | LOGGER.error("Grok API key not set in config!"); 38 | yield null; 39 | } 40 | yield new GrokClient(AIPlayer.CONFIG.getGrokKey(), AIPlayer.CONFIG.getSelectedLanguageModel()); 41 | } 42 | case "custom" -> { 43 | if (AIPlayer.CONFIG.getCustomApiKey().isEmpty()) { 44 | LOGGER.error("Custom API key not set in config!"); 45 | yield null; 46 | } 47 | if (AIPlayer.CONFIG.getCustomApiUrl().isEmpty()) { 48 | LOGGER.error("Custom API URL not set in config!"); 49 | yield null; 50 | } 51 | yield new GenericOpenAIClient(AIPlayer.CONFIG.getCustomApiKey(), AIPlayer.CONFIG.getSelectedLanguageModel(), AIPlayer.CONFIG.getCustomApiUrl()); 52 | } 53 | default -> { 54 | LOGGER.info("Defaulting to Ollama client"); 55 | yield null; 56 | } 57 | }; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/WebSearch/AISearchConfig.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.WebSearch; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.node.ObjectNode; 6 | import net.shasankp000.AIPlayer; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.*; 10 | import java.nio.charset.StandardCharsets; 11 | 12 | public class AISearchConfig { 13 | 14 | public static String GEMINI_API_KEY = ""; 15 | public static String SERPER_API_KEY = ""; 16 | public static String PREFERRED_PROVIDER = "gemini"; 17 | 18 | static { 19 | if (!AIPlayer.CONFIG.getGeminiKey().isEmpty()) { 20 | GEMINI_API_KEY = AIPlayer.CONFIG .getGeminiKey(); // using the same api key for web search purposes as well. 21 | } 22 | 23 | loadConfig(); 24 | } 25 | 26 | private static void loadConfig() { 27 | try { 28 | Path configPath = Paths.get("config", "ai_search_config.json"); 29 | if (!Files.exists(configPath)) { 30 | setupIfMissing(); // Auto-create if not found 31 | } 32 | String json = Files.readString(configPath); 33 | ObjectMapper mapper = new ObjectMapper(); 34 | JsonNode node = mapper.readTree(json); 35 | GEMINI_API_KEY = node.path("gemini_api_key").asText(); 36 | SERPER_API_KEY = node.path("serper_api_key").asText(); 37 | PREFERRED_PROVIDER = node.path("preferred_provider").asText(); 38 | } catch (Exception e) { 39 | System.err.println("❌ Failed to load AI Search config: " + e.getMessage()); 40 | } 41 | } 42 | 43 | public static void setupIfMissing() { 44 | try { 45 | Path configDir = Paths.get("config"); 46 | Path configPath = configDir.resolve("ai_search_config.json"); 47 | 48 | if (!Files.exists(configDir)) { 49 | Files.createDirectories(configDir); 50 | } 51 | 52 | if (!Files.exists(configPath)) { 53 | ObjectMapper mapper = new ObjectMapper(); 54 | ObjectNode root = mapper.createObjectNode(); 55 | root.put("gemini_api_key", "YOUR_GEMINI_API_KEY_HERE"); 56 | root.put("serper_api_key", "YOUR_SERPER_API_KEY_HERE"); 57 | root.put("preferred_provider", "gemini"); 58 | 59 | Files.writeString( 60 | configPath, 61 | mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root), 62 | StandardCharsets.UTF_8 63 | ); 64 | System.out.println("✅ Created default ai_search_config.json in config folder."); 65 | } else { 66 | System.out.println("ℹ️ ai_search_config.json already exists, no setup needed."); 67 | } 68 | } catch (IOException e) { 69 | System.err.println("❌ Failed to create AI Search config: " + e.getMessage()); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/MiningTool.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.block.BlockState; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.util.math.BlockPos; 6 | import net.minecraft.item.ItemStack; 7 | 8 | import java.util.concurrent.*; 9 | 10 | import net.shasankp000.Entity.LookController; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | public class MiningTool { 15 | 16 | private static final long ATTACK_INTERVAL_MS = 200; 17 | public static final Logger LOGGER = LoggerFactory.getLogger("mining-tool"); 18 | 19 | public static CompletableFuture mineBlock(ServerPlayerEntity bot, BlockPos targetBlockPos) { 20 | CompletableFuture miningResult = new CompletableFuture<>(); 21 | try { 22 | 23 | 24 | ScheduledExecutorService miningExecutor = Executors.newSingleThreadScheduledExecutor(); 25 | 26 | // Step 1: Face the block 27 | LookController.faceBlock(bot, targetBlockPos); 28 | 29 | // Step 2: Select best tool 30 | BlockState blockState = bot.getWorld().getBlockState(targetBlockPos); 31 | ItemStack bestTool = ToolSelector.selectBestToolForBlock(bot, blockState); 32 | 33 | // Step 3: Switch to that tool 34 | switchToTool(bot, bestTool); 35 | 36 | // Step 4: Start mining loop 37 | ScheduledFuture task = miningExecutor.scheduleAtFixedRate(() -> { 38 | BlockState currentState = bot.getWorld().getBlockState(targetBlockPos); 39 | 40 | if (currentState.isAir()) { 41 | System.out.println("✅ Mining complete!"); 42 | miningResult.complete("Mining complete!"); 43 | miningExecutor.shutdownNow(); 44 | return; 45 | } 46 | 47 | bot.swingHand(bot.getActiveHand()); 48 | bot.interactionManager.tryBreakBlock(targetBlockPos); 49 | System.out.println("⛏️ Mining..."); 50 | 51 | }, 0, ATTACK_INTERVAL_MS, TimeUnit.MILLISECONDS); 52 | 53 | // In case something else cancels this process 54 | miningResult.whenComplete((result, error) -> { 55 | if (!task.isCancelled() && !task.isDone()) { 56 | task.cancel(true); 57 | } 58 | if (!miningExecutor.isShutdown()) { 59 | miningExecutor.shutdownNow(); 60 | } 61 | }); 62 | 63 | 64 | } 65 | catch (Exception e) { 66 | LOGGER.error("Error in mining tool! {}", e.getMessage()); 67 | } 68 | 69 | return miningResult; 70 | } 71 | 72 | private static void switchToTool(ServerPlayerEntity bot, ItemStack tool) { 73 | for (int i = 0; i < 9; i++) { 74 | if (bot.getInventory().getStack(i) == tool) { 75 | bot.getInventory().selectedSlot = i; 76 | break; 77 | } 78 | } 79 | } 80 | 81 | } 82 | 83 | 84 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/blockDetectionUnit.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | import net.minecraft.util.hit.BlockHitResult; 7 | import net.minecraft.util.hit.HitResult; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.util.math.Direction; 10 | import net.minecraft.util.math.Vec3d; 11 | import net.minecraft.world.RaycastContext; 12 | import net.minecraft.registry.Registries; 13 | import net.minecraft.util.Identifier; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | 18 | public class blockDetectionUnit { 19 | private static final Logger logger = LoggerFactory.getLogger("block-detection-unit"); 20 | 21 | private static boolean isBlockDetectionActive = false; 22 | 23 | public static boolean getBlockDetectionStatus() { 24 | return isBlockDetectionActive; 25 | } 26 | 27 | public static void setIsBlockDetectionActive(boolean value) { 28 | isBlockDetectionActive = value; 29 | } 30 | 31 | 32 | 33 | /** 34 | * Detect a block in the bot's facing direction, but only return it if it matches the given blockType. 35 | * @param bot The bot/player entity. 36 | * @param blockType The block type to detect (e.g., "minecraft:oak_log"). Use Minecraft's registry IDs. 37 | * @return BlockPos of the matching block if found, otherwise null. 38 | */ 39 | public static BlockPos detectBlocks(ServerPlayerEntity bot, String blockType) { 40 | String normalized = BlockNameNormalizer.normalizeBlockName(blockType); 41 | logger.info("Normalized block name: {} → {}", blockType, normalized); 42 | 43 | Vec3d botPosition = bot.getPos(); 44 | Direction getDirection = bot.getHorizontalFacing(); 45 | Vec3d botDirection = Vec3d.of(getDirection.getVector()); 46 | double rayLength = 15.0; 47 | Vec3d rayEnd = botPosition.add(botDirection.multiply(rayLength)); 48 | BlockPos outputBlockpos = null; 49 | 50 | RaycastContext raycastContext = new RaycastContext( 51 | botPosition, 52 | rayEnd, 53 | RaycastContext.ShapeType.COLLIDER, 54 | RaycastContext.FluidHandling.ANY, 55 | bot 56 | ); 57 | 58 | BlockHitResult hitResult = bot.getWorld().raycast(raycastContext); 59 | 60 | if (hitResult.getType() == HitResult.Type.BLOCK) { 61 | BlockPos hitPos = hitResult.getBlockPos(); 62 | BlockState hitBlockState = bot.getWorld().getBlockState(hitPos); 63 | Block hitBlock = hitBlockState.getBlock(); 64 | Identifier hitBlockId = Registries.BLOCK.getId(hitBlock); 65 | 66 | System.out.println("Raycast hit block: " + hitBlockId); 67 | 68 | if (hitBlockId.toString().equals(normalized)) { 69 | System.out.println("Block type matches: " + normalized); 70 | outputBlockpos = hitPos; 71 | setIsBlockDetectionActive(true); 72 | } else { 73 | System.out.println("Block type does not match. Expected: " + normalized + ", Found: " + hitBlockId); 74 | } 75 | } else if (hitResult.getType() == HitResult.Type.MISS) { 76 | System.out.println("Nothing detected in front by raycast"); 77 | } 78 | 79 | return outputBlockpos; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/CART/TreeNodeDeserializer.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils.CART; 2 | 3 | import com.google.gson.*; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | public class TreeNodeDeserializer implements JsonDeserializer { 8 | 9 | @Override 10 | public CartClassifier.TreeNode deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 11 | JsonObject obj = json.getAsJsonObject(); 12 | CartClassifier.TreeNode node = new CartClassifier.TreeNode(); 13 | 14 | JsonElement typeElem = obj.get("type"); 15 | if (typeElem == null || typeElem.isJsonNull()) { 16 | throw new JsonParseException("Missing or invalid 'type' field in node: " + obj); 17 | } 18 | 19 | String nodeType = typeElem.getAsString(); 20 | 21 | if ("leaf".equals(nodeType)) { 22 | node.type = "leaf"; 23 | 24 | // Handle both "class" and "label" field names 25 | JsonElement classElem = obj.get("class"); 26 | if (classElem != null && !classElem.isJsonNull()) { 27 | node.label = classElem.getAsInt(); 28 | } else { 29 | JsonElement labelElem = obj.get("label"); 30 | if (labelElem != null && !labelElem.isJsonNull()) { 31 | node.label = labelElem.getAsInt(); 32 | } else { 33 | throw new JsonParseException("Missing 'class' or 'label' field in leaf node"); 34 | } 35 | } 36 | 37 | JsonElement confElem = obj.get("confidence"); 38 | if (confElem != null && !confElem.isJsonNull()) { 39 | node.confidence = confElem.getAsDouble(); 40 | } else { 41 | throw new JsonParseException("Missing 'confidence' field in leaf node"); 42 | } 43 | 44 | } else if ("split".equals(nodeType)) { 45 | node.type = "split"; 46 | 47 | JsonElement featureElem = obj.get("feature"); 48 | if (featureElem != null && !featureElem.isJsonNull()) { 49 | node.feature = featureElem.getAsString(); 50 | } else { 51 | throw new JsonParseException("Missing 'feature' field in split node"); 52 | } 53 | 54 | JsonElement thresholdElem = obj.get("threshold"); 55 | if (thresholdElem != null && !thresholdElem.isJsonNull()) { 56 | node.threshold = thresholdElem.getAsDouble(); 57 | } else { 58 | throw new JsonParseException("Missing 'threshold' field in split node"); 59 | } 60 | 61 | JsonElement leftElem = obj.get("left"); 62 | JsonElement rightElem = obj.get("right"); 63 | 64 | if (leftElem != null && !leftElem.isJsonNull()) { 65 | node.left = deserialize(leftElem, typeOfT, context); 66 | } else { 67 | throw new JsonParseException("Missing 'left' child in split node"); 68 | } 69 | 70 | if (rightElem != null && !rightElem.isJsonNull()) { 71 | node.right = deserialize(rightElem, typeOfT, context); 72 | } else { 73 | throw new JsonParseException("Missing 'right' child in split node"); 74 | } 75 | 76 | } else { 77 | throw new JsonParseException("Unknown node type: " + nodeType); 78 | } 79 | 80 | return node; 81 | } 82 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/GenericOpenAIModelFetcher.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.net.URI; 11 | import java.net.http.HttpClient; 12 | import java.net.http.HttpRequest; 13 | import java.net.http.HttpResponse; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Model fetcher for generic OpenAI-compatible APIs. 19 | * This fetcher can work with any provider that follows the OpenAI models API standard. 20 | */ 21 | public class GenericOpenAIModelFetcher implements ModelFetcher { 22 | 23 | private final HttpClient client; 24 | private final String baseUrl; 25 | private static final Logger LOGGER = LoggerFactory.getLogger("GenericOpenAIModelFetcher"); 26 | 27 | public GenericOpenAIModelFetcher(String baseUrl) { 28 | this.client = HttpClient.newHttpClient(); 29 | // Ensure baseUrl ends with "/" but doesn't have double slashes 30 | if (baseUrl == null || baseUrl.trim().isEmpty()) { 31 | throw new IllegalArgumentException("Base URL cannot be null or empty"); 32 | } 33 | String trimmedUrl = baseUrl.trim(); 34 | this.baseUrl = trimmedUrl.endsWith("/") ? trimmedUrl : trimmedUrl + "/"; 35 | } 36 | 37 | @Override 38 | public List fetchModels(String apiKey) { 39 | List modelList = new ArrayList<>(); 40 | if (apiKey == null || apiKey.trim().isEmpty()) { 41 | return modelList; // Return empty list if no API key is provided 42 | } 43 | 44 | try { 45 | HttpRequest request = HttpRequest.newBuilder() 46 | .uri(URI.create(baseUrl + "models")) 47 | .header("Authorization", "Bearer " + apiKey) 48 | .GET() 49 | .build(); 50 | 51 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 52 | 53 | if (response.statusCode() == 200) { 54 | 55 | LOGGER.info("Request url: {}", request.uri()); 56 | LOGGER.info("Response code: {}", response.statusCode()); 57 | 58 | 59 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 60 | JsonArray models = jsonResponse.getAsJsonArray("data"); 61 | 62 | for (JsonElement modelElement : models) { 63 | JsonObject modelObject = modelElement.getAsJsonObject(); 64 | String modelId = modelObject.get("id").getAsString(); 65 | 66 | // Add all models - let users choose what they want 67 | // Generic providers might have different naming conventions 68 | modelList.add(modelId); 69 | } 70 | 71 | LOGGER.info("Fetched {} models from Generic OpenAI API", modelList.size()); 72 | LOGGER.info("Models: \n {}", modelList); 73 | 74 | } 75 | 76 | else { 77 | LOGGER.error("Failed to fetch models. HTTP Status: {}. Response: {}", response.statusCode(), response.body()); 78 | } 79 | 80 | } catch (Exception e) { 81 | LOGGER.error("Error fetching models from Generic OpenAI API: {}", e.getMessage()); 82 | } 83 | 84 | return modelList; 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PathFinding/GoTo.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PathFinding; 2 | 3 | import net.minecraft.server.MinecraftServer; 4 | import net.minecraft.server.command.ServerCommandSource; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | import net.minecraft.server.world.ServerWorld; 7 | import net.minecraft.util.math.BlockPos; 8 | 9 | import java.util.List; 10 | import java.util.Queue; 11 | import java.util.concurrent.CompletableFuture; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import static net.shasankp000.PathFinding.PathFinder.*; 15 | 16 | public class GoTo { 17 | 18 | public static String goTo(ServerCommandSource botSource, int x, int y, int z, boolean sprint) { 19 | MinecraftServer server = botSource.getServer(); 20 | ServerPlayerEntity bot = botSource.getPlayer(); 21 | ServerWorld world = server.getOverworld(); 22 | String botName = botSource.getName(); 23 | 24 | if (bot == null) { 25 | System.out.println("Bot not found!"); 26 | return "Bot not found!"; 27 | } 28 | 29 | System.out.println("Found bot: " + botSource.getName()); 30 | 31 | try { 32 | // Calculate the path 33 | List rawPath = calculatePath(bot.getBlockPos(), new BlockPos(x, y, z), world); 34 | 35 | // Simplify + filter 36 | List finalPath = simplifyPath(rawPath, world); 37 | LOGGER.info("Path output: {}", finalPath); 38 | 39 | Queue segments = convertPathToSegments(finalPath, sprint); 40 | LOGGER.info("Generated segments: {}", segments); 41 | 42 | // ✅ Trace the path and wait for completion 43 | CompletableFuture pathFuture = PathTracer.tracePath(server, botSource, botName, segments, sprint); 44 | 45 | 46 | // Wait for path completion with timeout 47 | String result = pathFuture.get(60, TimeUnit.SECONDS); 48 | 49 | String finalOutput = ""; 50 | 51 | if (result.equals("Path cleared")) { 52 | finalOutput = String.format("Bot moved to position - x: %d y: %d z: %d", 53 | (int) bot.getX(), (int) bot.getY(), (int) bot.getZ()); 54 | } 55 | else if (result.equals("Player not found")){ 56 | finalOutput = "Error. Player not found"; 57 | } 58 | else if (result.equals("Max retries exceeded")) { 59 | finalOutput = String.format("Bot moved to position - x: %d y: %d z: %d", 60 | (int) bot.getX(), (int) bot.getY(), (int) bot.getZ()); 61 | } 62 | else if (result.equals("Re-pathing failed")) { 63 | finalOutput = String.format("Bot moved to position - x: %d y: %d z: %d", 64 | (int) bot.getX(), (int) bot.getY(), (int) bot.getZ()); 65 | } 66 | else if (result.contains("Path processing failed: ")) { 67 | finalOutput = "Error. Path tracer failed to process the pathfinder's data"; 68 | } 69 | else { 70 | finalOutput = PathTracer.BotSegmentManager.tracePathOutput(botSource); 71 | } 72 | 73 | System.out.println("Path tracer output: " + result); 74 | System.out.println("Final path output: " + finalOutput); 75 | 76 | return finalOutput; // Already in proper format from PathTracer 77 | 78 | } catch (Exception e) { 79 | LOGGER.error("Error executing goTo: ", e); 80 | return "Failed to execute goTo: " + e.getMessage(); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/Helper/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils.Helper; 2 | 3 | import com.google.gson.JsonParser; 4 | import com.google.gson.JsonSyntaxException; 5 | import com.google.gson.stream.JsonReader; 6 | 7 | import java.io.StringReader; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | public class JsonUtils { 12 | 13 | public static String cleanJsonString(String jsonString) { 14 | // Remove ```json and ``` markers 15 | jsonString = jsonString.replaceAll("```json", "").replaceAll("```", "").trim(); 16 | 17 | // Remove non-printable characters 18 | jsonString = jsonString.replaceAll("[^\\x20-\\x7E]", "").replaceAll("\\\\n", "").replaceAll("\\s+", " "); 19 | 20 | // Attempt to correct common JSON structure errors 21 | jsonString = correctParameterNames(jsonString); 22 | 23 | // Ensure proper JSON format 24 | jsonString = jsonString.replaceAll("\\s*:\\s*", ":").replaceAll("\\s*,\\s*", ","); 25 | jsonString = jsonString.replaceAll("}\\s*]", "}]"); 26 | 27 | // If the JSON still seems malformed, attempt to manually correct it 28 | if (!isValidJson(jsonString)) { 29 | jsonString = attemptManualCorrection(jsonString); 30 | } 31 | 32 | return jsonString; 33 | } 34 | 35 | private static String correctParameterNames(String jsonString) { 36 | // Fix parameter names in a malformed JSON string 37 | jsonString = jsonString.replaceAll("\"name\":", "\"parameterName\":"); 38 | jsonString = jsonString.replaceAll("\"value\":", "\"parameterValue\":"); 39 | 40 | // Fix other potential issues 41 | Pattern pattern = Pattern.compile("\"parameterName\\d+\":\"([a-zA-Z]+)\",\\s*\"parameterValue\":\"([^\"]+)\""); 42 | StringBuffer sb = getStringBuffer(jsonString, pattern); 43 | 44 | return sb.toString(); 45 | } 46 | 47 | private static StringBuffer getStringBuffer(String jsonString, Pattern pattern) { 48 | Matcher matcher = pattern.matcher(jsonString); 49 | StringBuffer sb = new StringBuffer(); 50 | 51 | int counter = 0; 52 | while (matcher.find()) { 53 | matcher.appendReplacement(sb, "\"parameterName\":\"" + matcher.group(1) + "\",\"parameterValue\":\"" + matcher.group(2) + "\""); 54 | counter++; 55 | } 56 | matcher.appendTail(sb); 57 | 58 | // Ensure the parameter array is correctly closed 59 | if (counter > 0 && !jsonString.endsWith("}]")) { 60 | sb.append("}]"); 61 | } 62 | return sb; 63 | } 64 | 65 | private static boolean isValidJson(String jsonString) { 66 | try { 67 | JsonReader reader = new JsonReader(new StringReader(jsonString)); 68 | reader.setLenient(true); 69 | JsonParser.parseReader(reader).getAsJsonObject(); 70 | return true; 71 | } catch (JsonSyntaxException | IllegalStateException e) { 72 | return false; 73 | } 74 | } 75 | 76 | private static String attemptManualCorrection(String jsonString) { 77 | // Attempt to manually correct known issues with the JSON string 78 | jsonString = jsonString.replaceAll("\"parameterName\\d+\":", "\"parameterName\":"); 79 | jsonString = jsonString.replaceAll("\"parameterValue([a-zA-Z]+)\":", "\"parameterValue\":"); 80 | 81 | // Fix trailing commas and other common mistakes 82 | jsonString = jsonString.replaceAll(",\\s*}", "}"); 83 | jsonString = jsonString.replaceAll(",\\s*]", "]"); 84 | 85 | return jsonString; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/GraphicalUserInterface/Widgets/DropdownMenuWidget.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.GraphicalUserInterface.Widgets; 2 | 3 | // This file is still heavily work in progress. At least it doesn't crash. 4 | 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.font.TextRenderer; 7 | import net.minecraft.client.gui.DrawContext; 8 | import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; 9 | import net.minecraft.client.gui.widget.ClickableWidget; 10 | import net.minecraft.text.Text; 11 | 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class DropdownMenuWidget extends ClickableWidget { 17 | private List options; 18 | private boolean isOpen; 19 | private int selectedIndex; 20 | private int width; 21 | private int height; 22 | 23 | public DropdownMenuWidget(int x, int y, int width, int height, Text message, List options) { 24 | super(x, y, width, height, message); 25 | this.options = options; 26 | this.isOpen = false; 27 | this.selectedIndex = -1; // No selection initially 28 | this.width = width; 29 | this.height = height; 30 | } 31 | 32 | 33 | @Override 34 | protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { 35 | // Render the main button 36 | drawCenteredText(context, MinecraftClient.getInstance().textRenderer, getMessage(), this.getX() + this.width / 2, this.getY() + (this.height - 8) / 2, 0xFFFFFF); 37 | 38 | // Render the dropdown menu if open 39 | if (isOpen) { 40 | for (int i = 0; i < options.size(); i++) { 41 | int optionY = this.getY() + this.height * (i + 1); 42 | context.fill(this.getX(), optionY, this.getX() + this.width, optionY + this.height, 0x80000000); 43 | drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.of(options.get(i)), this.getX() + this.width / 2, optionY + (this.height - 8) / 2, 0xFFFFFF); 44 | } 45 | } 46 | } 47 | 48 | @Override 49 | public boolean mouseClicked(double mouseX, double mouseY, int button) { 50 | if (this.isHovered()) { 51 | isOpen = !isOpen; 52 | return true; 53 | } 54 | if (isOpen) { 55 | for (int i = 0; i < options.size(); i++) { 56 | int optionY = this.getY() + this.height * (i + 1); 57 | if (mouseY >= optionY && mouseY < optionY + this.height) { 58 | selectedIndex = i; 59 | setMessage(Text.of(options.get(i))); 60 | isOpen = false; 61 | return true; 62 | } 63 | } 64 | } 65 | return false; 66 | } 67 | 68 | @Override 69 | protected void appendClickableNarrations(NarrationMessageBuilder builder) { 70 | // nothing to do there 71 | return; 72 | } 73 | 74 | public void updateOptions(List newOptions) { 75 | this.options = new ArrayList<>(newOptions); 76 | this.selectedIndex = -1; // Reset selection when options change 77 | this.isOpen = false; // Close dropdown when updating 78 | } 79 | 80 | public String getSelectedOption() { 81 | return selectedIndex >= 0 ? options.get(selectedIndex) : null; 82 | } 83 | 84 | private void drawCenteredText(DrawContext context, TextRenderer textRenderer, Text text, int centerX, int y, int color) { 85 | int textWidth = textRenderer.getWidth(text); 86 | context.drawText(textRenderer, text, centerX - textWidth / 2, y, color, true); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/PreProcessing/OpenNLPProcessor.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils.PreProcessing; 2 | 3 | import opennlp.tools.lemmatizer.LemmatizerME; 4 | import opennlp.tools.lemmatizer.LemmatizerModel; 5 | import opennlp.tools.postag.POSModel; 6 | import opennlp.tools.postag.POSTaggerME; 7 | import opennlp.tools.sentdetect.SentenceDetectorME; 8 | import opennlp.tools.sentdetect.SentenceModel; 9 | import opennlp.tools.tokenize.TokenizerME; 10 | import opennlp.tools.tokenize.TokenizerModel; 11 | 12 | import java.io.FileInputStream; 13 | import java.io.IOException; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | public class OpenNLPProcessor { 18 | 19 | private final SentenceDetectorME sentenceDetector; 20 | private final TokenizerME tokenizer; 21 | private final POSTaggerME posTagger; 22 | private final LemmatizerME lemmatizer; 23 | 24 | public OpenNLPProcessor(String modelPath) throws IOException { 25 | try ( 26 | FileInputStream sentenceModelIn = new FileInputStream(modelPath + "/opennlp-en-ud-ewt-sentence-1.3-2.5.4.bin"); 27 | FileInputStream tokenModelIn = new FileInputStream(modelPath + "/opennlp-en-ud-ewt-tokens-1.3-2.5.4.bin"); 28 | FileInputStream posModelIn = new FileInputStream(modelPath + "/opennlp-en-ud-ewt-pos-1.3-2.5.4.bin"); 29 | FileInputStream lemmatizerModelIn = new FileInputStream(modelPath + "/opennlp-en-ud-ewt-lemmas-1.3-2.5.4.bin") 30 | ) { 31 | SentenceModel sentenceModel = new SentenceModel(sentenceModelIn); 32 | TokenizerModel tokenizerModel = new TokenizerModel(tokenModelIn); 33 | POSModel posModel = new POSModel(posModelIn); 34 | LemmatizerModel lemmatizerModel = new LemmatizerModel(lemmatizerModelIn); 35 | 36 | sentenceDetector = new SentenceDetectorME(sentenceModel); 37 | tokenizer = new TokenizerME(tokenizerModel); 38 | posTagger = new POSTaggerME(posModel); 39 | lemmatizer = new LemmatizerME(lemmatizerModel); 40 | } 41 | } 42 | 43 | public String[] detectSentences(String text) { 44 | return sentenceDetector.sentDetect(text); 45 | } 46 | 47 | public String[] tokenize(String sentence) { 48 | return tokenizer.tokenize(sentence); 49 | } 50 | 51 | public String[] posTag(String[] tokens) { 52 | return posTagger.tag(tokens); 53 | } 54 | 55 | public String[] lemmatize(String[] tokens, String[] posTags) { 56 | return lemmatizer.lemmatize(tokens, posTags); 57 | } 58 | 59 | // Optional: Combined utility for token + POS + lemma in one call 60 | public List analyze(String sentence) { 61 | String[] tokens = tokenize(sentence); 62 | String[] posTags = posTag(tokens); 63 | String[] lemmas = lemmatize(tokens, posTags); 64 | 65 | List results = new ArrayList<>(); 66 | for (int i = 0; i < tokens.length; i++) { 67 | results.add(new TokenInfo(tokens[i], posTags[i], lemmas[i])); 68 | } 69 | return results; 70 | } 71 | 72 | // Helper class to encapsulate a token's metadata 73 | public static class TokenInfo { 74 | public final String token; 75 | public final String posTag; 76 | public final String lemma; 77 | 78 | public TokenInfo(String token, String posTag, String lemma) { 79 | this.token = token; 80 | this.posTag = posTag; 81 | this.lemma = lemma; 82 | } 83 | 84 | @Override 85 | public String toString() { 86 | return token + " (" + posTag + ") → " + lemma; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Database/QTableExporter.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Database; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import net.shasankp000.GameAI.State; 6 | 7 | import java.io.IOException; 8 | import java.nio.charset.StandardCharsets; 9 | import java.nio.file.Files; 10 | import java.nio.file.Paths; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class QTableExporter { 15 | 16 | public static void exportQTable(String qTableFilePath, String exportFilePath) { 17 | try { 18 | // Load the QTable from the binary file 19 | QTable qTable = QTableStorage.load(qTableFilePath); 20 | System.out.println("Loaded QTable from: " + qTableFilePath); 21 | 22 | // Prepare the export data structure 23 | Map exportData = new HashMap<>(); 24 | 25 | for (Map.Entry entry : qTable.getTable().entrySet()) { 26 | StateActionPair pair = entry.getKey(); 27 | QEntry qEntry = entry.getValue(); 28 | 29 | // Serialize state-action pair and corresponding QEntry 30 | String currentState = serializeState(pair.getState()); 31 | Map stateDetails = new HashMap<>(); 32 | stateDetails.put("stateDetails", serializeStateObject(pair.getState())); 33 | stateDetails.put("NextState", serializeStateObject(qEntry.getNextState())); 34 | 35 | exportData.put(currentState, stateDetails); 36 | } 37 | 38 | // Convert the export data to JSON 39 | Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); 40 | String jsonOutput = gson.toJson(exportData); 41 | 42 | // Write JSON to the export file 43 | Files.write(Paths.get(exportFilePath), jsonOutput.getBytes(StandardCharsets.UTF_8)); 44 | System.out.println("QTable exported successfully to: " + exportFilePath); 45 | 46 | } catch (IOException | ClassNotFoundException e) { 47 | System.err.println("Error exporting QTable: " + e.getMessage()); 48 | e.printStackTrace(); 49 | } 50 | } 51 | 52 | // Helper method to serialize a state to a string format 53 | private static String serializeState(State state) { 54 | return String.format("X:%d, Y:%d, Z:%d, Health:%d, Hunger:%d, Oxygen:%d, Frost:%d, Time:%s, Dimension:%s, SelectedItem:%s", 55 | state.getBotX(), 56 | state.getBotY(), 57 | state.getBotZ(), 58 | state.getBotHealth(), 59 | state.getBotHungerLevel(), 60 | state.getBotOxygenLevel(), 61 | state.getFrostLevel(), 62 | state.getTimeOfDay(), 63 | state.getDimensionType(), 64 | state.getSelectedItem()); 65 | } 66 | 67 | // Helper method to convert a State object into a map for detailed JSON export 68 | private static Map serializeStateObject(State state) { 69 | Map stateMap = new HashMap<>(); 70 | stateMap.put("coordinates", Map.of("X", state.getBotX(), "Y", state.getBotY(), "Z", state.getBotZ())); 71 | stateMap.put("Health", state.getBotHealth()); 72 | stateMap.put("Hunger", state.getBotHungerLevel()); 73 | stateMap.put("Oxygen", state.getBotOxygenLevel()); 74 | stateMap.put("Frost", state.getFrostLevel()); 75 | stateMap.put("Time", state.getTimeOfDay()); 76 | stateMap.put("Dimension", state.getDimensionType()); 77 | stateMap.put("SelectedItem", state.getSelectedItem()); 78 | stateMap.put("NearbyEntities", state.getNearbyEntities()); 79 | stateMap.put("RiskAppetite", state.getRiskAppetite()); 80 | stateMap.put("PodMap", state.getPodMap()); 81 | return stateMap; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/GeminiClient.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.net.URI; 10 | import java.net.http.HttpClient; 11 | import java.net.http.HttpRequest; 12 | import java.net.http.HttpRequest.BodyPublishers; 13 | import java.net.http.HttpResponse; 14 | import java.net.http.HttpResponse.BodyHandlers; 15 | 16 | public class GeminiClient implements LLMClient { 17 | private final String apiKey; 18 | private final String modelName; 19 | private final HttpClient client; 20 | public static final Logger LOGGER = LoggerFactory.getLogger("Gemini-Client"); 21 | 22 | public GeminiClient(String apiKey, String modelName) { 23 | this.apiKey = apiKey; 24 | this.modelName = modelName; 25 | this.client = HttpClient.newHttpClient(); 26 | } 27 | 28 | @Override 29 | public String sendPrompt(String systemPrompt, String userPrompt) { 30 | try { 31 | JsonObject requestBody = new JsonObject(); 32 | JsonArray contents = new JsonArray(); 33 | 34 | // 1. Combine the system and user prompts into a single user message 35 | JsonObject userPart = new JsonObject(); 36 | userPart.addProperty("role", "user"); 37 | 38 | JsonArray partsArray = new JsonArray(); 39 | JsonObject combinedTextPart = new JsonObject(); 40 | // Combining them is one of the ways to send system and user prompts 41 | combinedTextPart.addProperty("text", systemPrompt + "\n\n" + userPrompt); 42 | partsArray.add(combinedTextPart); 43 | 44 | userPart.add("parts", partsArray); 45 | contents.add(userPart); 46 | requestBody.add("contents", contents); 47 | 48 | HttpRequest request = HttpRequest.newBuilder() 49 | .uri(URI.create("https://generativelanguage.googleapis.com/v1beta/models/" + modelName + ":generateContent?key=" + apiKey)) 50 | .header("Content-Type", "application/json") 51 | .POST(BodyPublishers.ofString(requestBody.toString())) 52 | .build(); 53 | 54 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 55 | 56 | if (response.statusCode() != 200) { 57 | return "Error: " + response.statusCode() + " - " + response.body(); 58 | } 59 | 60 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 61 | return jsonResponse.getAsJsonArray("candidates") 62 | .get(0).getAsJsonObject() 63 | .getAsJsonObject("content") 64 | .getAsJsonArray("parts") 65 | .get(0).getAsJsonObject() 66 | .get("text").getAsString(); 67 | 68 | } catch (Exception e) { 69 | LOGGER.error("Error in Gemini Client: {}", e.getMessage()); 70 | return "Error: " + e.getMessage(); 71 | } 72 | } 73 | 74 | /** 75 | * Checks if the Gemini API is reachable and the key is valid by making a 76 | * quick, lightweight request to the models endpoint. 77 | * 78 | * @return true if the API returns a 200 status code, false otherwise. 79 | */ 80 | @Override 81 | public boolean isReachable() { 82 | try { 83 | HttpRequest request = HttpRequest.newBuilder() 84 | .uri(URI.create("https://generativelanguage.googleapis.com/v1beta/models?key=" + apiKey)) 85 | .GET() 86 | .build(); 87 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 88 | return response.statusCode() == 200; 89 | } catch (Exception e) { 90 | return false; 91 | } 92 | } 93 | 94 | @Override 95 | public String getProvider() { 96 | return "Google Gemini"; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/GrokClient.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.net.URI; 10 | import java.net.http.HttpClient; 11 | import java.net.http.HttpRequest; 12 | import java.net.http.HttpRequest.BodyPublishers; 13 | import java.net.http.HttpResponse; 14 | import java.net.http.HttpResponse.BodyHandlers; 15 | 16 | public class GrokClient implements LLMClient { 17 | private final String apiKey; 18 | private final String modelName; 19 | private final HttpClient client; 20 | public static final Logger LOGGER = LoggerFactory.getLogger("Grok-Client"); 21 | 22 | public GrokClient(String apiKey, String modelName) { 23 | this.apiKey = apiKey; 24 | this.modelName = modelName; 25 | this.client = HttpClient.newHttpClient(); 26 | } 27 | 28 | @Override 29 | public String sendPrompt(String systemPrompt, String userPrompt) { 30 | try { 31 | JsonObject requestBody = new JsonObject(); 32 | requestBody.addProperty("model", this.modelName); 33 | 34 | JsonArray messages = new JsonArray(); 35 | 36 | // 1. Create the system message object and add it to the array 37 | JsonObject systemMessage = new JsonObject(); 38 | systemMessage.addProperty("role", "system"); 39 | systemMessage.addProperty("content", systemPrompt); 40 | messages.add(systemMessage); 41 | 42 | // 2. Create the user message object and add it to the array 43 | JsonObject userMessage = new JsonObject(); 44 | userMessage.addProperty("role", "user"); 45 | userMessage.addProperty("content", userPrompt); 46 | messages.add(userMessage); 47 | 48 | requestBody.add("messages", messages); 49 | requestBody.addProperty("max_tokens", 150); 50 | 51 | HttpRequest request = HttpRequest.newBuilder() 52 | .uri(URI.create("https://api.x.ai/v1/chat/completions")) 53 | .header("Authorization", "Bearer " + apiKey) 54 | .header("Content-Type", "application/json") 55 | .POST(BodyPublishers.ofString(requestBody.toString())) 56 | .build(); 57 | 58 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 59 | 60 | if (response.statusCode() != 200) { 61 | return "Error: " + response.statusCode() + " - " + response.body(); 62 | } 63 | 64 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 65 | return jsonResponse.getAsJsonArray("choices") 66 | .get(0).getAsJsonObject() 67 | .getAsJsonObject("message") 68 | .get("content").getAsString(); 69 | 70 | } catch (Exception e) { 71 | LOGGER.error("Error in Grok Client: {}", e.getMessage()); 72 | return "Error: " + e.getMessage(); 73 | } 74 | } 75 | 76 | /** 77 | * Checks if the Grok API is reachable and the key is valid by making a 78 | * quick, lightweight request to the models endpoint. 79 | * 80 | * @return true if the API returns a 200 status code, false otherwise. 81 | */ 82 | @Override 83 | public boolean isReachable() { 84 | try { 85 | HttpRequest request = HttpRequest.newBuilder() 86 | .uri(URI.create("https://api.x.ai/v1/models")) 87 | .header("Authorization", "Bearer " + apiKey) 88 | .GET() 89 | .build(); 90 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 91 | return response.statusCode() == 200; 92 | } catch (Exception e) { 93 | return false; 94 | } 95 | } 96 | 97 | @Override 98 | public String getProvider() { 99 | return "xAI - Grok"; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/AnthropicClient.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.net.URI; 10 | import java.net.http.HttpClient; 11 | import java.net.http.HttpRequest; 12 | import java.net.http.HttpRequest.BodyPublishers; 13 | import java.net.http.HttpResponse; 14 | import java.net.http.HttpResponse.BodyHandlers; 15 | 16 | public class AnthropicClient implements LLMClient { 17 | private final String apiKey; 18 | private final String modelName; 19 | private final HttpClient client; 20 | public static final Logger LOGGER = LoggerFactory.getLogger("Anthropic-Client"); 21 | 22 | public AnthropicClient(String apiKey, String modelName) { 23 | this.apiKey = apiKey; 24 | this.modelName = modelName; 25 | this.client = HttpClient.newHttpClient(); 26 | } 27 | 28 | @Override 29 | public String sendPrompt(String systemPrompt, String userPrompt) { 30 | try { 31 | JsonObject requestBody = new JsonObject(); 32 | requestBody.addProperty("model", this.modelName); 33 | 34 | JsonArray messages = new JsonArray(); 35 | 36 | // 1. Create the system message object and add it to the array 37 | JsonObject systemMessage = new JsonObject(); 38 | systemMessage.addProperty("role", "system"); 39 | systemMessage.addProperty("content", systemPrompt); 40 | messages.add(systemMessage); 41 | 42 | // 2. Create the user message object and add it to the array 43 | JsonObject userMessage = new JsonObject(); 44 | userMessage.addProperty("role", "user"); 45 | userMessage.addProperty("content", userPrompt); 46 | messages.add(userMessage); 47 | 48 | requestBody.add("messages", messages); 49 | requestBody.addProperty("max_tokens", 1024); 50 | 51 | HttpRequest request = HttpRequest.newBuilder() 52 | .uri(URI.create("https://api.anthropic.com/v1/messages")) 53 | .header("x-api-key", apiKey) 54 | .header("anthropic-version", "2023-06-01") // Required header 55 | .header("Content-Type", "application/json") 56 | .POST(BodyPublishers.ofString(requestBody.toString())) 57 | .build(); 58 | 59 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 60 | 61 | if (response.statusCode() != 200) { 62 | return "Error: " + response.statusCode() + " - " + response.body(); 63 | } 64 | 65 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 66 | return jsonResponse.getAsJsonArray("content") 67 | .get(0).getAsJsonObject() 68 | .get("text").getAsString(); 69 | 70 | } catch (Exception e) { 71 | LOGGER.error("Error in Anthropic Client: {}", e.getMessage()); 72 | return "Error: " + e.getMessage(); 73 | } 74 | } 75 | 76 | /** 77 | * Checks if the Claude API is reachable and the key is valid by making a 78 | * quick, lightweight request to the models endpoint. 79 | * 80 | * @return true if the API returns a 200 status code, false otherwise. 81 | */ 82 | @Override 83 | public boolean isReachable() { 84 | try { 85 | HttpRequest request = HttpRequest.newBuilder() 86 | .uri(URI.create("https://api.anthropic.com/v1/models")) 87 | .header("x-api-key", apiKey) 88 | .header("anthropic-version", "2023-06-01") 89 | .GET() 90 | .build(); 91 | 92 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 93 | return response.statusCode() == 200; 94 | } catch (Exception e) { 95 | return false; 96 | } 97 | } 98 | 99 | @Override 100 | public String getProvider() { 101 | return "Anthropic"; 102 | } 103 | 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/BlockDistanceLimitedSearch.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.util.math.BlockPos; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.concurrent.CompletableFuture; 9 | 10 | public class BlockDistanceLimitedSearch { 11 | 12 | private final ServerPlayerEntity player; 13 | private final int maxDepth; // Vertical search range (both up and down) 14 | private final int searchRadius; // Horizontal search radius (x and z) 15 | private final List reachableBlockNames = new ArrayList<>(); 16 | 17 | /** 18 | * @param player The player whose surroundings will be scanned. 19 | * @param maxDepth The maximum vertical offset (both up and down) to search. 20 | * @param searchRadius The horizontal radius (x and z) to search. 21 | */ 22 | public BlockDistanceLimitedSearch(ServerPlayerEntity player, int maxDepth, int searchRadius) { 23 | this.player = player; 24 | this.maxDepth = maxDepth; 25 | this.searchRadius = searchRadius; 26 | } 27 | 28 | /** 29 | * Pre-compute reachable blocks from the player's position within the given range. 30 | * We scan within a horizontal circle (of radius searchRadius) and a vertical range of ±maxDepth. 31 | */ 32 | public void preCompute() { 33 | // Clear previous results if any. 34 | reachableBlockNames.clear(); 35 | 36 | BlockPos botPos = player.getBlockPos(); 37 | // Iterate horizontally over a circle. 38 | for (int dx = -searchRadius; dx <= searchRadius; dx++) { 39 | for (int dz = -searchRadius; dz <= searchRadius; dz++) { 40 | // Only consider positions within a circle. 41 | if (dx * dx + dz * dz > searchRadius * searchRadius) { 42 | continue; 43 | } 44 | // Iterate vertically from -maxDepth to maxDepth. 45 | for (int dy = -maxDepth; dy <= maxDepth; dy++) { 46 | BlockPos pos = botPos.add(dx, dy, dz); 47 | if (canReachBlock(pos)) { 48 | // Get the block's name and add it to the list. 49 | Block block = player.getEntityWorld().getBlockState(pos).getBlock(); 50 | String blockName = block.getName().getString(); 51 | reachableBlockNames.add(blockName); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * Determines if the block at the given position is considered "reachable." 60 | * For this example, we assume a block is reachable if it is not air. 61 | * 62 | * @param pos The block position to check. 63 | * @return true if the block is not air, false otherwise. 64 | */ 65 | private boolean canReachBlock(BlockPos pos) { 66 | Block block = player.getEntityWorld().getBlockState(pos).getBlock(); 67 | // Consider the block reachable if it's not air. 68 | return !block.getDefaultState().isAir(); 69 | } 70 | 71 | /** 72 | * Returns the list of reachable block names. 73 | * Make sure to call preCompute() (or one of the detect methods) first. 74 | * 75 | * @return List of block names representing reachable blocks. 76 | */ 77 | public List getReachableBlockNames() { 78 | return reachableBlockNames; 79 | } 80 | 81 | /** 82 | * Asynchronously compute and return the list of reachable block names. 83 | * 84 | * @return A CompletableFuture that will complete with the list of reachable block names. 85 | */ 86 | public CompletableFuture> detectNearbyBlocksAsync() { 87 | return CompletableFuture.supplyAsync(() -> { 88 | preCompute(); 89 | return getReachableBlockNames(); 90 | }); 91 | } 92 | 93 | /** 94 | * Synchronously compute and return the list of reachable block names. 95 | * 96 | * @return List of reachable block names. 97 | */ 98 | public List detectNearbyBlocks() { 99 | preCompute(); 100 | return getReachableBlockNames(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/OpenAIClient.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.net.URI; 10 | import java.net.http.HttpClient; 11 | import java.net.http.HttpRequest; 12 | import java.net.http.HttpRequest.BodyPublishers; 13 | import java.net.http.HttpResponse; 14 | import java.net.http.HttpResponse.BodyHandlers; 15 | 16 | public class OpenAIClient implements LLMClient { 17 | private final String apiKey; 18 | private final String modelName; // New field for the model name 19 | private final HttpClient client; 20 | public static final Logger LOGGER = LoggerFactory.getLogger("OpenAI-Client"); 21 | 22 | public OpenAIClient(String apiKey, String modelName) { 23 | this.apiKey = apiKey; 24 | this.modelName = modelName; // Initialize the new field 25 | this.client = HttpClient.newHttpClient(); 26 | } 27 | 28 | @Override 29 | public String sendPrompt(String systemPrompt, String userPrompt) { 30 | try { 31 | // Construct the request body for chat completions 32 | JsonObject requestBody = new JsonObject(); 33 | requestBody.addProperty("model", this.modelName); 34 | 35 | JsonArray messages = new JsonArray(); 36 | 37 | // 1. Create the system message object and add it to the array 38 | JsonObject systemMessage = new JsonObject(); 39 | systemMessage.addProperty("role", "system"); 40 | systemMessage.addProperty("content", systemPrompt); 41 | messages.add(systemMessage); 42 | 43 | // 2. Create the user message object and add it to the array 44 | JsonObject userMessage = new JsonObject(); 45 | userMessage.addProperty("role", "user"); 46 | userMessage.addProperty("content", userPrompt); 47 | messages.add(userMessage); 48 | 49 | requestBody.add("messages", messages); 50 | requestBody.addProperty("max_tokens", 150); 51 | 52 | HttpRequest request = HttpRequest.newBuilder() 53 | .uri(URI.create("https://api.openai.com/v1/chat/completions")) 54 | .header("Authorization", "Bearer " + apiKey) 55 | .header("Content-Type", "application/json") 56 | .POST(BodyPublishers.ofString(requestBody.toString())) 57 | .build(); 58 | 59 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 60 | 61 | // Handle HTTP error codes 62 | if (response.statusCode() != 200) { 63 | return "Error: " + response.statusCode() + " - " + response.body(); 64 | } 65 | 66 | // Parse the JSON response 67 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 68 | 69 | // Extract the content from the chat message 70 | return jsonResponse.getAsJsonArray("choices") 71 | .get(0).getAsJsonObject() 72 | .getAsJsonObject("message") 73 | .get("content").getAsString(); 74 | 75 | } catch (Exception e) { 76 | LOGGER.error("Error in OpenAI Client: {}", e.getMessage()); 77 | return "Error: " + e.getMessage(); 78 | } 79 | } 80 | 81 | /** 82 | * Checks if the OpenAI API is reachable and the key is valid by making a 83 | * quick, lightweight request to the models endpoint. 84 | * 85 | * @return true if the API returns a 200 status code, false otherwise. 86 | */ 87 | @Override 88 | public boolean isReachable() { 89 | try { 90 | HttpRequest request = HttpRequest.newBuilder() 91 | .uri(URI.create("https://api.openai.com/v1/models")) 92 | .header("Authorization", "Bearer " + apiKey) 93 | .GET() 94 | .build(); 95 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 96 | return response.statusCode() == 200; 97 | } catch (Exception e) { 98 | return false; 99 | } 100 | } 101 | 102 | @Override 103 | public String getProvider() { 104 | return "OpenAI"; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ServiceLLMClients/GenericOpenAIClient.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ServiceLLMClients; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.net.URI; 10 | import java.net.http.HttpClient; 11 | import java.net.http.HttpRequest; 12 | import java.net.http.HttpRequest.BodyPublishers; 13 | import java.net.http.HttpResponse; 14 | import java.net.http.HttpResponse.BodyHandlers; 15 | 16 | /** 17 | * Generic OpenAI-compatible client that supports custom API base URLs. 18 | * This allows using alternative providers like OpenRouter that follow the OpenAI API standard. 19 | */ 20 | public class GenericOpenAIClient implements LLMClient { 21 | private final String apiKey; 22 | private final String modelName; 23 | private final String baseUrl; 24 | private final HttpClient client; 25 | public static final Logger LOGGER = LoggerFactory.getLogger("GenericOpenAI-Client"); 26 | 27 | public GenericOpenAIClient(String apiKey, String modelName, String baseUrl) { 28 | this.apiKey = apiKey; 29 | this.modelName = modelName; 30 | // Ensure baseUrl ends with "/" but doesn't have double slashes 31 | if (baseUrl == null || baseUrl.trim().isEmpty()) { 32 | throw new IllegalArgumentException("Base URL cannot be null or empty"); 33 | } 34 | String trimmedUrl = baseUrl.trim(); 35 | this.baseUrl = trimmedUrl.endsWith("/") ? trimmedUrl : trimmedUrl + "/"; 36 | this.client = HttpClient.newHttpClient(); 37 | } 38 | 39 | @Override 40 | public String sendPrompt(String systemPrompt, String userPrompt) { 41 | try { 42 | // Construct the request body for chat completions 43 | JsonObject requestBody = new JsonObject(); 44 | requestBody.addProperty("model", this.modelName); 45 | 46 | JsonArray messages = new JsonArray(); 47 | 48 | // 1. Create the system message object and add it to the array 49 | JsonObject systemMessage = new JsonObject(); 50 | systemMessage.addProperty("role", "system"); 51 | systemMessage.addProperty("content", systemPrompt); 52 | messages.add(systemMessage); 53 | 54 | // 2. Create the user message object and add it to the array 55 | JsonObject userMessage = new JsonObject(); 56 | userMessage.addProperty("role", "user"); 57 | userMessage.addProperty("content", userPrompt); 58 | messages.add(userMessage); 59 | 60 | requestBody.add("messages", messages); 61 | requestBody.addProperty("max_tokens", 150); 62 | 63 | HttpRequest request = HttpRequest.newBuilder() 64 | .uri(URI.create(baseUrl + "chat/completions")) 65 | .header("Authorization", "Bearer " + apiKey) 66 | .header("Content-Type", "application/json") 67 | .POST(BodyPublishers.ofString(requestBody.toString())) 68 | .build(); 69 | 70 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 71 | 72 | // Handle HTTP error codes 73 | if (response.statusCode() != 200) { 74 | return "Error: " + response.statusCode() + " - " + response.body(); 75 | } 76 | 77 | // Parse the JSON response 78 | JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); 79 | 80 | // Extract the content from the chat message 81 | return jsonResponse.getAsJsonArray("choices") 82 | .get(0).getAsJsonObject() 83 | .getAsJsonObject("message") 84 | .get("content").getAsString(); 85 | 86 | } catch (Exception e) { 87 | LOGGER.error("Error occurred while sending prompt", e); 88 | return "Error: " + e.getMessage(); 89 | } 90 | } 91 | 92 | /** 93 | * Checks if the API is reachable and the key is valid by making a 94 | * quick, lightweight request to the models endpoint. 95 | * 96 | * @return true if the API returns a 200 status code, false otherwise. 97 | */ 98 | @Override 99 | public boolean isReachable() { 100 | try { 101 | HttpRequest request = HttpRequest.newBuilder() 102 | .uri(URI.create(baseUrl + "models")) 103 | .header("Authorization", "Bearer " + apiKey) 104 | .GET() 105 | .build(); 106 | HttpResponse response = client.send(request, BodyHandlers.ofString()); 107 | return response.statusCode() == 200; 108 | } catch (Exception e) { 109 | return false; 110 | } 111 | } 112 | 113 | @Override 114 | public String getProvider() { 115 | return "Generic OpenAI Compatible"; 116 | } 117 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/DangerZoneDetector/LavaDetector.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.DangerZoneDetector; 2 | 3 | import net.minecraft.block.Blocks; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.util.hit.BlockHitResult; 6 | import net.minecraft.util.math.BlockPos; 7 | import net.minecraft.util.math.Box; 8 | import net.minecraft.util.math.Vec3d; 9 | import net.minecraft.world.RaycastContext; 10 | import net.minecraft.world.World; 11 | 12 | public class LavaDetector { 13 | 14 | /** 15 | * Detects the nearest lava block by casting rays in multiple directions. 16 | * 17 | * @param source The bot entity (or player). 18 | * @param reach The maximum distance to check. 19 | * @return Distance to the nearest lava block, or Double.MAX_VALUE if none found. 20 | */ 21 | public static double detectNearestLavaWithRaycast(ServerPlayerEntity source, double reach) { 22 | double nearestDistance = Double.MAX_VALUE; 23 | 24 | // Cast rays in 6 cardinal directions (positive and negative X, Y, Z) 25 | Vec3d[] directions = new Vec3d[]{ 26 | new Vec3d(1, 0, 0), // +X 27 | new Vec3d(-1, 0, 0), // -X 28 | new Vec3d(0, 1, 0), // +Y 29 | new Vec3d(0, -1, 0), // -Y 30 | new Vec3d(0, 0, 1), // +Z 31 | new Vec3d(0, 0, -1) // -Z 32 | }; 33 | 34 | for (Vec3d direction : directions) { 35 | double distance = rayTraceForLava(source, direction, reach); 36 | if (distance < nearestDistance) { 37 | nearestDistance = distance; 38 | } 39 | } 40 | 41 | return nearestDistance; 42 | } 43 | 44 | /** 45 | * Casts a single ray in a given direction to detect lava blocks. 46 | * 47 | * @param source The bot entity (or player). 48 | * @param direction The direction to cast the ray. 49 | * @param reach The maximum distance to cast. 50 | * @return Distance to the nearest lava block, or Double.MAX_VALUE if none found. 51 | */ 52 | private static double rayTraceForLava(ServerPlayerEntity source, Vec3d direction, double reach) { 53 | Vec3d start = source.getCameraPosVec(1.0F); // Starting point of the ray 54 | Vec3d end = start.add(direction.multiply(reach)); // End point of the ray 55 | 56 | BlockHitResult blockHit = source.getWorld().raycast(new RaycastContext( 57 | start, 58 | end, 59 | RaycastContext.ShapeType.OUTLINE, 60 | RaycastContext.FluidHandling.ANY, 61 | source 62 | )); 63 | 64 | // Check if the block hit is lava 65 | if (blockHit != null && source.getWorld().getBlockState(blockHit.getBlockPos()).isOf(Blocks.LAVA)) { 66 | return start.distanceTo(blockHit.getPos()); 67 | } 68 | 69 | return Double.MAX_VALUE; // No lava found in this direction 70 | } 71 | 72 | /** 73 | * Detects the nearest lava block within a bounding box around the bot. 74 | * 75 | * @param source The bot entity. 76 | * @param range The search range (distance in blocks from the bot). 77 | * @return Distance to the nearest lava block, or Double.MAX_VALUE if none found. 78 | */ 79 | public static double detectNearestLavaWithBoundingBox(ServerPlayerEntity source, int range) { 80 | World world = source.getWorld(); 81 | 82 | // Define a bounding box around the bot 83 | Box boundingBox = source.getBoundingBox().expand(range, range, range); 84 | 85 | double nearestDistance = Double.MAX_VALUE; 86 | 87 | // Iterate through all block positions within the bounding box 88 | BlockPos.Mutable mutable = new BlockPos.Mutable(); 89 | for (int x = (int) boundingBox.minX; x <= (int) boundingBox.maxX; x++) { 90 | for (int y = (int) boundingBox.minY; y <= (int) boundingBox.maxY; y++) { 91 | for (int z = (int) boundingBox.minZ; z <= (int) boundingBox.maxZ; z++) { 92 | mutable.set(x, y, z); 93 | 94 | // Check if the block is a lava source or flowing lava 95 | if (world.getBlockState(mutable).isOf(Blocks.LAVA)) { 96 | double distance = source.getPos().distanceTo(Vec3d.ofCenter(mutable)); 97 | if (distance < nearestDistance) { 98 | nearestDistance = distance; 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | return nearestDistance; 106 | } 107 | 108 | public static double detectNearestLava(ServerPlayerEntity source, double reach, int range) { 109 | // Step 1: Try raycasting for visible lava 110 | double nearestLavaFromRaycast = detectNearestLavaWithRaycast(source, reach); 111 | 112 | // Step 2: If no visible lava, fall back to bounding box 113 | if (nearestLavaFromRaycast == Double.MAX_VALUE) { 114 | return detectNearestLavaWithBoundingBox(source, range); 115 | } 116 | 117 | return nearestLavaFromRaycast; 118 | } 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/armorUtils.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.entity.EquipmentSlot; 4 | import net.minecraft.entity.player.PlayerInventory; 5 | import net.minecraft.item.ArmorItem; 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.network.packet.s2c.play.EntityEquipmentUpdateS2CPacket; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import com.mojang.datafixers.util.Pair; // Import the correct Pair class 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | 15 | public class armorUtils { 16 | public static void autoEquipArmor(ServerPlayerEntity bot) { 17 | PlayerInventory inventory = bot.getInventory(); 18 | 19 | // Prepare a list of equipment updates to notify clients 20 | List> equipmentUpdates = new ArrayList<>(); 21 | 22 | // Iterate through all armor slots 23 | for (EquipmentSlot slot : new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}) { 24 | ItemStack equippedArmor = bot.getEquippedStack(slot); 25 | 26 | // Find the best armor piece in the inventory for this slot 27 | ItemStack bestArmor = findBestArmor(inventory, slot); 28 | 29 | // Equip the armor if it's better than what's currently equipped 30 | if (!bestArmor.isEmpty() && (equippedArmor.isEmpty() || isBetterArmor(bestArmor, equippedArmor))) { 31 | bot.equipStack(slot, bestArmor); 32 | inventory.removeOne(bestArmor); // Remove the equipped armor from inventory 33 | System.out.println("Equipped " + bestArmor.getName().getString() + " in slot " + slot.getName()); 34 | 35 | // Add this update to the list for notifying clients 36 | equipmentUpdates.add(new Pair<>(slot, bestArmor)); // Use com.mojang.datafixers.util.Pair 37 | 38 | bot.getInventory().armor.set(slot.getEntitySlotId(), bestArmor.copy()); // update the armor slots data for the server for the bot. 39 | } 40 | } 41 | 42 | // Send the equipment update packet to all nearby players 43 | if (!equipmentUpdates.isEmpty()) { 44 | bot.getServerWorld().getPlayers().forEach(player -> 45 | player.networkHandler.sendPacket(new EntityEquipmentUpdateS2CPacket(bot.getId(), equipmentUpdates)) 46 | ); 47 | } 48 | } 49 | 50 | // Helper method to find the best armor for a specific slot 51 | private static ItemStack findBestArmor(PlayerInventory inventory, EquipmentSlot slot) { 52 | ItemStack bestArmor = ItemStack.EMPTY; 53 | int bestProtection = 0; 54 | 55 | for (ItemStack item : inventory.main) { 56 | if (item.getItem() instanceof ArmorItem armorItem && armorItem.getSlotType() == slot) { 57 | int protection = armorItem.getProtection(); 58 | 59 | // Replace the bestArmor if the current item has higher protection 60 | if (protection > bestProtection) { 61 | bestProtection = protection; 62 | bestArmor = item; 63 | } 64 | } 65 | } 66 | return bestArmor; 67 | } 68 | 69 | // Helper method to compare two armor pieces 70 | private static boolean isBetterArmor(ItemStack newArmor, ItemStack currentArmor) { 71 | if (newArmor.isEmpty() || !(newArmor.getItem() instanceof ArmorItem)) { 72 | return false; 73 | } 74 | 75 | if (currentArmor.isEmpty() || !(currentArmor.getItem() instanceof ArmorItem)) { 76 | return true; 77 | } 78 | 79 | int newProtection = ((ArmorItem) newArmor.getItem()).getProtection(); 80 | int currentProtection = ((ArmorItem) currentArmor.getItem()).getProtection(); 81 | 82 | return newProtection > currentProtection; 83 | } 84 | 85 | public static void autoDeEquipArmor(ServerPlayerEntity bot) { 86 | // still a work-in-progress. 87 | 88 | PlayerInventory inventory = bot.getInventory(); 89 | 90 | // Prepare a list of equipment updates to notify clients 91 | List> equipmentUpdates = new ArrayList<>(); 92 | 93 | 94 | 95 | // Iterate through all armor slots 96 | for (EquipmentSlot slot : new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}) { 97 | ItemStack equippedArmor = bot.getEquippedStack(slot); 98 | 99 | System.out.println(equippedArmor.getName().getString()); 100 | 101 | // If the bot has armor equipped in this slot 102 | if (!equippedArmor.isEmpty()) { 103 | // Add the armor back to the inventory 104 | if (inventory.insertStack(equippedArmor)) { 105 | // Clear the equipped armor slot 106 | bot.equipStack(slot, ItemStack.EMPTY); 107 | 108 | // Add this update to the list for notifying clients 109 | equipmentUpdates.add(new Pair<>(slot, ItemStack.EMPTY)); 110 | 111 | System.out.println("De-equipped " + equippedArmor.getName().getString() + " from slot " + slot.getName()); 112 | } else { 113 | System.out.println("Inventory full! Could not de-equip " + equippedArmor.getName().getString() + " from slot " + slot.getName()); 114 | } 115 | } 116 | } 117 | 118 | // Send the equipment update packet to all nearby players 119 | if (!equipmentUpdates.isEmpty()) { 120 | bot.getServerWorld().getPlayers().forEach(player -> 121 | player.networkHandler.sendPacket(new EntityEquipmentUpdateS2CPacket(bot.getId(), equipmentUpdates)) 122 | ); 123 | } 124 | } 125 | 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/InternalMap.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.server.network.ServerPlayerEntity; 5 | import net.minecraft.util.math.BlockPos; 6 | 7 | import java.util.LinkedHashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Represents an internal 3D map of the blocks surrounding the bot. 12 | * The map is centered on the bot's current position. 13 | * The map array is indexed as: 14 | * map[dy + verticalRange][dx + horizontalRange][dz + horizontalRange] 15 | * where: 16 | * - dx and dz are the horizontal offsets (east-west and north-south, respectively) 17 | * - dy is the vertical offset (relative to the bot's Y coordinate) 18 | */ 19 | public class InternalMap { 20 | 21 | private final ServerPlayerEntity player; 22 | private final int verticalRange; // How many blocks above and below the bot to scan 23 | private final int horizontalRange; // How many blocks outward (in x and z) from the bot to scan 24 | private final Block[][][] map; // 3D array holding scanned block data 25 | 26 | /** 27 | * Constructs a new InternalMap for the given player. 28 | * 29 | * @param player The player (bot) whose surroundings will be mapped. 30 | * @param verticalRange The vertical range: scan from -verticalRange to +verticalRange. 31 | * @param horizontalRange The horizontal range: scan from -horizontalRange to +horizontalRange in x and z. 32 | */ 33 | public InternalMap(ServerPlayerEntity player, int verticalRange, int horizontalRange) { 34 | this.player = player; 35 | this.verticalRange = verticalRange; 36 | this.horizontalRange = horizontalRange; 37 | // Create the map with dimensions: height = (verticalRange*2 + 1), width & depth = (horizontalRange*2 + 1) 38 | map = new Block[verticalRange * 2 + 1][horizontalRange * 2 + 1][horizontalRange * 2 + 1]; 39 | } 40 | 41 | /** 42 | * Scans the blocks surrounding the bot and updates the internal map. 43 | * The bot's current position is treated as the center (offset 0,0,0) of the map. 44 | */ 45 | public void updateMap() { 46 | BlockPos botPos = player.getBlockPos(); 47 | // Loop through all relative offsets within the defined ranges. 48 | for (int dy = -verticalRange; dy <= verticalRange; dy++) { 49 | for (int dx = -horizontalRange; dx <= horizontalRange; dx++) { 50 | for (int dz = -horizontalRange; dz <= horizontalRange; dz++) { 51 | BlockPos pos = botPos.add(dx, dy, dz); 52 | Block block = player.getEntityWorld().getBlockState(pos).getBlock(); 53 | // Store the block in the map array at the corresponding shifted indices. 54 | map[dy + verticalRange][dx + horizontalRange][dz + horizontalRange] = block; 55 | } 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * Returns the block at the given relative coordinates (dx, dy, dz) from the bot's current position. 62 | * 63 | * @param dx Relative x offset (east-west) 64 | * @param dy Relative y offset (vertical) 65 | * @param dz Relative z offset (north-south) 66 | * @return The Block at that location, or null if the coordinates are out of bounds. 67 | */ 68 | public Block getBlockAt(int dx, int dy, int dz) { 69 | if (dx < -horizontalRange || dx > horizontalRange || 70 | dy < -verticalRange || dy > verticalRange || 71 | dz < -horizontalRange || dz > horizontalRange) { 72 | return null; // Out of the defined mapping range. 73 | } 74 | return map[dy + verticalRange][dx + horizontalRange][dz + horizontalRange]; 75 | } 76 | 77 | /** 78 | * For debugging: prints a simple representation of the internal map. 79 | * This example prints each vertical layer (relative dy) as a grid of block initials. 80 | */ 81 | public void printMap() { 82 | for (int dy = -verticalRange; dy <= verticalRange; dy++) { 83 | System.out.println("Layer at relative Y = " + dy + ":"); 84 | for (int dz = -horizontalRange; dz <= horizontalRange; dz++) { 85 | for (int dx = -horizontalRange; dx <= horizontalRange; dx++) { 86 | Block block = getBlockAt(dx, dy, dz); 87 | if (block != null) { 88 | // Use the block's translation key (e.g., "block.minecraft.stone") 89 | String key = block.getName().getString(); 90 | // Extract the short name (after the last '.') 91 | String shortName = key.contains(".") ? key.substring(key.lastIndexOf('.') + 1) : key; 92 | // Print the first character of the short name as a simple identifier. 93 | System.out.print(shortName + " "); 94 | } else { 95 | System.out.print("? "); 96 | } 97 | } 98 | System.out.println(); 99 | } 100 | System.out.println(); 101 | } 102 | } 103 | 104 | public Map summarizeSurroundings() { 105 | Map summary = new LinkedHashMap<>(); 106 | 107 | // Relative positions in cardinal directions 108 | int[][] directionOffsets = { 109 | {0, 0, -1}, // north 110 | {1, 0, 0}, // east 111 | {0, 0, 1}, // south 112 | {-1, 0, 0}, // west 113 | {0, 1, 0}, // above 114 | {0, -1, 0} // below 115 | }; 116 | String[] directionNames = {"north", "east", "south", "west", "up", "down"}; 117 | 118 | for (int i = 0; i < directionOffsets.length; i++) { 119 | int dx = directionOffsets[i][0]; 120 | int dy = directionOffsets[i][1]; 121 | int dz = directionOffsets[i][2]; 122 | 123 | Block block = getBlockAt(dx, dy, dz); 124 | String name = block != null ? block.getTranslationKey().replace("block.minecraft.", "") : "unknown"; 125 | summary.put(directionNames[i], name); 126 | } 127 | 128 | return summary; 129 | } 130 | 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/PlayerUtils/ResourceEvaluator.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.PlayerUtils; 2 | 3 | import java.util.List; 4 | 5 | public class ResourceEvaluator { 6 | 7 | public static double evaluateHotbarResourceValue(List hotbarItems) { 8 | double totalValue = 0.0; 9 | 10 | for (String item : hotbarItems) { 11 | if (item.contains("Sword")) { 12 | totalValue += evaluateSword(item); 13 | } else if (item.contains("Axe")) { 14 | totalValue += evaluateAxe(item); 15 | } else if (item.contains("Pickaxe")) { 16 | totalValue += evaluatePickaxe(item); 17 | } else if (item.contains("Hoe")) { 18 | totalValue += evaluateHoe(item); 19 | } else if (item.contains("Shovel")) { 20 | totalValue += evaluateShovel(item); 21 | } else if (item.contains("Bow")) { 22 | totalValue += evaluateBow(item); 23 | } else if (item.contains("Crossbow")) { 24 | totalValue += evaluateCrossbow(item); 25 | } else if (item.contains("Trident")) { 26 | totalValue += evaluateTrident(item); 27 | } else if (item.contains("Apple")) { 28 | totalValue += evaluateApple(item); 29 | } else if (item.contains("Bread")) { 30 | totalValue += 5.0; // Moderate value food 31 | } else if (item.contains("Steak")) { 32 | totalValue += 8.0; // High-value food 33 | } else if (item.contains("Water Bucket")) { 34 | totalValue += 10.0; // Utility item with high value 35 | } else if (item.contains("Planks")) { 36 | totalValue += 3.0; // Basic building material 37 | } else if (item.contains("Cobblestone")) { 38 | totalValue += 4.0; // Durable building material 39 | } else if (item.contains("Log")) { 40 | totalValue += 5.0; // Versatile resource 41 | } else if (item.contains("Stone")) { 42 | totalValue += 4.0; // Common building material 43 | } else if (item.contains("Tuff")) { 44 | totalValue += 3.0; // Decorative building material 45 | } else if (item.contains("Torch")) { 46 | totalValue += 2.0; // Basic lighting utility 47 | } else if (item.contains("Crafting Table")) { 48 | totalValue += 8.0; // Essential crafting block 49 | } else if (item.contains("Furnace")) { 50 | totalValue += 7.0; // Essential smelting block 51 | } else if (item.contains("Arrow")) { 52 | totalValue += 1.0; // Basic ammo for ranged weapons 53 | } 54 | } 55 | 56 | return totalValue; 57 | } 58 | 59 | private static double evaluateSword(String sword) { 60 | return switch (sword) { 61 | case "Wooden Sword" -> 5.0; 62 | case "Stone Sword" -> 10.0; 63 | case "Iron Sword" -> 15.0; 64 | case "Golden Sword" -> 12.0; // Less durable but high damage 65 | case "Diamond Sword" -> 25.0; 66 | case "Netherite Sword" -> 30.0; 67 | default -> 0.0; 68 | }; 69 | } 70 | 71 | private static double evaluateAxe(String axe) { 72 | return switch (axe) { 73 | case "Wooden Axe" -> 4.0; 74 | case "Stone Axe" -> 8.0; 75 | case "Iron Axe" -> 12.0; 76 | case "Golden Axe" -> 10.0; // Fast but less durable 77 | case "Diamond Axe" -> 20.0; 78 | case "Netherite Axe" -> 25.0; 79 | default -> 0.0; 80 | }; 81 | } 82 | 83 | private static double evaluatePickaxe(String pickaxe) { 84 | return switch (pickaxe) { 85 | case "Wooden Pickaxe" -> 3.0; 86 | case "Stone Pickaxe" -> 6.0; 87 | case "Iron Pickaxe" -> 10.0; 88 | case "Golden Pickaxe" -> 8.0; // High speed but low durability 89 | case "Diamond Pickaxe" -> 18.0; 90 | case "Netherite Pickaxe" -> 22.0; 91 | default -> 0.0; 92 | }; 93 | } 94 | 95 | private static double evaluateHoe(String hoe) { 96 | return switch (hoe) { 97 | case "Wooden Hoe" -> 2.0; 98 | case "Stone Hoe" -> 4.0; 99 | case "Iron Hoe" -> 6.0; 100 | case "Golden Hoe" -> 5.0; // Less durable but fast 101 | case "Diamond Hoe" -> 10.0; 102 | case "Netherite Hoe" -> 12.0; 103 | default -> 0.0; 104 | }; 105 | } 106 | 107 | private static double evaluateShovel(String shovel) { 108 | return switch (shovel) { 109 | case "Wooden Shovel" -> 3.0; 110 | case "Stone Shovel" -> 6.0; 111 | case "Iron Shovel" -> 9.0; 112 | case "Golden Shovel" -> 7.0; // Fast but low durability 113 | case "Diamond Shovel" -> 15.0; 114 | case "Netherite Shovel" -> 18.0; 115 | default -> 0.0; 116 | }; 117 | } 118 | 119 | private static double evaluateApple(String apple) { 120 | return switch (apple) { 121 | case "Apple" -> 4.0; // Normal apple 122 | case "Golden Apple" -> 20.0; // Rare and powerful 123 | case "Enchanted Golden Apple" -> 50.0; // Extremely rare and powerful 124 | default -> 0.0; 125 | }; 126 | } 127 | 128 | private static double evaluateBow(String bow) { 129 | return switch (bow) { 130 | case "Bow" -> 15.0; // Basic ranged weapon 131 | default -> 0.0; 132 | }; 133 | } 134 | 135 | private static double evaluateCrossbow(String crossbow) { 136 | return switch (crossbow) { 137 | case "Crossbow" -> 20.0; // Standard crossbow 138 | case "Arrow Loaded Crossbow" -> 25.0; // Crossbow loaded with arrow 139 | case "Firework Loaded Crossbow" -> 30.0; // Crossbow loaded with firework 140 | default -> 0.0; 141 | }; 142 | } 143 | 144 | private static double evaluateTrident(String trident) { 145 | return switch (trident) { 146 | case "Trident" -> 28.0; // Powerful melee and ranged weapon 147 | default -> 0.0; 148 | }; 149 | } 150 | 151 | } 152 | 153 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/AIPlayer.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000; 2 | 3 | import ai.djl.ModelException; 4 | import net.fabricmc.api.ModInitializer; 5 | import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; 6 | import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; 7 | import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 8 | import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; 9 | import net.fabricmc.loader.api.FabricLoader; 10 | import net.minecraft.server.MinecraftServer; 11 | import net.minecraft.server.network.ServerPlayerEntity; 12 | import net.shasankp000.ChatUtils.BERTModel.BertModelManager; 13 | import net.shasankp000.ChatUtils.NLPProcessor; 14 | import net.shasankp000.Commands.configCommand; 15 | import net.shasankp000.Commands.modCommandRegistry; 16 | import net.shasankp000.Database.SQLiteDB; 17 | import net.shasankp000.FilingSystem.ManualConfig; 18 | import net.shasankp000.GameAI.BotEventHandler; 19 | 20 | import net.shasankp000.Database.QTableStorage; 21 | import net.shasankp000.Entity.AutoFaceEntity; 22 | import net.shasankp000.Network.OpenConfigPayload; 23 | import net.shasankp000.Network.SaveAPIKeyPayload; 24 | import net.shasankp000.Network.SaveConfigPayload; 25 | import net.shasankp000.Network.SaveCustomProviderPayload; 26 | import net.shasankp000.Network.configNetworkManager; 27 | import net.shasankp000.WebSearch.AISearchConfig; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | 31 | import java.io.IOException; 32 | import java.util.concurrent.CompletableFuture; 33 | 34 | 35 | public class AIPlayer implements ModInitializer { 36 | 37 | public static final Logger LOGGER = LoggerFactory.getLogger("ai-player"); 38 | public static final ManualConfig CONFIG = ManualConfig.load(); 39 | public static MinecraftServer serverInstance = null; // default for now 40 | public static BertModelManager modelManager; 41 | public static boolean loadedBERTModelIntoMemory = false; 42 | 43 | 44 | @Override 45 | public void onInitialize() { 46 | 47 | LOGGER.info("Hello Fabric world!"); 48 | 49 | LOGGER.debug("Running on environment type: {}", FabricLoader.getInstance().getEnvironmentType()); 50 | 51 | String llmProvider = System.getProperty("aiplayer.llmMode", "ollama"); 52 | 53 | System.out.println("Using provider: " + llmProvider); 54 | 55 | // Debug: Print ALL system properties to see what's available 56 | System.out.println("=== ALL SYSTEM PROPERTIES ==="); 57 | System.getProperties().forEach((key, value) -> { 58 | if (key.toString().contains("aiplayer") || key.toString().contains("llm")) { 59 | System.out.println(key + " = " + value); 60 | } 61 | }); 62 | System.out.println("=== END DEBUG ==="); 63 | 64 | 65 | // registering the packets on the global entrypoint to recognise them 66 | 67 | PayloadTypeRegistry.playC2S().register(SaveConfigPayload.ID, SaveConfigPayload.CODEC); 68 | PayloadTypeRegistry.playS2C().register(OpenConfigPayload.ID, OpenConfigPayload.CODEC); 69 | PayloadTypeRegistry.playC2S().register(SaveAPIKeyPayload.ID, SaveAPIKeyPayload.CODEC); 70 | PayloadTypeRegistry.playC2S().register(SaveCustomProviderPayload.ID, SaveCustomProviderPayload.CODEC); 71 | 72 | 73 | modCommandRegistry.register(); 74 | configCommand.register(); 75 | SQLiteDB.createDB(); 76 | QTableStorage.setupQTableStorage(); 77 | 78 | 79 | CompletableFuture.runAsync(() -> { 80 | 81 | AISearchConfig.setupIfMissing(); 82 | NLPProcessor.ensureLocalNLPModel(); 83 | try { 84 | Thread.sleep(2000); 85 | System.out.println("NLP model deployment task complete"); 86 | } catch (InterruptedException e) { 87 | Thread.currentThread().interrupt(); 88 | } 89 | 90 | }); 91 | 92 | 93 | modelManager = BertModelManager.getInstance(); 94 | 95 | // Inside AIPlayer.onInitialize() 96 | ServerLifecycleEvents.SERVER_STARTED.register(server -> { 97 | configNetworkManager.registerServerModelNameSaveReceiver(server); 98 | configNetworkManager.registerServerAPIKeySaveReceiver(server); 99 | configNetworkManager.registerServerCustomProviderSaveReceiver(server); 100 | serverInstance = server; 101 | LOGGER.info("Server instance stored!"); 102 | 103 | System.out.println("Server instance is " + serverInstance); 104 | 105 | LOGGER.info("Proceeding to load BERT model into memory"); 106 | 107 | try { 108 | modelManager.loadModel(); 109 | loadedBERTModelIntoMemory = true; 110 | LOGGER.info("BERT model loaded into memory. It will stay in memory as long as any bot stays active in game."); 111 | } catch (IOException | ModelException e) { 112 | LOGGER.error("BERT Model loading failed! {}", e.getMessage()); 113 | } 114 | 115 | 116 | }); 117 | 118 | ServerLifecycleEvents.SERVER_STOPPED.register(server -> { 119 | 120 | AutoFaceEntity.onServerStopped(server); 121 | 122 | 123 | try { 124 | if (modelManager.isModelLoaded() || loadedBERTModelIntoMemory) { 125 | modelManager.unloadModel(); 126 | System.out.println("Unloaded BERT Model from memory"); 127 | } 128 | else { 129 | System.out.println("BERT Model was not loaded, skipping unloading..."); 130 | } 131 | 132 | } catch (IOException e) { 133 | LOGGER.error("BERT Model unloading failed!", e); 134 | } 135 | 136 | }); 137 | 138 | ServerLivingEntityEvents.AFTER_DEATH.register((entity, damageSource) -> { 139 | if (entity instanceof ServerPlayerEntity serverPlayer) { 140 | 141 | if (BotEventHandler.bot != null) { 142 | 143 | if(serverPlayer.getName().getString().equals(BotEventHandler.bot.getName().getString())) { 144 | 145 | QTableStorage.saveLastKnownState(BotEventHandler.getCurrentState(), BotEventHandler.qTableDir + "/lastKnownState.bin"); 146 | 147 | BotEventHandler.botDied = true; // set flag for bot's death. 148 | // System.out.println("Set botDied flag to true"); 149 | } 150 | } 151 | 152 | } 153 | 154 | }); 155 | 156 | ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> { 157 | // Check if the respawned player is the bot 158 | if (oldPlayer instanceof ServerPlayerEntity && newPlayer instanceof ServerPlayerEntity && oldPlayer.getName().getString().equals(newPlayer.getName().getString())) { 159 | System.out.println("Bot has respawned. Updating state..."); 160 | BotEventHandler.hasRespawned = true; 161 | BotEventHandler.botSpawnCount++; 162 | 163 | } 164 | }); 165 | 166 | } 167 | 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/FunctionCaller/ToolVerifiers.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.FunctionCaller; 2 | 3 | import net.minecraft.server.network.ServerPlayerEntity; 4 | import net.minecraft.util.math.BlockPos; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * State-based verifier class. Verifies tool outputs by checking sharedState directly, 11 | * without parsing strings. Each verifier is a lambda that takes params, sharedState, and bot entity. 12 | */ 13 | public class ToolVerifiers { 14 | 15 | // Result class for verifiers: success flag + extracted/calculated data 16 | public static class VerificationResult { 17 | public final boolean success; 18 | public final Map data; 19 | 20 | public VerificationResult(boolean success, Map data) { 21 | this.success = success; 22 | this.data = data != null ? data : new HashMap<>(); 23 | } 24 | } 25 | 26 | // Functional interface for verifiers 27 | @FunctionalInterface 28 | public interface StateVerifier { 29 | VerificationResult verify(Map params, Map sharedState, ServerPlayerEntity bot); 30 | } 31 | 32 | // Registry of verifiers per function (extend as needed) 33 | public static final Map VERIFIER_REGISTRY = Map.of( 34 | "goTo", (params, state, bot) -> { 35 | Object xObj = state.get("botPosition.x"); 36 | Object yObj = state.get("botPosition.y"); 37 | Object zObj = state.get("botPosition.z"); 38 | if (!(xObj instanceof Number) || !(yObj instanceof Number) || !(zObj instanceof Number)) { 39 | return new VerificationResult(false, Map.of("error", "Missing or invalid position in state")); 40 | } 41 | 42 | double actualX = ((Number) xObj).doubleValue(); 43 | double actualY = ((Number) yObj).doubleValue(); 44 | double actualZ = ((Number) zObj).doubleValue(); 45 | 46 | double targetX = Double.parseDouble(params.getOrDefault("x", "0")); 47 | double targetY = Double.parseDouble(params.getOrDefault("y", "0")); 48 | double targetZ = Double.parseDouble(params.getOrDefault("z", "0")); 49 | 50 | double distSq = Math.pow(actualX - targetX, 2) + Math.pow(actualY - targetY, 2) + Math.pow(actualZ - targetZ, 2); 51 | boolean success = distSq <= 16.0; // Tolerance for ~4 blocks, accounting for overshoot 52 | 53 | // Optional cross-check with bot's actual position 54 | if (bot != null) { 55 | BlockPos botPos = bot.getBlockPos(); 56 | double botDistSq = Math.pow(botPos.getX() - targetX, 2) + Math.pow(botPos.getY() - targetY, 2) + Math.pow(botPos.getZ() - targetZ, 2); 57 | success = success && botDistSq <= 16.0; 58 | } 59 | 60 | Map data = new HashMap<>(); 61 | data.put("actual", Map.of("x", actualX, "y", actualY, "z", actualZ)); 62 | data.put("distSq", distSq); 63 | return new VerificationResult(success, data); 64 | }, 65 | "detectBlocks", (params, state, bot) -> { 66 | Object x = state.get("lastDetectedBlock.x"); 67 | Object y = state.get("lastDetectedBlock.y"); 68 | Object z = state.get("lastDetectedBlock.z"); 69 | boolean success = x != null && y != null && z != null; 70 | Map data = success ? Map.of("coords", Map.of("x", x, "y", y, "z", z)) : null; 71 | return new VerificationResult(success, data); 72 | }, 73 | "turn", (params, state, bot) -> { 74 | String actualDirection = (String) state.get("facing.direction"); 75 | String expected = params.getOrDefault("direction", "").toLowerCase(); 76 | boolean success = actualDirection != null && actualDirection.toLowerCase().contains(expected); 77 | return new VerificationResult(success, Map.of("actualDirection", actualDirection != null ? actualDirection : "unknown")); 78 | }, 79 | "mineBlock", (params, state, bot) -> { 80 | String status = (String) state.get("lastMineStatus"); 81 | boolean success = status != null && status.equalsIgnoreCase("success"); 82 | return new VerificationResult(success, Map.of("status", status != null ? status : "unknown")); 83 | }, 84 | "getOxygenLevel", (params, state, bot) -> { 85 | Object levelObj = state.get("bot.oxygenLevel"); // Based on your original code 86 | if (!(levelObj instanceof Number)) return new VerificationResult(false, Map.of("error", "Missing or invalid oxygen level")); 87 | double level = ((Number) levelObj).doubleValue(); 88 | boolean success = level >= 0; 89 | // Optional cross-check with bot 90 | if (bot != null) success = success && bot.getAir() >= 0; 91 | return new VerificationResult(success, Map.of("level", level)); 92 | }, 93 | "getHungerLevel", (params, state, bot) -> { 94 | Object levelObj = state.get("bot.hungerLevel"); 95 | if (!(levelObj instanceof Number)) return new VerificationResult(false, Map.of("error", "Missing or invalid hunger level")); 96 | double level = ((Number) levelObj).doubleValue(); 97 | boolean success = level >= 0; 98 | if (bot != null) success = success && bot.getHungerManager().getFoodLevel() >= 0; 99 | return new VerificationResult(success, Map.of("level", level)); 100 | }, 101 | "getHealthLevel", (params, state, bot) -> { 102 | Object levelObj = state.get("bot.healthLevel"); 103 | if (!(levelObj instanceof Number)) return new VerificationResult(false, Map.of("error", "Missing or invalid health level")); 104 | double level = ((Number) levelObj).doubleValue(); 105 | boolean success = level >= 0; 106 | if (bot != null) success = success && bot.getHealth() >= 0; 107 | return new VerificationResult(success, Map.of("level", level)); 108 | } 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Database/OldSQLiteDB.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Database; 2 | 3 | import net.fabricmc.loader.api.FabricLoader; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.File; 8 | import java.sql.*; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | public class OldSQLiteDB { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(OldSQLiteDB.class); 15 | public static boolean dbExists = false; 16 | public static boolean dbEmpty = false; 17 | 18 | public static void createDB() { 19 | 20 | String gameDir = FabricLoader.getInstance().getGameDir().toString(); 21 | 22 | File dbDir = new File(gameDir + "/sqlite_databases"); 23 | if (!dbDir.exists()) { 24 | if(dbDir.mkdirs()) { 25 | System.out.println("Database directory created."); 26 | } 27 | } 28 | else{ 29 | System.out.println("Database directory already exists, ignoring..."); 30 | } 31 | 32 | String dbUrl = "jdbc:sqlite:" + gameDir + "/sqlite_databases/memory_agent.db"; 33 | 34 | System.out.println("Connecting to database at: " + dbUrl); 35 | try (Connection connection = DriverManager.getConnection(dbUrl); 36 | Statement statement = connection.createStatement()) { 37 | 38 | if (connection.isValid(30)) { 39 | 40 | System.out.println("Connection to database valid."); 41 | 42 | } 43 | 44 | if (connection != null) { 45 | System.out.println("Connection to SQLite has been established."); 46 | } 47 | 48 | // Check if the table exists 49 | String checkTableQuery = "SELECT name FROM sqlite_master WHERE type='table' AND name='conversations'"; 50 | ResultSet tableResultSet = statement.executeQuery(checkTableQuery); 51 | 52 | if (!tableResultSet.next()) { 53 | // Table does not exist, create table 54 | String createTableQuery1 = "CREATE TABLE conversations (" + 55 | "id INTEGER PRIMARY KEY AUTOINCREMENT, " + 56 | "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, " + 57 | "prompt TEXT NOT NULL, " + 58 | "response TEXT NOT NULL, " + 59 | "prompt_embedding BLOB, " + 60 | "response_embedding BLOB" + 61 | ")"; 62 | 63 | String createTableQuery2 = "CREATE TABLE events (" + 64 | "id INTEGER PRIMARY KEY AUTOINCREMENT, " + 65 | "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, " + 66 | "event TEXT NOT NULL, " + 67 | "event_context TEXT NOT NULL, " + 68 | "event_result TEXT NOT NULL, " + 69 | "event_embedding BLOB, " + 70 | "event_context_embedding BLOB, " + 71 | "event_result_embedding BLOB" + 72 | ")"; // Removed the extra comma here 73 | 74 | statement.executeUpdate(createTableQuery1); 75 | statement.executeUpdate(createTableQuery2); 76 | System.out.println("Setting up memory database...done."); 77 | 78 | dbExists = true; 79 | dbEmpty = true; 80 | } 81 | 82 | 83 | } catch (SQLException e) { 84 | logger.error("Caught SQLException: {}", (Object) e.getStackTrace()); 85 | } 86 | } 87 | 88 | public static void storeConversationWithEmbedding(String DB_URL,String prompt, String response, List prompt_embedding, List response_embedding) throws SQLException { 89 | String promptEmbeddingString = prompt_embedding.stream() 90 | .map(String::valueOf) 91 | .collect(Collectors.joining(",")); 92 | 93 | String responseEmbeddingString = response_embedding.stream() 94 | .map(String::valueOf) 95 | .collect(Collectors.joining(",")); 96 | 97 | try (Connection connection = DriverManager.getConnection(DB_URL); 98 | PreparedStatement pstmt = connection.prepareStatement( 99 | "INSERT INTO conversations (prompt, response, prompt_embedding, response_embedding) VALUES (?, ?, ?, ?)")) { 100 | pstmt.setString(1, prompt); 101 | pstmt.setString(2, response); 102 | pstmt.setString(3, promptEmbeddingString); 103 | pstmt.setString(4, responseEmbeddingString); 104 | pstmt.executeUpdate(); 105 | System.out.println("SYSTEM: Conversation saved to database."); 106 | 107 | dbEmpty = false; 108 | } 109 | } 110 | 111 | 112 | public static void storeEventWithEmbedding(String DB_URL,String event, String event_context, String event_result ,List event_embedding, List event_context_embedding, List event_result_embedding) throws SQLException { 113 | 114 | String eventEmbeddingString = event_embedding.stream() 115 | .map(String::valueOf) 116 | .collect(Collectors.joining(",")); 117 | 118 | String eventContextEmbeddingString = event_context_embedding.stream() 119 | .map(String::valueOf) 120 | .collect(Collectors.joining(",")); 121 | 122 | String eventResultEmbeddingString = event_result_embedding.stream() 123 | .map(String::valueOf) 124 | .collect(Collectors.joining(",")); 125 | 126 | try (Connection connection = DriverManager.getConnection(DB_URL); 127 | PreparedStatement pstmt = connection.prepareStatement( 128 | "INSERT INTO events (event, event_context, event_result, event_embedding, event_context_embedding, event_result_embedding) VALUES (?, ?, ?, ?, ?, ?)")) { 129 | pstmt.setString(1, event); 130 | pstmt.setString(2, event_context); 131 | pstmt.setString(3, event_result); 132 | pstmt.setString(4, eventEmbeddingString); 133 | pstmt.setString(5, eventContextEmbeddingString); 134 | pstmt.setString(6, eventResultEmbeddingString); 135 | pstmt.executeUpdate(); 136 | System.out.println("SYSTEM: Event data saved to database."); 137 | 138 | dbEmpty = false; 139 | } 140 | 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Network/configNetworkManager.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Network; 2 | 3 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 4 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 5 | import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; 6 | import net.minecraft.network.PacketByteBuf; 7 | 8 | import net.minecraft.server.MinecraftServer; 9 | import net.minecraft.server.command.ServerCommandSource; 10 | 11 | import net.minecraft.server.network.ServerPlayerEntity; 12 | import net.shasankp000.AIPlayer; 13 | import net.shasankp000.ChatUtils.ChatUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | 18 | public class configNetworkManager { 19 | 20 | public static final Logger LOGGER = LoggerFactory.getLogger("ConfigNetworkMan"); 21 | 22 | // Called on the server side: sends a packet to the specified player. 23 | public static void sendOpenConfigPacket(ServerPlayerEntity player) { 24 | String configData = ConfigJsonUtil.configToJson(); 25 | PacketByteBuf buf = PacketByteBufs.create(); 26 | buf.writeString(configData); 27 | OpenConfigPayload payload = new OpenConfigPayload(configData); // initialize the config data onto the payload 28 | ServerPlayNetworking.send(player, payload); 29 | } 30 | 31 | 32 | // --- Save Config Packet: Server Receives Updated Config Data from Client --- 33 | // Called on the client side to send updated config data. 34 | public static void sendSaveConfigPacket(String configData) { 35 | PacketByteBuf buf = PacketByteBufs.create(); 36 | buf.writeString(configData); 37 | SaveConfigPayload payload = new SaveConfigPayload(configData); 38 | ClientPlayNetworking.send(payload); 39 | } 40 | 41 | // Called on the client side to send updated config data. 42 | public static void sendSaveAPIPacket(String provider, String key) { 43 | PacketByteBuf serviceProvider = PacketByteBufs.create(); 44 | serviceProvider.writeString(provider); 45 | 46 | PacketByteBuf apiKey = PacketByteBufs.create(); 47 | apiKey.writeString(key); 48 | SaveAPIKeyPayload payload = new SaveAPIKeyPayload(provider, key); 49 | ClientPlayNetworking.send(payload); 50 | } 51 | 52 | // Called on the client side to send custom provider settings (both API key and URL). 53 | public static void sendSaveCustomProviderPacket(String apiKey, String apiUrl) { 54 | SaveCustomProviderPayload payload = new SaveCustomProviderPayload(apiKey, apiUrl); 55 | ClientPlayNetworking.send(payload); 56 | } 57 | 58 | 59 | // On the server side: register a receiver for the model name save config packet. 60 | @SuppressWarnings("resource") 61 | public static void registerServerModelNameSaveReceiver(MinecraftServer server) { 62 | ServerPlayNetworking.registerGlobalReceiver(SaveConfigPayload.ID, (payload, context) -> { 63 | // Retrieve the configuration data from the payload 64 | String newConfigData = payload.configData(); 65 | System.out.println("Config data to save: "); 66 | System.out.println(newConfigData); 67 | 68 | // Run the config update on the server thread 69 | context.server().execute(() -> { 70 | AIPlayer.CONFIG.setSelectedLanguageModel(newConfigData); 71 | AIPlayer.CONFIG.save(); 72 | ServerCommandSource serverCommandSource = server.getCommandSource().withSilent().withMaxLevel(4); 73 | ChatUtils.sendSystemMessage(serverCommandSource, "Config saved to server successfully!"); 74 | }); 75 | }); 76 | } 77 | 78 | // On the server side: register a single receiver for all API key save packets. 79 | public static void registerServerAPIKeySaveReceiver(MinecraftServer server) { 80 | ServerPlayNetworking.registerGlobalReceiver(SaveAPIKeyPayload.ID, (payload, context) -> { 81 | String provider = payload.provider(); 82 | String newKey = payload.key(); 83 | 84 | // Run the config update on the server thread 85 | context.server().execute(() -> { 86 | switch (provider) { 87 | case "openai": 88 | AIPlayer.CONFIG.setOpenAIKey(newKey); 89 | break; 90 | case "gemini": 91 | AIPlayer.CONFIG.setGeminiKey(newKey); 92 | break; 93 | case "claude": 94 | AIPlayer.CONFIG.setClaudeKey(newKey); 95 | break; 96 | case "grok": 97 | AIPlayer.CONFIG.setGrokKey(newKey); 98 | break; 99 | case "custom": 100 | AIPlayer.CONFIG.setCustomApiKey(newKey); 101 | break; 102 | case "ollama": 103 | LOGGER.error("Error! Ollama is not supported in this mode!"); 104 | return; 105 | default: 106 | LOGGER.error("Error! Unsupported provider!"); 107 | return; 108 | } 109 | AIPlayer.CONFIG.save(); 110 | ServerCommandSource serverCommandSource = server.getCommandSource().withSilent().withMaxLevel(4); 111 | ChatUtils.sendSystemMessage(serverCommandSource, "API Key for " + provider + " saved successfully!"); 112 | }); 113 | }); 114 | } 115 | 116 | // On the server side: register a receiver for custom provider settings. 117 | public static void registerServerCustomProviderSaveReceiver(MinecraftServer server) { 118 | ServerPlayNetworking.registerGlobalReceiver(SaveCustomProviderPayload.ID, (payload, context) -> { 119 | String newApiKey = payload.apiKey(); 120 | String newApiUrl = payload.apiUrl(); 121 | 122 | // Run the config update on the server thread 123 | context.server().execute(() -> { 124 | AIPlayer.CONFIG.setCustomApiKey(newApiKey); 125 | AIPlayer.CONFIG.setCustomApiUrl(newApiUrl); 126 | AIPlayer.CONFIG.save(); 127 | ServerCommandSource serverCommandSource = server.getCommandSource().withSilent().withMaxLevel(4); 128 | ChatUtils.sendSystemMessage(serverCommandSource, "Custom provider settings saved successfully!"); 129 | }); 130 | }); 131 | } 132 | 133 | 134 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/CART/CartClassifier.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils.CART; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.reflect.TypeToken; 6 | import java.lang.reflect.Type; 7 | 8 | import java.io.File; 9 | import java.io.FileReader; 10 | import java.io.IOException; 11 | import java.io.Reader; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.Set; 15 | import java.util.List; 16 | 17 | public class CartClassifier { 18 | private final TreeNode root; 19 | private final Map labelMap; 20 | private final Set vocabulary; 21 | 22 | // Original constructor - make sure files are in correct order! 23 | public CartClassifier(File treeFile, File labelsFile, File vocabFile) throws IOException { 24 | System.out.println("Loading tree from: " + treeFile.getName()); 25 | System.out.println("Loading labels from: " + labelsFile.getName()); 26 | System.out.println("Loading vocab from: " + vocabFile.getName()); 27 | 28 | this.root = loadTree(treeFile); 29 | this.labelMap = loadLabelMap(labelsFile); 30 | this.vocabulary = loadVocab(vocabFile); 31 | } 32 | 33 | // Alternative constructor with named parameters (safer) 34 | public static CartClassifier create(String treeFilePath, String labelsFilePath, String vocabFilePath) throws IOException { 35 | return new CartClassifier( 36 | new File(treeFilePath), 37 | new File(labelsFilePath), 38 | new File(vocabFilePath) 39 | ); 40 | } 41 | 42 | // Vectorizes the input using the training vocabulary (term frequency) 43 | public Map vectorize(String inputText) { 44 | Map vector = new HashMap<>(); 45 | 46 | for (String word : vocabulary) { 47 | vector.put(word, 0.0); 48 | } 49 | 50 | String[] tokens = inputText.toLowerCase().replaceAll("[^a-z0-9 ]", "").split("\\s+"); 51 | 52 | for (String token : tokens) { 53 | if (vocabulary.contains(token)) { 54 | vector.put(token, vector.get(token) + 1.0); 55 | } 56 | } 57 | 58 | return vector; 59 | } 60 | 61 | // Main classification method 62 | public ClassificationResult classify(String input) { 63 | Map features = vectorize(input); 64 | Leaf leaf = traverseTree(root, features); 65 | 66 | String label = labelMap.getOrDefault(leaf.label, "unknown"); 67 | return new ClassificationResult(label, leaf.confidence); 68 | } 69 | 70 | // Tree traversal based on input features 71 | private Leaf traverseTree(TreeNode node, Map features) { 72 | while (!node.isLeaf()) { 73 | double featureValue = features.getOrDefault(node.feature, 0.0); 74 | node = (featureValue <= node.threshold) ? node.left : node.right; 75 | } 76 | return node.getLeaf(); 77 | } 78 | 79 | // Tree loading from JSON - this should ONLY be called on the tree file! 80 | private TreeNode loadTree(File treeFile) throws IOException { 81 | // Validate that this looks like a tree file 82 | try (Reader reader = new FileReader(treeFile)) { 83 | // Read first few characters to check if it starts with tree structure 84 | char[] buffer = new char[100]; 85 | int charsRead = reader.read(buffer); 86 | String start = new String(buffer, 0, charsRead); 87 | 88 | if (!start.contains("\"type\":") || !start.contains("\"split\"")) { 89 | throw new IOException("File " + treeFile.getName() + " does not appear to be a tree file. Expected to find 'type' and 'split' fields."); 90 | } 91 | } 92 | 93 | Gson gson = new GsonBuilder() 94 | .registerTypeAdapter(TreeNode.class, new TreeNodeDeserializer()) 95 | .create(); 96 | 97 | try (Reader reader = new FileReader(treeFile)) { 98 | return gson.fromJson(reader, TreeNode.class); 99 | } 100 | } 101 | 102 | // Load labels as a list, then convert to map with indices 103 | private Map loadLabelMap(File labelsFile) throws IOException { 104 | Gson gson = new Gson(); 105 | try (Reader reader = new FileReader(labelsFile)) { 106 | Type listType = new TypeToken>() {}.getType(); 107 | List labelsList = gson.fromJson(reader, listType); 108 | 109 | Map labelMap = new HashMap<>(); 110 | for (int i = 0; i < labelsList.size(); i++) { 111 | labelMap.put(i, labelsList.get(i)); 112 | } 113 | return labelMap; 114 | } 115 | } 116 | 117 | // Load vocabulary properly 118 | private Set loadVocab(File vocabFile) throws IOException { 119 | Gson gson = new Gson(); 120 | try (Reader reader = new FileReader(vocabFile)) { 121 | Type type = new TypeToken>() {}.getType(); 122 | Map vocab = gson.fromJson(reader, type); 123 | return vocab.keySet(); 124 | } 125 | } 126 | 127 | // Polymorphic TreeNode supporting both split and leaf nodes 128 | public static class TreeNode { 129 | String type; // "split" or "leaf" 130 | String feature; // only if type == "split" 131 | double threshold; // only if type == "split" 132 | TreeNode left; // only if type == "split" 133 | TreeNode right; // only if type == "split" 134 | Integer label; // only if type == "leaf" 135 | Double confidence; // only if type == "leaf" 136 | 137 | boolean isLeaf() { 138 | return "leaf".equalsIgnoreCase(type); 139 | } 140 | 141 | Leaf getLeaf() { 142 | return new Leaf(label != null ? label : -1, confidence != null ? confidence : 0.0); 143 | } 144 | } 145 | 146 | public static class Leaf { 147 | int label; 148 | double confidence; 149 | 150 | Leaf(int label, double confidence) { 151 | this.label = label; 152 | this.confidence = confidence; 153 | } 154 | } 155 | 156 | public static class ClassificationResult { 157 | public final String label; 158 | public final double confidence; 159 | 160 | public ClassificationResult(String label, double confidence) { 161 | this.label = label; 162 | this.confidence = confidence; 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/Database/StateActionTransition.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.Database; 2 | 3 | import net.shasankp000.GameAI.State; 4 | import net.shasankp000.GameAI.StateActions; 5 | 6 | import java.io.*; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import static net.shasankp000.GameAI.State.isStateConsistent; 13 | 14 | /** 15 | * Wrapper class to represent a complex state-action-nextState-reward structure. 16 | */ 17 | public class StateActionTransition implements Serializable { 18 | private final Map>> transitionMap; 19 | 20 | public StateActionTransition() { 21 | this.transitionMap = new HashMap<>(); 22 | } 23 | 24 | public Map>> getTransitionMap() { 25 | return transitionMap; 26 | } 27 | 28 | /** 29 | * Add or update a transition in the map. 30 | * 31 | * @param initialState the initial state 32 | * @param action the action taken 33 | * @param nextState the resulting state 34 | * @param reward the reward for the transition 35 | */ 36 | public void addTransition(State initialState, StateActions.Action action, State nextState, double reward) { 37 | transitionMap 38 | .computeIfAbsent(initialState, k -> new HashMap<>()) 39 | .computeIfAbsent(action, k -> new HashMap<>()) 40 | .put(nextState, reward); 41 | } 42 | 43 | /** 44 | * Get the reward for a specific transition. 45 | * 46 | * @param initialState the initial state 47 | * @param action the action taken 48 | * @param nextState the resulting state 49 | * @return the reward for the transition, or null if not found 50 | */ 51 | public Double getReward(State initialState, StateActions.Action action, State nextState) { 52 | return transitionMap.getOrDefault(initialState, new HashMap<>()) 53 | .getOrDefault(action, new HashMap<>()) 54 | .get(nextState); 55 | } 56 | 57 | /** 58 | * Check if a specific transition exists. 59 | * 60 | * @param initialState the initial state 61 | * @param action the action taken 62 | * @param nextState the resulting state 63 | * @return true if the transition exists, false otherwise 64 | */ 65 | public boolean hasTransition(State initialState, StateActions.Action action, State nextState) { 66 | return transitionMap.containsKey(initialState) && 67 | transitionMap.get(initialState).containsKey(action) && 68 | transitionMap.get(initialState).get(action).containsKey(nextState); 69 | } 70 | 71 | /** 72 | * Find similar states in the transition map based on consistency. 73 | * 74 | * @param currentState the current state to match 75 | * @return a map of similar states 76 | */ 77 | public Map>> getSimilarStates(State currentState) { 78 | Map>> similarStates = new HashMap<>(); 79 | 80 | for (Map.Entry>> entry : transitionMap.entrySet()) { 81 | State knownState = entry.getKey(); 82 | if (isStateConsistent(knownState, currentState)) { 83 | similarStates.put(knownState, entry.getValue()); 84 | } 85 | } 86 | 87 | return similarStates; 88 | } 89 | 90 | /** 91 | * Append a single transition to the Q-table file. 92 | * 93 | * @param filePath the file to save to 94 | */ 95 | public void appendTransition(String filePath) throws IOException { 96 | try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath, true)) { 97 | @Override 98 | protected void writeStreamHeader() throws IOException { 99 | if (new File(filePath).length() == 0) super.writeStreamHeader(); 100 | } 101 | }) { 102 | oos.writeObject(this); 103 | } 104 | } 105 | 106 | /** 107 | * Load the Q-table file in chunks. 108 | * 109 | * @param filePath the file to read from 110 | * @return a list of StateActionTransition objects 111 | */ 112 | public static List loadQTableInChunks(String filePath) throws IOException, ClassNotFoundException { 113 | List transitions = new ArrayList<>(); 114 | 115 | try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) { 116 | while (true) { 117 | try { 118 | StateActionTransition transition = (StateActionTransition) ois.readObject(); 119 | transitions.add(transition); 120 | } catch (EOFException e) { 121 | break; 122 | } 123 | } 124 | } 125 | 126 | System.out.println("Loaded Q-table: Total chunks = " + transitions.size()); 127 | return transitions; 128 | } 129 | 130 | /** 131 | * Combine multiple StateActionTransition objects into one. 132 | * 133 | * @param transitions the list of transitions to combine 134 | * @return a single StateActionTransition object 135 | */ 136 | public static StateActionTransition combineTransitions(List transitions) { 137 | StateActionTransition combined = new StateActionTransition(); 138 | 139 | for (StateActionTransition transition : transitions) { 140 | for (Map.Entry>> entry : transition.getTransitionMap().entrySet()) { 141 | State initialState = entry.getKey(); 142 | for (Map.Entry> actionEntry : entry.getValue().entrySet()) { 143 | StateActions.Action action = actionEntry.getKey(); 144 | for (Map.Entry nextStateEntry : actionEntry.getValue().entrySet()) { 145 | State nextState = nextStateEntry.getKey(); 146 | Double reward = nextStateEntry.getValue(); 147 | combined.addTransition(initialState, action, nextState, reward); 148 | } 149 | } 150 | } 151 | } 152 | 153 | return combined; 154 | } 155 | 156 | /** 157 | * Save the entire Q-table to a file. 158 | * 159 | * @param filePath the file to save to 160 | */ 161 | public void saveTransitionMap(String filePath) throws IOException { 162 | try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) { 163 | oos.writeObject(this); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/net/shasankp000/ChatUtils/LIDSNetModel/LIDSNetModelManager.java: -------------------------------------------------------------------------------- 1 | package net.shasankp000.ChatUtils.LIDSNetModel; 2 | 3 | import ai.djl.MalformedModelException; 4 | import ai.djl.ModelException; 5 | import ai.djl.inference.Predictor; 6 | import ai.djl.modality.Classifications; 7 | import ai.djl.repository.zoo.Criteria; 8 | import ai.djl.repository.zoo.ZooModel; 9 | import ai.djl.training.util.ProgressBar; 10 | import java.io.IOException; 11 | import java.nio.file.Path; 12 | import java.util.List; 13 | import java.util.concurrent.locks.ReentrantReadWriteLock; 14 | 15 | 16 | public class LIDSNetModelManager { 17 | 18 | private static LIDSNetModelManager instance; 19 | private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 20 | private ZooModel lidsnetModel; 21 | private Predictor predictor; 22 | private boolean isModelLoaded = false; 23 | private volatile boolean isLoadingInProgress = false; 24 | private static Path modelDir = null; 25 | 26 | private LIDSNetModelManager() {} 27 | 28 | public static LIDSNetModelManager getInstance(Path modelDir) { 29 | if (instance == null) { 30 | synchronized (LIDSNetModelManager.class) { 31 | if (instance == null) { 32 | instance = new LIDSNetModelManager(); 33 | } 34 | } 35 | } 36 | 37 | LIDSNetModelManager.modelDir = modelDir; 38 | 39 | return instance; 40 | } 41 | 42 | /** 43 | * Load the LIDSNet model into memory. Threadsafe and only loads once. 44 | */ 45 | public void loadModel(List classNames) throws IOException, ModelException, MalformedModelException { 46 | // Quick check without lock 47 | if (isModelLoaded) return; 48 | 49 | lock.writeLock().lock(); 50 | try { 51 | if (isModelLoaded || isLoadingInProgress) return; 52 | isLoadingInProgress = true; 53 | 54 | Path torchModelDir = modelDir.resolve("LIDSNet_intent_detect.pt"); // adjust path as needed 55 | 56 | LIDSNetTranslator translator = new LIDSNetTranslator(classNames); 57 | 58 | Criteria criteria = Criteria.builder() 59 | .setTypes(float[].class, Classifications.class) 60 | .optModelPath(torchModelDir) 61 | .optEngine("PyTorch") 62 | .optTranslator(translator) 63 | .optProgress(new ProgressBar()) 64 | .build(); 65 | 66 | lidsnetModel = criteria.loadModel(); 67 | predictor = lidsnetModel.newPredictor(); 68 | 69 | isModelLoaded = true; 70 | System.out.println("LIDSNet model loaded successfully and ready for inference."); 71 | 72 | } catch (Exception e) { 73 | throw new RuntimeException(e); 74 | } finally { 75 | isLoadingInProgress = false; 76 | lock.writeLock().unlock(); 77 | } 78 | } 79 | 80 | public boolean isModelLoaded() { 81 | lock.readLock().lock(); 82 | try { 83 | return isModelLoaded; 84 | } finally { 85 | lock.readLock().unlock(); 86 | } 87 | } 88 | 89 | public Predictor getPredictor(List classNames) throws IOException, ModelException, MalformedModelException { 90 | lock.readLock().lock(); 91 | try { 92 | if (!isModelLoaded) { 93 | lock.readLock().unlock(); 94 | loadModel(classNames); 95 | lock.readLock().lock(); 96 | } 97 | return predictor; 98 | } finally { 99 | lock.readLock().unlock(); 100 | } 101 | } 102 | 103 | public Classifications predict(float[] featureVector, List classNames) throws Exception { 104 | lock.readLock().lock(); 105 | try { 106 | if (!isModelLoaded) { 107 | lock.readLock().unlock(); 108 | loadModel(classNames); 109 | lock.readLock().lock(); 110 | } 111 | return predictor.predict(featureVector); 112 | } finally { 113 | lock.readLock().unlock(); 114 | } 115 | } 116 | 117 | // Optional: result wrapper 118 | public PredictionResult predictWithConfidence(float[] featureVector, List classNames) throws Exception { 119 | Classifications classifications = predict(featureVector, classNames); 120 | Classifications.Classification bestResult = classifications.best(); 121 | return new PredictionResult( 122 | bestResult.getClassName(), 123 | bestResult.getProbability(), 124 | classifications 125 | ); 126 | } 127 | 128 | public void unloadModel() throws IOException { 129 | lock.writeLock().lock(); 130 | try { 131 | if (!isModelLoaded) { 132 | System.out.println("Model is not loaded."); 133 | return; 134 | } 135 | System.out.println("Unloading LIDSNet model from memory..."); 136 | if (predictor != null) { 137 | predictor.close(); 138 | predictor = null; 139 | } 140 | if (lidsnetModel != null) { 141 | lidsnetModel.close(); 142 | lidsnetModel = null; 143 | } 144 | isModelLoaded = false; 145 | System.gc(); 146 | System.out.println("Model unloaded successfully."); 147 | } finally { 148 | lock.writeLock().unlock(); 149 | } 150 | } 151 | 152 | // Simple class to hold prediction results with confidence 153 | public static class PredictionResult { 154 | private final String className; 155 | private final double confidence; 156 | private final Classifications fullClassifications; 157 | 158 | public PredictionResult(String className, double confidence, Classifications fullClassifications) { 159 | this.className = className; 160 | this.confidence = confidence; 161 | this.fullClassifications = fullClassifications; 162 | } 163 | public String getClassName() { return className; } 164 | public double getConfidence() { return confidence; } 165 | public double getConfidencePercentage() { return confidence * 100.0; } 166 | public Classifications getFullClassifications() { return fullClassifications; } 167 | public boolean isHighConfidence(double threshold) { return confidence >= threshold; } 168 | @Override 169 | public String toString() { 170 | return String.format("PredictionResult{class='%s', confidence=%.4f (%.2f%%)}", 171 | className, confidence, getConfidencePercentage()); 172 | } 173 | } 174 | } 175 | --------------------------------------------------------------------------------