Commit 4b61ae92 by Jonathan Thomas

Replacing TextRenderer line wrapping with custom java, so both server and client…

Replacing TextRenderer line wrapping with custom java, so both server and client can use it for logic. Show 'start chat' button for entities with no conversation. Show text bubbles once a message is generated. Allow user to page through the messages, 3 lines at a time. Also adding some padding around the text (top and bottom).
parent 48cd575b
Pipeline #11642 passed with stage
in 19 seconds
...@@ -105,9 +105,16 @@ public class ClickHandler { ...@@ -105,9 +105,16 @@ public class ClickHandler {
// Handle the click for the closest entity after the loop // Handle the click for the closest entity after the loop
if (closestEntity != null) { if (closestEntity != null) {
LOGGER.info("Clicked on text bubble above: " + closestEntity.getType().getName().getString()); // Look-up conversation
//client.player.sendMessage(Text.literal("Clicked on text bubble above: " + closestEntity.getType().getName().getString()), false); ChatDataManager.EntityChatData chatData = ChatDataManager.getInstance().getOrCreateChatData(closestEntity.getId());
ModPackets.sendEntityClickPacket(closestEntity);
if (chatData.currentMessage.isEmpty()) {
// Start conversation
ModPackets.sendGenerateGreeting(closestEntity);
} else {
// Update lines read
ModPackets.sendUpdateLineNumber(closestEntity, chatData.currentLineNumber + ClientInit.DISPLAY_NUM_LINES);
}
} }
} }
......
...@@ -31,6 +31,8 @@ import java.util.stream.Collectors; ...@@ -31,6 +31,8 @@ import java.util.stream.Collectors;
public class ClientInit implements ClientModInitializer { public class ClientInit implements ClientModInitializer {
public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt"); public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt");
protected static TextureLoader textures = new TextureLoader();; protected static TextureLoader textures = new TextureLoader();;
public static int DISPLAY_NUM_LINES = 3;
public static int DISPLAY_PADDING = 2;
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
...@@ -74,6 +76,30 @@ public class ClientInit implements ClientModInitializer { ...@@ -74,6 +76,30 @@ public class ClientInit implements ClientModInitializer {
Tessellator.getInstance().draw(); Tessellator.getInstance().draw();
} }
private void drawStartIcon(MatrixStack matrices, Entity entity, float x, float y, float width, float height) {
// Draw button icon
Identifier button_texture = textures.Get("ui", "button-chat");
RenderSystem.setShaderTexture(0, button_texture);
RenderSystem.enableDepthTest();
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.setShader(GameRenderer::getPositionTexProgram);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder bufferBuilder = tessellator.getBuffer();
bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE);
float z = -0.01F;
bufferBuilder.vertex(matrices.peek().getPositionMatrix(), x, y + height, z).texture(0, 1).next(); // bottom left
bufferBuilder.vertex(matrices.peek().getPositionMatrix(), x + width, y + height, z).texture(1, 1).next(); // bottom right
bufferBuilder.vertex(matrices.peek().getPositionMatrix(), x + width, y, z).texture(1, 0).next(); // top right
bufferBuilder.vertex(matrices.peek().getPositionMatrix(), x, y, z).texture(0, 0).next(); // top left
tessellator.draw();
RenderSystem.disableBlend();
RenderSystem.disableDepthTest();
}
private void drawEntityIcon(MatrixStack matrices, Entity entity, float x, float y, float width, float height) { private void drawEntityIcon(MatrixStack matrices, Entity entity, float x, float y, float width, float height) {
// Draw face icon // Draw face icon
String entity_name = entity.getType().getUntranslatedName().toLowerCase(Locale.ROOT); String entity_name = entity.getType().getUntranslatedName().toLowerCase(Locale.ROOT);
...@@ -103,7 +129,6 @@ public class ClientInit implements ClientModInitializer { ...@@ -103,7 +129,6 @@ public class ClientInit implements ClientModInitializer {
RenderSystem.disableDepthTest(); RenderSystem.disableDepthTest();
} }
private void drawTextAboveEntities(WorldRenderContext context, float partialTicks) { private void drawTextAboveEntities(WorldRenderContext context, float partialTicks) {
Camera camera = context.camera(); Camera camera = context.camera();
Entity cameraEntity = camera.getFocusedEntity(); Entity cameraEntity = camera.getFocusedEntity();
...@@ -155,10 +180,11 @@ public class ClientInit implements ClientModInitializer { ...@@ -155,10 +180,11 @@ public class ClientInit implements ClientModInitializer {
// Look-up greeting (if any) // Look-up greeting (if any)
ChatDataManager.EntityChatData chatData = ChatDataManager.getInstance().getOrCreateChatData(entity.getId()); ChatDataManager.EntityChatData chatData = ChatDataManager.getInstance().getOrCreateChatData(entity.getId());
List<String> lines = chatData.getWrappedLines();
// Generate ChatGPT random greeting // Set the range of lines to display
String baseText = chatData.currentMessage + " - " + entity.getType().getName().getString(); int starting_line = chatData.currentLineNumber;
List<OrderedText> lines = fontRenderer.wrapLines(StringVisitable.plain(baseText), 20 * fontRenderer.getWidth("W")); int ending_line = Math.min(chatData.currentLineNumber + DISPLAY_NUM_LINES, lines.size());
// Push a new matrix onto the stack. // Push a new matrix onto the stack.
matrices.push(); matrices.push();
...@@ -201,20 +227,16 @@ public class ClientInit implements ClientModInitializer { ...@@ -201,20 +227,16 @@ public class ClientInit implements ClientModInitializer {
matrices.multiply(yawRotation); matrices.multiply(yawRotation);
// Determine max line length // Determine max line length
int maxLineLength = 0; float linesDisplayed = ending_line - starting_line;
float lineSpacing = 1F; float lineSpacing = 1F;
float textHeaderHeight = 40F; float textHeaderHeight = 40F;
float textFooterHeight = 5F; float textFooterHeight = 5F;
for (OrderedText lineText : lines) {
int lineLength = fontRenderer.getWidth(lineText);
if (lineLength > maxLineLength) {
maxLineLength = lineLength;
}
}
// Calculate size of text scaled to world // Calculate size of text scaled to world
float scaledTextHeight = (float) lines.size() * (fontRenderer.fontHeight + lineSpacing); float scaledTextHeight = linesDisplayed * (fontRenderer.fontHeight + lineSpacing);
scaledTextHeight = Math.max(scaledTextHeight, 50F); float minTextHeight = (DISPLAY_NUM_LINES * (fontRenderer.fontHeight + lineSpacing)) + (DISPLAY_PADDING * 2);
scaledTextHeight = Math.max(scaledTextHeight, minTextHeight);
// Scale down before rendering textures (otherwise font is huge) // Scale down before rendering textures (otherwise font is huge)
matrices.scale(-0.02F, -0.02F, 0.02F); matrices.scale(-0.02F, -0.02F, 0.02F);
...@@ -222,20 +244,44 @@ public class ClientInit implements ClientModInitializer { ...@@ -222,20 +244,44 @@ public class ClientInit implements ClientModInitializer {
// Translate above the entity // Translate above the entity
matrices.translate(0F, -scaledTextHeight + -textHeaderHeight + -textFooterHeight, 0F); matrices.translate(0F, -scaledTextHeight + -textHeaderHeight + -textFooterHeight, 0F);
// Draw text background (no smaller than 50F tall) // Check if conversation has started
drawTextBubbleBackground(matrices, entity, -64, 0, 128, scaledTextHeight); if (chatData.currentMessage.isEmpty()) {
// Draw 'start' button
// Draw face of entity drawStartIcon(matrices, entity, 0, textHeaderHeight, 32, 17);
drawEntityIcon(matrices, entity, -60, 7, 32, 32);
} else {
// Draw text background (no smaller than 50F tall)
drawTextBubbleBackground(matrices, entity, -64, 0, 128, scaledTextHeight);
// Draw face of entity
drawEntityIcon(matrices, entity, -60, 7, 32, 32);
// Render each line of the text
int fullBright = 0xF000F0;
Matrix4f matrix = matrices.peek().getPositionMatrix();
float yOffset = 40.0F + DISPLAY_PADDING;
int currentLineIndex = 0; // We'll use this to track which line we're on
for (String lineText : lines) {
// Only draw lines that are within the specified range
if (currentLineIndex >= starting_line && currentLineIndex < ending_line) {
fontRenderer.draw(lineText, -fontRenderer.getWidth(lineText) / 2f, yOffset, 0xffffff,
false, matrix, immediate, TextLayerType.NORMAL, 0, fullBright);
yOffset += fontRenderer.fontHeight + lineSpacing;
}
currentLineIndex++;
if (currentLineIndex > ending_line) {
break;
}
}
// Render each line of the text // Add end of message
int fullBright = 0xF000F0; if (starting_line > 0 && starting_line == ending_line) {
Matrix4f matrix = matrices.peek().getPositionMatrix(); String lineText = "<end of message>";
float yOffset = 42.0F; fontRenderer.draw(lineText, -fontRenderer.getWidth(lineText) / 2f, yOffset + 10F, 0xffffff,
for (OrderedText lineText : lines) { false, matrix, immediate, TextLayerType.NORMAL, 0, fullBright);
fontRenderer.draw(lineText, -fontRenderer.getWidth(lineText) / 2f, yOffset, 0xffffff, }
false, matrix, immediate, TextLayerType.NORMAL, 0, fullBright);
yOffset += fontRenderer.fontHeight + lineSpacing;
} }
// Pop the matrix to return to the original state. // Pop the matrix to return to the original state.
......
...@@ -11,12 +11,21 @@ import org.slf4j.LoggerFactory; ...@@ -11,12 +11,21 @@ import org.slf4j.LoggerFactory;
public class ModPackets { public class ModPackets {
public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt"); public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt");
public static void sendEntityClickPacket(Entity entity) { public static void sendGenerateGreeting(Entity entity) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeInt(entity.getId()); buf.writeInt(entity.getId());
// Send C2S packet // Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_CLIENT_CLICK, buf); ClientPlayNetworking.send(ModInit.PACKET_CLIENT_GREETING, buf);
}
public static void sendUpdateLineNumber(Entity entity, Integer lineNumber) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeInt(entity.getId());
buf.writeInt(lineNumber);
// Send C2S packet
ClientPlayNetworking.send(ModInit.PACKET_CLIENT_READ_NEXT, buf);
} }
} }
...@@ -8,6 +8,7 @@ import java.util.ArrayList; ...@@ -8,6 +8,7 @@ import java.util.ArrayList;
public class ChatDataManager { public class ChatDataManager {
// Use a static instance to manage our data globally // Use a static instance to manage our data globally
private static final ChatDataManager INSTANCE = new ChatDataManager(); private static final ChatDataManager INSTANCE = new ChatDataManager();
public static int MAX_CHAR_PER_LINE = 22;
// 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;
...@@ -15,11 +16,13 @@ public class ChatDataManager { ...@@ -15,11 +16,13 @@ public class ChatDataManager {
// Inner class to hold entity-specific data // Inner class to hold entity-specific data
public static class EntityChatData { public static class EntityChatData {
public String currentMessage; public String currentMessage;
public int currentLineNumber;
public List<String> previousMessages; public List<String> previousMessages;
public String characterSheet; public String characterSheet;
public EntityChatData() { public EntityChatData() {
this.currentMessage = ""; this.currentMessage = "";
this.currentLineNumber = 0;
this.previousMessages = new ArrayList<>(); this.previousMessages = new ArrayList<>();
this.characterSheet = ""; this.characterSheet = "";
} }
...@@ -28,7 +31,7 @@ public class ChatDataManager { ...@@ -28,7 +31,7 @@ public class ChatDataManager {
public void generateGreeting() { public void generateGreeting() {
ChatGPTRequest.fetchGreetingFromChatGPT().thenAccept(greeting -> { ChatGPTRequest.fetchGreetingFromChatGPT().thenAccept(greeting -> {
if (greeting != null) { if (greeting != null) {
this.currentMessage = greeting; this.addMessage(greeting);
} }
}); });
} }
...@@ -39,6 +42,20 @@ public class ChatDataManager { ...@@ -39,6 +42,20 @@ public class ChatDataManager {
previousMessages.add(currentMessage); previousMessages.add(currentMessage);
} }
currentMessage = message; currentMessage = message;
// Set line number of displayed text
this.currentLineNumber = 0;
}
// Get wrapped lines
public List<String> getWrappedLines() {
return LineWrapper.wrapLines(this.currentMessage, MAX_CHAR_PER_LINE);
}
// Update starting line number of displayed text
public void setLineNumber(Integer lineNumber) {
// Update displayed starting line # (between 0 and # of lines)
this.currentLineNumber = Math.min(Math.max(lineNumber, 0), this.getWrappedLines().size());
} }
} }
......
package com.owlmaddie;
import java.util.ArrayList;
import java.util.List;
/*
Wrap lines of text on a space character
*/
public class LineWrapper {
public static List<String> wrapLines(String text, int maxWidth) {
List<String> wrappedLines = new ArrayList<>();
String[] words = text.split(" ");
StringBuilder currentLine = new StringBuilder();
for (String word : words) {
// Check if adding the next word exceeds the line length
if (currentLine.length() + word.length() + 1 > maxWidth) {
if (currentLine.length() > 0) {
wrappedLines.add(currentLine.toString());
currentLine = new StringBuilder();
}
// If the word itself is longer than maxWidth, split the word
while (word.length() > maxWidth) {
wrappedLines.add(word.substring(0, maxWidth));
word = word.substring(maxWidth);
}
}
// Append the word to the line
if (currentLine.length() > 0) {
currentLine.append(" ");
}
currentLine.append(word);
}
// Add the last line if there's anything left
if (currentLine.length() > 0) {
wrappedLines.add(currentLine.toString());
}
return wrappedLines;
}
}
...@@ -11,7 +11,8 @@ import org.slf4j.LoggerFactory; ...@@ -11,7 +11,8 @@ import org.slf4j.LoggerFactory;
public class ModInit implements ModInitializer { public class ModInit implements ModInitializer {
public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt"); public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt");
public static final Identifier PACKET_CLIENT_CLICK = new Identifier("mobgpt", "packet_client_click"); public static final Identifier PACKET_CLIENT_GREETING = new Identifier("mobgpt", "packet_client_greeting");
public static final Identifier PACKET_CLIENT_READ_NEXT = new Identifier("mobgpt", "packet_client_read_next");
@Override @Override
public void onInitialize() { public void onInitialize() {
...@@ -19,7 +20,8 @@ public class ModInit implements ModInitializer { ...@@ -19,7 +20,8 @@ public class ModInit implements ModInitializer {
// However, some things (like resources) may still be uninitialized. // However, some things (like resources) may still be uninitialized.
// Proceed with mild caution. // Proceed with mild caution.
ServerPlayNetworking.registerGlobalReceiver(PACKET_CLIENT_CLICK, (server, player, handler, buf, responseSender) -> { // Handle packet for Greeting
ServerPlayNetworking.registerGlobalReceiver(PACKET_CLIENT_GREETING, (server, player, handler, buf, responseSender) -> {
int entityId = buf.readInt(); int entityId = buf.readInt();
// Ensure that the task is synced with the server thread // Ensure that the task is synced with the server thread
...@@ -28,13 +30,31 @@ public class ModInit implements ModInitializer { ...@@ -28,13 +30,31 @@ public class ModInit implements ModInitializer {
Entity entity = player.getServerWorld().getEntityById(entityId); Entity entity = player.getServerWorld().getEntityById(entityId);
if (entity != null) { if (entity != null) {
// Perform action with the clicked entity // Perform action with the clicked entity
LOGGER.info("Entity received: " + entity.getType().toString()); LOGGER.info("Generate greeting for: " + entity.getType().toString());
ChatDataManager.EntityChatData chatData = ChatDataManager.getInstance().getOrCreateChatData(entityId); ChatDataManager.EntityChatData chatData = ChatDataManager.getInstance().getOrCreateChatData(entityId);
chatData.generateGreeting(); chatData.generateGreeting();
} }
}); });
}); });
// Handle packet for reading lines of message
ServerPlayNetworking.registerGlobalReceiver(PACKET_CLIENT_READ_NEXT, (server, player, handler, buf, responseSender) -> {
int entityId = buf.readInt();
int lineNumber = buf.readInt();
// Ensure that the task is synced with the server thread
server.execute(() -> {
// Your logic here, e.g., handle the entity click
Entity entity = player.getServerWorld().getEntityById(entityId);
if (entity != null) {
// Perform action with the clicked entity
LOGGER.info("Increment read lines to " + lineNumber + " for: " + entity.getType().toString());
ChatDataManager.EntityChatData chatData = ChatDataManager.getInstance().getOrCreateChatData(entityId);
chatData.setLineNumber(lineNumber);
}
});
});
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());
......
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