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 {
// Handle the click for the closest entity after the loop
if (closestEntity != null) {
LOGGER.info("Clicked on text bubble above: " + closestEntity.getType().getName().getString());
//client.player.sendMessage(Text.literal("Clicked on text bubble above: " + closestEntity.getType().getName().getString()), false);
ModPackets.sendEntityClickPacket(closestEntity);
// Look-up conversation
ChatDataManager.EntityChatData chatData = ChatDataManager.getInstance().getOrCreateChatData(closestEntity.getId());
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;
public class ClientInit implements ClientModInitializer {
public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt");
protected static TextureLoader textures = new TextureLoader();;
public static int DISPLAY_NUM_LINES = 3;
public static int DISPLAY_PADDING = 2;
@Override
public void onInitializeClient() {
......@@ -74,6 +76,30 @@ public class ClientInit implements ClientModInitializer {
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) {
// Draw face icon
String entity_name = entity.getType().getUntranslatedName().toLowerCase(Locale.ROOT);
......@@ -103,7 +129,6 @@ public class ClientInit implements ClientModInitializer {
RenderSystem.disableDepthTest();
}
private void drawTextAboveEntities(WorldRenderContext context, float partialTicks) {
Camera camera = context.camera();
Entity cameraEntity = camera.getFocusedEntity();
......@@ -155,10 +180,11 @@ public class ClientInit implements ClientModInitializer {
// Look-up greeting (if any)
ChatDataManager.EntityChatData chatData = ChatDataManager.getInstance().getOrCreateChatData(entity.getId());
List<String> lines = chatData.getWrappedLines();
// Generate ChatGPT random greeting
String baseText = chatData.currentMessage + " - " + entity.getType().getName().getString();
List<OrderedText> lines = fontRenderer.wrapLines(StringVisitable.plain(baseText), 20 * fontRenderer.getWidth("W"));
// Set the range of lines to display
int starting_line = chatData.currentLineNumber;
int ending_line = Math.min(chatData.currentLineNumber + DISPLAY_NUM_LINES, lines.size());
// Push a new matrix onto the stack.
matrices.push();
......@@ -201,20 +227,16 @@ public class ClientInit implements ClientModInitializer {
matrices.multiply(yawRotation);
// Determine max line length
int maxLineLength = 0;
float linesDisplayed = ending_line - starting_line;
float lineSpacing = 1F;
float textHeaderHeight = 40F;
float textFooterHeight = 5F;
for (OrderedText lineText : lines) {
int lineLength = fontRenderer.getWidth(lineText);
if (lineLength > maxLineLength) {
maxLineLength = lineLength;
}
}
// Calculate size of text scaled to world
float scaledTextHeight = (float) lines.size() * (fontRenderer.fontHeight + lineSpacing);
scaledTextHeight = Math.max(scaledTextHeight, 50F);
float scaledTextHeight = linesDisplayed * (fontRenderer.fontHeight + lineSpacing);
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)
matrices.scale(-0.02F, -0.02F, 0.02F);
......@@ -222,20 +244,44 @@ public class ClientInit implements ClientModInitializer {
// Translate above the entity
matrices.translate(0F, -scaledTextHeight + -textHeaderHeight + -textFooterHeight, 0F);
// 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);
// Check if conversation has started
if (chatData.currentMessage.isEmpty()) {
// Draw 'start' button
drawStartIcon(matrices, entity, 0, textHeaderHeight, 32, 17);
} 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
int fullBright = 0xF000F0;
Matrix4f matrix = matrices.peek().getPositionMatrix();
float yOffset = 42.0F;
for (OrderedText lineText : lines) {
fontRenderer.draw(lineText, -fontRenderer.getWidth(lineText) / 2f, yOffset, 0xffffff,
false, matrix, immediate, TextLayerType.NORMAL, 0, fullBright);
yOffset += fontRenderer.fontHeight + lineSpacing;
// Add end of message
if (starting_line > 0 && starting_line == ending_line) {
String lineText = "<end of message>";
fontRenderer.draw(lineText, -fontRenderer.getWidth(lineText) / 2f, yOffset + 10F, 0xffffff,
false, matrix, immediate, TextLayerType.NORMAL, 0, fullBright);
}
}
// Pop the matrix to return to the original state.
......
......@@ -11,12 +11,21 @@ import org.slf4j.LoggerFactory;
public class ModPackets {
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());
buf.writeInt(entity.getId());
// 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;
public class ChatDataManager {
// Use a static instance to manage our data globally
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
private HashMap<Integer, EntityChatData> entityChatDataMap;
......@@ -15,11 +16,13 @@ public class ChatDataManager {
// Inner class to hold entity-specific data
public static class EntityChatData {
public String currentMessage;
public int currentLineNumber;
public List<String> previousMessages;
public String characterSheet;
public EntityChatData() {
this.currentMessage = "";
this.currentLineNumber = 0;
this.previousMessages = new ArrayList<>();
this.characterSheet = "";
}
......@@ -28,7 +31,7 @@ public class ChatDataManager {
public void generateGreeting() {
ChatGPTRequest.fetchGreetingFromChatGPT().thenAccept(greeting -> {
if (greeting != null) {
this.currentMessage = greeting;
this.addMessage(greeting);
}
});
}
......@@ -39,6 +42,20 @@ public class ChatDataManager {
previousMessages.add(currentMessage);
}
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;
public class ModInit implements ModInitializer {
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
public void onInitialize() {
......@@ -19,7 +20,8 @@ public class ModInit implements ModInitializer {
// However, some things (like resources) may still be uninitialized.
// 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();
// Ensure that the task is synced with the server thread
......@@ -28,13 +30,31 @@ public class ModInit implements ModInitializer {
Entity entity = player.getServerWorld().getEntityById(entityId);
if (entity != null) {
// 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);
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) -> {
// Load chat data...
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