mirror of
https://github.com/JonasunderscoreJones/BuildGPT.git
synced 2025-10-22 18:29:18 +02:00
Added mod
This commit is contained in:
parent
96f177842d
commit
e7605ea083
8 changed files with 249 additions and 89 deletions
|
@ -6,12 +6,12 @@ org.gradle.parallel=true
|
|||
# check these on https://fabricmc.net/develop
|
||||
minecraft_version=1.21.3
|
||||
yarn_mappings=1.21.3+build.2
|
||||
loader_version=0.16.7
|
||||
loader_version=0.16.9
|
||||
|
||||
# Fabric API
|
||||
fabric_version=0.109.0+1.21.3
|
||||
|
||||
# Mod Properties
|
||||
mod_version=1.0.0
|
||||
maven_group=com.example
|
||||
archives_base_name=modid
|
||||
|
||||
# Dependencies
|
||||
fabric_version=0.106.1+1.21.3
|
||||
maven_group=dev.jonasjones
|
||||
archives_base_name=buildgpt
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package com.example.mixin.client;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(MinecraftClient.class)
|
||||
public class ExampleClientMixin {
|
||||
@Inject(at = @At("HEAD"), method = "run")
|
||||
private void init(CallbackInfo info) {
|
||||
// This code is injected into the start of MinecraftClient.run()V
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package com.example;
|
||||
package dev.jonasjones;
|
||||
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
|
||||
public class ExampleModClient implements ClientModInitializer {
|
||||
public class BuildGPTClient implements ClientModInitializer {
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
// This entrypoint is suitable for setting up client-specific logic, such as rendering.
|
|
@ -1,24 +0,0 @@
|
|||
package com.example;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ExampleMod implements ModInitializer {
|
||||
public static final String MOD_ID = "modid";
|
||||
|
||||
// This logger is used to write text to the console and the log file.
|
||||
// It is considered best practice to use your mod id as the logger's name.
|
||||
// That way, it's clear which mod wrote info, warnings, and errors.
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
// This code runs as soon as Minecraft is in a mod-load-ready state.
|
||||
// However, some things (like resources) may still be uninitialized.
|
||||
// Proceed with mild caution.
|
||||
|
||||
LOGGER.info("Hello Fabric world!");
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package com.example.mixin;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(MinecraftServer.class)
|
||||
public class ExampleMixin {
|
||||
@Inject(at = @At("HEAD"), method = "loadWorld")
|
||||
private void init(CallbackInfo info) {
|
||||
// This code is injected into the start of MinecraftServer.loadWorld()V
|
||||
}
|
||||
}
|
235
src/main/java/dev/jonasjones/BuildGPT.java
Normal file
235
src/main/java/dev/jonasjones/BuildGPT.java
Normal file
|
@ -0,0 +1,235 @@
|
|||
package dev.jonasjones;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
|
||||
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.command.argument.BlockPosArgumentType;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Box;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.logging.log4j.core.jmx.Server;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.Vector;
|
||||
|
||||
public class BuildGPT implements ModInitializer {
|
||||
public static final String MOD_ID = "modid";
|
||||
|
||||
// This logger is used to write text to the console and the log file.
|
||||
// It is considered best practice to use your mod id as the logger's name.
|
||||
// That way, it's clear which mod wrote info, warnings, and errors.
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
private static final String API_KEY_FILE = FabricLoader.getInstance().getConfigDir() + "/.openaikey";
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
// This code runs as soon as Minecraft is in a mod-load-ready state.
|
||||
// However, some things (like resources) may still be uninitialized.
|
||||
// Proceed with mild caution.
|
||||
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||
dispatcher.register(CommandManager.literal("buildgpt")
|
||||
.then(CommandManager.literal("bound")
|
||||
.then(CommandManager.argument("start_pos", BlockPosArgumentType.blockPos())
|
||||
.then(CommandManager.argument("end_pos", BlockPosArgumentType.blockPos())
|
||||
.then(CommandManager.argument("building", StringArgumentType.greedyString())
|
||||
.executes(BuildGPT::executeBuildGptdBound)))))
|
||||
.then(CommandManager.literal("unbound")
|
||||
.then(CommandManager.argument("start_pos", BlockPosArgumentType.blockPos())
|
||||
.then(CommandManager.argument("building", StringArgumentType.greedyString())
|
||||
.executes(BuildGPT::executeBuildGptUnboud)))));
|
||||
});
|
||||
}
|
||||
|
||||
private static int executeBuildGptdBound(CommandContext<ServerCommandSource> context) {
|
||||
String building = StringArgumentType.getString(context, "building");
|
||||
BlockPos start_pos = BlockPosArgumentType.getBlockPos(context, "start_pos");
|
||||
String prompt;
|
||||
int x1 = start_pos.getX();
|
||||
int y1 = start_pos.getY();
|
||||
int z1 = start_pos.getZ();
|
||||
BlockPos end_pos = BlockPosArgumentType.getBlockPos(context, "end_pos");
|
||||
int x2 = end_pos.getX();
|
||||
int y2 = end_pos.getY();
|
||||
int z2 = end_pos.getZ();
|
||||
|
||||
prompt = String.format(
|
||||
"Imagine, You're an architect. Design the structure of a minecraft %s within the coordinate range x: %d-%d, y: %d-%d, z: %d-%d in minecraft." +
|
||||
"Return the blocks in a json list of objects {x:1,y:1,z:1,block:minecraft:block}. Return only the json without any formatting or explanation as plaintext.",
|
||||
building, x1, x2, y1, y2, z1, z2
|
||||
);
|
||||
|
||||
return executeBuildGpt(context, prompt);
|
||||
}
|
||||
|
||||
private static int executeBuildGptUnboud(CommandContext<ServerCommandSource> context) {
|
||||
String building = StringArgumentType.getString(context, "building");
|
||||
BlockPos start_pos = BlockPosArgumentType.getBlockPos(context, "start_pos");
|
||||
int x1 = start_pos.getX();
|
||||
int y1 = start_pos.getY();
|
||||
int z1 = start_pos.getZ();
|
||||
|
||||
String prompt = String.format(
|
||||
"Imagine, You're an architect. Design the structure of a %s at the coordinate x: %d, y: %d, z: %d in minecraft." +
|
||||
"Return the blocks in a json list of objects {x:1,y:1,z:1,block:minecraft:block}. Return only the json without any formatting or explanation as plaintext.",
|
||||
building, x1, y1, z1
|
||||
);
|
||||
|
||||
return executeBuildGpt(context, prompt);
|
||||
}
|
||||
|
||||
private static int executeBuildGpt(CommandContext<ServerCommandSource> context, String prompt) {
|
||||
ServerCommandSource source = context.getSource();
|
||||
source.sendMessage(Text.of("Requesting Structure from GPT..."));
|
||||
String jsonResponse = requestGpt(prompt);
|
||||
if (jsonResponse == null) {
|
||||
source.sendError(Text.of("Failed to get a response from GPT. Rerun the command to try again..."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<Map<String, Object>> blocks;
|
||||
|
||||
try {
|
||||
blocks = parseJson(jsonResponse);
|
||||
} catch (JsonSyntaxException ignored) {
|
||||
source.sendError(Text.of("Failed to parse the response from GPT. Rerun the command to try again..."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (Map<String, Object> block : blocks) {
|
||||
int x = ((Number) block.get("x")).intValue();
|
||||
int y = ((Number) block.get("y")).intValue();
|
||||
int z = ((Number) block.get("z")).intValue();
|
||||
String blockType = (String) block.get("block");
|
||||
|
||||
source.getServer().getCommandManager().executeWithPrefix(
|
||||
source, String.format("/setblock %d %d %d %s", x, y, z, blockType)
|
||||
);
|
||||
}
|
||||
|
||||
source.sendMessage(Text.of("Done building!"));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static String requestGpt(String prompt) {
|
||||
// HttpClient with 60 second response timeout
|
||||
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(60 * 1000).build();
|
||||
CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build();
|
||||
try {
|
||||
HttpPost request = new HttpPost("https://api.openai.com/v1/chat/completions");
|
||||
request.addHeader("Content-Type", "application/json");
|
||||
String apiKey = getApiKey();
|
||||
if (apiKey == null) {
|
||||
LOGGER.error("API key not found. Please provide a valid API key.");
|
||||
return null;
|
||||
}
|
||||
request.addHeader("Authorization", "Bearer " + apiKey);
|
||||
|
||||
JsonObject payload = new JsonObject();
|
||||
payload.addProperty("model", "gpt-4o");
|
||||
|
||||
JsonArray messages = new JsonArray();
|
||||
JsonObject userMessage = new JsonObject();
|
||||
userMessage.addProperty("role", "user");
|
||||
userMessage.addProperty("content", prompt);
|
||||
messages.add(userMessage);
|
||||
|
||||
payload.add("messages", messages);
|
||||
request.setEntity(new StringEntity(payload.toString()));
|
||||
|
||||
try (CloseableHttpResponse response = httpClient.execute(request)) {
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(response.getEntity().getContent())
|
||||
);
|
||||
StringBuilder responseContent = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
responseContent.append(line);
|
||||
}
|
||||
|
||||
JsonObject jsonResponse = JsonParser.parseString(responseContent.toString()).getAsJsonObject();
|
||||
return jsonResponse.get("choices").getAsJsonArray()
|
||||
.get(0).getAsJsonObject()
|
||||
.get("message").getAsJsonObject()
|
||||
.get("content").getAsString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Map<String, Object>> parseJson(String jsonResponse) {
|
||||
// Use Gson to parse JSON string to List<Map>
|
||||
Gson gson = new Gson();
|
||||
return gson.fromJson(jsonResponse, List.class);
|
||||
}
|
||||
|
||||
public static String getApiKey() {
|
||||
File keyFile = new File(API_KEY_FILE);
|
||||
|
||||
if (keyFile.exists()) {
|
||||
// Read the API key from the file
|
||||
try {
|
||||
String apiKey = new String(Files.readAllBytes(Paths.get(API_KEY_FILE))).trim();
|
||||
if (!apiKey.isEmpty()) {
|
||||
return apiKey;
|
||||
} else {
|
||||
LOGGER.error("API key file is empty. Please provide a valid API key.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
// File doesn't exist, create it and prompt user for the key
|
||||
try {
|
||||
LOGGER.warn("No API key found. Creating .openaikey file...");
|
||||
|
||||
// Write the API key to the file
|
||||
Files.write(Paths.get(API_KEY_FILE), "".getBytes(), StandardOpenOption.CREATE);
|
||||
|
||||
LOGGER.warn("Add your OpenAI API key to " + API_KEY_FILE);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to create API key file.");
|
||||
LOGGER.error("Create it manually as '.openaikey' in the config directory.");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "modid",
|
||||
"id": "buildbpt",
|
||||
"version": "${version}",
|
||||
"name": "Example mod",
|
||||
"description": "This is an example description! Tell everyone what your mod is about!",
|
||||
"name": "BuildGPT",
|
||||
"description": "Build Structures designed by ChatGPT!",
|
||||
"authors": [
|
||||
"Me!"
|
||||
"Jonas_Jones"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://fabricmc.net/",
|
||||
|
@ -16,26 +16,16 @@
|
|||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"com.example.ExampleMod"
|
||||
"dev.jonasjones.BuildGPT"
|
||||
],
|
||||
"client": [
|
||||
"com.example.ExampleModClient"
|
||||
"dev.jonasjones.BuildGPTClient"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
"modid.mixins.json",
|
||||
{
|
||||
"config": "modid.client.mixins.json",
|
||||
"environment": "client"
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.16.7",
|
||||
"minecraft": "~1.21.3",
|
||||
"java": ">=21",
|
||||
"fabric-api": "*"
|
||||
},
|
||||
"suggests": {
|
||||
"another-mod": "*"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "com.example.mixin",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"mixins": [
|
||||
"ExampleMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue