Commit bfa46037 by Jonathan Thomas

Initial chat screen UI, with Chat GPT integration! Full circle achieved!

parent 485c80e8
Pipeline #11656 passed with stage
in 21 seconds
package com.owlmaddie;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.entity.Entity;
import net.minecraft.text.Text;
import org.lwjgl.glfw.GLFW;
public class ChatScreen extends Screen {
private TextFieldWidget textField;
private ButtonWidget sendButton;
private ButtonWidget cancelButton;
private Entity screenEntity;
private final Text labelText = Text.literal("Enter your message:");
public ChatScreen(Entity entity) {
super(Text.literal("Simple Chat"));
screenEntity = entity;
}
@Override
protected void init() {
super.init();
// Centered text field dimensions
int textFieldWidth = 220;
int textFieldHeight = 20;
int textFieldX = (this.width - textFieldWidth) / 2; // Centered X position
int textFieldY = 100; // Y position
// Initialize the text field
textField = new TextFieldWidget(textRenderer, textFieldX, textFieldY, textFieldWidth, textFieldHeight, Text.literal("Chat Input"));
textField.setMaxLength(512);
textField.setDrawsBackground(true);
textField.setText("");
this.addDrawableChild(textField);
// Set focus to the text field
setFocused(textField); // Set the text field as the focused element
textField.setFocused(true); // Request focus for the text field
// Button dimensions and positions
int buttonWidth = 100;
int buttonHeight = 20;
int buttonSpacing = 20; // Space between buttons
int buttonsY = textFieldY + textFieldHeight + 15; // Y position under the text field
// Initialize the cancel button
cancelButton = new ButtonWidget.Builder(Text.literal("Cancel"), button -> close())
.size(buttonWidth, buttonHeight)
.position(textFieldX, buttonsY)
.build();
this.addDrawableChild(cancelButton);
// Initialize the send button
sendButton = new ButtonWidget.Builder(Text.literal("Send"), button -> sendChatMessage())
.size(buttonWidth, buttonHeight)
.position(textFieldX + buttonWidth + buttonSpacing, buttonsY)
.build();
this.addDrawableChild(sendButton);
}
private void sendChatMessage() {
// Send message to server
String message = textField.getText();
ModPackets.sendChat(screenEntity, message);
close();
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) {
if (textField.isFocused()) {
// Close window on ENTER key press
sendChatMessage();
return true;
}
}
return super.keyPressed(keyCode, scanCode, modifiers); // Handle other key presses
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
// Render the background
this.renderBackground(context, mouseX, mouseY, delta);
// Render the label text above the text field
int labelWidth = textRenderer.getWidth(labelText);
int labelX = (this.width - labelWidth) / 2; // Centered X position
int labelY = textField.getY() - 15; // Positioned above the text field
context.drawTextWithShadow(textRenderer, labelText, labelX, labelY, 0xFFFFFF);
// Render the text field
textField.render(context, mouseX, mouseY, delta);
// Render the buttons
sendButton.render(context, mouseX, mouseY, delta);
cancelButton.render(context, mouseX, mouseY, delta);
// Call super.render if necessary
super.render(context, mouseX, mouseY, delta);
}
@Override
public boolean shouldCloseOnEsc() {
// Return true if you want the screen to close when the ESC key is pressed
return true;
}
@Override
public boolean shouldPause() {
// Return false to prevent the game from pausing when the screen is open
return false;
}
}
...@@ -43,6 +43,7 @@ public class ClickHandler { ...@@ -43,6 +43,7 @@ public class ClickHandler {
String message = buffer.readString(32767); String message = buffer.readString(32767);
int line = buffer.readInt(); int line = buffer.readInt();
String status_name = buffer.readString(32767); String status_name = buffer.readString(32767);
String sender_name = buffer.readString(32767);
// Update the chat data manager on the client-side // Update the chat data manager on the client-side
client.execute(() -> { // Make sure to run on the client thread client.execute(() -> { // Make sure to run on the client thread
...@@ -55,6 +56,7 @@ public class ClickHandler { ...@@ -55,6 +56,7 @@ public class ClickHandler {
} }
chatData.currentLineNumber = line; chatData.currentLineNumber = line;
chatData.status = ChatDataManager.ChatStatus.valueOf(status_name); chatData.status = ChatDataManager.ChatStatus.valueOf(status_name);
chatData.sender = ChatDataManager.ChatSender.valueOf(sender_name);
} }
}); });
}); });
...@@ -131,12 +133,16 @@ public class ClickHandler { ...@@ -131,12 +133,16 @@ public class ClickHandler {
// Look-up conversation // Look-up conversation
ChatDataManager.EntityChatData chatData = ChatDataManager.getClientInstance().getOrCreateChatData(closestEntity.getId()); ChatDataManager.EntityChatData chatData = ChatDataManager.getClientInstance().getOrCreateChatData(closestEntity.getId());
if (chatData.currentMessage.isEmpty()) { if (chatData.status == ChatDataManager.ChatStatus.NONE) {
// Start conversation // Start conversation
ModPackets.sendGenerateGreeting(closestEntity); ModPackets.sendGenerateGreeting(closestEntity);
} else { } else if (chatData.status == ChatDataManager.ChatStatus.DISPLAY) {
// Update lines read // Update lines read
ModPackets.sendUpdateLineNumber(closestEntity, chatData.currentLineNumber + ClientInit.DISPLAY_NUM_LINES); ModPackets.sendUpdateLineNumber(closestEntity, chatData.currentLineNumber + ClientInit.DISPLAY_NUM_LINES);
} else if (chatData.status == ChatDataManager.ChatStatus.END) {
// End of chat (open player chat screen)
ModPackets.sendStartChat(closestEntity); // Slow down entity while chat screen is open
client.setScreen(new ChatScreen(closestEntity));
} }
} }
......
...@@ -27,5 +27,22 @@ public class ModPackets { ...@@ -27,5 +27,22 @@ public class ModPackets {
// Send C2S packet // Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_C2S_READ_NEXT, buf); ClientPlayNetworking.send(ModInit.PACKET_C2S_READ_NEXT, buf);
} }
public static void sendStartChat(Entity entity) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeInt(entity.getId());
// Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_C2S_START_CHAT, buf);
}
public static void sendChat(Entity entity, String message) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeInt(entity.getId());
buf.writeString(message);
// Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_C2S_SEND_CHAT, buf);
}
} }
...@@ -18,6 +18,12 @@ public class ChatDataManager { ...@@ -18,6 +18,12 @@ public class ChatDataManager {
END // Chat has ended or been dismissed END // Chat has ended or been dismissed
} }
public enum ChatSender {
NONE, // A blank chat message
USER, // A user chat message
ASSISTANT // A GPT generated message
}
// HashMap to associate unique entity IDs with their chat data // HashMap to associate unique entity IDs with their chat data
private HashMap<Integer, EntityChatData> entityChatDataMap; private HashMap<Integer, EntityChatData> entityChatDataMap;
...@@ -29,6 +35,7 @@ public class ChatDataManager { ...@@ -29,6 +35,7 @@ public class ChatDataManager {
public ChatStatus status; public ChatStatus status;
public List<String> previousMessages; public List<String> previousMessages;
public String characterSheet; public String characterSheet;
public ChatSender sender;
public EntityChatData(int entityId) { public EntityChatData(int entityId) {
this.entityId = entityId; this.entityId = entityId;
...@@ -37,14 +44,19 @@ public class ChatDataManager { ...@@ -37,14 +44,19 @@ public class ChatDataManager {
this.previousMessages = new ArrayList<>(); this.previousMessages = new ArrayList<>();
this.characterSheet = ""; this.characterSheet = "";
this.status = ChatStatus.NONE; this.status = ChatStatus.NONE;
this.sender = ChatSender.NONE;
} }
// Generate greeting // Generate greeting
public void generateGreeting() { public void generateMessage(String user_message) {
this.status = ChatStatus.PENDING; this.status = ChatStatus.PENDING;
ChatGPTRequest.fetchGreetingFromChatGPT().thenAccept(greeting -> { // Add USER Message
if (greeting != null) { //this.addMessage(user_message, ChatSender.USER);
this.addMessage(greeting);
ChatGPTRequest.fetchMessageFromChatGPT(user_message).thenAccept(output_message -> {
if (output_message != null) {
// Add ASSISTANT message
this.addMessage(output_message, ChatSender.ASSISTANT);
} }
}); });
...@@ -53,9 +65,9 @@ public class ChatDataManager { ...@@ -53,9 +65,9 @@ public class ChatDataManager {
} }
// Add a message to the history and update the current message // Add a message to the history and update the current message
public void addMessage(String message) { public void addMessage(String message, ChatSender sender) {
if (!currentMessage.isEmpty()) { if (!currentMessage.isEmpty()) {
previousMessages.add(currentMessage); previousMessages.add(sender.toString() + ": " + currentMessage);
} }
currentMessage = message; currentMessage = message;
...@@ -76,7 +88,7 @@ public class ChatDataManager { ...@@ -76,7 +88,7 @@ public class ChatDataManager {
public void setLineNumber(Integer lineNumber) { public void setLineNumber(Integer lineNumber) {
// Update displayed starting line # (between 0 and # of lines) // Update displayed starting line # (between 0 and # of lines)
currentLineNumber = Math.min(Math.max(lineNumber, 0), this.getWrappedLines().size()); currentLineNumber = Math.min(Math.max(lineNumber, 0), this.getWrappedLines().size());
if (lineNumber == this.getWrappedLines().size()) { if (currentLineNumber >= this.getWrappedLines().size()) {
status = ChatStatus.END; status = ChatStatus.END;
} }
......
...@@ -16,7 +16,7 @@ import java.util.concurrent.CompletableFuture; ...@@ -16,7 +16,7 @@ import java.util.concurrent.CompletableFuture;
public class ChatGPTRequest { public class ChatGPTRequest {
public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt"); public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt");
public static CompletableFuture<String> fetchGreetingFromChatGPT() { public static CompletableFuture<String> fetchMessageFromChatGPT(String user_message) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try { try {
URL url = new URL("https://api.openai.com/v1/chat/completions"); URL url = new URL("https://api.openai.com/v1/chat/completions");
...@@ -30,7 +30,7 @@ public class ChatGPTRequest { ...@@ -30,7 +30,7 @@ public class ChatGPTRequest {
+ "\"model\": \"gpt-3.5-turbo\"," + "\"model\": \"gpt-3.5-turbo\","
+ "\"messages\": [" + "\"messages\": ["
+ "{ \"role\": \"system\", \"content\": \"You are a silly Minecraft entity who speaks to the player in short riddles.\" }," + "{ \"role\": \"system\", \"content\": \"You are a silly Minecraft entity who speaks to the player in short riddles.\" },"
+ "{ \"role\": \"user\", \"content\": \"Hello!\" }" + "{ \"role\": \"user\", \"content\": \"" + user_message.replace("\"", "") + "\" }"
+ "]" + "]"
+ "}"; + "}";
LOGGER.info(jsonInputString); LOGGER.info(jsonInputString);
......
...@@ -22,6 +22,8 @@ public class ModInit implements ModInitializer { ...@@ -22,6 +22,8 @@ public class ModInit implements ModInitializer {
private static MinecraftServer serverInstance; private static MinecraftServer serverInstance;
public static final Identifier PACKET_C2S_GREETING = new Identifier("mobgpt", "packet_c2s_greeting"); public static final Identifier PACKET_C2S_GREETING = new Identifier("mobgpt", "packet_c2s_greeting");
public static final Identifier PACKET_C2S_READ_NEXT = new Identifier("mobgpt", "packet_c2s_read_next"); public static final Identifier PACKET_C2S_READ_NEXT = new Identifier("mobgpt", "packet_c2s_read_next");
public static final Identifier PACKET_C2S_START_CHAT = new Identifier("mobgpt", "packet_c2s_start_chat");
public static final Identifier PACKET_C2S_SEND_CHAT = new Identifier("mobgpt", "packet_c2s_send_chat");
public static final Identifier PACKET_S2C_MESSAGE = new Identifier("mobgpt", "packet_s2c_message"); public static final Identifier PACKET_S2C_MESSAGE = new Identifier("mobgpt", "packet_s2c_message");
@Override @Override
...@@ -46,7 +48,7 @@ public class ModInit implements ModInitializer { ...@@ -46,7 +48,7 @@ public class ModInit implements ModInitializer {
chatData.status == ChatDataManager.ChatStatus.END) { chatData.status == ChatDataManager.ChatStatus.END) {
// Only generate a new greeting if not already doing so // Only generate a new greeting if not already doing so
LOGGER.info("Generate greeting for: " + entity.getType().toString()); LOGGER.info("Generate greeting for: " + entity.getType().toString());
chatData.generateGreeting(); chatData.generateMessage("Hello!");
} }
} }
}); });
...@@ -74,6 +76,42 @@ public class ModInit implements ModInitializer { ...@@ -74,6 +76,42 @@ public class ModInit implements ModInitializer {
}); });
}); });
// Handle packet for Start Chat
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_START_CHAT, (server, player, handler, buf, responseSender) -> {
int entityId = buf.readInt();
// Ensure that the task is synced with the server thread
server.execute(() -> {
Entity entity = player.getServerWorld().getEntityById(entityId);
if (entity != null) {
// Slow entity, so it does NOT walk away during player typing
SlowEntity((LivingEntity) entity, 7F);
}
});
});
// Handle packet for new chat message
ServerPlayNetworking.registerGlobalReceiver(PACKET_C2S_SEND_CHAT, (server, player, handler, buf, responseSender) -> {
int entityId = buf.readInt();
String message = buf.readString(32767);
// Ensure that the task is synced with the server thread
server.execute(() -> {
Entity entity = player.getServerWorld().getEntityById(entityId);
if (entity != null) {
// Slow entity
SlowEntity((LivingEntity) entity, 3.5F);
ChatDataManager.EntityChatData chatData = ChatDataManager.getServerInstance().getOrCreateChatData(entityId);
if (chatData.status == ChatDataManager.ChatStatus.END) {
// Add new message
LOGGER.info("Add new message (" + message + ") to Entity: " + entity.getType().toString());
chatData.generateMessage(message);
}
}
});
});
ServerWorldEvents.LOAD.register((server, world) -> { ServerWorldEvents.LOAD.register((server, world) -> {
// Load chat data... // Load chat data...
LOGGER.info("LOAD chat data from NBT: " + world.getRegistryKey().getValue()); LOGGER.info("LOAD chat data from NBT: " + world.getRegistryKey().getValue());
...@@ -100,6 +138,7 @@ public class ModInit implements ModInitializer { ...@@ -100,6 +138,7 @@ public class ModInit implements ModInitializer {
buffer.writeString(chatData.currentMessage); buffer.writeString(chatData.currentMessage);
buffer.writeInt(chatData.currentLineNumber); buffer.writeInt(chatData.currentLineNumber);
buffer.writeString(chatData.status.toString()); buffer.writeString(chatData.status.toString());
buffer.writeString(chatData.sender.toString());
// Iterate over all players and send the packet // Iterate over all players and send the packet
for (ServerPlayerEntity player : serverInstance.getPlayerManager().getPlayerList()) { for (ServerPlayerEntity player : serverInstance.getPlayerManager().getPlayerList()) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment