From 8cbaa6927d90e3141ee6e24d9417517dc87be0b2 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 16 Aug 2025 23:54:19 -0500 Subject: [PATCH 01/26] Adding book background asset --- .../assets/creaturechat/textures/ui/book.png | Bin 0 -> 7591 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book.png diff --git a/src/main/resources/assets/creaturechat/textures/ui/book.png b/src/main/resources/assets/creaturechat/textures/ui/book.png new file mode 100644 index 0000000000000000000000000000000000000000..97a18292cb8155f3797b9859ceb569b3c0f454b2 GIT binary patch literal 7591 zcmX9@c|4Tg_h%Xzgds+Vv1H5GN@N@RPEw4?Iw+K#ELlDn#xk;$on$HN*o`fP5fLg& zGxoA%-w9>?J$--w%xh-uz2~0i-1qyO_qk8p-8;IB^j!2*R8)-mdRq6WsHhRZ_g=_F z;Pc+kDRU|+9uIvjb+bU~ja-vu-k#7;7VeTHCJ6qdM7YfCxKLnFeW;uE@ESoTK}OPF zok>{63>s2*0eJ=PBQ1GFAWut;Mm*E*EY^Sny9J-X7l{=n09q=$@|k22b(H^DLoVM;gHyl zOi0MVIBoEZ$tk>Rb*^PA=(P3ES0T@4khU6FD_=H3u!kwwm{(!vBYok1E0j>eJpJJG zAS~?u>Dh;X%4<4fWIJ0cIt##3IOHimm3Lv=_LwtsN^$k+PtWqOzxx^O=R!F%goA=O76-{0 z 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 Date: Sun, 17 Aug 2025 15:44:27 -0500 Subject: [PATCH 02/26] Adding initial book prototype - still breaks on old minecraft versions --- src/client/java/com/owlmaddie/ClientInit.java | 15 + .../java/com/owlmaddie/ui/BookScreen.java | 357 ++++++++++++++++++ .../java/com/owlmaddie/ui/ChatScreen.java | 5 + .../java/com/owlmaddie/ui/ScreenHelper.java | 13 +- src/main/java/com/owlmaddie/ModInit.java | 4 +- .../com/owlmaddie/chat/EntityChatData.java | 4 + .../java/com/owlmaddie/items/ModItems.java | 35 ++ .../assets/creaturechat/lang/en_us.json | 4 + .../creaturechat/models/item/memory_book.json | 7 + .../textures/item/memory_book.png | Bin 0 -> 115 bytes .../java/com/owlmaddie/ui/ScreenHelper.java | 13 +- .../java/com/owlmaddie/ui/ScreenHelper.java | 13 +- .../java/com/owlmaddie/ui/ScreenHelper.java | 13 +- .../java/com/owlmaddie/ui/ScreenHelper.java | 13 +- 14 files changed, 485 insertions(+), 11 deletions(-) create mode 100644 src/client/java/com/owlmaddie/ui/BookScreen.java create mode 100644 src/main/java/com/owlmaddie/items/ModItems.java create mode 100644 src/main/resources/assets/creaturechat/lang/en_us.json create mode 100644 src/main/resources/assets/creaturechat/models/item/memory_book.json create mode 100644 src/main/resources/assets/creaturechat/textures/item/memory_book.png diff --git a/src/client/java/com/owlmaddie/ClientInit.java b/src/client/java/com/owlmaddie/ClientInit.java index 70b66275..26e7e395 100644 --- a/src/client/java/com/owlmaddie/ClientInit.java +++ b/src/client/java/com/owlmaddie/ClientInit.java @@ -15,12 +15,17 @@ import com.owlmaddie.utils.TickDelta; import com.owlmaddie.inventory.ModMenus; import com.owlmaddie.inventory.MobInventoryScreen; +import com.owlmaddie.items.ModItems; +import com.owlmaddie.ui.BookScreen; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.fabricmc.fabric.api.event.player.UseItemCallback; import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraft.client.Minecraft; +import net.minecraft.world.InteractionResult; /** * The {@code ClientInit} class initializes this mod in the client and defines all hooks into the @@ -56,6 +61,16 @@ public void onInitializeClient() { ClientPackets.register(); MenuScreens.register(ModMenus.MOB_INVENTORY, MobInventoryScreen::new); + UseItemCallback.EVENT.register((player, world, hand) -> { + if (player.getItemInHand(hand).is(ModItems.MEMORY_BOOK)) { + if (world.isClientSide) { + Minecraft.getInstance().setScreen(new BookScreen()); + } + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + }); + // Register an event callback to render text bubbles WorldRenderEvents.BEFORE_DEBUG_RENDER.register(ctx -> { float delta = TickDelta.get(ctx); diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java new file mode 100644 index 00000000..fdb3516a --- /dev/null +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -0,0 +1,357 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.ui; + +import com.owlmaddie.chat.ChatDataManager; +import com.owlmaddie.chat.ChatMessage; +import com.owlmaddie.chat.EntityChatData; +import com.owlmaddie.chat.PlayerData; +import com.owlmaddie.render.EntityTextureHelper; +import com.owlmaddie.utils.TextureLoader; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; + +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Screen that displays a two-page log of recently chatted entities. + */ +public class BookScreen extends ScreenHelper { + private static final int BOOK_WIDTH = 300; + private static final int BOOK_HEIGHT = 200; + + private int index; + private final List ordered; + private Button prevButton; + private Button nextButton; + private EditBox dummyField; + private static final TextureLoader textures = new TextureLoader(); + private static final int PAGE_CONTENT_W = 120; // width of text block per page + private static final int PAGE_CONTENT_H = 120; // height of text block (for scissor) + private static final int LABEL_COLOR = 0xFF6B4A3B; // warm brown, matches book UI + private static final int BODY_COLOR = 0xFF2A2A2A; // dark text + private static final int LIGHT_GRAY = 0xFFB0B0B0; + private static final Random RNG = new Random(); + + public BookScreen() { + super(Component.literal("Creature Log")); + ChatDataManager mgr = ChatDataManager.getClientInstance(); + ordered = new ArrayList<>(mgr.entityChatDataMap.values()); + ordered.sort(Comparator.comparingLong(BookScreen::getLastInteraction).reversed()); + } + + private static long getLastInteraction(EntityChatData data) { + List msgs = data.previousMessages; + if (msgs != null && !msgs.isEmpty()) { + Long t = msgs.get(msgs.size() - 1).timestamp; + return t == null ? 0L : t; + } + return 0L; + } + + @Override + protected void init() { + super.init(); + + BG_WIDTH = BOOK_WIDTH; + BG_HEIGHT = BOOK_HEIGHT; + TITLE_OFFSET = 0; + + bgX = (this.width - BG_WIDTH) / 2; + bgY = (this.height - BG_HEIGHT) / 2; + + dummyField = new EditBox(font, bgX, bgY, 0, 0, Component.empty()); + + prevButton = ButtonHelper.createImageButton( + bgX + 29, bgY + 155, + 14, 14, + textures.GetUI("arrow-left"), + textures.GetUI("arrow-left"), + b -> { + index = Math.max(0, index - 2); + updateButtons(); + }, + button -> Component.empty() + ); + + nextButton = ButtonHelper.createImageButton( + bgX + 257, bgY + 155, + 14, 14, + textures.GetUI("arrow-right"), + textures.GetUI("arrow-right"), + b -> { + index = Math.min(ordered.size(), index + 2); + updateButtons(); + }, + button -> Component.empty() + ); + + addRenderableWidget(prevButton); + addRenderableWidget(nextButton); + updateButtons(); + } + + private void updateButtons() { + prevButton.active = index > 0; + nextButton.active = index + 2 < ordered.size(); + } + + @Override + protected void renderContent(net.minecraft.client.gui.GuiGraphics ctx, int mouseX, int mouseY, float delta) { + renderPage(ctx, bgX + 32, bgY + 51, index); + renderPage(ctx, bgX + 162, bgY + 51, index + 1); + } + + private void renderPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, int dataIndex) { + if (dataIndex >= ordered.size()) return; + + EntityChatData data = ordered.get(dataIndex); + Player player = Minecraft.getInstance().player; + if (player == null) return; + + PlayerData pData = data.getPlayerData(player.getName().getString()); + int friendship = pData != null ? pData.friendship : 0; + + // Character-sheet props (use lorem fallback) + String csName = data.getCharacterProp("Name"); // keep real name logic for the title + String personality = orLoremSeeded(data.getCharacterProp("Personality"), data.entityId, "Personality", 20, 100); + String speaking = orLoremSeeded(firstNonNull( + data.getCharacterProp("Speaking Style / Tone"), + data.getCharacterProp("Speaking Style"), + data.getCharacterProp("Tone") + ), data.entityId, "SpeakingStyle", 20, 100); + String skills = orLoremSeeded(data.getCharacterProp("Skills"), data.entityId, "Skills", 20, 100); + String likes = orLoremSeeded(data.getCharacterProp("Likes"), data.entityId, "Likes", 20, 100); + String dislikes = orLoremSeeded(data.getCharacterProp("Dislikes"), data.entityId, "Dislikes", 20, 100); + String background = orLoremSeeded(data.getCharacterProp("Background"), data.entityId, "Background", 20, 100); + + String lastMsg = safe(data.currentMessage); + if (lastMsg.isEmpty()) { + lastMsg = loremSeeded(30, 90, seedFrom(data.entityId, "LastMessage")); + } + + // Name to show: sheet name > entity name > "Unknown" + Entity entity = getEntity(data.entityId); + String displayName = (csName != null && !csName.equals("N/A")) ? csName + : (entity != null ? entity.getName().getString() : "Unknown"); + + // Title color by friendship + int titleColor = 0xFF808080; // neutral + if (friendship < 0) titleColor = 0xFFFF3A3A; // red + else if (friendship > 0) titleColor = 0xFF2ECC40; // green + + // Title (slightly larger, soft shadow) + ctx.pose().pushMatrix(); + ctx.pose().translate((float)x, (float)y); + ctx.pose().scale(1.12f, 1.12f); + ctx.drawString(this.font, displayName, 1, 1, 0x66000000, false); // shadow + ctx.drawString(this.font, displayName, 0, 0, titleColor, false); + ctx.pose().popMatrix(); + + int offsetY = y + Math.round(this.font.lineHeight * 1.12f) + 6; + + // Icon + friendship badge + if (entity != null) drawEntityIcon(ctx, entity, x, offsetY); + ResourceLocation frTex = textures.GetUI("friendship" + friendship); + if (frTex != null) { + ctx.blit(RenderPipelines.GUI_TEXTURED, frTex, x + 34, offsetY, 0, 0, 31, 21, 31, 21); + } + + int lineY = offsetY + 35; + + // Clip everything to the page content box so nothing bleeds outside + ctx.enableScissor(x, y, x + PAGE_CONTENT_W, y + PAGE_CONTENT_H); + + // Layout: label on one line (small), value below it (wrapped, slightly smaller) + lineY = drawPair(ctx, "Personality", personality, x, lineY, PAGE_CONTENT_W); + lineY = drawPair(ctx, "Speaking Style / Tone", speaking, x, lineY, PAGE_CONTENT_W); + lineY = drawPair(ctx, "Skills", skills, x, lineY, PAGE_CONTENT_W); + lineY = drawPair(ctx, "Likes", likes, x, lineY, PAGE_CONTENT_W); + lineY = drawPair(ctx, "Dislikes", dislikes, x, lineY, PAGE_CONTENT_W); + lineY = drawPair(ctx, "Background", background, x, lineY, PAGE_CONTENT_W); + + // Last Message (cap to 4 lines, smaller and muted) + ctx.drawString(this.font, "Last Message", x, lineY, LABEL_COLOR, false); + lineY += this.font.lineHeight; + lineY = drawWrapped(ctx, lastMsg, x, lineY, PAGE_CONTENT_W, 0.9f, 4, 0xFF444444); + + ctx.disableScissor(); + + // UUID footer (tiny light gray) aligned to page bottom + int debugY = bgY + BG_HEIGHT - 6; + ctx.pose().pushMatrix(); + ctx.pose().translate((float)x, (float)debugY); + ctx.pose().scale(0.8f, 0.8f); + ctx.drawString(this.font, data.entityId, 0, 0, LIGHT_GRAY, false); + ctx.pose().popMatrix(); + } + +// ---------- helpers ---------- + + private static String firstNonNull(String... vals) { + for (String v : vals) if (v != null && !v.isEmpty()) return v; + return null; + } + private static String orLorem(String v) { + if (v == null || v.isEmpty() || "N/A".equalsIgnoreCase(v)) return lorem(20, 100); + return v; + } + private static String lorem(int min, int max) { + String[] words = ("lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut " + + "labore et dolore magna aliqua ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut " + + "aliquip ex ea commodo consequat duis aute irure dolor in reprehenderit in voluptate velit esse cillum " + + "dolore eu fugiat nulla pariatur excepteur sint occaecat cupidatat non proident sunt in culpa qui officia " + + "deserunt mollit anim id est laborum").split(" "); + int target = min + RNG.nextInt(Math.max(1, max - min + 1)); + StringBuilder sb = new StringBuilder(); + while (sb.length() < target) { + if (sb.length() > 0) sb.append(' '); + sb.append(words[RNG.nextInt(words.length)]); + } + String s = sb.toString(); + // Trim to nearest word under target and capitalize first letter + if (s.length() > target) s = s.substring(0, Math.max(0, s.lastIndexOf(' ', target))); + if (!s.isEmpty()) s = Character.toUpperCase(s.charAt(0)) + s.substring(1); + return s + "."; + } + + /** Draw a label then a wrapped value; returns next y. */ + private int drawPair(net.minecraft.client.gui.GuiGraphics ctx, String label, String value, int x, int y, int widthPx) { + ctx.drawString(this.font, label, x, y, LABEL_COLOR, false); + y += this.font.lineHeight; + return drawWrapped(ctx, value, x, y, widthPx, 0.92f, 2, BODY_COLOR) + 4; // slight gap after pair + } + + /** Wrapped text with scaling and maxLines, adds ellipsis if truncated. Returns next y. */ + private int drawWrapped(net.minecraft.client.gui.GuiGraphics ctx, String text, int x, int y, + int maxWidthPx, float scale, int maxLines, int color) { + int avail = (int)Math.floor(maxWidthPx / scale); + String rest = text; + int drawnPx = 0; + + ctx.pose().pushMatrix(); + ctx.pose().translate((float)x, (float)y); + ctx.pose().scale(scale, scale); + + for (int line = 0; line < maxLines && !rest.isEmpty(); line++) { + String piece = this.font.plainSubstrByWidth(rest, avail); + if (piece.isEmpty()) break; + boolean hasMore = piece.length() < rest.length(); + boolean lastLine = (line == maxLines - 1); + if (lastLine && hasMore) { + // ellipsize last line + while (!piece.isEmpty() && this.font.width(piece + "…") > avail) { + piece = piece.substring(0, piece.length() - 1); + } + piece = piece + "…"; + rest = ""; + } else { + rest = rest.substring(piece.length()); + } + ctx.drawString(this.font, piece, 0, drawnPx, color, false); + drawnPx += this.font.lineHeight; + } + + ctx.pose().popMatrix(); + return y + Math.round(drawnPx * scale); + } + + private String safe(String s) { + return s == null ? "" : s; + } + + private void drawEntityIcon(net.minecraft.client.gui.GuiGraphics ctx, Entity entity, int x, int y) { + var dispatcher = Minecraft.getInstance().getEntityRenderDispatcher(); + var renderer = dispatcher.getRenderer(entity); + ResourceLocation skinId = EntityTextureHelper.getTexture(renderer, entity); + if (skinId == null) return; + ResourceLocation icon = textures.GetEntity(skinId.getPath()); + if (icon == null) return; + ctx.blit(RenderPipelines.GUI_TEXTURED, icon, + x, y, + 0, 0, + 30, 30, + 30, 30); + } + + private Entity getEntity(String id) { + try { + UUID uuid = UUID.fromString(id); + return Minecraft.getInstance().level.getEntity(uuid); + } catch (Exception e) { + return null; + } + } + + private String getEntityName(String id) { + Entity e = getEntity(id); + return e != null ? e.getName().getString() : id; + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + protected EditBox getTextField() { + return this.dummyField; + } + + @Override + protected Component getLabelText() { + return Component.empty(); + } + + @Override + protected String getBackgroundTextureId() { + return "book"; + } + + // Deterministic seed from entityId + field key (FNV-1a 64-bit) + private static long seedFrom(String id, String key) { + long h = 0xcbf29ce484222325L; + for (int i = 0; i < id.length(); i++) { h ^= id.charAt(i); h *= 0x100000001b3L; } + h ^= 0x9e3779b97f4a7c15L; // scramble between id and key + for (int i = 0; i < key.length(); i++) { h ^= key.charAt(i); h *= 0x100000001b3L; } + return h; + } + + // Lorem with a provided seed (so it's stable per entity/field) + private static String loremSeeded(int min, int max, long seed) { + java.util.Random r = new java.util.Random(seed); + String[] words = ("lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut " + + "labore et dolore magna aliqua ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut " + + "aliquip ex ea commodo consequat duis aute irure dolor in reprehenderit in voluptate velit esse cillum " + + "dolore eu fugiat nulla pariatur excepteur sint occaecat cupidatat non proident sunt in culpa qui officia " + + "deserunt mollit anim id est laborum").split(" "); + int target = min + r.nextInt(Math.max(1, max - min + 1)); + StringBuilder sb = new StringBuilder(); + while (sb.length() < target) { + if (sb.length() > 0) sb.append(' '); + sb.append(words[r.nextInt(words.length)]); + } + String s = sb.toString(); + if (s.length() > target) s = s.substring(0, Math.max(0, s.lastIndexOf(' ', target))); + if (!s.isEmpty()) s = Character.toUpperCase(s.charAt(0)) + s.substring(1); + return s + "."; + } + + // If v is null/empty/"N/A", return stable lorem seeded by entityId+field + private static String orLoremSeeded(String v, String entityId, String fieldKey, int min, int max) { + if (v == null || v.isEmpty() || "N/A".equalsIgnoreCase(v)) { + return loremSeeded(min, max, seedFrom(entityId, fieldKey)); + } + return v; + } + +} + diff --git a/src/client/java/com/owlmaddie/ui/ChatScreen.java b/src/client/java/com/owlmaddie/ui/ChatScreen.java index eab65877..b19a059a 100644 --- a/src/client/java/com/owlmaddie/ui/ChatScreen.java +++ b/src/client/java/com/owlmaddie/ui/ChatScreen.java @@ -157,4 +157,9 @@ protected EditBox getTextField() { protected Component getLabelText() { return this.labelText; } + + @Override + protected String getBackgroundTextureId() { + return "chat-background"; + } } diff --git a/src/client/java/com/owlmaddie/ui/ScreenHelper.java b/src/client/java/com/owlmaddie/ui/ScreenHelper.java index 4d5ee240..c7d6ef97 100644 --- a/src/client/java/com/owlmaddie/ui/ScreenHelper.java +++ b/src/client/java/com/owlmaddie/ui/ScreenHelper.java @@ -28,13 +28,19 @@ protected ScreenHelper(Component title) { /** Subclass must return its label Text here */ protected abstract Component getLabelText(); + /** Subclass must supply the UI texture id for its background */ + protected abstract String getBackgroundTextureId(); + + /** Hook for subclasses to render additional content above the background */ + protected void renderContent(GuiGraphics context, int mouseX, int mouseY, float delta) {} + @Override public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { // Full-screen vanilla gradient renderBackground(context); - // Chat-box texture - ResourceLocation bgTex = textures.GetUI("chat-background"); + // Background texture + ResourceLocation bgTex = textures.GetUI(getBackgroundTextureId()); if (bgTex != null) { context.blit( bgTex, @@ -45,6 +51,9 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { ); } + // Allow subclass to render content on top of background but before widgets + renderContent(context, mouseX, mouseY, delta); + // Render all children (textField, buttons) super.render(context, mouseX, mouseY, delta); diff --git a/src/main/java/com/owlmaddie/ModInit.java b/src/main/java/com/owlmaddie/ModInit.java index 187fec39..d235c858 100644 --- a/src/main/java/com/owlmaddie/ModInit.java +++ b/src/main/java/com/owlmaddie/ModInit.java @@ -6,6 +6,7 @@ import com.owlmaddie.commands.CreatureChatCommands; import com.owlmaddie.inventory.ModMenus; import com.owlmaddie.network.ServerPackets; +import com.owlmaddie.items.ModItems; import net.fabricmc.api.ModInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,8 +28,9 @@ public void onInitialize() { // Register server commands CreatureChatCommands.register(); - // Register menus and events + // Register menus, items, and events ModMenus.register(); + ModItems.register(); ServerPackets.register(); LOGGER.info("CreatureChat MOD Initialized!"); diff --git a/src/main/java/com/owlmaddie/chat/EntityChatData.java b/src/main/java/com/owlmaddie/chat/EntityChatData.java index e969147f..073a9fbb 100644 --- a/src/main/java/com/owlmaddie/chat/EntityChatData.java +++ b/src/main/java/com/owlmaddie/chat/EntityChatData.java @@ -155,6 +155,10 @@ public EntityChatDataLight toLightVersion(String playerName) { } public String getCharacterProp(String propertyName) { + if (characterSheet == null || characterSheet.isEmpty()) { + return "N/A"; + } + // Create a case-insensitive regex pattern to match the property name and capture its value Pattern pattern = Pattern.compile("-?\\s*" + Pattern.quote(propertyName) + ":\\s*(.+)", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(characterSheet); diff --git a/src/main/java/com/owlmaddie/items/ModItems.java b/src/main/java/com/owlmaddie/items/ModItems.java new file mode 100644 index 00000000..88df5586 --- /dev/null +++ b/src/main/java/com/owlmaddie/items/ModItems.java @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.items; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; + +/** + * Registers items used by the mod. + */ +public final class ModItems { + private ModItems() {} + + /** The book that opens the creature log UI. */ + private static final ResourceLocation MEMORY_BOOK_ID = new ResourceLocation("creaturechat", "memory_book"); + + public static final Item MEMORY_BOOK = Registry.register( + BuiltInRegistries.ITEM, + MEMORY_BOOK_ID, + new Item(new Item.Properties() + .stacksTo(1) + .setId(ResourceKey.create(Registries.ITEM, MEMORY_BOOK_ID)) + .useItemDescriptionPrefix() + ) + ); + + /** Placeholder for future item registrations. */ + public static void register() {} +} + diff --git a/src/main/resources/assets/creaturechat/lang/en_us.json b/src/main/resources/assets/creaturechat/lang/en_us.json new file mode 100644 index 00000000..75ce6a2b --- /dev/null +++ b/src/main/resources/assets/creaturechat/lang/en_us.json @@ -0,0 +1,4 @@ +{ + "item.creaturechat.memory_book": "Creature Book" +} + diff --git a/src/main/resources/assets/creaturechat/models/item/memory_book.json b/src/main/resources/assets/creaturechat/models/item/memory_book.json new file mode 100644 index 00000000..596f4e34 --- /dev/null +++ b/src/main/resources/assets/creaturechat/models/item/memory_book.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "creaturechat:item/memory_book" + } +} + diff --git a/src/main/resources/assets/creaturechat/textures/item/memory_book.png b/src/main/resources/assets/creaturechat/textures/item/memory_book.png new file mode 100644 index 0000000000000000000000000000000000000000..98092c079a08397cb4d62fa592f9119634e663a2 GIT binary patch literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`R-P`7Ar*6y*L+C$ao&M3VUde5 zGZ1k11nz#s*Y@OO>~4uOckgV-%IOPO*vIY0D`A>YAaTIr!A?Pj=MkLq?XoNtfu=Bc My85}Sb4q9e04dWW_y7O^ literal 0 HcmV?d00001 diff --git a/src/vs/v1_20_2/client/java/com/owlmaddie/ui/ScreenHelper.java b/src/vs/v1_20_2/client/java/com/owlmaddie/ui/ScreenHelper.java index af3df5d1..50650b50 100644 --- a/src/vs/v1_20_2/client/java/com/owlmaddie/ui/ScreenHelper.java +++ b/src/vs/v1_20_2/client/java/com/owlmaddie/ui/ScreenHelper.java @@ -30,13 +30,19 @@ protected ScreenHelper(Component title) { /** Subclass must return its label Text here */ protected abstract Component getLabelText(); + /** Subclass must supply the UI texture id for its background */ + protected abstract String getBackgroundTextureId(); + + /** Hook for subclasses to render additional content above the background */ + protected void renderContent(GuiGraphics context, int mouseX, int mouseY, float delta) {} + @Override public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { // Draw the vanilla gradient once renderBackground(context, mouseX, mouseY, delta); - // Draw the chat-box texture - ResourceLocation bgTex = textures.GetUI("chat-background"); + // Draw the background texture + ResourceLocation bgTex = textures.GetUI(getBackgroundTextureId()); if (bgTex != null) { context.blit( bgTex, @@ -47,6 +53,9 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { ); } + // Allow subclass content between background and widgets + renderContent(context, mouseX, mouseY, delta); + // Render children, but suppress their background call skipNextBackground = true; super.render(context, mouseX, mouseY, delta); diff --git a/src/vs/v1_21_2/client/java/com/owlmaddie/ui/ScreenHelper.java b/src/vs/v1_21_2/client/java/com/owlmaddie/ui/ScreenHelper.java index 54552cc9..055db6e5 100644 --- a/src/vs/v1_21_2/client/java/com/owlmaddie/ui/ScreenHelper.java +++ b/src/vs/v1_21_2/client/java/com/owlmaddie/ui/ScreenHelper.java @@ -35,13 +35,19 @@ protected ScreenHelper(Component title) { */ protected abstract Component getLabelText(); + /** Subclass must supply the UI texture id for its background */ + protected abstract String getBackgroundTextureId(); + + /** Hook for subclasses to render additional content above the background */ + protected void renderContent(GuiGraphics context, int mouseX, int mouseY, float delta) {} + @Override public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { // Draw the vanilla gradient once super.renderBackground(context, mouseX, mouseY, delta); - // Draw the chat-box texture - ResourceLocation bgTex = textures.GetUI("chat-background"); + // Draw the background texture + ResourceLocation bgTex = textures.GetUI(getBackgroundTextureId()); if (bgTex != null) { RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); @@ -56,6 +62,9 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { RenderSystem.disableBlend(); } + // Allow subclass content between background and widgets + renderContent(context, mouseX, mouseY, delta); + // Render children/widgets but suppress their background call skipNextBackground = true; super.render(context, mouseX, mouseY, delta); diff --git a/src/vs/v1_21_5/client/java/com/owlmaddie/ui/ScreenHelper.java b/src/vs/v1_21_5/client/java/com/owlmaddie/ui/ScreenHelper.java index 0f66d39d..aea9198f 100644 --- a/src/vs/v1_21_5/client/java/com/owlmaddie/ui/ScreenHelper.java +++ b/src/vs/v1_21_5/client/java/com/owlmaddie/ui/ScreenHelper.java @@ -35,13 +35,19 @@ protected ScreenHelper(Component title) { */ protected abstract Component getLabelText(); + /** Subclass must supply the UI texture id for its background */ + protected abstract String getBackgroundTextureId(); + + /** Hook for subclasses to render additional content above the background */ + protected void renderContent(GuiGraphics context, int mouseX, int mouseY, float delta) {} + @Override public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { // Draw the vanilla gradient once super.renderBackground(context, mouseX, mouseY, delta); - // Draw the chat-box texture - ResourceLocation bgTex = textures.GetUI("chat-background"); + // Draw the background texture + ResourceLocation bgTex = textures.GetUI(getBackgroundTextureId()); if (bgTex != null) { BlendHelper.enableBlend(); BlendHelper.defaultBlendFunc(); @@ -56,6 +62,9 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { BlendHelper.disableBlend(); } + // Allow subclass content between background and widgets + renderContent(context, mouseX, mouseY, delta); + // Render children/widgets but suppress their background call skipNextBackground = true; super.render(context, mouseX, mouseY, delta); diff --git a/src/vs/v1_21_6/client/java/com/owlmaddie/ui/ScreenHelper.java b/src/vs/v1_21_6/client/java/com/owlmaddie/ui/ScreenHelper.java index b27a5995..84d11169 100644 --- a/src/vs/v1_21_6/client/java/com/owlmaddie/ui/ScreenHelper.java +++ b/src/vs/v1_21_6/client/java/com/owlmaddie/ui/ScreenHelper.java @@ -35,13 +35,19 @@ protected ScreenHelper(Component title) { */ protected abstract Component getLabelText(); + /** Subclass must supply the UI texture id for its background */ + protected abstract String getBackgroundTextureId(); + + /** Hook for subclasses to render additional content above the background */ + protected void renderContent(GuiGraphics context, int mouseX, int mouseY, float delta) {} + @Override public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { // Draw the vanilla gradient once super.renderBackground(context, mouseX, mouseY, delta); - // Draw the chat-box texture - ResourceLocation bgTex = textures.GetUI("chat-background"); + // Draw the background texture + ResourceLocation bgTex = textures.GetUI(getBackgroundTextureId()); if (bgTex != null) { BlendHelper.enableBlend(); BlendHelper.defaultBlendFunc(); @@ -56,6 +62,9 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { BlendHelper.disableBlend(); } + // Allow subclass content between background and widgets + renderContent(context, mouseX, mouseY, delta); + // Render children/widgets but suppress their background call skipNextBackground = true; super.render(context, mouseX, mouseY, delta); From 2c4217e73f4108b3460a10840134b4b9308ed4b6 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 17 Aug 2025 18:50:40 -0500 Subject: [PATCH 03/26] Fixing build issues on book screen for all versions of Minecraft - still lots of issues though. --- src/client/java/com/owlmaddie/ClientInit.java | 7 +- .../java/com/owlmaddie/render/PoseHelper.java | 29 +++++++ .../render/RenderPipelineHelper.java | 25 ++++++ .../java/com/owlmaddie/ui/BookScreen.java | 45 ++++++---- .../java/com/owlmaddie/ui/ChatScreen.java | 2 - .../utils/UseItemCallbackHelper.java | 2 +- .../java/com/owlmaddie/items/ModItems.java | 4 - .../java/com/owlmaddie/items/ModItems.java | 34 ++++++++ .../java/com/owlmaddie/render/PoseHelper.java | 29 +++++++ .../client/java/com/owlmaddie/ClientInit.java | 86 +++++++++++++++++++ .../render/RenderPipelineHelper.java | 27 ++++++ .../utils/UseItemCallbackHelper.java | 20 +++-- .../java/com/owlmaddie/render/PoseHelper.java | 30 +++++++ .../render/RenderPipelineHelper.java | 27 ++++++ 14 files changed, 331 insertions(+), 36 deletions(-) create mode 100644 src/client/java/com/owlmaddie/render/PoseHelper.java create mode 100644 src/client/java/com/owlmaddie/render/RenderPipelineHelper.java create mode 100644 src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java create mode 100644 src/vs/v1_21_0/client/java/com/owlmaddie/render/PoseHelper.java create mode 100644 src/vs/v1_21_2/client/java/com/owlmaddie/ClientInit.java create mode 100644 src/vs/v1_21_2/client/java/com/owlmaddie/render/RenderPipelineHelper.java create mode 100644 src/vs/v1_21_6/client/java/com/owlmaddie/render/PoseHelper.java create mode 100644 src/vs/v1_21_6/client/java/com/owlmaddie/render/RenderPipelineHelper.java diff --git a/src/client/java/com/owlmaddie/ClientInit.java b/src/client/java/com/owlmaddie/ClientInit.java index 26e7e395..f204e361 100644 --- a/src/client/java/com/owlmaddie/ClientInit.java +++ b/src/client/java/com/owlmaddie/ClientInit.java @@ -13,6 +13,7 @@ import com.owlmaddie.ui.InventoryKeyHandler; import com.owlmaddie.ui.PlayerMessageManager; import com.owlmaddie.utils.TickDelta; +import com.owlmaddie.utils.UseItemCallbackHelper; import com.owlmaddie.inventory.ModMenus; import com.owlmaddie.inventory.MobInventoryScreen; import com.owlmaddie.items.ModItems; @@ -25,7 +26,7 @@ import net.fabricmc.fabric.api.event.player.UseItemCallback; import net.minecraft.client.gui.screens.MenuScreens; import net.minecraft.client.Minecraft; -import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionResultHolder; /** * The {@code ClientInit} class initializes this mod in the client and defines all hooks into the @@ -66,9 +67,9 @@ public void onInitializeClient() { if (world.isClientSide) { Minecraft.getInstance().setScreen(new BookScreen()); } - return InteractionResult.SUCCESS; + return InteractionResultHolder.success(player.getItemInHand(hand)); } - return InteractionResult.PASS; + return UseItemCallbackHelper.handleUseItemAction(player, world, hand); }); // Register an event callback to render text bubbles diff --git a/src/client/java/com/owlmaddie/render/PoseHelper.java b/src/client/java/com/owlmaddie/render/PoseHelper.java new file mode 100644 index 00000000..5d1a19b8 --- /dev/null +++ b/src/client/java/com/owlmaddie/render/PoseHelper.java @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.render; + +import com.mojang.blaze3d.vertex.PoseStack; + +/** + * Cross-version wrapper for simple pose transformations. + */ +public final class PoseHelper { + private PoseHelper() {} + + public static void push(PoseStack stack) { + stack.pushPose(); + } + + public static void pop(PoseStack stack) { + stack.popPose(); + } + + public static void translate(PoseStack stack, float x, float y) { + stack.translate(x, y, 0); + } + + public static void scale(PoseStack stack, float sx, float sy) { + stack.scale(sx, sy, 1.0f); + } +} diff --git a/src/client/java/com/owlmaddie/render/RenderPipelineHelper.java b/src/client/java/com/owlmaddie/render/RenderPipelineHelper.java new file mode 100644 index 00000000..c97a1385 --- /dev/null +++ b/src/client/java/com/owlmaddie/render/RenderPipelineHelper.java @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.render; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.resources.ResourceLocation; + +/** + * Helper to blit GUI textures across Minecraft versions. + */ +public final class RenderPipelineHelper { + private RenderPipelineHelper() {} + + public static void blitGuiTexture( + GuiGraphics ctx, + ResourceLocation tex, + int x, int y, + int u, int v, + int width, int height, + int texWidth, int texHeight + ) { + ctx.blit(tex, x, y, u, v, width, height, texWidth, texHeight); + } +} diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index fdb3516a..2bfe3be9 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -8,11 +8,12 @@ import com.owlmaddie.chat.EntityChatData; import com.owlmaddie.chat.PlayerData; import com.owlmaddie.render.EntityTextureHelper; -import com.owlmaddie.utils.TextureLoader; +import com.owlmaddie.render.PoseHelper; +import com.owlmaddie.render.RenderPipelineHelper; +import com.owlmaddie.utils.ClientEntityFinder; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.EditBox; -import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; @@ -33,7 +34,6 @@ public class BookScreen extends ScreenHelper { private Button prevButton; private Button nextButton; private EditBox dummyField; - private static final TextureLoader textures = new TextureLoader(); private static final int PAGE_CONTENT_W = 120; // width of text block per page private static final int PAGE_CONTENT_H = 120; // height of text block (for scissor) private static final int LABEL_COLOR = 0xFF6B4A3B; // warm brown, matches book UI @@ -104,6 +104,13 @@ private void updateButtons() { nextButton.active = index + 2 < ordered.size(); } + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (prevButton.mouseClicked(mouseX, mouseY, button)) return true; + if (nextButton.mouseClicked(mouseX, mouseY, button)) return true; + return super.mouseClicked(mouseX, mouseY, button); + } + @Override protected void renderContent(net.minecraft.client.gui.GuiGraphics ctx, int mouseX, int mouseY, float delta) { renderPage(ctx, bgX + 32, bgY + 51, index); @@ -149,12 +156,12 @@ private void renderPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, else if (friendship > 0) titleColor = 0xFF2ECC40; // green // Title (slightly larger, soft shadow) - ctx.pose().pushMatrix(); - ctx.pose().translate((float)x, (float)y); - ctx.pose().scale(1.12f, 1.12f); + PoseHelper.push(ctx.pose()); + PoseHelper.translate(ctx.pose(), (float)x, (float)y); + PoseHelper.scale(ctx.pose(), 1.12f, 1.12f); ctx.drawString(this.font, displayName, 1, 1, 0x66000000, false); // shadow ctx.drawString(this.font, displayName, 0, 0, titleColor, false); - ctx.pose().popMatrix(); + PoseHelper.pop(ctx.pose()); int offsetY = y + Math.round(this.font.lineHeight * 1.12f) + 6; @@ -162,7 +169,7 @@ private void renderPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, if (entity != null) drawEntityIcon(ctx, entity, x, offsetY); ResourceLocation frTex = textures.GetUI("friendship" + friendship); if (frTex != null) { - ctx.blit(RenderPipelines.GUI_TEXTURED, frTex, x + 34, offsetY, 0, 0, 31, 21, 31, 21); + RenderPipelineHelper.blitGuiTexture(ctx, frTex, x + 34, offsetY, 0, 0, 31, 21, 31, 21); } int lineY = offsetY + 35; @@ -187,11 +194,11 @@ private void renderPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, // UUID footer (tiny light gray) aligned to page bottom int debugY = bgY + BG_HEIGHT - 6; - ctx.pose().pushMatrix(); - ctx.pose().translate((float)x, (float)debugY); - ctx.pose().scale(0.8f, 0.8f); + PoseHelper.push(ctx.pose()); + PoseHelper.translate(ctx.pose(), (float)x, (float)debugY); + PoseHelper.scale(ctx.pose(), 0.8f, 0.8f); ctx.drawString(this.font, data.entityId, 0, 0, LIGHT_GRAY, false); - ctx.pose().popMatrix(); + PoseHelper.pop(ctx.pose()); } // ---------- helpers ---------- @@ -237,9 +244,9 @@ private int drawWrapped(net.minecraft.client.gui.GuiGraphics ctx, String text, i String rest = text; int drawnPx = 0; - ctx.pose().pushMatrix(); - ctx.pose().translate((float)x, (float)y); - ctx.pose().scale(scale, scale); + PoseHelper.push(ctx.pose()); + PoseHelper.translate(ctx.pose(), (float)x, (float)y); + PoseHelper.scale(ctx.pose(), scale, scale); for (int line = 0; line < maxLines && !rest.isEmpty(); line++) { String piece = this.font.plainSubstrByWidth(rest, avail); @@ -260,7 +267,7 @@ private int drawWrapped(net.minecraft.client.gui.GuiGraphics ctx, String text, i drawnPx += this.font.lineHeight; } - ctx.pose().popMatrix(); + PoseHelper.pop(ctx.pose()); return y + Math.round(drawnPx * scale); } @@ -275,7 +282,7 @@ private void drawEntityIcon(net.minecraft.client.gui.GuiGraphics ctx, Entity ent if (skinId == null) return; ResourceLocation icon = textures.GetEntity(skinId.getPath()); if (icon == null) return; - ctx.blit(RenderPipelines.GUI_TEXTURED, icon, + RenderPipelineHelper.blitGuiTexture(ctx, icon, x, y, 0, 0, 30, 30, @@ -285,7 +292,9 @@ private void drawEntityIcon(net.minecraft.client.gui.GuiGraphics ctx, Entity ent private Entity getEntity(String id) { try { UUID uuid = UUID.fromString(id); - return Minecraft.getInstance().level.getEntity(uuid); + var level = Minecraft.getInstance().level; + if (level == null) return null; + return ClientEntityFinder.getEntityByUUID(level, uuid); } catch (Exception e) { return null; } diff --git a/src/client/java/com/owlmaddie/ui/ChatScreen.java b/src/client/java/com/owlmaddie/ui/ChatScreen.java index b19a059a..0c55d8eb 100644 --- a/src/client/java/com/owlmaddie/ui/ChatScreen.java +++ b/src/client/java/com/owlmaddie/ui/ChatScreen.java @@ -5,7 +5,6 @@ import com.owlmaddie.chat.ChatDataManager; import com.owlmaddie.network.ClientPackets; -import com.owlmaddie.utils.TextureLoader; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.EditBox; import net.minecraft.network.chat.Component; @@ -41,7 +40,6 @@ public class ChatScreen extends ScreenHelper { private Button cancelButton; private Entity screenEntity; private final Component labelText = Component.literal("Enter your message:"); - private static final TextureLoader textures = new TextureLoader(); public ChatScreen(Entity entity, Player player) { super(Component.literal("Simple Chat")); diff --git a/src/client/java/com/owlmaddie/utils/UseItemCallbackHelper.java b/src/client/java/com/owlmaddie/utils/UseItemCallbackHelper.java index 667e72a3..d46f0313 100644 --- a/src/client/java/com/owlmaddie/utils/UseItemCallbackHelper.java +++ b/src/client/java/com/owlmaddie/utils/UseItemCallbackHelper.java @@ -17,7 +17,7 @@ public final class UseItemCallbackHelper { private UseItemCallbackHelper() {} /** - * Fabric 1.20.x & 1.21.2 handler using TypedActionResult<ItemStack>. + * Fabric 1.20.x–1.21.1 handler using InteractionResultHolder. */ public static InteractionResultHolder handleUseItemAction( Player player, diff --git a/src/main/java/com/owlmaddie/items/ModItems.java b/src/main/java/com/owlmaddie/items/ModItems.java index 88df5586..e0ee8e5e 100644 --- a/src/main/java/com/owlmaddie/items/ModItems.java +++ b/src/main/java/com/owlmaddie/items/ModItems.java @@ -5,8 +5,6 @@ import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; @@ -24,8 +22,6 @@ private ModItems() {} MEMORY_BOOK_ID, new Item(new Item.Properties() .stacksTo(1) - .setId(ResourceKey.create(Registries.ITEM, MEMORY_BOOK_ID)) - .useItemDescriptionPrefix() ) ); diff --git a/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java b/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java new file mode 100644 index 00000000..05938725 --- /dev/null +++ b/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.items; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; + +/** + * Registers items used by the mod. + */ +public final class ModItems { + private ModItems() {} + + /** The book that opens the creature log UI. */ + private static final ResourceLocation MEMORY_BOOK_ID = new ResourceLocation("creaturechat", "memory_book"); + + public static final Item MEMORY_BOOK = Registry.register( + BuiltInRegistries.ITEM, + MEMORY_BOOK_ID, + new Item(new Item.Properties() + .stacksTo(1) + .useItemDescriptionPrefix() + .setId(ResourceKey.create(Registries.ITEM, MEMORY_BOOK_ID)) + ) + ); + + /** Placeholder for future item registrations. */ + public static void register() {} +} diff --git a/src/vs/v1_21_0/client/java/com/owlmaddie/render/PoseHelper.java b/src/vs/v1_21_0/client/java/com/owlmaddie/render/PoseHelper.java new file mode 100644 index 00000000..934f9a94 --- /dev/null +++ b/src/vs/v1_21_0/client/java/com/owlmaddie/render/PoseHelper.java @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.render; + +import com.mojang.blaze3d.vertex.PoseStack; + +/** + * Cross-version wrapper for simple pose transformations. Modified for Minecraft 1.21. + */ +public final class PoseHelper { + private PoseHelper() {} + + public static void push(PoseStack stack) { + stack.pushPose(); + } + + public static void pop(PoseStack stack) { + stack.popPose(); + } + + public static void translate(PoseStack stack, float x, float y) { + stack.translate(x, y, 0); + } + + public static void scale(PoseStack stack, float sx, float sy) { + stack.scale(sx, sy, 1.0f); + } +} diff --git a/src/vs/v1_21_2/client/java/com/owlmaddie/ClientInit.java b/src/vs/v1_21_2/client/java/com/owlmaddie/ClientInit.java new file mode 100644 index 00000000..26e7e395 --- /dev/null +++ b/src/vs/v1_21_2/client/java/com/owlmaddie/ClientInit.java @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie; + +import com.owlmaddie.chat.ChatDataManager; +import com.owlmaddie.network.ClientPackets; +import com.owlmaddie.particle.CreatureParticleFactory; +import com.owlmaddie.particle.LeadParticleFactory; +import com.owlmaddie.particle.Particles; +import com.owlmaddie.ui.BubbleRenderer; +import com.owlmaddie.ui.ClickHandler; +import com.owlmaddie.ui.InventoryKeyHandler; +import com.owlmaddie.ui.PlayerMessageManager; +import com.owlmaddie.utils.TickDelta; +import com.owlmaddie.inventory.ModMenus; +import com.owlmaddie.inventory.MobInventoryScreen; +import com.owlmaddie.items.ModItems; +import com.owlmaddie.ui.BookScreen; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.fabricmc.fabric.api.event.player.UseItemCallback; +import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraft.client.Minecraft; +import net.minecraft.world.InteractionResult; + +/** + * The {@code ClientInit} class initializes this mod in the client and defines all hooks into the + * render pipeline to draw chat bubbles, text, and entity icons. + */ +public class ClientInit implements ClientModInitializer { + private static long tickCounter = 0; + + @Override + public void onInitializeClient() { + // Register particle factories + ParticleFactoryRegistry.getInstance().register(Particles.HEART_SMALL_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.HEART_BIG_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.FIRE_SMALL_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.FIRE_BIG_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.ATTACK_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.FLEE_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.FOLLOW_FRIEND_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.FOLLOW_ENEMY_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.PROTECT_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.LEAD_FRIEND_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.LEAD_ENEMY_PARTICLE, CreatureParticleFactory::new); + ParticleFactoryRegistry.getInstance().register(Particles.LEAD_PARTICLE, LeadParticleFactory::new); + + ClientTickEvents.END_CLIENT_TICK.register(client -> { + tickCounter++; + PlayerMessageManager.tickUpdate(); + }); + + // Register events + ClickHandler.register(); + InventoryKeyHandler.register(); + ClientPackets.register(); + MenuScreens.register(ModMenus.MOB_INVENTORY, MobInventoryScreen::new); + + UseItemCallback.EVENT.register((player, world, hand) -> { + if (player.getItemInHand(hand).is(ModItems.MEMORY_BOOK)) { + if (world.isClientSide) { + Minecraft.getInstance().setScreen(new BookScreen()); + } + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + }); + + // Register an event callback to render text bubbles + WorldRenderEvents.BEFORE_DEBUG_RENDER.register(ctx -> { + float delta = TickDelta.get(ctx); + BubbleRenderer.drawTextAboveEntities(ctx, tickCounter, delta); + }); + + // Register an event callback for when the client disconnects from a server or changes worlds + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { + // Clear or reset the ChatDataManager + ChatDataManager.getClientInstance().clearData(); + }); + } +} diff --git a/src/vs/v1_21_2/client/java/com/owlmaddie/render/RenderPipelineHelper.java b/src/vs/v1_21_2/client/java/com/owlmaddie/render/RenderPipelineHelper.java new file mode 100644 index 00000000..c33886ec --- /dev/null +++ b/src/vs/v1_21_2/client/java/com/owlmaddie/render/RenderPipelineHelper.java @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.render; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; + +/** + * Helper to blit GUI textures across Minecraft versions. + * Modified for Minecraft 1.21.2 where a RenderType supplier is required. + */ +public final class RenderPipelineHelper { + private RenderPipelineHelper() {} + + public static void blitGuiTexture( + GuiGraphics ctx, + ResourceLocation tex, + int x, int y, + int u, int v, + int width, int height, + int texWidth, int texHeight + ) { + ctx.blit(RenderType::guiTextured, tex, x, y, (float) u, (float) v, width, height, texWidth, texHeight); + } +} diff --git a/src/vs/v1_21_2/client/java/com/owlmaddie/utils/UseItemCallbackHelper.java b/src/vs/v1_21_2/client/java/com/owlmaddie/utils/UseItemCallbackHelper.java index 5dd5b362..0d811b2f 100644 --- a/src/vs/v1_21_2/client/java/com/owlmaddie/utils/UseItemCallbackHelper.java +++ b/src/vs/v1_21_2/client/java/com/owlmaddie/utils/UseItemCallbackHelper.java @@ -4,25 +4,29 @@ package com.owlmaddie.utils; import com.owlmaddie.ui.ClickHandler; -import net.fabricmc.fabric.api.event.player.UseItemCallback; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; -public class UseItemCallbackHelper { +/** + * Helper for UseItemCallback, forwarding to the shared shouldCancelAction logic. + */ +public final class UseItemCallbackHelper { + private UseItemCallbackHelper() {} + /** - * Fabric 1.21.2+ handler using ActionResult + * Fabric 1.21.2+ handler returning InteractionResult. */ public static InteractionResult handleUseItemAction( Player player, Level world, InteractionHand hand ) { - // fully qualified call into your ClickHandler - if (ClickHandler.shouldCancelAction(world)) { - return InteractionResult.FAIL; - } - return InteractionResult.PASS; + return shouldCancelAction(world) ? InteractionResult.FAIL : InteractionResult.PASS; + } + + private static boolean shouldCancelAction(Level world) { + return ClickHandler.shouldCancelAction(world); } } diff --git a/src/vs/v1_21_6/client/java/com/owlmaddie/render/PoseHelper.java b/src/vs/v1_21_6/client/java/com/owlmaddie/render/PoseHelper.java new file mode 100644 index 00000000..86d28329 --- /dev/null +++ b/src/vs/v1_21_6/client/java/com/owlmaddie/render/PoseHelper.java @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.render; + +import org.joml.Matrix3x2fStack; + +/** + * Cross-version wrapper for simple pose transformations. + * Modified for Minecraft 1.21.6+ which uses JOML matrix stacks. + */ +public final class PoseHelper { + private PoseHelper() {} + + public static void push(Matrix3x2fStack stack) { + stack.pushMatrix(); + } + + public static void pop(Matrix3x2fStack stack) { + stack.popMatrix(); + } + + public static void translate(Matrix3x2fStack stack, float x, float y) { + stack.translate(x, y); + } + + public static void scale(Matrix3x2fStack stack, float sx, float sy) { + stack.scale(sx, sy); + } +} diff --git a/src/vs/v1_21_6/client/java/com/owlmaddie/render/RenderPipelineHelper.java b/src/vs/v1_21_6/client/java/com/owlmaddie/render/RenderPipelineHelper.java new file mode 100644 index 00000000..ebfdc94a --- /dev/null +++ b/src/vs/v1_21_6/client/java/com/owlmaddie/render/RenderPipelineHelper.java @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.render; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.resources.ResourceLocation; + +/** + * Helper to blit GUI textures across Minecraft versions. + * Modified for Minecraft 1.21.6 where a pipeline must be specified. + */ +public final class RenderPipelineHelper { + private RenderPipelineHelper() {} + + public static void blitGuiTexture( + GuiGraphics ctx, + ResourceLocation tex, + int x, int y, + int u, int v, + int width, int height, + int texWidth, int texHeight + ) { + ctx.blit(RenderPipelines.GUI_TEXTURED, tex, x, y, (float)u, (float)v, width, height, texWidth, texHeight); + } +} From 27cd2b254cfbad8751c362f889fa9197d99a55ac Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 17 Aug 2025 23:55:47 -0500 Subject: [PATCH 04/26] New Book Item - Load client entity chat data and display friends and enemies by most recent - Using Fabric datagen to create book item --- CHANGELOG.md | 8 ++++ src/client/java/com/owlmaddie/ClientInit.java | 2 +- .../datagen/CreatureChatDataGenerator.java | 2 + .../CreatureChatEnglishLangProvider.java | 23 ++++++++++ .../datagen/CreatureChatModelProvider.java | 41 ++++++++++++++++++ .../java/com/owlmaddie/items/ModItems.java | 6 +-- .../assets/creaturechat/lang/en_us.json | 4 -- .../creaturechat/models/item/memory_book.json | 7 --- .../item/{memory_book.png => book.png} | Bin .../java/com/owlmaddie/items/ModItems.java | 8 ++-- .../CreatureChatEnglishLangProvider.java | 25 +++++++++++ .../client/java/com/owlmaddie/ClientInit.java | 2 +- 12 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java create mode 100644 src/main/java/com/owlmaddie/datagen/CreatureChatModelProvider.java delete mode 100644 src/main/resources/assets/creaturechat/lang/en_us.json delete mode 100644 src/main/resources/assets/creaturechat/models/item/memory_book.json rename src/main/resources/assets/creaturechat/textures/item/{memory_book.png => book.png} (100%) create mode 100644 src/vs/v1_21_0/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c5ee671a..50de7ca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to **CreatureChat™** are documented in this file. The form [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added +- New Book Item + - Load client entity chat data and display friends and enemies by most recent + - Using Fabric datagen to create book item + + ## Unreleased ### Added diff --git a/src/client/java/com/owlmaddie/ClientInit.java b/src/client/java/com/owlmaddie/ClientInit.java index f204e361..0fcd4ecf 100644 --- a/src/client/java/com/owlmaddie/ClientInit.java +++ b/src/client/java/com/owlmaddie/ClientInit.java @@ -63,7 +63,7 @@ public void onInitializeClient() { MenuScreens.register(ModMenus.MOB_INVENTORY, MobInventoryScreen::new); UseItemCallback.EVENT.register((player, world, hand) -> { - if (player.getItemInHand(hand).is(ModItems.MEMORY_BOOK)) { + if (player.getItemInHand(hand).is(ModItems.BOOK)) { if (world.isClientSide) { Minecraft.getInstance().setScreen(new BookScreen()); } diff --git a/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java b/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java index 9ae8ec08..2bb21d4a 100644 --- a/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java +++ b/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java @@ -11,5 +11,7 @@ public class CreatureChatDataGenerator implements DataGeneratorEntrypoint { public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) { FabricDataGenerator.Pack pack = fabricDataGenerator.createPack(); pack.addProvider(CreatureChatLootTableProvider::new); + pack.addProvider(CreatureChatModelProvider::new); + pack.addProvider(CreatureChatEnglishLangProvider::new); } } diff --git a/src/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java b/src/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java new file mode 100644 index 00000000..44083341 --- /dev/null +++ b/src/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.datagen; + +import com.owlmaddie.items.ModItems; +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider; + +/** + * Generates the English language translations for the mod. + */ +public class CreatureChatEnglishLangProvider extends FabricLanguageProvider { + public CreatureChatEnglishLangProvider(FabricDataOutput dataOutput) { + super(dataOutput, "en_us"); + } + + @Override + public void generateTranslations(TranslationBuilder builder) { + builder.add(ModItems.BOOK, "Creature Book"); + } +} + diff --git a/src/main/java/com/owlmaddie/datagen/CreatureChatModelProvider.java b/src/main/java/com/owlmaddie/datagen/CreatureChatModelProvider.java new file mode 100644 index 00000000..66d5746c --- /dev/null +++ b/src/main/java/com/owlmaddie/datagen/CreatureChatModelProvider.java @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.datagen; + +import com.google.gson.JsonObject; +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataProvider; + +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +/** + * Generates item model JSON files. + */ +public class CreatureChatModelProvider implements DataProvider { + private final FabricDataOutput output; + + public CreatureChatModelProvider(FabricDataOutput output) { + this.output = output; + } + + @Override + public CompletableFuture run(CachedOutput cachedOutput) { + JsonObject root = new JsonObject(); + root.addProperty("parent", "minecraft:item/generated"); + JsonObject textures = new JsonObject(); + textures.addProperty("layer0", "creaturechat:item/book"); + root.add("textures", textures); + + Path path = this.output.getOutputFolder().resolve("assets/creaturechat/models/item/book.json"); + return DataProvider.saveStable(cachedOutput, root, path); + } + + @Override + public String getName() { + return "CreatureChatModelProvider"; + } +} + diff --git a/src/main/java/com/owlmaddie/items/ModItems.java b/src/main/java/com/owlmaddie/items/ModItems.java index e0ee8e5e..d57e8b8e 100644 --- a/src/main/java/com/owlmaddie/items/ModItems.java +++ b/src/main/java/com/owlmaddie/items/ModItems.java @@ -15,11 +15,11 @@ public final class ModItems { private ModItems() {} /** The book that opens the creature log UI. */ - private static final ResourceLocation MEMORY_BOOK_ID = new ResourceLocation("creaturechat", "memory_book"); + private static final ResourceLocation BOOK_ID = new ResourceLocation("creaturechat", "book"); - public static final Item MEMORY_BOOK = Registry.register( + public static final Item BOOK = Registry.register( BuiltInRegistries.ITEM, - MEMORY_BOOK_ID, + BOOK_ID, new Item(new Item.Properties() .stacksTo(1) ) diff --git a/src/main/resources/assets/creaturechat/lang/en_us.json b/src/main/resources/assets/creaturechat/lang/en_us.json deleted file mode 100644 index 75ce6a2b..00000000 --- a/src/main/resources/assets/creaturechat/lang/en_us.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "item.creaturechat.memory_book": "Creature Book" -} - diff --git a/src/main/resources/assets/creaturechat/models/item/memory_book.json b/src/main/resources/assets/creaturechat/models/item/memory_book.json deleted file mode 100644 index 596f4e34..00000000 --- a/src/main/resources/assets/creaturechat/models/item/memory_book.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "parent": "minecraft:item/generated", - "textures": { - "layer0": "creaturechat:item/memory_book" - } -} - diff --git a/src/main/resources/assets/creaturechat/textures/item/memory_book.png b/src/main/resources/assets/creaturechat/textures/item/book.png similarity index 100% rename from src/main/resources/assets/creaturechat/textures/item/memory_book.png rename to src/main/resources/assets/creaturechat/textures/item/book.png diff --git a/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java b/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java index 05938725..6d48f969 100644 --- a/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java +++ b/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java @@ -17,15 +17,15 @@ public final class ModItems { private ModItems() {} /** The book that opens the creature log UI. */ - private static final ResourceLocation MEMORY_BOOK_ID = new ResourceLocation("creaturechat", "memory_book"); + private static final ResourceLocation BOOK_ID = new ResourceLocation("creaturechat", "book"); - public static final Item MEMORY_BOOK = Registry.register( + public static final Item BOOK = Registry.register( BuiltInRegistries.ITEM, - MEMORY_BOOK_ID, + BOOK_ID, new Item(new Item.Properties() .stacksTo(1) .useItemDescriptionPrefix() - .setId(ResourceKey.create(Registries.ITEM, MEMORY_BOOK_ID)) + .setId(ResourceKey.create(Registries.ITEM, BOOK_ID)) ) ); diff --git a/src/vs/v1_21_0/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java b/src/vs/v1_21_0/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java new file mode 100644 index 00000000..4e81f83a --- /dev/null +++ b/src/vs/v1_21_0/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.datagen; + +import com.owlmaddie.items.ModItems; +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider; +import net.minecraft.core.HolderLookup; + +import java.util.concurrent.CompletableFuture; + +/** + * Generates the English language translations for the mod. + */ +public class CreatureChatEnglishLangProvider extends FabricLanguageProvider { + public CreatureChatEnglishLangProvider(FabricDataOutput dataOutput, CompletableFuture registries) { + super(dataOutput, "en_us", registries); + } + + @Override + public void generateTranslations(HolderLookup.Provider registries, TranslationBuilder builder) { + builder.add(ModItems.BOOK, "Creature Book"); + } +} diff --git a/src/vs/v1_21_2/client/java/com/owlmaddie/ClientInit.java b/src/vs/v1_21_2/client/java/com/owlmaddie/ClientInit.java index 26e7e395..1279bded 100644 --- a/src/vs/v1_21_2/client/java/com/owlmaddie/ClientInit.java +++ b/src/vs/v1_21_2/client/java/com/owlmaddie/ClientInit.java @@ -62,7 +62,7 @@ public void onInitializeClient() { MenuScreens.register(ModMenus.MOB_INVENTORY, MobInventoryScreen::new); UseItemCallback.EVENT.register((player, world, hand) -> { - if (player.getItemInHand(hand).is(ModItems.MEMORY_BOOK)) { + if (player.getItemInHand(hand).is(ModItems.BOOK)) { if (world.isClientSide) { Minecraft.getInstance().setScreen(new BookScreen()); } From edce13c5f9c7403a531c8a7744027eab3a899025 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 17 Aug 2025 23:58:51 -0500 Subject: [PATCH 05/26] Updating book item png --- .../assets/creaturechat/textures/item/book.png | Bin 115 -> 9041 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/main/resources/assets/creaturechat/textures/item/book.png b/src/main/resources/assets/creaturechat/textures/item/book.png index 98092c079a08397cb4d62fa592f9119634e663a2..c3daf448ec6613cfdd0d36e6cab19a3739a70f70 100644 GIT binary patch literal 9041 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 Date: Tue, 19 Aug 2025 16:14:17 -0500 Subject: [PATCH 06/26] New packet types for sending / receiving all entity data (by UUID - per page) --- CHANGELOG.md | 2 +- .../com/owlmaddie/network/ClientPackets.java | 37 ++++++++++ .../java/com/owlmaddie/ui/BookScreen.java | 74 +++++++++++++++---- .../com/owlmaddie/network/ServerPackets.java | 31 ++++++++ 4 files changed, 128 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50de7ca6..c0dbc02d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ All notable changes to **CreatureChat™** are documented in this file. The form - New Book Item - Load client entity chat data and display friends and enemies by most recent - Using Fabric datagen to create book item - + - New packet types for sending / receiving all entity data (by UUID - per page) ## Unreleased diff --git a/src/client/java/com/owlmaddie/network/ClientPackets.java b/src/client/java/com/owlmaddie/network/ClientPackets.java index 6984069b..081c55d2 100644 --- a/src/client/java/com/owlmaddie/network/ClientPackets.java +++ b/src/client/java/com/owlmaddie/network/ClientPackets.java @@ -94,6 +94,12 @@ public static void sendChat(Entity entity, String message) { ClientPacketHelper.send(ServerPackets.PACKET_C2S_SEND_CHAT, buf); } + public static void requestEntityData(UUID entityId) { + FriendlyByteBuf buf = ClientBufferHelper.create(); + buf.writeUtf(entityId.toString()); + ClientPacketHelper.send(ServerPackets.PACKET_C2S_REQUEST_ENTITY_DATA, buf); + } + // Reading a Map from the buffer public static Map readPlayerDataMap(FriendlyByteBuf buffer) { int size = buffer.readInt(); // Read the size of the map @@ -207,6 +213,37 @@ public static void register() { }); }); + ClientPacketHelper.registerReceiver(ServerPackets.PACKET_S2C_ENTITY_DATA, (client, handler, buffer, responseSender) -> { + String entityId = buffer.readUtf(); + byte[] compressed = buffer.readByteArray(); + client.execute(() -> { + String json = Decompression.decompressString(compressed); + if (json == null || json.isEmpty()) { + return; + } + Gson gson = new Gson(); + EntityChatData data = gson.fromJson(json, EntityChatData.class); + data.postDeserializeInitialization(); + LOGGER.info("Client received full data for entity {}", entityId); + ChatDataManager mgr = ChatDataManager.getClientInstance(); + EntityChatData existing = mgr.entityChatDataMap.get(entityId); + if (existing != null) { + existing.currentMessage = data.currentMessage; + existing.currentLineNumber = data.currentLineNumber; + existing.status = data.status; + existing.sender = data.sender; + existing.players = data.players; + existing.characterSheet = data.characterSheet; + existing.auto_generated = data.auto_generated; + existing.previousMessages = data.previousMessages; + existing.born = data.born; + existing.death = data.death; + } else { + mgr.entityChatDataMap.put(entityId, data); + } + }); + }); + // Client-side packet handler, receive entire whitelist / blacklist, and update BubbleRenderer ClientPacketHelper.registerReceiver(ServerPackets.PACKET_S2C_WHITELIST, (client, handler, buffer, responseSender) -> { // Read the whitelist data from the buffer diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 2bfe3be9..4c391f09 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -7,6 +7,7 @@ import com.owlmaddie.chat.ChatMessage; import com.owlmaddie.chat.EntityChatData; import com.owlmaddie.chat.PlayerData; +import com.owlmaddie.network.ClientPackets; import com.owlmaddie.render.EntityTextureHelper; import com.owlmaddie.render.PoseHelper; import com.owlmaddie.render.RenderPipelineHelper; @@ -17,9 +18,9 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.player.Player; -import java.text.SimpleDateFormat; import java.util.*; /** @@ -44,8 +45,34 @@ public class BookScreen extends ScreenHelper { public BookScreen() { super(Component.literal("Creature Log")); ChatDataManager mgr = ChatDataManager.getClientInstance(); - ordered = new ArrayList<>(mgr.entityChatDataMap.values()); - ordered.sort(Comparator.comparingLong(BookScreen::getLastInteraction).reversed()); + ordered = mgr.entityChatDataMap.values().stream() + .filter(data -> { + String nameText = resolveName(data); + boolean hasName = nameText != null && !nameText.isBlank(); + boolean hasMsg = data.currentMessage != null && !data.currentMessage.isBlank(); + return hasName && hasMsg; + }) + .sorted(Comparator.comparingLong(BookScreen::getLastInteraction).reversed()) + .toList(); + } + + private String resolveName(EntityChatData data) { + Entity entity = getEntity(data.entityId); + String nameText = null; + if (entity instanceof Mob) { + if (entity.getCustomName() != null) { + nameText = entity.getCustomName().getString(); + } + } else if (entity instanceof Player) { + nameText = entity.getName().getString(); + } + if (nameText == null || nameText.isBlank()) { + String sheetName = data.getCharacterProp("Name"); + if (sheetName != null && !sheetName.isBlank() && !"N/A".equals(sheetName)) { + nameText = sheetName; + } + } + return nameText; } private static long getLastInteraction(EntityChatData data) { @@ -78,6 +105,7 @@ protected void init() { b -> { index = Math.max(0, index - 2); updateButtons(); + requestDataForCurrentPages(); }, button -> Component.empty() ); @@ -90,6 +118,7 @@ protected void init() { b -> { index = Math.min(ordered.size(), index + 2); updateButtons(); + requestDataForCurrentPages(); }, button -> Component.empty() ); @@ -97,6 +126,7 @@ protected void init() { addRenderableWidget(prevButton); addRenderableWidget(nextButton); updateButtons(); + requestDataForCurrentPages(); } private void updateButtons() { @@ -106,8 +136,14 @@ private void updateButtons() { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (prevButton.mouseClicked(mouseX, mouseY, button)) return true; - if (nextButton.mouseClicked(mouseX, mouseY, button)) return true; + if (prevButton.active && prevButton.isMouseOver(mouseX, mouseY)) { + prevButton.onPress(); + return true; + } + if (nextButton.active && nextButton.isMouseOver(mouseX, mouseY)) { + nextButton.onPress(); + return true; + } return super.mouseClicked(mouseX, mouseY, button); } @@ -124,11 +160,11 @@ private void renderPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, Player player = Minecraft.getInstance().player; if (player == null) return; - PlayerData pData = data.getPlayerData(player.getName().getString()); + String playerName = player.getDisplayName().getString(); + PlayerData pData = data.getPlayerData(playerName); int friendship = pData != null ? pData.friendship : 0; // Character-sheet props (use lorem fallback) - String csName = data.getCharacterProp("Name"); // keep real name logic for the title String personality = orLoremSeeded(data.getCharacterProp("Personality"), data.entityId, "Personality", 20, 100); String speaking = orLoremSeeded(firstNonNull( data.getCharacterProp("Speaking Style / Tone"), @@ -145,10 +181,12 @@ private void renderPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, lastMsg = loremSeeded(30, 90, seedFrom(data.entityId, "LastMessage")); } - // Name to show: sheet name > entity name > "Unknown" + // Name to show: custom name > sheet name > entity name Entity entity = getEntity(data.entityId); - String displayName = (csName != null && !csName.equals("N/A")) ? csName - : (entity != null ? entity.getName().getString() : "Unknown"); + String displayName = resolveName(data); + if (displayName == null || displayName.isBlank()) { + displayName = (entity != null ? entity.getName().getString() : "Unknown"); + } // Title color by friendship int titleColor = 0xFF808080; // neutral @@ -201,6 +239,17 @@ private void renderPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, PoseHelper.pop(ctx.pose()); } + private void requestDataForCurrentPages() { + if (index < ordered.size()) { + UUID left = UUID.fromString(ordered.get(index).entityId); + ClientPackets.requestEntityData(left); + } + if (index + 1 < ordered.size()) { + UUID right = UUID.fromString(ordered.get(index + 1).entityId); + ClientPackets.requestEntityData(right); + } + } + // ---------- helpers ---------- private static String firstNonNull(String... vals) { @@ -300,11 +349,6 @@ private Entity getEntity(String id) { } } - private String getEntityName(String id) { - Entity e = getEntity(id); - return e != null ? e.getName().getString() : id; - } - @Override public boolean isPauseScreen() { return false; diff --git a/src/main/java/com/owlmaddie/network/ServerPackets.java b/src/main/java/com/owlmaddie/network/ServerPackets.java index 43d33789..0877ea6f 100644 --- a/src/main/java/com/owlmaddie/network/ServerPackets.java +++ b/src/main/java/com/owlmaddie/network/ServerPackets.java @@ -18,6 +18,7 @@ import com.owlmaddie.utils.Compression; import com.owlmaddie.utils.Randomizer; import com.owlmaddie.utils.ServerEntityFinder; +import com.google.gson.Gson; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; @@ -66,9 +67,11 @@ public class ServerPackets { public static final ResourceLocation PACKET_C2S_OPEN_CHAT = new ResourceLocation("creaturechat", "packet_c2s_open_chat"); public static final ResourceLocation PACKET_C2S_CLOSE_CHAT = new ResourceLocation("creaturechat", "packet_c2s_close_chat"); public static final ResourceLocation PACKET_C2S_SEND_CHAT = new ResourceLocation("creaturechat", "packet_c2s_send_chat"); + public static final ResourceLocation PACKET_C2S_REQUEST_ENTITY_DATA = new ResourceLocation("creaturechat", "packet_c2s_request_entity_data"); public static final ResourceLocation PACKET_S2C_ENTITY_MESSAGE = new ResourceLocation("creaturechat", "packet_s2c_entity_message"); public static final ResourceLocation PACKET_S2C_PLAYER_MESSAGE = new ResourceLocation("creaturechat", "packet_s2c_player_message"); public static final ResourceLocation PACKET_S2C_LOGIN = new ResourceLocation("creaturechat", "packet_s2c_login"); + public static final ResourceLocation PACKET_S2C_ENTITY_DATA = new ResourceLocation("creaturechat", "packet_s2c_entity_data"); public static final ResourceLocation PACKET_S2C_WHITELIST = new ResourceLocation("creaturechat", "packet_s2c_whitelist"); public static final ResourceLocation PACKET_S2C_PLAYER_STATUS = new ResourceLocation("creaturechat", "packet_s2c_player_status"); public static final ParticleType HEART_SMALL_PARTICLE = Particles.HEART_SMALL_PARTICLE; @@ -203,6 +206,34 @@ public static void register() { }); }); + PacketHelper.registerReceiver(PACKET_C2S_REQUEST_ENTITY_DATA, (server, player, buf) -> { + String entityId = buf.readUtf(); + server.execute(() -> { + EntityChatData source = ChatDataManager.getServerInstance().getOrCreateChatData(entityId); + EntityChatData sendData = new EntityChatData(entityId); + sendData.currentMessage = source.currentMessage; + sendData.currentLineNumber = source.currentLineNumber; + sendData.status = source.status; + sendData.sender = source.sender; + sendData.characterSheet = source.characterSheet; + sendData.auto_generated = source.auto_generated; + sendData.previousMessages = source.previousMessages; + sendData.born = source.born; + sendData.death = source.death; + String pName = player.getDisplayName().getString(); + sendData.players.put(pName, source.getPlayerData(pName)); + Gson gson = new Gson(); + String json = gson.toJson(sendData); + byte[] compressed = Compression.compressString(json); + if (compressed == null) return; + FriendlyByteBuf buffer = BufferHelper.create(); + buffer.writeUtf(entityId); + buffer.writeByteArray(compressed); + PacketHelper.send(player, PACKET_S2C_ENTITY_DATA, buffer); + LOGGER.info("Server sending full data for entity {} to player {}", entityId, pName); + }); + }); + // Send lite chat data JSON to new player (to populate client data) // Data is sent in chunks, to prevent exceeding the 32767 limit per String. ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { From d2eb891d650dc7f201114b7b9460f6ce313d42bb Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 19 Aug 2025 21:56:26 -0500 Subject: [PATCH 07/26] Fixing click detection on 1.20 to 1.21.7. --- .../java/com/owlmaddie/ui/BookScreen.java | 134 ++++++++++++------ .../java/com/owlmaddie/ui/ButtonHelper.java | 14 ++ 2 files changed, 105 insertions(+), 43 deletions(-) diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 4c391f09..0144b209 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -13,15 +13,17 @@ import com.owlmaddie.render.RenderPipelineHelper; import com.owlmaddie.utils.ClientEntityFinder; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.EditBox; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.player.Player; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; +import java.util.stream.Collectors; /** * Screen that displays a two-page log of recently chatted entities. @@ -30,11 +32,20 @@ public class BookScreen extends ScreenHelper { private static final int BOOK_WIDTH = 300; private static final int BOOK_HEIGHT = 200; + private static final Logger LOGGER = LoggerFactory.getLogger("creaturechat"); + private int index; - private final List ordered; - private Button prevButton; - private Button nextButton; + private final List all; + private List ordered; private EditBox dummyField; + private EditBox searchField; + private boolean searchVisible; + private boolean prevActive; + private boolean nextActive; + private static final int PREV_X = 29, PREV_Y = 156, PREV_W = 14, PREV_H = 12; + private static final int NEXT_X = 257, NEXT_Y = 156, NEXT_W = 14, NEXT_H = 12; + private static final int SEARCH_BTN_X = 10, SEARCH_BTN_Y = 9, SEARCH_BTN_W = 31, SEARCH_BTN_H = 21; + private static final int CLOSE_X = 259, CLOSE_Y = 9, CLOSE_W = 30, CLOSE_H = 22; private static final int PAGE_CONTENT_W = 120; // width of text block per page private static final int PAGE_CONTENT_H = 120; // height of text block (for scissor) private static final int LABEL_COLOR = 0xFF6B4A3B; // warm brown, matches book UI @@ -45,7 +56,7 @@ public class BookScreen extends ScreenHelper { public BookScreen() { super(Component.literal("Creature Log")); ChatDataManager mgr = ChatDataManager.getClientInstance(); - ordered = mgr.entityChatDataMap.values().stream() + all = mgr.entityChatDataMap.values().stream() .filter(data -> { String nameText = resolveName(data); boolean hasName = nameText != null && !nameText.isBlank(); @@ -53,7 +64,8 @@ public BookScreen() { return hasName && hasMsg; }) .sorted(Comparator.comparingLong(BookScreen::getLastInteraction).reversed()) - .toList(); + .collect(Collectors.toList()); + ordered = new ArrayList<>(all); } private String resolveName(EntityChatData data) { @@ -97,56 +109,92 @@ protected void init() { dummyField = new EditBox(font, bgX, bgY, 0, 0, Component.empty()); - prevButton = ButtonHelper.createImageButton( - bgX + 29, bgY + 155, - 14, 14, - textures.GetUI("arrow-left"), - textures.GetUI("arrow-left"), - b -> { - index = Math.max(0, index - 2); - updateButtons(); - requestDataForCurrentPages(); - }, - button -> Component.empty() - ); - - nextButton = ButtonHelper.createImageButton( - bgX + 257, bgY + 155, - 14, 14, - textures.GetUI("arrow-right"), - textures.GetUI("arrow-right"), - b -> { - index = Math.min(ordered.size(), index + 2); - updateButtons(); - requestDataForCurrentPages(); - }, - button -> Component.empty() - ); - - addRenderableWidget(prevButton); - addRenderableWidget(nextButton); + searchField = new EditBox(font, bgX + 46, bgY + 9, 208, 21, Component.empty()); + searchField.visible = false; + searchField.active = false; + searchField.setResponder(text -> { + if (searchVisible) onSearchChanged(text); + }); + + addRenderableWidget(searchField); updateButtons(); requestDataForCurrentPages(); } private void updateButtons() { - prevButton.active = index > 0; - nextButton.active = index + 2 < ordered.size(); + prevActive = index > 0; + nextActive = index + 2 < ordered.size(); + } + + private boolean inside(double mx, double my, int x, int y, int w, int h) { + return mx >= x && mx <= x + w && my >= y && my <= y + h; } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (prevButton.active && prevButton.isMouseOver(mouseX, mouseY)) { - prevButton.onPress(); - return true; - } - if (nextButton.active && nextButton.isMouseOver(mouseX, mouseY)) { - nextButton.onPress(); - return true; + if (button == 0) { + if (prevActive && inside(mouseX, mouseY, bgX + PREV_X, bgY + PREV_Y, PREV_W, PREV_H)) { + index = Math.max(0, index - 2); + updateButtons(); + requestDataForCurrentPages(); + LOGGER.info("BookScreen: previous page index={}", index); + return true; + } + if (nextActive && inside(mouseX, mouseY, bgX + NEXT_X, bgY + NEXT_Y, NEXT_W, NEXT_H)) { + index = Math.min(ordered.size(), index + 2); + updateButtons(); + requestDataForCurrentPages(); + LOGGER.info("BookScreen: next page index={}", index); + return true; + } + if (inside(mouseX, mouseY, bgX + SEARCH_BTN_X, bgY + SEARCH_BTN_Y, SEARCH_BTN_W, SEARCH_BTN_H)) { + toggleSearch(); + return true; + } + if (inside(mouseX, mouseY, bgX + CLOSE_X, bgY + CLOSE_Y, CLOSE_W, CLOSE_H)) { + LOGGER.info("BookScreen: close button pressed"); + onClose(); + return true; + } } return super.mouseClicked(mouseX, mouseY, button); } + private void toggleSearch() { + searchVisible = !searchVisible; + searchField.visible = searchVisible; + searchField.active = searchVisible; + searchField.setFocused(searchVisible); + if (searchVisible) { + setFocused(searchField); + setInitialFocus(searchField); + searchField.setCursorPosition(searchField.getValue().length()); + LOGGER.info("BookScreen: search opened and focused"); + } else { + setFocused(null); + searchField.setValue(""); + ordered = new ArrayList<>(all); + index = 0; + updateButtons(); + requestDataForCurrentPages(); + LOGGER.info("BookScreen: search closed"); + } + } + + private void onSearchChanged(String text) { + String q = text.toLowerCase(Locale.ROOT); + ordered = all.stream() + .filter(d -> { + String n = resolveName(d); + return n != null && n.toLowerCase(Locale.ROOT).contains(q); + }) + .collect(Collectors.toList()); + index = 0; + updateButtons(); + requestDataForCurrentPages(); + LOGGER.info("BookScreen: search '{}' results={}", q, ordered.size()); + } + @Override protected void renderContent(net.minecraft.client.gui.GuiGraphics ctx, int mouseX, int mouseY, float delta) { renderPage(ctx, bgX + 32, bgY + 51, index); diff --git a/src/client/java/com/owlmaddie/ui/ButtonHelper.java b/src/client/java/com/owlmaddie/ui/ButtonHelper.java index f2c0e434..d1b36a2d 100644 --- a/src/client/java/com/owlmaddie/ui/ButtonHelper.java +++ b/src/client/java/com/owlmaddie/ui/ButtonHelper.java @@ -7,8 +7,12 @@ import net.minecraft.client.gui.components.Button; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ButtonHelper { + private static final Logger LOGGER = LoggerFactory.getLogger("creaturechat"); + /** * Create an image‐only button that swaps between normal/hover textures. * Version‐specific subclasses just override the rendering hook. @@ -21,9 +25,19 @@ public static Button createImageButton( Button.OnPress onPress, Button.CreateNarration narrate ) { + final boolean missing = normalTex == null || hoverTex == null; + if (missing) { + LOGGER.warn("ButtonHelper: missing texture for button at ({}, {})", x, y); + } + return new Button(x, y, width, height, Component.empty(), onPress, narrate) { @Override public void renderWidget(GuiGraphics ctx, int mouseX, int mouseY, float delta) { + if (missing) { + // fall back to default rendering when textures are unavailable + super.renderWidget(ctx, mouseX, mouseY, delta); + return; + } ResourceLocation tex = isHovered() ? hoverTex : normalTex; ctx.blit(tex, getX(), getY(), 0, 0, width, height, width, height); } From 04000e0e6395beae1cc580be4ae6f4af7ae10631 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 19 Aug 2025 22:11:34 -0500 Subject: [PATCH 08/26] Moving book textures to book sub-folder, and removing next/prev from background --- .../creaturechat/textures/ui/{ => book}/book.png | Bin .../assets/creaturechat/textures/ui/book/next.png | Bin 0 -> 268 bytes .../creaturechat/textures/ui/book/previous.png | Bin 0 -> 263 bytes 3 files changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/assets/creaturechat/textures/ui/{ => book}/book.png (100%) create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/next.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/previous.png diff --git a/src/main/resources/assets/creaturechat/textures/ui/book.png b/src/main/resources/assets/creaturechat/textures/ui/book/book.png similarity index 100% rename from src/main/resources/assets/creaturechat/textures/ui/book.png rename to src/main/resources/assets/creaturechat/textures/ui/book/book.png diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/next.png b/src/main/resources/assets/creaturechat/textures/ui/book/next.png new file mode 100644 index 0000000000000000000000000000000000000000..5bf86cac0c6bd5a2128486216feb25ff0211dc2d GIT binary patch literal 268 zcmeAS@N?(olHy`uVBq!ia0vp^AT|dF8<0HkD{mW+Vo7)Ob!1@J*w6hZkrl{iEDmya zV!U}$ryj^rNcITwWnidMV_;}#VPNV)rX!1?^)i1!4`8xAq>cX9I%T;8Y%0gMzZ}t}UJ7{`XUNRQ%HOyDV8) z$#%1C!@r$9IzR4IeEbsIc1@XG;$szCq#!d8Ftqt9*(*(WBnq^R!PC{xWt~$(69A1s BPh0>1 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/previous.png b/src/main/resources/assets/creaturechat/textures/ui/book/previous.png new file mode 100644 index 0000000000000000000000000000000000000000..fbe536ebafe561552e83ab416fbe4ec12ef117d8 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^AT|dF8<0HkD{mW+Vo7)Ob!1@J*w6hZkrl{iEDmya zV!U}$ryj^rNcITwWnidMV_;}#VPNUkhrmJRr|5xYA z{4-8q^f}QrX;qWN7vHIp1zIhdhu9@9iq5@TE9iXj=;d<3u*Xc|{`XsFaBP;knVa~x zSaRd8jbBeNy3O#5X<$8;VO!L&aaQ9)d%ac$+1ZK?%5qILK#LeWUHx3vIVCg!0Qp%? AQvd(} literal 0 HcmV?d00001 From 3eea80526d11644da0f8bfa11ba9a22d3a84c6fc Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 19 Aug 2025 22:15:24 -0500 Subject: [PATCH 09/26] Placeholder button hover assets for book --- .../creaturechat/textures/ui/book/next-hover.png | Bin 0 -> 275 bytes .../textures/ui/book/previous-hover.png | Bin 0 -> 270 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/next-hover.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/previous-hover.png diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/next-hover.png b/src/main/resources/assets/creaturechat/textures/ui/book/next-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..60a5a3ab7e867cb14b2aceb302bf4cdbbd785f0e GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^AT|dF8<0HkD{mW+Vo7)Ob!1@J*w6hZkrl{iEDmya zV!U}$ryj^rNcITwWnidMV_;}#VPNZ6|OsjHb{;k*Y z`FGxdBjmQlpa1{=+b141Xg$#t36e23Ffe#ATPyjh$Sw2sL{a9$^=dN?9KBpFsW)$5 z_hm7+m@wtr0@v)PPh_1Rtv_$y=?hg)7?hcTK-jQL0tgsBI~x3Oe9U$bXeooItDnm{ Hr-UW|`UkhrmJRr|5xYA z{4;*Q3}S*neQ2UlSgVhLt8V-E$EF>Eu@_%065(+b_rKpdqk})jrdB*j=}cB_aO#ef zi2pHeYw~hziy8!%35zE*8b Date: Tue, 19 Aug 2025 22:35:05 -0500 Subject: [PATCH 10/26] Dynamic Next/Previous buttons with hover --- CHANGELOG.md | 1 + .../java/com/owlmaddie/ui/BookScreen.java | 64 +++++++++++++------ 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0dbc02d..f711488f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to **CreatureChat™** are documented in this file. The form - Load client entity chat data and display friends and enemies by most recent - Using Fabric datagen to create book item - New packet types for sending / receiving all entity data (by UUID - per page) + - Dynamic Next/Previous buttons with hover ## Unreleased diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 0144b209..87b04f32 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -14,6 +14,7 @@ import com.owlmaddie.utils.ClientEntityFinder; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.Button; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; @@ -40,8 +41,8 @@ public class BookScreen extends ScreenHelper { private EditBox dummyField; private EditBox searchField; private boolean searchVisible; - private boolean prevActive; - private boolean nextActive; + private Button prevButton; + private Button nextButton; private static final int PREV_X = 29, PREV_Y = 156, PREV_W = 14, PREV_H = 12; private static final int NEXT_X = 257, NEXT_Y = 156, NEXT_W = 14, NEXT_H = 12; private static final int SEARCH_BTN_X = 10, SEARCH_BTN_Y = 9, SEARCH_BTN_W = 31, SEARCH_BTN_H = 21; @@ -117,13 +118,52 @@ protected void init() { }); addRenderableWidget(searchField); + + prevButton = ButtonHelper.createImageButton( + bgX + PREV_X, bgY + PREV_Y, + PREV_W, PREV_H, + textures.GetUI("book/previous"), + textures.GetUI("book/previous-hover"), + w -> { + index = Math.max(0, index - 2); + updateButtons(); + requestDataForCurrentPages(); + LOGGER.info("BookScreen: previous page index={}", index); + }, + w -> Component.empty() + ); + addRenderableWidget(prevButton); + + nextButton = ButtonHelper.createImageButton( + bgX + NEXT_X, bgY + NEXT_Y, + NEXT_W, NEXT_H, + textures.GetUI("book/next"), + textures.GetUI("book/next-hover"), + w -> { + index = Math.min(ordered.size(), index + 2); + updateButtons(); + requestDataForCurrentPages(); + LOGGER.info("BookScreen: next page index={}", index); + }, + w -> Component.empty() + ); + addRenderableWidget(nextButton); + updateButtons(); requestDataForCurrentPages(); } private void updateButtons() { - prevActive = index > 0; - nextActive = index + 2 < ordered.size(); + boolean prevActive = index > 0; + boolean nextActive = index + 2 < ordered.size(); + if (prevButton != null) { + prevButton.active = prevActive; + prevButton.visible = prevActive; + } + if (nextButton != null) { + nextButton.active = nextActive; + nextButton.visible = nextActive; + } } private boolean inside(double mx, double my, int x, int y, int w, int h) { @@ -133,20 +173,6 @@ private boolean inside(double mx, double my, int x, int y, int w, int h) { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (button == 0) { - if (prevActive && inside(mouseX, mouseY, bgX + PREV_X, bgY + PREV_Y, PREV_W, PREV_H)) { - index = Math.max(0, index - 2); - updateButtons(); - requestDataForCurrentPages(); - LOGGER.info("BookScreen: previous page index={}", index); - return true; - } - if (nextActive && inside(mouseX, mouseY, bgX + NEXT_X, bgY + NEXT_Y, NEXT_W, NEXT_H)) { - index = Math.min(ordered.size(), index + 2); - updateButtons(); - requestDataForCurrentPages(); - LOGGER.info("BookScreen: next page index={}", index); - return true; - } if (inside(mouseX, mouseY, bgX + SEARCH_BTN_X, bgY + SEARCH_BTN_Y, SEARCH_BTN_W, SEARCH_BTN_H)) { toggleSearch(); return true; @@ -414,7 +440,7 @@ protected Component getLabelText() { @Override protected String getBackgroundTextureId() { - return "book"; + return "book/book"; } // Deterministic seed from entityId + field key (FNV-1a 64-bit) From 5098111a411b2355a936d1ff63cf6afb9bbdb80f Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 20 Aug 2025 02:01:44 -0500 Subject: [PATCH 11/26] Fixing datagen to support ALL versions of minecraft --- build.gradle | 1 + .../datagen/CreatureChatModelProvider.java | 33 +++++++++++++++ .../datagen/CreatureChatDataGenerator.java | 22 +++++++++- .../datagen/CreatureChatModelProvider.java | 41 ------------------- .../datagen/CreatureChatModelProvider.java | 33 +++++++++++++++ 5 files changed, 88 insertions(+), 42 deletions(-) create mode 100644 src/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java delete mode 100644 src/main/java/com/owlmaddie/datagen/CreatureChatModelProvider.java create mode 100644 src/vs/v1_21_0/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java diff --git a/build.gradle b/build.gradle index d43399e5..2aff0f8d 100644 --- a/build.gradle +++ b/build.gradle @@ -134,6 +134,7 @@ loom { runs { datagen { server() + client() vmArg "-Dfabric-api.datagen" vmArg "-Dfabric-api.datagen.output-dir=${file('src/main/generated').absolutePath}" vmArg "-Dfabric-api.datagen.modid=creaturechat" diff --git a/src/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java b/src/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java new file mode 100644 index 00000000..71743ddc --- /dev/null +++ b/src/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.datagen; + +import com.owlmaddie.items.ModItems; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider; +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.minecraft.data.models.BlockModelGenerators; +import net.minecraft.data.models.ItemModelGenerators; +import net.minecraft.data.models.model.ModelTemplates; + +/** + * Generates item model JSON files using Minecraft's model data generators + * instead of manually constructing JSON. + */ +public class CreatureChatModelProvider extends FabricModelProvider { + public CreatureChatModelProvider(FabricDataOutput output) { + super(output); + } + + @Override + public void generateBlockStateModels(BlockModelGenerators blockStateModelGenerators) { + // No block models are generated for this mod. + } + + @Override + public void generateItemModels(ItemModelGenerators itemModelGenerators) { + // Generates the "minecraft:item/generated" style model for the book item. + itemModelGenerators.generateFlatItem(ModItems.BOOK, ModelTemplates.FLAT_ITEM); + } +} + diff --git a/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java b/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java index 2bb21d4a..2bf03357 100644 --- a/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java +++ b/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java @@ -5,13 +5,33 @@ import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.minecraft.data.DataProvider; +/** + * Registers all data generation providers for the mod. + */ public class CreatureChatDataGenerator implements DataGeneratorEntrypoint { @Override public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) { FabricDataGenerator.Pack pack = fabricDataGenerator.createPack(); pack.addProvider(CreatureChatLootTableProvider::new); - pack.addProvider(CreatureChatModelProvider::new); pack.addProvider(CreatureChatEnglishLangProvider::new); + + // Load the client-side model provider reflectively so the common + // sources don't depend on client-only classes at compile time. + pack.addProvider((FabricDataOutput out) -> createModelProvider(out)); + } + + private DataProvider createModelProvider(FabricDataOutput output) { + try { + Class clazz = Class.forName("com.owlmaddie.datagen.CreatureChatModelProvider"); + return (DataProvider) clazz + .getConstructor(FabricDataOutput.class) + .newInstance(output); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to load model provider", e); + } } } + diff --git a/src/main/java/com/owlmaddie/datagen/CreatureChatModelProvider.java b/src/main/java/com/owlmaddie/datagen/CreatureChatModelProvider.java deleted file mode 100644 index 66d5746c..00000000 --- a/src/main/java/com/owlmaddie/datagen/CreatureChatModelProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2025 owlmaddie LLC -// SPDX-License-Identifier: GPL-3.0-or-later -// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited -package com.owlmaddie.datagen; - -import com.google.gson.JsonObject; -import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; -import net.minecraft.data.CachedOutput; -import net.minecraft.data.DataProvider; - -import java.nio.file.Path; -import java.util.concurrent.CompletableFuture; - -/** - * Generates item model JSON files. - */ -public class CreatureChatModelProvider implements DataProvider { - private final FabricDataOutput output; - - public CreatureChatModelProvider(FabricDataOutput output) { - this.output = output; - } - - @Override - public CompletableFuture run(CachedOutput cachedOutput) { - JsonObject root = new JsonObject(); - root.addProperty("parent", "minecraft:item/generated"); - JsonObject textures = new JsonObject(); - textures.addProperty("layer0", "creaturechat:item/book"); - root.add("textures", textures); - - Path path = this.output.getOutputFolder().resolve("assets/creaturechat/models/item/book.json"); - return DataProvider.saveStable(cachedOutput, root, path); - } - - @Override - public String getName() { - return "CreatureChatModelProvider"; - } -} - diff --git a/src/vs/v1_21_0/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java b/src/vs/v1_21_0/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java new file mode 100644 index 00000000..6a9bc2b3 --- /dev/null +++ b/src/vs/v1_21_0/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.datagen; + +import com.owlmaddie.items.ModItems; +import net.fabricmc.fabric.api.client.datagen.v1.provider.FabricModelProvider; +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.minecraft.client.data.models.BlockModelGenerators; +import net.minecraft.client.data.models.ItemModelGenerators; +import net.minecraft.client.data.models.model.ModelTemplates; + +/** + * Generates item model JSON files using Minecraft's model data generators + * instead of manually constructing JSON. + */ +public class CreatureChatModelProvider extends FabricModelProvider { + public CreatureChatModelProvider(FabricDataOutput output) { + super(output); + } + + @Override + public void generateBlockStateModels(BlockModelGenerators blockStateModelGenerators) { + // No block models are generated for this mod. + } + + @Override + public void generateItemModels(ItemModelGenerators itemModelGenerators) { + // Generates the "minecraft:item/generated" style model for the book item. + itemModelGenerators.generateFlatItem(ModItems.BOOK, ModelTemplates.FLAT_ITEM); + } +} + From 6c3656f08538ca17eda95362afe0ad2c38bdae94 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 20 Aug 2025 15:15:08 -0500 Subject: [PATCH 12/26] Summary pages are displayed first, then Detail pages with more info --- CHANGELOG.md | 3 +- .../java/com/owlmaddie/ui/BookScreen.java | 442 +++++++++++++----- .../creaturechat/textures/ui/book/book.png | Bin 7591 -> 2646 bytes 3 files changed, 334 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f711488f..869e6d7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ All notable changes to **CreatureChat™** are documented in this file. The form - Load client entity chat data and display friends and enemies by most recent - Using Fabric datagen to create book item - New packet types for sending / receiving all entity data (by UUID - per page) - - Dynamic Next/Previous buttons with hover + - Dynamic Next/Previous buttons with hover (hidden when no page to turn) + - Summary pages are displayed first, then Detail pages with more info ## Unreleased diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 87b04f32..a03b2a41 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -35,7 +35,22 @@ public class BookScreen extends ScreenHelper { private static final Logger LOGGER = LoggerFactory.getLogger("creaturechat"); - private int index; + private enum Mode { SUMMARY, DETAIL } + private Mode mode = Mode.SUMMARY; + + // summary state + private int summaryIndex; + private int hoveredSummary = -1; + + // detail state + private EntityChatData detailEntity; + private int detailPage; // left page index + private int detailTotalPages; // total single pages for detailEntity + private List detailSections; // character sheet sections + private List> sectionPages; + private List detailMessages; + private List> messagePages; + private final List all; private List ordered; private EditBox dummyField; @@ -43,17 +58,28 @@ public class BookScreen extends ScreenHelper { private boolean searchVisible; private Button prevButton; private Button nextButton; - private static final int PREV_X = 29, PREV_Y = 156, PREV_W = 14, PREV_H = 12; - private static final int NEXT_X = 257, NEXT_Y = 156, NEXT_W = 14, NEXT_H = 12; + private static final int PREV_X = 28, PREV_Y = 170, PREV_W = 14, PREV_H = 12; + private static final int NEXT_X = 267, NEXT_Y = 170, NEXT_W = 14, NEXT_H = 12; private static final int SEARCH_BTN_X = 10, SEARCH_BTN_Y = 9, SEARCH_BTN_W = 31, SEARCH_BTN_H = 21; private static final int CLOSE_X = 259, CLOSE_Y = 9, CLOSE_W = 30, CLOSE_H = 22; - private static final int PAGE_CONTENT_W = 120; // width of text block per page - private static final int PAGE_CONTENT_H = 120; // height of text block (for scissor) + private static final int PAGE_CONTENT_W = 116; // width of text block per page + private static final int PAGE_CONTENT_H = 124; // height of text block (for scissor) + private static final int PAGE1_X = 28, PAGE1_Y = 46; // left page top-left + private static final int PAGE2_X = 157, PAGE2_Y = 46; // right page top-left private static final int LABEL_COLOR = 0xFF6B4A3B; // warm brown, matches book UI private static final int BODY_COLOR = 0xFF2A2A2A; // dark text private static final int LIGHT_GRAY = 0xFFB0B0B0; private static final Random RNG = new Random(); + private static final int SUMMARY_ROWS_PER_PAGE = 4; // per single page + private static final int SUMMARY_ROW_H = 30; + + private static class Pair { + final String label; + final String value; + Pair(String l, String v) { this.label = l; this.value = v; } + } + public BookScreen() { super(Component.literal("Creature Log")); ChatDataManager mgr = ChatDataManager.getClientInstance(); @@ -67,6 +93,7 @@ public BookScreen() { .sorted(Comparator.comparingLong(BookScreen::getLastInteraction).reversed()) .collect(Collectors.toList()); ordered = new ArrayList<>(all); + summaryIndex = 0; } private String resolveName(EntityChatData data) { @@ -97,6 +124,24 @@ private static long getLastInteraction(EntityChatData data) { return 0L; } + private String friendlyTime(long millis) { + long minutes = millis / 60000L; + if (minutes < 60) return minutes + "min"; + long hours = minutes / 60; + long mins = minutes % 60; + if (hours < 24) { + String res = hours + "Hr"; + if (hours < 4 && mins > 0) res += " " + mins + "min"; + return res; + } + long days = hours / 24; + if (days < 7) return days + (days == 1 ? " day" : " days"); + long weeks = days / 7; + if (weeks < 4) return weeks + (weeks == 1 ? " week" : " weeks"); + long months = days / 30; + return months + (months == 1 ? " month" : " months"); + } + @Override protected void init() { super.init(); @@ -125,10 +170,19 @@ protected void init() { textures.GetUI("book/previous"), textures.GetUI("book/previous-hover"), w -> { - index = Math.max(0, index - 2); + if (mode == Mode.SUMMARY) { + summaryIndex = Math.max(0, summaryIndex - SUMMARY_ROWS_PER_PAGE * 2); + } else { + if (detailPage == 0) { + mode = Mode.SUMMARY; + detailEntity = null; + } else { + detailPage = Math.max(0, detailPage - 2); + } + } updateButtons(); requestDataForCurrentPages(); - LOGGER.info("BookScreen: previous page index={}", index); + LOGGER.info("BookScreen: previous clicked mode={} summaryIndex={} detailPage={}", mode, summaryIndex, detailPage); }, w -> Component.empty() ); @@ -140,10 +194,17 @@ protected void init() { textures.GetUI("book/next"), textures.GetUI("book/next-hover"), w -> { - index = Math.min(ordered.size(), index + 2); + if (mode == Mode.SUMMARY) { + int spread = SUMMARY_ROWS_PER_PAGE * 2; + int maxIndex = Math.max(0, ((ordered.size() - 1) / spread) * spread); + summaryIndex = Math.min(summaryIndex + spread, maxIndex); + } else { + int maxLeft = ((detailTotalPages - 1) / 2) * 2; + detailPage = Math.min(detailPage + 2, maxLeft); + } updateButtons(); requestDataForCurrentPages(); - LOGGER.info("BookScreen: next page index={}", index); + LOGGER.info("BookScreen: next clicked mode={} summaryIndex={} detailPage={}", mode, summaryIndex, detailPage); }, w -> Component.empty() ); @@ -154,8 +215,15 @@ protected void init() { } private void updateButtons() { - boolean prevActive = index > 0; - boolean nextActive = index + 2 < ordered.size(); + boolean prevActive; + boolean nextActive; + if (mode == Mode.SUMMARY) { + prevActive = summaryIndex > 0; + nextActive = summaryIndex + SUMMARY_ROWS_PER_PAGE * 2 < ordered.size(); + } else { + prevActive = true; // always allow returning to summary + nextActive = detailPage + 2 < detailTotalPages; + } if (prevButton != null) { prevButton.active = prevActive; prevButton.visible = prevActive; @@ -182,6 +250,10 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { onClose(); return true; } + if (mode == Mode.SUMMARY && hoveredSummary >= 0) { + openDetail(hoveredSummary); + return true; + } } return super.mouseClicked(mouseX, mouseY, button); } @@ -200,7 +272,8 @@ private void toggleSearch() { setFocused(null); searchField.setValue(""); ordered = new ArrayList<>(all); - index = 0; + summaryIndex = 0; + mode = Mode.SUMMARY; updateButtons(); requestDataForCurrentPages(); LOGGER.info("BookScreen: search closed"); @@ -215,112 +288,246 @@ private void onSearchChanged(String text) { return n != null && n.toLowerCase(Locale.ROOT).contains(q); }) .collect(Collectors.toList()); - index = 0; + summaryIndex = 0; + mode = Mode.SUMMARY; updateButtons(); requestDataForCurrentPages(); LOGGER.info("BookScreen: search '{}' results={}", q, ordered.size()); } - @Override - protected void renderContent(net.minecraft.client.gui.GuiGraphics ctx, int mouseX, int mouseY, float delta) { - renderPage(ctx, bgX + 32, bgY + 51, index); - renderPage(ctx, bgX + 162, bgY + 51, index + 1); - } - - private void renderPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, int dataIndex) { - if (dataIndex >= ordered.size()) return; + private void openDetail(int idx) { + if (idx < 0 || idx >= ordered.size()) return; + detailEntity = ordered.get(idx); + detailPage = 0; + detailSections = new ArrayList<>(); + + detailSections.add(new Pair("Personality", orLoremSeeded(detailEntity.getCharacterProp("Personality"), detailEntity.entityId, "Personality", 20, 100))); + detailSections.add(new Pair("Speaking Style / Tone", orLoremSeeded(firstNonNull( + detailEntity.getCharacterProp("Speaking Style / Tone"), + detailEntity.getCharacterProp("Speaking Style"), + detailEntity.getCharacterProp("Tone") + ), detailEntity.entityId, "SpeakingStyle", 20, 100))); + detailSections.add(new Pair("Skills", orLoremSeeded(detailEntity.getCharacterProp("Skills"), detailEntity.entityId, "Skills", 20, 100))); + detailSections.add(new Pair("Likes", orLoremSeeded(detailEntity.getCharacterProp("Likes"), detailEntity.entityId, "Likes", 20, 100))); + detailSections.add(new Pair("Dislikes", orLoremSeeded(detailEntity.getCharacterProp("Dislikes"), detailEntity.entityId, "Dislikes", 20, 100))); + detailSections.add(new Pair("Background", orLoremSeeded(detailEntity.getCharacterProp("Background"), detailEntity.entityId, "Background", 20, 100))); + + detailMessages = new ArrayList<>(); + if (detailEntity.previousMessages != null) { + Player player = Minecraft.getInstance().player; + String pName = player != null ? player.getDisplayName().getString() : ""; + List filtered = detailEntity.previousMessages.stream() + .filter(m -> !(m.sender == ChatDataManager.ChatSender.USER && !pName.equals(m.name))) + .collect(Collectors.toList()); + for (int i = Math.max(0, filtered.size() - 4); i < filtered.size(); i++) { + detailMessages.add(filtered.get(i)); + } + Collections.reverse(detailMessages); + } - EntityChatData data = ordered.get(dataIndex); - Player player = Minecraft.getInstance().player; - if (player == null) return; + paginateSections(); + paginateMessages(); - String playerName = player.getDisplayName().getString(); - PlayerData pData = data.getPlayerData(playerName); - int friendship = pData != null ? pData.friendship : 0; + detailTotalPages = sectionPages.size() + messagePages.size(); + mode = Mode.DETAIL; + updateButtons(); + requestDataForCurrentPages(); + } - // Character-sheet props (use lorem fallback) - String personality = orLoremSeeded(data.getCharacterProp("Personality"), data.entityId, "Personality", 20, 100); - String speaking = orLoremSeeded(firstNonNull( - data.getCharacterProp("Speaking Style / Tone"), - data.getCharacterProp("Speaking Style"), - data.getCharacterProp("Tone") - ), data.entityId, "SpeakingStyle", 20, 100); - String skills = orLoremSeeded(data.getCharacterProp("Skills"), data.entityId, "Skills", 20, 100); - String likes = orLoremSeeded(data.getCharacterProp("Likes"), data.entityId, "Likes", 20, 100); - String dislikes = orLoremSeeded(data.getCharacterProp("Dislikes"), data.entityId, "Dislikes", 20, 100); - String background = orLoremSeeded(data.getCharacterProp("Background"), data.entityId, "Background", 20, 100); - - String lastMsg = safe(data.currentMessage); - if (lastMsg.isEmpty()) { - lastMsg = loremSeeded(30, 90, seedFrom(data.entityId, "LastMessage")); + private void paginateSections() { + sectionPages = new ArrayList<>(); + List page = new ArrayList<>(); + int used = 0; + int headerHeight = Math.round(this.font.lineHeight * 1.12f) + 6 + 35; + for (Pair p : detailSections) { + int h = this.font.lineHeight; + h += measureWrappedHeight(p.value, PAGE_CONTENT_W, 0.92f, 2); + h += 4; + int available = sectionPages.isEmpty() ? PAGE_CONTENT_H - headerHeight : PAGE_CONTENT_H; + if (used + h > available && !page.isEmpty()) { + sectionPages.add(page); + page = new ArrayList<>(); + used = 0; + available = PAGE_CONTENT_H; + } + page.add(p); + used += h; + } + if (!page.isEmpty()) { + sectionPages.add(page); } + if (sectionPages.isEmpty()) { + sectionPages.add(Collections.emptyList()); + } + } - // Name to show: custom name > sheet name > entity name - Entity entity = getEntity(data.entityId); - String displayName = resolveName(data); - if (displayName == null || displayName.isBlank()) { - displayName = (entity != null ? entity.getName().getString() : "Unknown"); + private void paginateMessages() { + messagePages = new ArrayList<>(); + List page = new ArrayList<>(); + int used = 0; + for (ChatMessage m : detailMessages) { + int h = font.lineHeight + 1; + h += measureWrappedHeight(safe(m.message), PAGE_CONTENT_W - 4, 0.9f, 3) + 4; + if (used + h > PAGE_CONTENT_H && !page.isEmpty()) { + messagePages.add(page); + page = new ArrayList<>(); + used = 0; + } + page.add(m); + used += h; + } + if (!page.isEmpty()) { + messagePages.add(page); } + if (messagePages.isEmpty()) { + messagePages.add(Collections.emptyList()); + } + } - // Title color by friendship - int titleColor = 0xFF808080; // neutral - if (friendship < 0) titleColor = 0xFFFF3A3A; // red - else if (friendship > 0) titleColor = 0xFF2ECC40; // green + @Override + protected void renderContent(net.minecraft.client.gui.GuiGraphics ctx, int mouseX, int mouseY, float delta) { + if (mode == Mode.SUMMARY) { + hoveredSummary = -1; + renderSummaryPage(ctx, bgX + PAGE1_X, bgY + PAGE1_Y, summaryIndex, mouseX, mouseY); + renderSummaryPage(ctx, bgX + PAGE2_X, bgY + PAGE2_Y, summaryIndex + SUMMARY_ROWS_PER_PAGE, mouseX, mouseY); + } else if (detailEntity != null) { + renderDetailPage(ctx, bgX + PAGE1_X, bgY + PAGE1_Y, detailPage); + renderDetailPage(ctx, bgX + PAGE2_X, bgY + PAGE2_Y, detailPage + 1); + } + } - // Title (slightly larger, soft shadow) - PoseHelper.push(ctx.pose()); - PoseHelper.translate(ctx.pose(), (float)x, (float)y); - PoseHelper.scale(ctx.pose(), 1.12f, 1.12f); - ctx.drawString(this.font, displayName, 1, 1, 0x66000000, false); // shadow - ctx.drawString(this.font, displayName, 0, 0, titleColor, false); - PoseHelper.pop(ctx.pose()); + private void renderSummaryPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, int startIndex, int mouseX, int mouseY) { + ctx.enableScissor(x, y, x + PAGE_CONTENT_W, y + PAGE_CONTENT_H); + for (int i = 0; i < SUMMARY_ROWS_PER_PAGE; i++) { + int idx = startIndex + i; + if (idx >= ordered.size()) break; + int rowY = y + i * SUMMARY_ROW_H; + boolean hover = mouseX >= x && mouseX <= x + PAGE_CONTENT_W && mouseY >= rowY && mouseY <= rowY + SUMMARY_ROW_H; + if (hover) { + ctx.fill(x, rowY, x + PAGE_CONTENT_W, rowY + SUMMARY_ROW_H, 0x406B4A3B); + hoveredSummary = idx; + } + EntityChatData data = ordered.get(idx); + Entity entity = getEntity(data.entityId); + if (entity != null) { + PoseHelper.push(ctx.pose()); + PoseHelper.scale(ctx.pose(), 0.6f, 0.6f); + drawEntityIcon(ctx, entity, (int) (x / 0.6f), (int) (rowY / 0.6f)); + PoseHelper.pop(ctx.pose()); + } - int offsetY = y + Math.round(this.font.lineHeight * 1.12f) + 6; + String name = resolveName(data); + if (name == null) name = "Unknown"; + name = this.font.plainSubstrByWidth(name, PAGE_CONTENT_W - 22); + ctx.drawString(this.font, name, x + 22, rowY + 2, BODY_COLOR, false); + + Player player = Minecraft.getInstance().player; + PlayerData pData = player != null ? data.getPlayerData(player.getDisplayName().getString()) : null; + int friendship = pData != null ? pData.friendship : 0; + ResourceLocation frTex = textures.GetUI("friendship" + friendship); + int iconW = 0; + if (frTex != null) { + PoseHelper.push(ctx.pose()); + PoseHelper.translate(ctx.pose(), x + 22, rowY + 12); + PoseHelper.scale(ctx.pose(), 0.6f, 0.6f); + RenderPipelineHelper.blitGuiTexture(ctx, frTex, 0, 0, 0, 0, 31, 21, 31, 21); + PoseHelper.pop(ctx.pose()); + iconW = Math.round(31 * 0.6f); + } - // Icon + friendship badge - if (entity != null) drawEntityIcon(ctx, entity, x, offsetY); - ResourceLocation frTex = textures.GetUI("friendship" + friendship); - if (frTex != null) { - RenderPipelineHelper.blitGuiTexture(ctx, frTex, x + 34, offsetY, 0, 0, 31, 21, 31, 21); + long last = getLastInteraction(data); + String time = friendlyTime(System.currentTimeMillis() - last); + ctx.drawString(this.font, time, x + 22 + iconW + 4, rowY + this.font.lineHeight + 4, BODY_COLOR, false); } + ctx.disableScissor(); + } - int lineY = offsetY + 35; - - // Clip everything to the page content box so nothing bleeds outside - ctx.enableScissor(x, y, x + PAGE_CONTENT_W, y + PAGE_CONTENT_H); + private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, int pageIndex) { + if (pageIndex >= detailTotalPages) return; + if (detailEntity == null) return; - // Layout: label on one line (small), value below it (wrapped, slightly smaller) - lineY = drawPair(ctx, "Personality", personality, x, lineY, PAGE_CONTENT_W); - lineY = drawPair(ctx, "Speaking Style / Tone", speaking, x, lineY, PAGE_CONTENT_W); - lineY = drawPair(ctx, "Skills", skills, x, lineY, PAGE_CONTENT_W); - lineY = drawPair(ctx, "Likes", likes, x, lineY, PAGE_CONTENT_W); - lineY = drawPair(ctx, "Dislikes", dislikes, x, lineY, PAGE_CONTENT_W); - lineY = drawPair(ctx, "Background", background, x, lineY, PAGE_CONTENT_W); + Player player = Minecraft.getInstance().player; + String playerName = player != null ? player.getDisplayName().getString() : ""; + PlayerData pData = detailEntity.getPlayerData(playerName); + int friendship = pData != null ? pData.friendship : 0; + Entity entity = getEntity(detailEntity.entityId); - // Last Message (cap to 4 lines, smaller and muted) - ctx.drawString(this.font, "Last Message", x, lineY, LABEL_COLOR, false); - lineY += this.font.lineHeight; - lineY = drawWrapped(ctx, lastMsg, x, lineY, PAGE_CONTENT_W, 0.9f, 4, 0xFF444444); + int sectionPageCount = sectionPages.size(); - ctx.disableScissor(); + if (pageIndex < sectionPageCount) { // character sheet pages + List page = sectionPages.get(pageIndex); + int lineY = y; + if (pageIndex == 0) { + String displayName = resolveName(detailEntity); + if (displayName == null || displayName.isBlank()) { + displayName = entity != null ? entity.getName().getString() : "Unknown"; + } + int titleColor = 0xFF000000; + if (friendship < 0) titleColor = 0xFFFF3A3A; + else if (friendship > 0) titleColor = 0xFF2ECC40; + int availNameW = (int)Math.floor(PAGE_CONTENT_W / 1.12f); + displayName = this.font.plainSubstrByWidth(displayName, availNameW); + PoseHelper.push(ctx.pose()); + PoseHelper.translate(ctx.pose(), (float)x, (float)y); + PoseHelper.scale(ctx.pose(), 1.12f, 1.12f); + ctx.drawString(this.font, displayName, 1, 1, 0x66000000, false); + ctx.drawString(this.font, displayName, 0, 0, titleColor, false); + PoseHelper.pop(ctx.pose()); + lineY = y + Math.round(this.font.lineHeight * 1.12f) + 6; + if (entity != null) drawEntityIcon(ctx, entity, x, lineY); + ResourceLocation frTex = textures.GetUI("friendship" + friendship); + if (frTex != null) { + RenderPipelineHelper.blitGuiTexture(ctx, frTex, x + 34, lineY, 0, 0, 31, 21, 31, 21); + } + lineY += 35; + } + ctx.enableScissor(x, y, x + PAGE_CONTENT_W, y + PAGE_CONTENT_H); + for (Pair p : page) { + lineY = drawPair(ctx, p.label, p.value, x, lineY, PAGE_CONTENT_W); + } + ctx.disableScissor(); + } else { // messages pages + int msgPage = pageIndex - sectionPageCount; + List msgs = messagePages.get(msgPage); + int lineY = y; + ctx.enableScissor(x, y, x + PAGE_CONTENT_W, y + PAGE_CONTENT_H); + ctx.drawString(this.font, "Recent Messages", x, lineY, LABEL_COLOR, false); + lineY += this.font.lineHeight + 2; + for (ChatMessage m : msgs) { + String speaker = m.sender == ChatDataManager.ChatSender.USER ? "You" : resolveName(detailEntity); + if (speaker == null || speaker.isBlank()) speaker = "Unknown"; + long ts = m.timestamp == null ? 0L : m.timestamp; + String ago = friendlyTime(System.currentTimeMillis() - ts) + " ago"; + int timeW = this.font.width(ago); + speaker = this.font.plainSubstrByWidth(speaker, PAGE_CONTENT_W - timeW - 2); + ctx.drawString(this.font, speaker, x, lineY, LABEL_COLOR, false); + ctx.drawString(this.font, ago, x + PAGE_CONTENT_W - timeW, lineY, LABEL_COLOR, false); + lineY += this.font.lineHeight + 1; + lineY = drawWrapped(ctx, safe(m.message), x + 4, lineY, PAGE_CONTENT_W - 4, 0.9f, 3, BODY_COLOR); + lineY += 4; + } + ctx.disableScissor(); + } - // UUID footer (tiny light gray) aligned to page bottom int debugY = bgY + BG_HEIGHT - 6; PoseHelper.push(ctx.pose()); PoseHelper.translate(ctx.pose(), (float)x, (float)debugY); PoseHelper.scale(ctx.pose(), 0.8f, 0.8f); - ctx.drawString(this.font, data.entityId, 0, 0, LIGHT_GRAY, false); + ctx.drawString(this.font, detailEntity.entityId, 0, 0, LIGHT_GRAY, false); PoseHelper.pop(ctx.pose()); } private void requestDataForCurrentPages() { - if (index < ordered.size()) { - UUID left = UUID.fromString(ordered.get(index).entityId); - ClientPackets.requestEntityData(left); - } - if (index + 1 < ordered.size()) { - UUID right = UUID.fromString(ordered.get(index + 1).entityId); - ClientPackets.requestEntityData(right); + if (mode == Mode.SUMMARY) { + for (int i = 0; i < SUMMARY_ROWS_PER_PAGE * 2; i++) { + int idx = summaryIndex + i; + if (idx >= ordered.size()) break; + UUID id = UUID.fromString(ordered.get(idx).entityId); + ClientPackets.requestEntityData(id); + } + } else if (detailEntity != null) { + UUID id = UUID.fromString(detailEntity.entityId); + ClientPackets.requestEntityData(id); } } @@ -363,35 +570,50 @@ private int drawPair(net.minecraft.client.gui.GuiGraphics ctx, String label, Str /** Wrapped text with scaling and maxLines, adds ellipsis if truncated. Returns next y. */ private int drawWrapped(net.minecraft.client.gui.GuiGraphics ctx, String text, int x, int y, int maxWidthPx, float scale, int maxLines, int color) { - int avail = (int)Math.floor(maxWidthPx / scale); - String rest = text; - int drawnPx = 0; - + List lines = wrapLines(text, maxWidthPx, scale, maxLines); PoseHelper.push(ctx.pose()); PoseHelper.translate(ctx.pose(), (float)x, (float)y); PoseHelper.scale(ctx.pose(), scale, scale); + int drawnPx = 0; + for (String line : lines) { + ctx.drawString(this.font, line, 0, drawnPx, color, false); + drawnPx += this.font.lineHeight; + } + PoseHelper.pop(ctx.pose()); + return y + Math.round(drawnPx * scale); + } - for (int line = 0; line < maxLines && !rest.isEmpty(); line++) { + private int measureWrappedHeight(String text, int maxWidthPx, float scale, int maxLines) { + List lines = wrapLines(text, maxWidthPx, scale, maxLines); + return Math.round(lines.size() * this.font.lineHeight * scale); + } + + private List wrapLines(String text, int maxWidthPx, float scale, int maxLines) { + int avail = (int)Math.floor(maxWidthPx / scale); + List lines = new ArrayList<>(); + String rest = text == null ? "" : text.trim(); + while (!rest.isEmpty() && lines.size() < maxLines) { String piece = this.font.plainSubstrByWidth(rest, avail); - if (piece.isEmpty()) break; - boolean hasMore = piece.length() < rest.length(); - boolean lastLine = (line == maxLines - 1); - if (lastLine && hasMore) { - // ellipsize last line - while (!piece.isEmpty() && this.font.width(piece + "…") > avail) { - piece = piece.substring(0, piece.length() - 1); + int cut = piece.length(); + if (cut <= 0) break; + if (cut < rest.length()) { + int space = piece.lastIndexOf(' '); + if (space > 0) { + cut = space; + piece = piece.substring(0, space); } - piece = piece + "…"; - rest = ""; - } else { - rest = rest.substring(piece.length()); } - ctx.drawString(this.font, piece, 0, drawnPx, color, false); - drawnPx += this.font.lineHeight; + lines.add(piece.trim()); + rest = rest.substring(Math.min(rest.length(), cut)).trim(); } - - PoseHelper.pop(ctx.pose()); - return y + Math.round(drawnPx * scale); + if (!rest.isEmpty() && !lines.isEmpty()) { + String last = lines.get(lines.size() - 1); + while (!last.isEmpty() && this.font.width(last + "…") > avail) { + last = last.substring(0, last.length() - 1); + } + lines.set(lines.size() - 1, last + "…"); + } + return lines; } private String safe(String s) { diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/book.png b/src/main/resources/assets/creaturechat/textures/ui/book/book.png index 97a18292cb8155f3797b9859ceb569b3c0f454b2..deea842456e52d8bf9799312b0c63b07cc7aa738 100644 GIT binary patch literal 2646 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 Ef}Cgm&j0`b literal 7591 zcmX9@c|4Tg_h%Xzgds+Vv1H5GN@N@RPEw4?Iw+K#ELlDn#xk;$on$HN*o`fP5fLg& zGxoA%-w9>?J$--w%xh-uz2~0i-1qyO_qk8p-8;IB^j!2*R8)-mdRq6WsHhRZ_g=_F z;Pc+kDRU|+9uIvjb+bU~ja-vu-k#7;7VeTHCJ6qdM7YfCxKLnFeW;uE@ESoTK}OPF zok>{63>s2*0eJ=PBQ1GFAWut;Mm*E*EY^Sny9J-X7l{=n09q=$@|k22b(H^DLoVM;gHyl zOi0MVIBoEZ$tk>Rb*^PA=(P3ES0T@4khU6FD_=H3u!kwwm{(!vBYok1E0j>eJpJJG zAS~?u>Dh;X%4<4fWIJ0cIt##3IOHimm3Lv=_LwtsN^$k+PtWqOzxx^O=R!F%goA=O76-{0 z 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 Date: Wed, 20 Aug 2025 16:07:03 -0500 Subject: [PATCH 13/26] Improvements to page contents, layout, pagination - and page turn sounds --- CHANGELOG.md | 1 + .../java/com/owlmaddie/ui/BookScreen.java | 107 +++++++++++++----- 2 files changed, 80 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 869e6d7b..3d8304db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to **CreatureChat™** are documented in this file. The form - New packet types for sending / receiving all entity data (by UUID - per page) - Dynamic Next/Previous buttons with hover (hidden when no page to turn) - Summary pages are displayed first, then Detail pages with more info + - Improvements to page contents, layout, pagination - and page turn sounds ## Unreleased diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index a03b2a41..2c69dc37 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -15,6 +15,8 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.components.Button; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.sounds.SoundEvents; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; @@ -50,6 +52,8 @@ private enum Mode { SUMMARY, DETAIL } private List> sectionPages; private List detailMessages; private List> messagePages; + private int sectionRemainingSpace; + private boolean messagesOnSectionPage; private final List all; private List ordered; @@ -93,6 +97,7 @@ public BookScreen() { .sorted(Comparator.comparingLong(BookScreen::getLastInteraction).reversed()) .collect(Collectors.toList()); ordered = new ArrayList<>(all); + sortOrdered(); summaryIndex = 0; } @@ -164,7 +169,7 @@ protected void init() { addRenderableWidget(searchField); - prevButton = ButtonHelper.createImageButton( + prevButton = createPageButton( bgX + PREV_X, bgY + PREV_Y, PREV_W, PREV_H, textures.GetUI("book/previous"), @@ -183,12 +188,11 @@ protected void init() { updateButtons(); requestDataForCurrentPages(); LOGGER.info("BookScreen: previous clicked mode={} summaryIndex={} detailPage={}", mode, summaryIndex, detailPage); - }, - w -> Component.empty() + } ); addRenderableWidget(prevButton); - nextButton = ButtonHelper.createImageButton( + nextButton = createPageButton( bgX + NEXT_X, bgY + NEXT_Y, NEXT_W, NEXT_H, textures.GetUI("book/next"), @@ -205,8 +209,7 @@ protected void init() { updateButtons(); requestDataForCurrentPages(); LOGGER.info("BookScreen: next clicked mode={} summaryIndex={} detailPage={}", mode, summaryIndex, detailPage); - }, - w -> Component.empty() + } ); addRenderableWidget(nextButton); @@ -234,6 +237,27 @@ private void updateButtons() { } } + private Button createPageButton(int x, int y, int width, int height, + ResourceLocation normalTex, ResourceLocation hoverTex, + Button.OnPress onPress) { + return new Button(x, y, width, height, Component.empty(), onPress, w -> Component.empty()) { + @Override + public void renderWidget(net.minecraft.client.gui.GuiGraphics ctx, int mouseX, int mouseY, float delta) { + ResourceLocation tex = isHovered() ? hoverTex : normalTex; + RenderPipelineHelper.blitGuiTexture(ctx, tex, getX(), getY(), 0, 0, width, height, width, height); + } + + @Override + public void playDownSound(net.minecraft.client.sounds.SoundManager mgr) { + mgr.play(SimpleSoundInstance.forUI(SoundEvents.BOOK_PAGE_TURN, 1.0F)); + } + }; + } + + private void sortOrdered() { + ordered.sort(Comparator.comparingLong(BookScreen::getLastInteraction).reversed()); + } + private boolean inside(double mx, double my, int x, int y, int w, int h) { return mx >= x && mx <= x + w && my >= y && my <= y + h; } @@ -272,6 +296,7 @@ private void toggleSearch() { setFocused(null); searchField.setValue(""); ordered = new ArrayList<>(all); + sortOrdered(); summaryIndex = 0; mode = Mode.SUMMARY; updateButtons(); @@ -288,6 +313,7 @@ private void onSearchChanged(String text) { return n != null && n.toLowerCase(Locale.ROOT).contains(q); }) .collect(Collectors.toList()); + sortOrdered(); summaryIndex = 0; mode = Mode.SUMMARY; updateButtons(); @@ -328,7 +354,7 @@ private void openDetail(int idx) { paginateSections(); paginateMessages(); - detailTotalPages = sectionPages.size() + messagePages.size(); + detailTotalPages = sectionPages.size() + messagePages.size() - (messagesOnSectionPage ? 1 : 0); mode = Mode.DETAIL; updateButtons(); requestDataForCurrentPages(); @@ -336,12 +362,13 @@ private void openDetail(int idx) { private void paginateSections() { sectionPages = new ArrayList<>(); + sectionRemainingSpace = 0; List page = new ArrayList<>(); int used = 0; int headerHeight = Math.round(this.font.lineHeight * 1.12f) + 6 + 35; for (Pair p : detailSections) { int h = this.font.lineHeight; - h += measureWrappedHeight(p.value, PAGE_CONTENT_W, 0.92f, 2); + h += measureWrappedHeight(p.value, PAGE_CONTENT_W, 0.92f, 3); h += 4; int available = sectionPages.isEmpty() ? PAGE_CONTENT_H - headerHeight : PAGE_CONTENT_H; if (used + h > available && !page.isEmpty()) { @@ -358,20 +385,34 @@ private void paginateSections() { } if (sectionPages.isEmpty()) { sectionPages.add(Collections.emptyList()); + sectionRemainingSpace = PAGE_CONTENT_H - headerHeight; + } else { + int available = sectionPages.size() == 1 ? PAGE_CONTENT_H - headerHeight : PAGE_CONTENT_H; + sectionRemainingSpace = Math.max(0, available - used); } } private void paginateMessages() { messagePages = new ArrayList<>(); + int headerH = this.font.lineHeight + 2; + int minMsgH = this.font.lineHeight + 1 + Math.round(this.font.lineHeight * 0.9f) + 4; + messagesOnSectionPage = sectionRemainingSpace >= headerH + minMsgH && !detailMessages.isEmpty(); + int available = messagesOnSectionPage ? sectionRemainingSpace - headerH : PAGE_CONTENT_H - headerH; + if (available < 0) available = 0; List page = new ArrayList<>(); int used = 0; for (ChatMessage m : detailMessages) { int h = font.lineHeight + 1; - h += measureWrappedHeight(safe(m.message), PAGE_CONTENT_W - 4, 0.9f, 3) + 4; - if (used + h > PAGE_CONTENT_H && !page.isEmpty()) { + h += measureWrappedHeight(safe(m.message), PAGE_CONTENT_W - 4, 0.9f, 4) + 4; + if (used + h > available && !page.isEmpty()) { messagePages.add(page); page = new ArrayList<>(); used = 0; + available = PAGE_CONTENT_H - headerH; + } + if (used + h > available && page.isEmpty()) { + // ensure progress even if single message exceeds available + available = PAGE_CONTENT_H - headerH; } page.add(m); used += h; @@ -485,27 +526,16 @@ private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, i for (Pair p : page) { lineY = drawPair(ctx, p.label, p.value, x, lineY, PAGE_CONTENT_W); } + // append messages on same page if space was reserved + if (messagesOnSectionPage && pageIndex == sectionPageCount - 1) { + lineY = renderMessagesPage(ctx, x, lineY, messagePages.get(0)); + } ctx.disableScissor(); } else { // messages pages - int msgPage = pageIndex - sectionPageCount; + int msgPage = pageIndex - sectionPageCount + (messagesOnSectionPage ? 1 : 0); List msgs = messagePages.get(msgPage); - int lineY = y; ctx.enableScissor(x, y, x + PAGE_CONTENT_W, y + PAGE_CONTENT_H); - ctx.drawString(this.font, "Recent Messages", x, lineY, LABEL_COLOR, false); - lineY += this.font.lineHeight + 2; - for (ChatMessage m : msgs) { - String speaker = m.sender == ChatDataManager.ChatSender.USER ? "You" : resolveName(detailEntity); - if (speaker == null || speaker.isBlank()) speaker = "Unknown"; - long ts = m.timestamp == null ? 0L : m.timestamp; - String ago = friendlyTime(System.currentTimeMillis() - ts) + " ago"; - int timeW = this.font.width(ago); - speaker = this.font.plainSubstrByWidth(speaker, PAGE_CONTENT_W - timeW - 2); - ctx.drawString(this.font, speaker, x, lineY, LABEL_COLOR, false); - ctx.drawString(this.font, ago, x + PAGE_CONTENT_W - timeW, lineY, LABEL_COLOR, false); - lineY += this.font.lineHeight + 1; - lineY = drawWrapped(ctx, safe(m.message), x + 4, lineY, PAGE_CONTENT_W - 4, 0.9f, 3, BODY_COLOR); - lineY += 4; - } + renderMessagesPage(ctx, x, y, msgs); ctx.disableScissor(); } @@ -517,7 +547,28 @@ private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, i PoseHelper.pop(ctx.pose()); } + private int renderMessagesPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int startY, List msgs) { + int lineY = startY; + ctx.drawString(this.font, "Recent Messages", x, lineY, LABEL_COLOR, false); + lineY += this.font.lineHeight + 2; + for (ChatMessage m : msgs) { + String speaker = m.sender == ChatDataManager.ChatSender.USER ? "You" : resolveName(detailEntity); + if (speaker == null || speaker.isBlank()) speaker = "Unknown"; + long ts = m.timestamp == null ? 0L : m.timestamp; + String ago = friendlyTime(System.currentTimeMillis() - ts) + " ago"; + int timeW = this.font.width(ago); + speaker = this.font.plainSubstrByWidth(speaker, PAGE_CONTENT_W - timeW - 2); + ctx.drawString(this.font, speaker, x, lineY, LABEL_COLOR, false); + ctx.drawString(this.font, ago, x + PAGE_CONTENT_W - timeW, lineY, LABEL_COLOR, false); + lineY += this.font.lineHeight + 1; + lineY = drawWrapped(ctx, safe(m.message), x + 4, lineY, PAGE_CONTENT_W - 4, 0.9f, 4, BODY_COLOR); + lineY += 4; + } + return lineY; + } + private void requestDataForCurrentPages() { + sortOrdered(); if (mode == Mode.SUMMARY) { for (int i = 0; i < SUMMARY_ROWS_PER_PAGE * 2; i++) { int idx = summaryIndex + i; @@ -564,7 +615,7 @@ private static String lorem(int min, int max) { private int drawPair(net.minecraft.client.gui.GuiGraphics ctx, String label, String value, int x, int y, int widthPx) { ctx.drawString(this.font, label, x, y, LABEL_COLOR, false); y += this.font.lineHeight; - return drawWrapped(ctx, value, x, y, widthPx, 0.92f, 2, BODY_COLOR) + 4; // slight gap after pair + return drawWrapped(ctx, value, x, y, widthPx, 0.92f, 3, BODY_COLOR) + 4; // slight gap after pair } /** Wrapped text with scaling and maxLines, adds ellipsis if truncated. Returns next y. */ From e9355b3bd05e840920afe0e01d7679d9c8110ed1 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 20 Aug 2025 17:29:45 -0500 Subject: [PATCH 14/26] Integrating entity_type into chat data, and incorporating death into broadcast, login, and book pages --- CHANGELOG.md | 1 + .../com/owlmaddie/network/ClientPackets.java | 1 + .../java/com/owlmaddie/ui/BookScreen.java | 65 ++++++++++++++++--- .../com/owlmaddie/chat/ChatDataManager.java | 15 ++++- .../com/owlmaddie/chat/EntityChatData.java | 2 + .../owlmaddie/chat/EntityChatDataLight.java | 4 ++ .../com/owlmaddie/network/ServerPackets.java | 12 +++- 7 files changed, 88 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d8304db..a92d1dc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to **CreatureChat™** are documented in this file. The form - Dynamic Next/Previous buttons with hover (hidden when no page to turn) - Summary pages are displayed first, then Detail pages with more info - Improvements to page contents, layout, pagination - and page turn sounds + - Integrating entity_type into chat data, and incorporating death into broadcast, login, and book pages ## Unreleased diff --git a/src/client/java/com/owlmaddie/network/ClientPackets.java b/src/client/java/com/owlmaddie/network/ClientPackets.java index 081c55d2..d43f0b29 100644 --- a/src/client/java/com/owlmaddie/network/ClientPackets.java +++ b/src/client/java/com/owlmaddie/network/ClientPackets.java @@ -238,6 +238,7 @@ public static void register() { existing.previousMessages = data.previousMessages; existing.born = data.born; existing.death = data.death; + existing.entity_type = data.entity_type; } else { mgr.entityChatDataMap.put(entityId, data); } diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 2c69dc37..809446d3 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -18,10 +18,15 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.sounds.SoundEvents; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.player.Player; +import java.time.Instant; +import java.time.ZoneId; +import net.minecraft.world.entity.EntitySpawnReason; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,7 +97,7 @@ public BookScreen() { String nameText = resolveName(data); boolean hasName = nameText != null && !nameText.isBlank(); boolean hasMsg = data.currentMessage != null && !data.currentMessage.isBlank(); - return hasName && hasMsg; + return hasName && (hasMsg || data.death != null); }) .sorted(Comparator.comparingLong(BookScreen::getLastInteraction).reversed()) .collect(Collectors.toList()); @@ -326,6 +331,13 @@ private void openDetail(int idx) { detailEntity = ordered.get(idx); detailPage = 0; detailSections = new ArrayList<>(); + if (detailEntity.death != null) { + String deathDate = Instant.ofEpochMilli(detailEntity.death) + .atZone(ZoneId.systemDefault()) + .toLocalDate() + .toString(); + detailSections.add(new Pair("Death Date", deathDate)); + } detailSections.add(new Pair("Personality", orLoremSeeded(detailEntity.getCharacterProp("Personality"), detailEntity.entityId, "Personality", 20, 100))); detailSections.add(new Pair("Speaking Style / Tone", orLoremSeeded(firstNonNull( @@ -345,10 +357,20 @@ private void openDetail(int idx) { List filtered = detailEntity.previousMessages.stream() .filter(m -> !(m.sender == ChatDataManager.ChatSender.USER && !pName.equals(m.name))) .collect(Collectors.toList()); - for (int i = Math.max(0, filtered.size() - 4); i < filtered.size(); i++) { - detailMessages.add(filtered.get(i)); + if (detailEntity.death != null) { + for (int i = filtered.size() - 1; i >= 0; i--) { + ChatMessage m = filtered.get(i); + if (m.sender == ChatDataManager.ChatSender.ASSISTANT) { + detailMessages.add(m); + break; + } + } + } else { + for (int i = Math.max(0, filtered.size() - 4); i < filtered.size(); i++) { + detailMessages.add(filtered.get(i)); + } + Collections.reverse(detailMessages); } - Collections.reverse(detailMessages); } paginateSections(); @@ -450,6 +472,12 @@ private void renderSummaryPage(net.minecraft.client.gui.GuiGraphics ctx, int x, } EntityChatData data = ordered.get(idx); Entity entity = getEntity(data.entityId); + if (entity == null && data.entity_type != null) { + EntityType type = EntityType.byString(data.entity_type).orElse(null); + if (type != null && Minecraft.getInstance().level != null) { + entity = type.create(Minecraft.getInstance().level, EntitySpawnReason.TRIGGERED); + } + } if (entity != null) { PoseHelper.push(ctx.pose()); PoseHelper.scale(ctx.pose(), 0.6f, 0.6f); @@ -459,8 +487,16 @@ private void renderSummaryPage(net.minecraft.client.gui.GuiGraphics ctx, int x, String name = resolveName(data); if (name == null) name = "Unknown"; - name = this.font.plainSubstrByWidth(name, PAGE_CONTENT_W - 22); - ctx.drawString(this.font, name, x + 22, rowY + 2, BODY_COLOR, false); + int avail = PAGE_CONTENT_W - 22; + if (data.death != null) avail -= this.font.width("RIP: "); + name = this.font.plainSubstrByWidth(name, avail); + if (data.death != null) { + Component comp = Component.literal("RIP: ") + .append(Component.literal(name).withStyle(Style.EMPTY.withStrikethrough(true))); + ctx.drawString(this.font, comp, x + 22, rowY + 2, BODY_COLOR, false); + } else { + ctx.drawString(this.font, name, x + 22, rowY + 2, BODY_COLOR, false); + } Player player = Minecraft.getInstance().player; PlayerData pData = player != null ? data.getPlayerData(player.getDisplayName().getString()) : null; @@ -492,6 +528,12 @@ private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, i PlayerData pData = detailEntity.getPlayerData(playerName); int friendship = pData != null ? pData.friendship : 0; Entity entity = getEntity(detailEntity.entityId); + if (entity == null && detailEntity.entity_type != null) { + EntityType type = EntityType.byString(detailEntity.entity_type).orElse(null); + if (type != null && Minecraft.getInstance().level != null) { + entity = type.create(Minecraft.getInstance().level, EntitySpawnReason.TRIGGERED); + } + } int sectionPageCount = sectionPages.size(); @@ -507,12 +549,16 @@ private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, i if (friendship < 0) titleColor = 0xFFFF3A3A; else if (friendship > 0) titleColor = 0xFF2ECC40; int availNameW = (int)Math.floor(PAGE_CONTENT_W / 1.12f); + if (detailEntity.death != null) availNameW -= this.font.width("RIP: "); displayName = this.font.plainSubstrByWidth(displayName, availNameW); + Component comp = detailEntity.death != null + ? Component.literal("RIP: ").append(Component.literal(displayName).withStyle(Style.EMPTY.withStrikethrough(true))) + : Component.literal(displayName); PoseHelper.push(ctx.pose()); PoseHelper.translate(ctx.pose(), (float)x, (float)y); PoseHelper.scale(ctx.pose(), 1.12f, 1.12f); - ctx.drawString(this.font, displayName, 1, 1, 0x66000000, false); - ctx.drawString(this.font, displayName, 0, 0, titleColor, false); + ctx.drawString(this.font, comp, 1, 1, 0x66000000, false); + ctx.drawString(this.font, comp, 0, 0, titleColor, false); PoseHelper.pop(ctx.pose()); lineY = y + Math.round(this.font.lineHeight * 1.12f) + 6; if (entity != null) drawEntityIcon(ctx, entity, x, lineY); @@ -549,7 +595,8 @@ private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, i private int renderMessagesPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int startY, List msgs) { int lineY = startY; - ctx.drawString(this.font, "Recent Messages", x, lineY, LABEL_COLOR, false); + String header = detailEntity != null && detailEntity.death != null ? "Last Words" : "Recent Messages"; + ctx.drawString(this.font, header, x, lineY, LABEL_COLOR, false); lineY += this.font.lineHeight + 2; for (ChatMessage m : msgs) { String speaker = m.sender == ChatDataManager.ChatSender.USER ? "You" : resolveName(detailEntity); diff --git a/src/main/java/com/owlmaddie/chat/ChatDataManager.java b/src/main/java/com/owlmaddie/chat/ChatDataManager.java index 8fee5465..03a717d1 100644 --- a/src/main/java/com/owlmaddie/chat/ChatDataManager.java +++ b/src/main/java/com/owlmaddie/chat/ChatDataManager.java @@ -97,9 +97,20 @@ public void updateUUID(String oldUUID, String newUUID) { // Save chat data to file public String GetLightChatData(String playerName) { try { - // Create "light" version of entire chat data HashMap + // Create light versions for living entities HashMap lightVersionMap = new HashMap<>(); - this.entityChatDataMap.forEach((name, entityChatData) -> lightVersionMap.put(name, entityChatData.toLightVersion(playerName))); + + this.entityChatDataMap.values().stream() + .filter(data -> data.death == null) + .forEach(data -> lightVersionMap.put(data.entityId, data.toLightVersion(playerName))); + + // Include up to 100 most recent dead entities + this.entityChatDataMap.values().stream() + .filter(data -> data.death != null) + .sorted((a, b) -> Long.compare(b.death, a.death)) + .limit(100) + .forEach(data -> lightVersionMap.put(data.entityId, data.toLightVersion(playerName))); + return GSON.toJson(lightVersionMap); } catch (Exception e) { // Handle exceptions diff --git a/src/main/java/com/owlmaddie/chat/EntityChatData.java b/src/main/java/com/owlmaddie/chat/EntityChatData.java index 073a9fbb..41894f96 100644 --- a/src/main/java/com/owlmaddie/chat/EntityChatData.java +++ b/src/main/java/com/owlmaddie/chat/EntityChatData.java @@ -61,6 +61,7 @@ public class EntityChatData { public List previousMessages; public Long born; public Long death; + public String entity_type; @SerializedName("playerId") @Expose(serialize = false) @@ -84,6 +85,7 @@ public EntityChatData(String entityId) { this.auto_generated = 0; this.previousMessages = new ArrayList<>(); this.born = System.currentTimeMillis();; + this.entity_type = null; // Old, unused migrated properties this.legacyPlayerId = null; diff --git a/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java b/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java index 4c348d9a..7422c1a3 100644 --- a/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java +++ b/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java @@ -19,6 +19,8 @@ public class EntityChatDataLight { public ChatDataManager.ChatStatus status; public ChatDataManager.ChatSender sender; public Map players; + public Long death; + public String entity_type; // Constructor to initialize the light version from the full version public EntityChatDataLight(EntityChatData fullData, String playerName) { @@ -27,6 +29,8 @@ public EntityChatDataLight(EntityChatData fullData, String playerName) { this.currentLineNumber = fullData.currentLineNumber; this.status = fullData.status; this.sender = fullData.sender; + this.death = fullData.death; + this.entity_type = fullData.entity_type; // Initialize the players map and add only the current player's data this.players = new HashMap<>(); diff --git a/src/main/java/com/owlmaddie/network/ServerPackets.java b/src/main/java/com/owlmaddie/network/ServerPackets.java index 0877ea6f..3910bce7 100644 --- a/src/main/java/com/owlmaddie/network/ServerPackets.java +++ b/src/main/java/com/owlmaddie/network/ServerPackets.java @@ -220,6 +220,7 @@ public static void register() { sendData.previousMessages = source.previousMessages; sendData.born = source.born; sendData.death = source.death; + sendData.entity_type = source.entity_type; String pName = player.getDisplayName().getString(); sendData.players.put(pName, source.getPlayerData(pName)); Gson gson = new Gson(); @@ -296,7 +297,12 @@ public static void register() { String entityUUID = entity.getStringUUID(); if (entity.getRemovalReason() == Entity.RemovalReason.KILLED && ChatDataManager.getServerInstance().entityChatDataMap.containsKey(entityUUID)) { LOGGER.debug("Entity killed (" + entityUUID + "), updating death time stamp."); - ChatDataManager.getServerInstance().entityChatDataMap.get(entityUUID).death = System.currentTimeMillis(); + EntityChatData data = ChatDataManager.getServerInstance().entityChatDataMap.get(entityUUID); + data.death = System.currentTimeMillis(); + if (data.entity_type == null || data.entity_type.isEmpty()) { + data.entity_type = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + } + BroadcastEntityMessage(data); } }); } @@ -336,6 +342,10 @@ public static void generate_character(String userLanguage, EntityChatData chatDa TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F); EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER); + if (chatData.entity_type == null || chatData.entity_type.isEmpty()) { + chatData.entity_type = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + } + // Grab random adjective String randomAdjective = Randomizer.getRandomMessage(Randomizer.RandomType.ADJECTIVE); String randomClass = Randomizer.getRandomMessage(Randomizer.RandomType.CLASS); From 61593596464b7e035d4c29785b151c45652a3b3f Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 20 Aug 2025 17:54:41 -0500 Subject: [PATCH 15/26] Fixing more out-of-bounds text wrapping --- src/client/java/com/owlmaddie/ui/BookScreen.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 809446d3..0a5c2396 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -393,7 +393,7 @@ private void paginateSections() { h += measureWrappedHeight(p.value, PAGE_CONTENT_W, 0.92f, 3); h += 4; int available = sectionPages.isEmpty() ? PAGE_CONTENT_H - headerHeight : PAGE_CONTENT_H; - if (used + h > available && !page.isEmpty()) { + if (used + h >= available && !page.isEmpty()) { sectionPages.add(page); page = new ArrayList<>(); used = 0; @@ -426,13 +426,13 @@ private void paginateMessages() { for (ChatMessage m : detailMessages) { int h = font.lineHeight + 1; h += measureWrappedHeight(safe(m.message), PAGE_CONTENT_W - 4, 0.9f, 4) + 4; - if (used + h > available && !page.isEmpty()) { + if (used + h >= available && !page.isEmpty()) { messagePages.add(page); page = new ArrayList<>(); used = 0; available = PAGE_CONTENT_H - headerH; } - if (used + h > available && page.isEmpty()) { + if (used + h >= available && page.isEmpty()) { // ensure progress even if single message exceeds available available = PAGE_CONTENT_H - headerH; } @@ -683,7 +683,7 @@ private int drawWrapped(net.minecraft.client.gui.GuiGraphics ctx, String text, i private int measureWrappedHeight(String text, int maxWidthPx, float scale, int maxLines) { List lines = wrapLines(text, maxWidthPx, scale, maxLines); - return Math.round(lines.size() * this.font.lineHeight * scale); + return (int)Math.ceil(lines.size() * this.font.lineHeight * scale); } private List wrapLines(String text, int maxWidthPx, float scale, int maxLines) { From a656a6956c80e3ea367e5ef0b0fbd0e29046a4ce Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 20 Aug 2025 22:08:15 -0500 Subject: [PATCH 16/26] Making entity type load from a helper - and not require the UUID of the entity to be alive on the server --- .../java/com/owlmaddie/ui/BookScreen.java | 10 +++--- .../owlmaddie/utils/EntityCreationHelper.java | 19 ++++++++++ .../java/com/owlmaddie/items/ModItems.java | 8 +---- .../CreatureChatEnglishLangProvider.java | 0 .../owlmaddie/utils/EntityCreationHelper.java | 23 ++++++++++++ .../java/com/owlmaddie/items/ModItems.java | 35 +++++++++++++++++++ .../datagen/CreatureChatModelProvider.java | 0 7 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 src/client/java/com/owlmaddie/utils/EntityCreationHelper.java rename src/vs/{v1_21_0 => v1_20_5}/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java (100%) create mode 100644 src/vs/v1_21_2/client/java/com/owlmaddie/utils/EntityCreationHelper.java create mode 100644 src/vs/v1_21_2/main/java/com/owlmaddie/items/ModItems.java rename src/vs/{v1_21_0 => v1_21_4}/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java (100%) diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 0a5c2396..ace09460 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -12,6 +12,7 @@ import com.owlmaddie.render.PoseHelper; import com.owlmaddie.render.RenderPipelineHelper; import com.owlmaddie.utils.ClientEntityFinder; +import com.owlmaddie.utils.EntityCreationHelper; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.components.Button; @@ -26,7 +27,6 @@ import net.minecraft.world.entity.player.Player; import java.time.Instant; import java.time.ZoneId; -import net.minecraft.world.entity.EntitySpawnReason; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -474,8 +474,8 @@ private void renderSummaryPage(net.minecraft.client.gui.GuiGraphics ctx, int x, Entity entity = getEntity(data.entityId); if (entity == null && data.entity_type != null) { EntityType type = EntityType.byString(data.entity_type).orElse(null); - if (type != null && Minecraft.getInstance().level != null) { - entity = type.create(Minecraft.getInstance().level, EntitySpawnReason.TRIGGERED); + if (type != null) { + entity = EntityCreationHelper.create(type); } } if (entity != null) { @@ -530,8 +530,8 @@ private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, i Entity entity = getEntity(detailEntity.entityId); if (entity == null && detailEntity.entity_type != null) { EntityType type = EntityType.byString(detailEntity.entity_type).orElse(null); - if (type != null && Minecraft.getInstance().level != null) { - entity = type.create(Minecraft.getInstance().level, EntitySpawnReason.TRIGGERED); + if (type != null) { + entity = EntityCreationHelper.create(type); } } diff --git a/src/client/java/com/owlmaddie/utils/EntityCreationHelper.java b/src/client/java/com/owlmaddie/utils/EntityCreationHelper.java new file mode 100644 index 00000000..4543a147 --- /dev/null +++ b/src/client/java/com/owlmaddie/utils/EntityCreationHelper.java @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.utils; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.Level; + +/** + * Utility for creating client-side entity instances across versions. + */ +public class EntityCreationHelper { + public static Entity create(EntityType type) { + Level level = Minecraft.getInstance().level; + return level == null ? null : type.create(level); + } +} diff --git a/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java b/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java index 6d48f969..fdbbabfb 100644 --- a/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java +++ b/src/vs/v1_20_2/main/java/com/owlmaddie/items/ModItems.java @@ -5,8 +5,6 @@ import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; @@ -22,11 +20,7 @@ private ModItems() {} public static final Item BOOK = Registry.register( BuiltInRegistries.ITEM, BOOK_ID, - new Item(new Item.Properties() - .stacksTo(1) - .useItemDescriptionPrefix() - .setId(ResourceKey.create(Registries.ITEM, BOOK_ID)) - ) + new Item(new Item.Properties().stacksTo(1)) ); /** Placeholder for future item registrations. */ diff --git a/src/vs/v1_21_0/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java b/src/vs/v1_20_5/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java similarity index 100% rename from src/vs/v1_21_0/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java rename to src/vs/v1_20_5/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java diff --git a/src/vs/v1_21_2/client/java/com/owlmaddie/utils/EntityCreationHelper.java b/src/vs/v1_21_2/client/java/com/owlmaddie/utils/EntityCreationHelper.java new file mode 100644 index 00000000..674bc2a6 --- /dev/null +++ b/src/vs/v1_21_2/client/java/com/owlmaddie/utils/EntityCreationHelper.java @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.utils; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.level.Level; + +/** + * Utility for creating client-side entity instances across versions. + * + *

In 1.21+, {@link EntityType#create(Level, EntitySpawnReason)} replaces the + * older overload. This implementation supplies the {@code TRIGGERED} spawn reason.

+ */ +public class EntityCreationHelper { + public static Entity create(EntityType type) { + Level level = Minecraft.getInstance().level; + return level == null ? null : type.create(level, EntitySpawnReason.TRIGGERED); + } +} diff --git a/src/vs/v1_21_2/main/java/com/owlmaddie/items/ModItems.java b/src/vs/v1_21_2/main/java/com/owlmaddie/items/ModItems.java new file mode 100644 index 00000000..23c8325b --- /dev/null +++ b/src/vs/v1_21_2/main/java/com/owlmaddie/items/ModItems.java @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 owlmaddie LLC +// SPDX-License-Identifier: GPL-3.0-or-later +// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited +package com.owlmaddie.items; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; + +/** + * Registers items used by the mod. + */ +public final class ModItems { + private ModItems() {} + + /** The book that opens the creature log UI. */ + private static final ResourceLocation BOOK_ID = new ResourceLocation("creaturechat", "book"); + + public static final Item BOOK = Registry.register( + BuiltInRegistries.ITEM, + BOOK_ID, + new Item(new Item.Properties() + .stacksTo(1) + .useItemDescriptionPrefix() + .setId(ResourceKey.create(Registries.ITEM, BOOK_ID)) + ) + ); + + /** Placeholder for future item registrations. */ + public static void register() {} +} + diff --git a/src/vs/v1_21_0/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java b/src/vs/v1_21_4/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java similarity index 100% rename from src/vs/v1_21_0/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java rename to src/vs/v1_21_4/client/java/com/owlmaddie/datagen/CreatureChatModelProvider.java From 5fc147d7e8cec298191f0b80c2996aba01cda23b Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 21 Aug 2025 01:14:49 -0500 Subject: [PATCH 17/26] Adding entityName into chat data, for dead mobs and refactoring entityType to use similar case. --- CHANGELOG.md | 3 ++- .../com/owlmaddie/network/ClientPackets.java | 3 ++- .../java/com/owlmaddie/ui/BookScreen.java | 13 ++++++++---- .../com/owlmaddie/chat/EntityChatData.java | 6 ++++-- .../owlmaddie/chat/EntityChatDataLight.java | 7 ++++--- .../com/owlmaddie/network/ServerPackets.java | 21 ++++++++++++------- 6 files changed, 35 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a92d1dc6..ab46526e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ All notable changes to **CreatureChat™** are documented in this file. The form - Dynamic Next/Previous buttons with hover (hidden when no page to turn) - Summary pages are displayed first, then Detail pages with more info - Improvements to page contents, layout, pagination - and page turn sounds - - Integrating entity_type into chat data, and incorporating death into broadcast, login, and book pages + - Integrating entityType into chat data, and incorporating death into broadcast, login, and book pages + - Adding entityName into chat data, for dead mobs ## Unreleased diff --git a/src/client/java/com/owlmaddie/network/ClientPackets.java b/src/client/java/com/owlmaddie/network/ClientPackets.java index d43f0b29..778612af 100644 --- a/src/client/java/com/owlmaddie/network/ClientPackets.java +++ b/src/client/java/com/owlmaddie/network/ClientPackets.java @@ -238,7 +238,8 @@ public static void register() { existing.previousMessages = data.previousMessages; existing.born = data.born; existing.death = data.death; - existing.entity_type = data.entity_type; + existing.entityType = data.entityType; + existing.entityName = data.entityName; } else { mgr.entityChatDataMap.put(entityId, data); } diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index ace09460..517852e8 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -116,6 +116,11 @@ private String resolveName(EntityChatData data) { } else if (entity instanceof Player) { nameText = entity.getName().getString(); } + if (nameText == null || nameText.isBlank()) { + if (data.entityName != null && !data.entityName.isBlank()) { + nameText = data.entityName; + } + } if (nameText == null || nameText.isBlank()) { String sheetName = data.getCharacterProp("Name"); if (sheetName != null && !sheetName.isBlank() && !"N/A".equals(sheetName)) { @@ -472,8 +477,8 @@ private void renderSummaryPage(net.minecraft.client.gui.GuiGraphics ctx, int x, } EntityChatData data = ordered.get(idx); Entity entity = getEntity(data.entityId); - if (entity == null && data.entity_type != null) { - EntityType type = EntityType.byString(data.entity_type).orElse(null); + if (entity == null && data.entityType != null) { + EntityType type = EntityType.byString(data.entityType).orElse(null); if (type != null) { entity = EntityCreationHelper.create(type); } @@ -528,8 +533,8 @@ private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, i PlayerData pData = detailEntity.getPlayerData(playerName); int friendship = pData != null ? pData.friendship : 0; Entity entity = getEntity(detailEntity.entityId); - if (entity == null && detailEntity.entity_type != null) { - EntityType type = EntityType.byString(detailEntity.entity_type).orElse(null); + if (entity == null && detailEntity.entityType != null) { + EntityType type = EntityType.byString(detailEntity.entityType).orElse(null); if (type != null) { entity = EntityCreationHelper.create(type); } diff --git a/src/main/java/com/owlmaddie/chat/EntityChatData.java b/src/main/java/com/owlmaddie/chat/EntityChatData.java index 41894f96..bd6b7bf7 100644 --- a/src/main/java/com/owlmaddie/chat/EntityChatData.java +++ b/src/main/java/com/owlmaddie/chat/EntityChatData.java @@ -61,7 +61,8 @@ public class EntityChatData { public List previousMessages; public Long born; public Long death; - public String entity_type; + public String entityName; + public String entityType; @SerializedName("playerId") @Expose(serialize = false) @@ -85,7 +86,8 @@ public EntityChatData(String entityId) { this.auto_generated = 0; this.previousMessages = new ArrayList<>(); this.born = System.currentTimeMillis();; - this.entity_type = null; + this.entityName = ""; + this.entityType = null; // Old, unused migrated properties this.legacyPlayerId = null; diff --git a/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java b/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java index 7422c1a3..38684525 100644 --- a/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java +++ b/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java @@ -5,7 +5,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.UUID; /** * The {@code EntityChatDataLight} class represents the current displayed message, and no @@ -20,7 +19,8 @@ public class EntityChatDataLight { public ChatDataManager.ChatSender sender; public Map players; public Long death; - public String entity_type; + public String entityName; + public String entityType; // Constructor to initialize the light version from the full version public EntityChatDataLight(EntityChatData fullData, String playerName) { @@ -30,7 +30,8 @@ public EntityChatDataLight(EntityChatData fullData, String playerName) { this.status = fullData.status; this.sender = fullData.sender; this.death = fullData.death; - this.entity_type = fullData.entity_type; + this.entityName = fullData.entityName; + this.entityType = fullData.entityType; // Initialize the players map and add only the current player's data this.players = new HashMap<>(); diff --git a/src/main/java/com/owlmaddie/network/ServerPackets.java b/src/main/java/com/owlmaddie/network/ServerPackets.java index 3910bce7..2e900c2d 100644 --- a/src/main/java/com/owlmaddie/network/ServerPackets.java +++ b/src/main/java/com/owlmaddie/network/ServerPackets.java @@ -210,6 +210,11 @@ public static void register() { String entityId = buf.readUtf(); server.execute(() -> { EntityChatData source = ChatDataManager.getServerInstance().getOrCreateChatData(entityId); + Mob entity = (Mob) ServerEntityFinder.getEntityByUUID((ServerLevel) player.level(), UUID.fromString(entityId)); + if (entity != null) { + source.entityType = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + source.entityName = entity.getDisplayName().getString(); + } EntityChatData sendData = new EntityChatData(entityId); sendData.currentMessage = source.currentMessage; sendData.currentLineNumber = source.currentLineNumber; @@ -220,7 +225,8 @@ public static void register() { sendData.previousMessages = source.previousMessages; sendData.born = source.born; sendData.death = source.death; - sendData.entity_type = source.entity_type; + sendData.entityName = source.entityName; + sendData.entityType = source.entityType; String pName = player.getDisplayName().getString(); sendData.players.put(pName, source.getPlayerData(pName)); Gson gson = new Gson(); @@ -299,9 +305,8 @@ public static void register() { LOGGER.debug("Entity killed (" + entityUUID + "), updating death time stamp."); EntityChatData data = ChatDataManager.getServerInstance().entityChatDataMap.get(entityUUID); data.death = System.currentTimeMillis(); - if (data.entity_type == null || data.entity_type.isEmpty()) { - data.entity_type = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); - } + data.entityType = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + data.entityName = entity.getDisplayName().getString(); BroadcastEntityMessage(data); } }); @@ -342,9 +347,8 @@ public static void generate_character(String userLanguage, EntityChatData chatDa TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F); EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER); - if (chatData.entity_type == null || chatData.entity_type.isEmpty()) { - chatData.entity_type = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); - } + chatData.entityType = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + chatData.entityName = entity.getDisplayName().getString(); // Grab random adjective String randomAdjective = Randomizer.getRandomMessage(Randomizer.RandomType.ADJECTIVE); @@ -433,6 +437,9 @@ public static void generate_chat(String userLanguage, EntityChatData chatData, S TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F); EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER); + chatData.entityType = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + chatData.entityName = entity.getDisplayName().getString(); + // Add new message chatData.generateMessage(userLanguage, player, message, is_auto_message); } From 4e8084ef0f22d6781221668f01674e2ced33ab9c Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 22 Aug 2025 19:48:01 -0500 Subject: [PATCH 18/26] Fixing pagination and wrapping of BookScreen, and Remember book screen state when exiting (so book resumes exactly where you left it) --- CHANGELOG.md | 1 + .../java/com/owlmaddie/ui/BookScreen.java | 264 ++++++++++++------ 2 files changed, 184 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab46526e..d4ccc122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to **CreatureChat™** are documented in this file. The form - Improvements to page contents, layout, pagination - and page turn sounds - Integrating entityType into chat data, and incorporating death into broadcast, login, and book pages - Adding entityName into chat data, for dead mobs + - Remember book screen state when exiting (so book resumes exactly where you left it) ## Unreleased diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 517852e8..39efee4c 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -45,6 +45,18 @@ public class BookScreen extends ScreenHelper { private enum Mode { SUMMARY, DETAIL } private Mode mode = Mode.SUMMARY; + // remember last opened state + private static Mode lastMode = Mode.SUMMARY; + private static int lastSummaryIndex = 0; + private static int lastDetailPage = 0; + private static String lastDetailEntityId = null; + private static String lastSearchQuery = ""; + private static boolean lastSearchVisible = false; + + // detail state restoration + private int pendingDetailIndex = -1; + private int pendingDetailPage; + // summary state private int summaryIndex; private int hoveredSummary = -1; @@ -53,10 +65,10 @@ private enum Mode { SUMMARY, DETAIL } private EntityChatData detailEntity; private int detailPage; // left page index private int detailTotalPages; // total single pages for detailEntity - private List detailSections; // character sheet sections + private List detailSections = Collections.emptyList(); // character sheet sections private List> sectionPages; private List detailMessages; - private List> messagePages; + private List> messagePages; private int sectionRemainingSpace; private boolean messagesOnSectionPage; @@ -89,6 +101,17 @@ private static class Pair { Pair(String l, String v) { this.label = l; this.value = v; } } + private static class MessageChunk { + final ChatMessage msg; + final List lines; + final boolean showSpeaker; + MessageChunk(ChatMessage m, List lines, boolean showSpeaker) { + this.msg = m; + this.lines = lines; + this.showSpeaker = showSpeaker; + } + } + public BookScreen() { super(Component.literal("Creature Log")); ChatDataManager mgr = ChatDataManager.getClientInstance(); @@ -103,7 +126,34 @@ public BookScreen() { .collect(Collectors.toList()); ordered = new ArrayList<>(all); sortOrdered(); - summaryIndex = 0; + + // restore prior search filtering + summaryIndex = lastSummaryIndex; + mode = lastMode; + if (lastSearchQuery != null && !lastSearchQuery.isEmpty()) { + String q = lastSearchQuery.toLowerCase(Locale.ROOT); + ordered = ordered.stream() + .filter(d -> { + String n = resolveName(d); + return n != null && n.toLowerCase(Locale.ROOT).contains(q); + }) + .collect(Collectors.toList()); + sortOrdered(); + } + + // attempt to restore detail view after initialization + if (mode == Mode.DETAIL && lastDetailEntityId != null) { + pendingDetailPage = lastDetailPage; + for (int i = 0; i < ordered.size(); i++) { + if (ordered.get(i).entityId.equals(lastDetailEntityId)) { + pendingDetailIndex = i; + break; + } + } + if (pendingDetailIndex < 0) { + mode = Mode.SUMMARY; + } + } } private String resolveName(EntityChatData data) { @@ -171,13 +221,20 @@ protected void init() { dummyField = new EditBox(font, bgX, bgY, 0, 0, Component.empty()); searchField = new EditBox(font, bgX + 46, bgY + 9, 208, 21, Component.empty()); - searchField.visible = false; - searchField.active = false; + searchField.visible = lastSearchVisible; + searchField.active = lastSearchVisible; + searchVisible = lastSearchVisible; + searchField.setValue(lastSearchQuery); searchField.setResponder(text -> { if (searchVisible) onSearchChanged(text); }); addRenderableWidget(searchField); + if (searchVisible) { + setFocused(searchField); + setInitialFocus(searchField); + searchField.setCursorPosition(searchField.getValue().length()); + } prevButton = createPageButton( bgX + PREV_X, bgY + PREV_Y, @@ -225,6 +282,14 @@ protected void init() { updateButtons(); requestDataForCurrentPages(); + + if (pendingDetailIndex >= 0) { + openDetail(pendingDetailIndex); + detailPage = Math.min(pendingDetailPage, detailTotalPages - 1); + updateButtons(); + requestDataForCurrentPages(); + pendingDetailIndex = -1; + } } private void updateButtons() { @@ -371,10 +436,7 @@ private void openDetail(int idx) { } } } else { - for (int i = Math.max(0, filtered.size() - 4); i < filtered.size(); i++) { - detailMessages.add(filtered.get(i)); - } - Collections.reverse(detailMessages); + detailMessages.addAll(filtered); } } @@ -388,24 +450,43 @@ private void openDetail(int idx) { } private void paginateSections() { + if (detailSections == null) { + detailSections = Collections.emptyList(); + } sectionPages = new ArrayList<>(); sectionRemainingSpace = 0; List page = new ArrayList<>(); - int used = 0; int headerHeight = Math.round(this.font.lineHeight * 1.12f) + 6 + 35; - for (Pair p : detailSections) { - int h = this.font.lineHeight; - h += measureWrappedHeight(p.value, PAGE_CONTENT_W, 0.92f, 3); - h += 4; - int available = sectionPages.isEmpty() ? PAGE_CONTENT_H - headerHeight : PAGE_CONTENT_H; - if (used + h >= available && !page.isEmpty()) { - sectionPages.add(page); - page = new ArrayList<>(); - used = 0; - available = PAGE_CONTENT_H; + int available = PAGE_CONTENT_H - headerHeight; + int used = 0; + for (Pair orig : detailSections) { + List lines = wrapLines(orig.value, PAGE_CONTENT_W, 0.92f); + int lineH = Math.round(this.font.lineHeight * 0.92f); + int idx = 0; + boolean first = true; + while (idx < lines.size()) { + if (used >= available) { + sectionPages.add(page); + page = new ArrayList<>(); + available = PAGE_CONTENT_H; + used = 0; + } + int labelH = first ? this.font.lineHeight : 0; + int maxLines = (available - used - labelH - 4) / lineH; + if (maxLines <= 0) { + sectionPages.add(page); + page = new ArrayList<>(); + available = PAGE_CONTENT_H; + used = 0; + continue; + } + int end = Math.min(idx + maxLines, lines.size()); + String valChunk = String.join(" ", lines.subList(idx, end)); + page.add(new Pair(first ? orig.label : null, valChunk)); + used += labelH + (end - idx) * lineH + 4; + idx = end; + first = false; } - page.add(p); - used += h; } if (!page.isEmpty()) { sectionPages.add(page); @@ -414,35 +495,46 @@ private void paginateSections() { sectionPages.add(Collections.emptyList()); sectionRemainingSpace = PAGE_CONTENT_H - headerHeight; } else { - int available = sectionPages.size() == 1 ? PAGE_CONTENT_H - headerHeight : PAGE_CONTENT_H; - sectionRemainingSpace = Math.max(0, available - used); + int availLast = sectionPages.size() == 1 ? PAGE_CONTENT_H - headerHeight : PAGE_CONTENT_H; + sectionRemainingSpace = Math.max(0, availLast - used); } } private void paginateMessages() { messagePages = new ArrayList<>(); int headerH = this.font.lineHeight + 2; - int minMsgH = this.font.lineHeight + 1 + Math.round(this.font.lineHeight * 0.9f) + 4; + int lineH = Math.round(this.font.lineHeight * 0.9f); + int minMsgH = this.font.lineHeight + 1 + lineH + 4; messagesOnSectionPage = sectionRemainingSpace >= headerH + minMsgH && !detailMessages.isEmpty(); int available = messagesOnSectionPage ? sectionRemainingSpace - headerH : PAGE_CONTENT_H - headerH; if (available < 0) available = 0; - List page = new ArrayList<>(); - int used = 0; + List page = new ArrayList<>(); + int remaining = available; for (ChatMessage m : detailMessages) { - int h = font.lineHeight + 1; - h += measureWrappedHeight(safe(m.message), PAGE_CONTENT_W - 4, 0.9f, 4) + 4; - if (used + h >= available && !page.isEmpty()) { - messagePages.add(page); - page = new ArrayList<>(); - used = 0; - available = PAGE_CONTENT_H - headerH; - } - if (used + h >= available && page.isEmpty()) { - // ensure progress even if single message exceeds available - available = PAGE_CONTENT_H - headerH; + List lines = wrapLines(safe(m.message), PAGE_CONTENT_W - 4, 0.9f); + boolean first = true; + int idx = 0; + while (idx < lines.size()) { + if (remaining <= 0) { + messagePages.add(page); + page = new ArrayList<>(); + remaining = PAGE_CONTENT_H - headerH; + } + int metaH = first ? this.font.lineHeight + 1 : 0; + int maxLines = (remaining - metaH - 4) / lineH; + if (maxLines <= 0) { + messagePages.add(page); + page = new ArrayList<>(); + remaining = PAGE_CONTENT_H - headerH; + continue; + } + int end = Math.min(idx + maxLines, lines.size()); + List chunkLines = new ArrayList<>(lines.subList(idx, end)); + page.add(new MessageChunk(m, chunkLines, first)); + remaining -= metaH + (end - idx) * lineH + 4; + idx = end; + first = false; } - page.add(m); - used += h; } if (!page.isEmpty()) { messagePages.add(page); @@ -579,41 +671,39 @@ private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, i } // append messages on same page if space was reserved if (messagesOnSectionPage && pageIndex == sectionPageCount - 1) { - lineY = renderMessagesPage(ctx, x, lineY, messagePages.get(0)); + lineY = renderMessagesPage(ctx, x, lineY, messagePages.get(0), true); } ctx.disableScissor(); } else { // messages pages int msgPage = pageIndex - sectionPageCount + (messagesOnSectionPage ? 1 : 0); - List msgs = messagePages.get(msgPage); + List msgs = messagePages.get(msgPage); ctx.enableScissor(x, y, x + PAGE_CONTENT_W, y + PAGE_CONTENT_H); - renderMessagesPage(ctx, x, y, msgs); + renderMessagesPage(ctx, x, y, msgs, msgPage == 0 && !messagesOnSectionPage); ctx.disableScissor(); } - int debugY = bgY + BG_HEIGHT - 6; - PoseHelper.push(ctx.pose()); - PoseHelper.translate(ctx.pose(), (float)x, (float)debugY); - PoseHelper.scale(ctx.pose(), 0.8f, 0.8f); - ctx.drawString(this.font, detailEntity.entityId, 0, 0, LIGHT_GRAY, false); - PoseHelper.pop(ctx.pose()); } - private int renderMessagesPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int startY, List msgs) { + private int renderMessagesPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int startY, List chunks, boolean showHeader) { int lineY = startY; - String header = detailEntity != null && detailEntity.death != null ? "Last Words" : "Recent Messages"; - ctx.drawString(this.font, header, x, lineY, LABEL_COLOR, false); - lineY += this.font.lineHeight + 2; - for (ChatMessage m : msgs) { - String speaker = m.sender == ChatDataManager.ChatSender.USER ? "You" : resolveName(detailEntity); - if (speaker == null || speaker.isBlank()) speaker = "Unknown"; - long ts = m.timestamp == null ? 0L : m.timestamp; - String ago = friendlyTime(System.currentTimeMillis() - ts) + " ago"; - int timeW = this.font.width(ago); - speaker = this.font.plainSubstrByWidth(speaker, PAGE_CONTENT_W - timeW - 2); - ctx.drawString(this.font, speaker, x, lineY, LABEL_COLOR, false); - ctx.drawString(this.font, ago, x + PAGE_CONTENT_W - timeW, lineY, LABEL_COLOR, false); - lineY += this.font.lineHeight + 1; - lineY = drawWrapped(ctx, safe(m.message), x + 4, lineY, PAGE_CONTENT_W - 4, 0.9f, 4, BODY_COLOR); + if (showHeader) { + String header = detailEntity != null && detailEntity.death != null ? "Last Words" : "Recent Messages"; + ctx.drawString(this.font, header, x, lineY, LABEL_COLOR, false); + lineY += this.font.lineHeight + 2; + } + for (MessageChunk mc : chunks) { + if (mc.showSpeaker) { + String speaker = mc.msg.sender == ChatDataManager.ChatSender.USER ? "You" : resolveName(detailEntity); + if (speaker == null || speaker.isBlank()) speaker = "Unknown"; + long ts = mc.msg.timestamp == null ? 0L : mc.msg.timestamp; + String ago = friendlyTime(System.currentTimeMillis() - ts) + " ago"; + int timeW = this.font.width(ago); + speaker = this.font.plainSubstrByWidth(speaker, PAGE_CONTENT_W - timeW - 2); + ctx.drawString(this.font, speaker, x, lineY, LABEL_COLOR, false); + ctx.drawString(this.font, ago, x + PAGE_CONTENT_W - timeW, lineY, LABEL_COLOR, false); + lineY += this.font.lineHeight + 1; + } + lineY = drawLines(ctx, mc.lines, x + 4, lineY, 0.9f, BODY_COLOR); lineY += 4; } return lineY; @@ -665,15 +755,22 @@ private static String lorem(int min, int max) { /** Draw a label then a wrapped value; returns next y. */ private int drawPair(net.minecraft.client.gui.GuiGraphics ctx, String label, String value, int x, int y, int widthPx) { - ctx.drawString(this.font, label, x, y, LABEL_COLOR, false); - y += this.font.lineHeight; - return drawWrapped(ctx, value, x, y, widthPx, 0.92f, 3, BODY_COLOR) + 4; // slight gap after pair + if (label != null) { + ctx.drawString(this.font, label, x, y, LABEL_COLOR, false); + y += this.font.lineHeight; + } + return drawWrapped(ctx, value, x, y, widthPx, 0.92f, BODY_COLOR) + 4; } - /** Wrapped text with scaling and maxLines, adds ellipsis if truncated. Returns next y. */ + /** Wrapped text with scaling. Returns next y. */ private int drawWrapped(net.minecraft.client.gui.GuiGraphics ctx, String text, int x, int y, - int maxWidthPx, float scale, int maxLines, int color) { - List lines = wrapLines(text, maxWidthPx, scale, maxLines); + int maxWidthPx, float scale, int color) { + List lines = wrapLines(text, maxWidthPx, scale); + return drawLines(ctx, lines, x, y, scale, color); + } + + private int drawLines(net.minecraft.client.gui.GuiGraphics ctx, List lines, int x, int y, + float scale, int color) { PoseHelper.push(ctx.pose()); PoseHelper.translate(ctx.pose(), (float)x, (float)y); PoseHelper.scale(ctx.pose(), scale, scale); @@ -686,16 +783,16 @@ private int drawWrapped(net.minecraft.client.gui.GuiGraphics ctx, String text, i return y + Math.round(drawnPx * scale); } - private int measureWrappedHeight(String text, int maxWidthPx, float scale, int maxLines) { - List lines = wrapLines(text, maxWidthPx, scale, maxLines); + private int measureWrappedHeight(String text, int maxWidthPx, float scale) { + List lines = wrapLines(text, maxWidthPx, scale); return (int)Math.ceil(lines.size() * this.font.lineHeight * scale); } - private List wrapLines(String text, int maxWidthPx, float scale, int maxLines) { + private List wrapLines(String text, int maxWidthPx, float scale) { int avail = (int)Math.floor(maxWidthPx / scale); List lines = new ArrayList<>(); String rest = text == null ? "" : text.trim(); - while (!rest.isEmpty() && lines.size() < maxLines) { + while (!rest.isEmpty()) { String piece = this.font.plainSubstrByWidth(rest, avail); int cut = piece.length(); if (cut <= 0) break; @@ -709,13 +806,7 @@ private List wrapLines(String text, int maxWidthPx, float scale, int max lines.add(piece.trim()); rest = rest.substring(Math.min(rest.length(), cut)).trim(); } - if (!rest.isEmpty() && !lines.isEmpty()) { - String last = lines.get(lines.size() - 1); - while (!last.isEmpty() && this.font.width(last + "…") > avail) { - last = last.substring(0, last.length() - 1); - } - lines.set(lines.size() - 1, last + "…"); - } + if (lines.isEmpty()) lines.add(""); return lines; } @@ -748,6 +839,17 @@ private Entity getEntity(String id) { } } + @Override + public void onClose() { + lastMode = mode; + lastSummaryIndex = summaryIndex; + lastDetailPage = detailPage; + lastDetailEntityId = detailEntity != null ? detailEntity.entityId : null; + lastSearchQuery = searchField != null ? searchField.getValue() : ""; + lastSearchVisible = searchVisible; + super.onClose(); + } + @Override public boolean isPauseScreen() { return false; From 0422b820ff801ace4ebddc2a6bec71817aef6691 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 24 Aug 2025 16:48:15 -0500 Subject: [PATCH 19/26] Adding top level images and hovers --- CHANGELOG.md | 1 + .../java/com/owlmaddie/ui/BookScreen.java | 9 ++++----- .../creaturechat/textures/ui/book/book.png | Bin 2646 -> 6391 bytes .../textures/ui/book/close-hover.png | Bin 0 -> 406 bytes .../creaturechat/textures/ui/book/close.png | Bin 0 -> 417 bytes .../textures/ui/book/index-hover.png | Bin 0 -> 266 bytes .../creaturechat/textures/ui/book/index.png | Bin 0 -> 267 bytes .../textures/ui/book/search-hover.png | Bin 0 -> 401 bytes .../creaturechat/textures/ui/book/search.png | Bin 0 -> 406 bytes 9 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/close-hover.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/close.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/index-hover.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/index.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/search-hover.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/search.png diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ccc122..e34c70c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to **CreatureChat™** are documented in this file. The form - Integrating entityType into chat data, and incorporating death into broadcast, login, and book pages - Adding entityName into chat data, for dead mobs - Remember book screen state when exiting (so book resumes exactly where you left it) + - Adding top buttons + hover states ## Unreleased diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 39efee4c..bcad62b0 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -83,13 +83,12 @@ private enum Mode { SUMMARY, DETAIL } private static final int NEXT_X = 267, NEXT_Y = 170, NEXT_W = 14, NEXT_H = 12; private static final int SEARCH_BTN_X = 10, SEARCH_BTN_Y = 9, SEARCH_BTN_W = 31, SEARCH_BTN_H = 21; private static final int CLOSE_X = 259, CLOSE_Y = 9, CLOSE_W = 30, CLOSE_H = 22; - private static final int PAGE_CONTENT_W = 116; // width of text block per page - private static final int PAGE_CONTENT_H = 124; // height of text block (for scissor) - private static final int PAGE1_X = 28, PAGE1_Y = 46; // left page top-left - private static final int PAGE2_X = 157, PAGE2_Y = 46; // right page top-left + private static final int PAGE_CONTENT_W = 106; // width of text block per page + private static final int PAGE_CONTENT_H = 122; // height of text block (for scissor) + private static final int PAGE1_X = 32, PAGE1_Y = 51; // left page top-left + private static final int PAGE2_X = 162, PAGE2_Y = 51; // right page top-left private static final int LABEL_COLOR = 0xFF6B4A3B; // warm brown, matches book UI private static final int BODY_COLOR = 0xFF2A2A2A; // dark text - private static final int LIGHT_GRAY = 0xFFB0B0B0; private static final Random RNG = new Random(); private static final int SUMMARY_ROWS_PER_PAGE = 4; // per single page diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/book.png b/src/main/resources/assets/creaturechat/textures/ui/book/book.png index deea842456e52d8bf9799312b0c63b07cc7aa738..5f31c4c8b1c48599a64e54cde7faa1268dcaa464 100644 GIT binary patch literal 6391 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 literal 2646 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 Ef}Cgm&j0`b diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/close-hover.png b/src/main/resources/assets/creaturechat/textures/ui/book/close-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..19d568cfb4fae9087b2c334c026e91fdb10f1586 GIT binary patch literal 406 zcmV;H0crk;P)K#pJrSzcqU%3$>MGXtxU0$c$t z8QUzXleVMU%JOX2^wcXiS6bf&nHNW1whw2oQ1rva54_RIw^AS@IYz z-V-AWXX9g#6ar=IQyD-Q*)cG~L6%S59}C7XF=A+v8~`&A-4P%Mfh;En5bXd^uy+N{ zhT8#i1jup_8wRo+7c&qW{2)C5@nGx60MqpF0|$&r zF-=kwfNX=Mc66ITi4Zv(qRZprljHz&%R#{o!Z1LNBZzhYHp@Y&9fT=y1knzF`4U8< zCpdIRAgd+BCb}qwSq!4F<#Tc!L9_#~S&HsEV$`7PAu9^d4WWb&4*^gqM@}{($MT*S zRd|I4Z<57o>yl#@*?JaVy$o)WfgJ!cWuO|R0BUrG=wqEk<^TWy07*qoM6N<$f;_gE AivR!s literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/close.png b/src/main/resources/assets/creaturechat/textures/ui/book/close.png new file mode 100644 index 0000000000000000000000000000000000000000..503aad7c8ea1c8ff2e3ec564df390684f5c8be71 GIT binary patch literal 417 zcmV;S0bc%zP)XLvfN%ljp5J7PYhC0{BQ-d zWSl+wn1PE;nBn=?zYQ!fi>|%~vVaow0ARbI7BSJoLVR|>Lja#5kQ}nBwMDV3-h1~a zT*6tI9nQwbASndmJY*R_7}+r}!$Fppg{gosOpF+sBnQ9@M0W(pK_JUv;O4uBFeX8o zXa|6TebEj?B852uWI2cp1EB1cWvT|_5~PWC0LUb4j-bSHkeMVo0GlH~Vjw`O&mp?V zZ~#c-i%*Eu4l{^cn&c3GrFL`+K}Gqr)3?xt3GzvD0J`O%UK#pJrSzcqU%3$>MGXtxU0$c$t z8QUzXleVMU%JOX2^wcXiS6bf&nHNW1#2(cK|#JhzSACWCvoDF@WNj zSU{5yK#pTV4k0N7Kt3bIa6*=YNJFlVDu|*QAjtuvX<#%BjHZEMnFeT56q8a) z!3!c#30Jb@F)4bRU9t@%N8{qFm%&XkumeB_3{;~O06#TLUlm(9 Q`~Uy|07*qoM6N<$g0ItJvj6}9 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/index.png b/src/main/resources/assets/creaturechat/textures/ui/book/index.png new file mode 100644 index 0000000000000000000000000000000000000000..fc9a310a71a319cad97b63979fb7532eba61d418 GIT binary patch literal 267 zcmV+m0rdWfP)XLvfN%ljp5J7PYhC0{BQ-d zWSl+wn1PE;nBn=?zYQ!fi>|%~vVaow0ARbI7BSJoLVR`%xC7u(fX|gMIb&m47!xlI zieq8{O+o-UjtM!0q!0l4j1K#pJrSzcqU%3$>MGXtxU0$c$t z8QUzXleVMU%JOX2^wcXiS6bf&nHNW1whw2$16d&SVD+ZJ^*K1&|#A z*enNG3=&5UVnU7~%K^xicLmO7fMJ+rW$ROkb_7LH0J1pOM-^r-*dZXxNdaVq04@uV z9rN%52QFnO;#742ipfNBC~^RLTqCCqBJF1&D-D3+9v0Uy8ri~Z$HfdiF|x=)IN2nJ z0LYi1xCU8*4Ul3vNIyv-01ILeEx!el{m>H|u|9`6o@fWaEC*>s&wen&$f1df0_2j7 znwEncOmqlH?4B4^cwNB=vIW#Ciq+Po vnT6O4UVQa3xJd?f0L&5C6w*oz)+hx4V+UnjG+XGy00000NkvXXu0mjf&DNS} literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/search.png b/src/main/resources/assets/creaturechat/textures/ui/book/search.png new file mode 100644 index 0000000000000000000000000000000000000000..76e09334bdac35804b92f578f0bcb9b59f43a129 GIT binary patch literal 406 zcmV;H0crk;P)XLvfN%ljp5J7PYhC0{BQ-d zWSl+wn1PE;nBn=?zYQ!fi>|%~vVaow0ARbI7BSJoLVR|>Lja#5Tyn<7vKUgJ;3Wl+ z9Rk=a2U!dfM-E~_jv>nd$d)hK@t6UIVV1>t5bp@G900QjWU;m=0~~|+qymZ@fNmMc zF);Ar(@z)^FHKbk;I#>dBt;HDk89+#fx|uwVY0FzD6T=4g8+<1Ho0rn9R_D*c4Q%( zY?4C&4j_nIc!iu zg+(j8Nv19&PmWn+>$&;vA-G8fb^yqffohZj09-(23@3JlCjbBd07*qoM6N<$g0q02 Ad;kCd literal 0 HcmV?d00001 From fe690bd3407d9ac14e5b2515bede672fb9f1aaea Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 24 Aug 2025 16:49:29 -0500 Subject: [PATCH 20/26] Fix sorting book index --- src/client/java/com/owlmaddie/ui/BookScreen.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index bcad62b0..ac743e0a 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -332,6 +332,12 @@ private void sortOrdered() { ordered.sort(Comparator.comparingLong(BookScreen::getLastInteraction).reversed()); } + @Override + public void tick() { + super.tick(); + sortOrdered(); + } + private boolean inside(double mx, double my, int x, int y, int w, int h) { return mx >= x && mx <= x + w && my >= y && my <= y + h; } From 8c4c4235842fde99e1c265202cfa5c8ac955ffb8 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 24 Aug 2025 17:10:39 -0500 Subject: [PATCH 21/26] Adding top buttons + hover states - integrated hover assets and new arrow buttons --- CHANGELOG.md | 2 +- .../java/com/owlmaddie/ui/BookScreen.java | 52 +++++++++++++++--- .../creaturechat/textures/ui/book/book.png | Bin 6391 -> 6356 bytes .../book/{close-hover.png => exit-hover.png} | Bin .../textures/ui/book/{close.png => exit.png} | Bin .../textures/ui/book/next-hover.png | Bin 275 -> 255 bytes .../creaturechat/textures/ui/book/next.png | Bin 268 -> 254 bytes .../textures/ui/book/previous-hover.png | Bin 270 -> 243 bytes .../textures/ui/book/previous.png | Bin 263 -> 247 bytes 9 files changed, 46 insertions(+), 8 deletions(-) rename src/main/resources/assets/creaturechat/textures/ui/book/{close-hover.png => exit-hover.png} (100%) rename src/main/resources/assets/creaturechat/textures/ui/book/{close.png => exit.png} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e34c70c7..7232873b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ All notable changes to **CreatureChat™** are documented in this file. The form - Integrating entityType into chat data, and incorporating death into broadcast, login, and book pages - Adding entityName into chat data, for dead mobs - Remember book screen state when exiting (so book resumes exactly where you left it) - - Adding top buttons + hover states + - Adding top buttons + hover states - integrated hover assets and new arrow buttons ## Unreleased diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index ac743e0a..25e90946 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -79,10 +79,15 @@ private enum Mode { SUMMARY, DETAIL } private boolean searchVisible; private Button prevButton; private Button nextButton; - private static final int PREV_X = 28, PREV_Y = 170, PREV_W = 14, PREV_H = 12; - private static final int NEXT_X = 267, NEXT_Y = 170, NEXT_W = 14, NEXT_H = 12; - private static final int SEARCH_BTN_X = 10, SEARCH_BTN_Y = 9, SEARCH_BTN_W = 31, SEARCH_BTN_H = 21; - private static final int CLOSE_X = 259, CLOSE_Y = 9, CLOSE_W = 30, CLOSE_H = 22; + private static final int PREV_X = 20, PREV_Y = 164, PREV_W = 32, PREV_H = 22; + private static final int NEXT_X = 244, NEXT_Y = 164, NEXT_W = 32, NEXT_H = 22; + private static final int INDEX_BTN_X = 10, INDEX_BTN_Y = 8, INDEX_BTN_W = 32, INDEX_BTN_H = 22; + private static final int SEARCH_BTN_X = 46, SEARCH_BTN_Y = 8, SEARCH_BTN_W = 32, SEARCH_BTN_H = 22; + private static final int EXIT_BTN_X = 258, EXIT_BTN_Y = 8, EXIT_BTN_W = 32, EXIT_BTN_H = 22; + private static final int SEARCH_FIELD_X = SEARCH_BTN_X + SEARCH_BTN_W + 5; + private static final int SEARCH_FIELD_Y = 9; + private static final int SEARCH_FIELD_W = EXIT_BTN_X - 5 - SEARCH_FIELD_X; + private static final int SEARCH_FIELD_H = 21; private static final int PAGE_CONTENT_W = 106; // width of text block per page private static final int PAGE_CONTENT_H = 122; // height of text block (for scissor) private static final int PAGE1_X = 32, PAGE1_Y = 51; // left page top-left @@ -219,7 +224,12 @@ protected void init() { dummyField = new EditBox(font, bgX, bgY, 0, 0, Component.empty()); - searchField = new EditBox(font, bgX + 46, bgY + 9, 208, 21, Component.empty()); + searchField = new EditBox(font, + bgX + SEARCH_FIELD_X, + bgY + SEARCH_FIELD_Y, + SEARCH_FIELD_W, + SEARCH_FIELD_H, + Component.empty()); searchField.visible = lastSearchVisible; searchField.active = lastSearchVisible; searchVisible = lastSearchVisible; @@ -345,12 +355,22 @@ private boolean inside(double mx, double my, int x, int y, int w, int h) { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (button == 0) { + if (inside(mouseX, mouseY, bgX + INDEX_BTN_X, bgY + INDEX_BTN_Y, INDEX_BTN_W, INDEX_BTN_H)) { + mode = Mode.SUMMARY; + detailEntity = null; + detailPage = 0; + summaryIndex = 0; + updateButtons(); + requestDataForCurrentPages(); + LOGGER.info("BookScreen: index button pressed"); + return true; + } if (inside(mouseX, mouseY, bgX + SEARCH_BTN_X, bgY + SEARCH_BTN_Y, SEARCH_BTN_W, SEARCH_BTN_H)) { toggleSearch(); return true; } - if (inside(mouseX, mouseY, bgX + CLOSE_X, bgY + CLOSE_Y, CLOSE_W, CLOSE_H)) { - LOGGER.info("BookScreen: close button pressed"); + if (inside(mouseX, mouseY, bgX + EXIT_BTN_X, bgY + EXIT_BTN_Y, EXIT_BTN_W, EXIT_BTN_H)) { + LOGGER.info("BookScreen: exit button pressed"); onClose(); return true; } @@ -559,6 +579,24 @@ protected void renderContent(net.minecraft.client.gui.GuiGraphics ctx, int mouse renderDetailPage(ctx, bgX + PAGE1_X, bgY + PAGE1_Y, detailPage); renderDetailPage(ctx, bgX + PAGE2_X, bgY + PAGE2_Y, detailPage + 1); } + + renderTopButtons(ctx, mouseX, mouseY); + } + + private void renderTopButtons(net.minecraft.client.gui.GuiGraphics ctx, int mouseX, int mouseY) { + blitButton(ctx, mouseX, mouseY, INDEX_BTN_X, INDEX_BTN_Y, INDEX_BTN_W, INDEX_BTN_H, + "book/index", "book/index-hover"); + blitButton(ctx, mouseX, mouseY, SEARCH_BTN_X, SEARCH_BTN_Y, SEARCH_BTN_W, SEARCH_BTN_H, + "book/search", "book/search-hover"); + blitButton(ctx, mouseX, mouseY, EXIT_BTN_X, EXIT_BTN_Y, EXIT_BTN_W, EXIT_BTN_H, + "book/exit", "book/exit-hover"); + } + + private void blitButton(net.minecraft.client.gui.GuiGraphics ctx, int mouseX, int mouseY, + int x, int y, int w, int h, String normal, String hover) { + boolean hov = inside(mouseX, mouseY, bgX + x, bgY + y, w, h); + ResourceLocation tex = textures.GetUI(hov ? hover : normal); + RenderPipelineHelper.blitGuiTexture(ctx, tex, bgX + x, bgY + y, 0, 0, w, h, w, h); } private void renderSummaryPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, int startIndex, int mouseX, int mouseY) { diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/book.png b/src/main/resources/assets/creaturechat/textures/ui/book/book.png index 5f31c4c8b1c48599a64e54cde7faa1268dcaa464..5fe84fb2701318b6a53e90c02bb29ad5d74b50b6 100644 GIT binary patch literal 6356 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 literal 6391 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 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/close-hover.png b/src/main/resources/assets/creaturechat/textures/ui/book/exit-hover.png similarity index 100% rename from src/main/resources/assets/creaturechat/textures/ui/book/close-hover.png rename to src/main/resources/assets/creaturechat/textures/ui/book/exit-hover.png diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/close.png b/src/main/resources/assets/creaturechat/textures/ui/book/exit.png similarity index 100% rename from src/main/resources/assets/creaturechat/textures/ui/book/close.png rename to src/main/resources/assets/creaturechat/textures/ui/book/exit.png diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/next-hover.png b/src/main/resources/assets/creaturechat/textures/ui/book/next-hover.png index 60a5a3ab7e867cb14b2aceb302bf4cdbbd785f0e..ab6594584ea5fd437a4d8c14492dacb36551514f 100644 GIT binary patch literal 255 zcmVUflZ*006FB_ zgslJM7(*2YfGj505kyA;a-4%KoV?{Wh(ZBiN01mDR0{!^OGpg~syhJY5MpVfqX1+e zda!?=dw_w{Oa{fSuDbmU<$HD$oh^x0h++$n14$h92VA*1Zk5E!r!006-}IcxCdch3L-002ovPDHLk FV1ik0UNQgx literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^AT|dF8<0HkD{mW+Vo7)Ob!1@J*w6hZkrl{iEDmya zV!U}$ryj^rNcITwWnidMV_;}#VPNZ6|OsjHb{;k*Y z`FGxdBjmQlpa1{=+b141Xg$#t36e23Ffe#ATPyjh$Sw2sL{a9$^=dN?9KBpFsW)$5 z_hm7+m@wtr0@v)PPh_1Rtv_$y=?hg)7?hcTK-jQL0tgsBI~x3Oe9U$bXeooItDnm{ Hr-UW|`+VVEHGaXqLJeqr1tFD#~50`A;FORpOiE~wGe>0hScbwx&vSiA(kdO3P1*; z2mA5O=NY6#xKZrdyz>S_=b>FhXG@|LqSylDfYOAoy9SCdsg{E@Q!NU}u^i+8lA-{l z0U11d^A*emMGYyzj%)@+4uGWzaxF)8$S8X>1O_Yw0A4#gM!t&3MF0Q*07*qoM6N<$ Eg8XV@m;e9( literal 268 zcmeAS@N?(olHy`uVBq!ia0vp^AT|dF8<0HkD{mW+Vo7)Ob!1@J*w6hZkrl{iEDmya zV!U}$ryj^rNcITwWnidMV_;}#VPNV)rX!1?^)i1!4`8xAq>cX9I%T;8Y%0gMzZ}t}UJ7{`XUNRQ%HOyDV8) z$#%1C!@r$9IzR4IeEbsIc1@XG;$szCq#!d8Ftqt9*(*(WBnq^R!PC{xWt~$(69A1s BPh0>1 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/previous-hover.png b/src/main/resources/assets/creaturechat/textures/ui/book/previous-hover.png index de0e4e52fb2862f76ff6b2ddf5a7b45e7aa2d3bb..4a36b8b616ca2a040f192c27f2404a5b8c840783 100644 GIT binary patch literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Ez!3HE3M$U5qQoB4|978;KlM^IZ7b_YZP(0w# z`S-qB%3t=E^Y=V8bN)H+ZpzG~PV1N*rWLDMJ8WT=bw2Kf!A!No2OJzN9g0L6WG;qopD%Mh>8f)O#{r*)R+n}CT0R{) rvvk-Sa+)oha%Q`m_O%^I;ACW|pK2HMgL9=F&=(Azu6{1-oD!MUkhrmJRr|5xYA z{4;*Q3}S*neQ2UlSgVhLt8V-E$EF>Eu@_%065(+b_rKpdqk})jrdB*j=}cB_aO#ef zi2pHeYw~hziy8!%35zE*8bG1a`YqX zA=v@cv>fCRlA-{gSJnNC|f>Dj5Au!M(002BFJp&HC_UHfr002ovPDHLkV1kK^Tzvol literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^AT|dF8<0HkD{mW+Vo7)Ob!1@J*w6hZkrl{iEDmya zV!U}$ryj^rNcITwWnidMV_;}#VPNUkhrmJRr|5xYA z{4-8q^f}QrX;qWN7vHIp1zIhdhu9@9iq5@TE9iXj=;d<3u*Xc|{`XsFaBP;knVa~x zSaRd8jbBeNy3O#5X<$8;VO!L&aaQ9)d%ac$+1ZK?%5qILK#LeWUHx3vIVCg!0Qp%? AQvd(} From 1a91cd3065ee9e2360fd6826b36aaac205014577 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 24 Aug 2025 18:44:20 -0500 Subject: [PATCH 22/26] Fix last message date, adding field to EntityChatData - for better sorting on BookScreen, and populating messages if they change --- .../com/owlmaddie/network/ClientPackets.java | 3 ++ .../java/com/owlmaddie/ui/BookScreen.java | 40 +++++++++++-------- .../com/owlmaddie/chat/EntityChatData.java | 14 +++++-- .../owlmaddie/chat/EntityChatDataLight.java | 2 + .../com/owlmaddie/network/ServerPackets.java | 1 + 5 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/client/java/com/owlmaddie/network/ClientPackets.java b/src/client/java/com/owlmaddie/network/ClientPackets.java index 778612af..ecd66d86 100644 --- a/src/client/java/com/owlmaddie/network/ClientPackets.java +++ b/src/client/java/com/owlmaddie/network/ClientPackets.java @@ -125,6 +125,7 @@ public static void register() { String sender_name = buffer.readUtf(32767); ChatDataManager.ChatSender sender = ChatDataManager.ChatSender.valueOf(sender_name); Map players = readPlayerDataMap(buffer); + long lastTs = buffer.readLong(); // Update the chat data manager on the client-side client.execute(() -> { // Make sure to run on the client thread @@ -146,6 +147,7 @@ public static void register() { chatData.status = status; chatData.sender = sender; chatData.players = players; + chatData.lastMessage = lastTs; // Play sound with volume based on distance (from player or entity) Mob entity = ClientEntityFinder.getEntityByUUID(client.level, entityId); @@ -238,6 +240,7 @@ public static void register() { existing.previousMessages = data.previousMessages; existing.born = data.born; existing.death = data.death; + existing.lastMessage = data.lastMessage; existing.entityType = data.entityType; existing.entityName = data.entityName; } else { diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index 25e90946..a5ccf6d0 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -71,6 +71,8 @@ private enum Mode { SUMMARY, DETAIL } private List> messagePages; private int sectionRemainingSpace; private boolean messagesOnSectionPage; + private int lastPrevMsgCount; + private String lastCurrentMsg; private final List all; private List ordered; @@ -185,11 +187,8 @@ private String resolveName(EntityChatData data) { } private static long getLastInteraction(EntityChatData data) { - List msgs = data.previousMessages; - if (msgs != null && !msgs.isEmpty()) { - Long t = msgs.get(msgs.size() - 1).timestamp; - return t == null ? 0L : t; - } + if (data.lastMessage != null) return data.lastMessage; + if (data.death != null) return data.death; return 0L; } @@ -346,6 +345,15 @@ private void sortOrdered() { public void tick() { super.tick(); sortOrdered(); + if (mode == Mode.DETAIL && detailEntity != null) { + int count = detailEntity.previousMessages == null ? 0 : detailEntity.previousMessages.size(); + String current = detailEntity.currentMessage; + if (count != lastPrevMsgCount || !Objects.equals(current, lastCurrentMsg)) { + int prevPage = detailPage; + buildDetailContent(); + detailPage = Math.min(prevPage, detailTotalPages - 1); + } + } } private boolean inside(double mx, double my, int x, int y, int w, int h) { @@ -425,6 +433,13 @@ private void openDetail(int idx) { if (idx < 0 || idx >= ordered.size()) return; detailEntity = ordered.get(idx); detailPage = 0; + buildDetailContent(); + mode = Mode.DETAIL; + updateButtons(); + requestDataForCurrentPages(); + } + + private void buildDetailContent() { detailSections = new ArrayList<>(); if (detailEntity.death != null) { String deathDate = Instant.ofEpochMilli(detailEntity.death) @@ -467,11 +482,9 @@ private void openDetail(int idx) { paginateSections(); paginateMessages(); - detailTotalPages = sectionPages.size() + messagePages.size() - (messagesOnSectionPage ? 1 : 0); - mode = Mode.DETAIL; - updateButtons(); - requestDataForCurrentPages(); + lastPrevMsgCount = detailEntity.previousMessages == null ? 0 : detailEntity.previousMessages.size(); + lastCurrentMsg = detailEntity.currentMessage; } private void paginateSections() { @@ -754,14 +767,7 @@ private int renderMessagesPage(net.minecraft.client.gui.GuiGraphics ctx, int x, private void requestDataForCurrentPages() { sortOrdered(); - if (mode == Mode.SUMMARY) { - for (int i = 0; i < SUMMARY_ROWS_PER_PAGE * 2; i++) { - int idx = summaryIndex + i; - if (idx >= ordered.size()) break; - UUID id = UUID.fromString(ordered.get(idx).entityId); - ClientPackets.requestEntityData(id); - } - } else if (detailEntity != null) { + if (mode == Mode.DETAIL && detailEntity != null) { UUID id = UUID.fromString(detailEntity.entityId); ClientPackets.requestEntityData(id); } diff --git a/src/main/java/com/owlmaddie/chat/EntityChatData.java b/src/main/java/com/owlmaddie/chat/EntityChatData.java index bd6b7bf7..0d75f835 100644 --- a/src/main/java/com/owlmaddie/chat/EntityChatData.java +++ b/src/main/java/com/owlmaddie/chat/EntityChatData.java @@ -61,6 +61,7 @@ public class EntityChatData { public List previousMessages; public Long born; public Long death; + public Long lastMessage; public String entityName; public String entityType; @@ -86,6 +87,7 @@ public EntityChatData(String entityId) { this.auto_generated = 0; this.previousMessages = new ArrayList<>(); this.born = System.currentTimeMillis();; + this.lastMessage = null; this.entityName = ""; this.entityType = null; @@ -102,6 +104,10 @@ public void postDeserializeInitialization() { if (this.legacyPlayerId != null && !this.legacyPlayerId.isEmpty()) { this.migrateData(); } + if (this.previousMessages != null && !this.previousMessages.isEmpty()) { + ChatMessage last = this.previousMessages.get(this.previousMessages.size() - 1); + this.lastMessage = last.timestamp; + } } // Migrate old data into the new structure @@ -688,8 +694,8 @@ public void addMessage(String message, ChatDataManager.ChatSender sender, Server // Add context-switching logic for USER messages only String playerName = player.getDisplayName().getString(); if (sender == ChatDataManager.ChatSender.USER && previousMessages.size() > 1) { - ChatMessage lastMessage = previousMessages.get(previousMessages.size() - 1); - if (lastMessage.name == null || !lastMessage.name.equals(playerName)) { // Null-safe check + ChatMessage lastMsg = previousMessages.get(previousMessages.size() - 1); + if (lastMsg.name == null || !lastMsg.name.equals(playerName)) { // Null-safe check boolean isReturningPlayer = previousMessages.stream().anyMatch(msg -> playerName.equals(msg.name)); // Avoid NPE here too String note = isReturningPlayer ? "" @@ -703,7 +709,9 @@ public void addMessage(String message, ChatDataManager.ChatSender sender, Server } // Add message to history - previousMessages.add(new ChatMessage(truncatedMessage, sender, playerName)); + ChatMessage chatMsg = new ChatMessage(truncatedMessage, sender, playerName); + previousMessages.add(chatMsg); + this.lastMessage = chatMsg.timestamp; // Log regular message addition LOGGER.info("Message added: status={}, sender={}, message={}, player={}, entity={}", diff --git a/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java b/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java index 38684525..259cc9ee 100644 --- a/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java +++ b/src/main/java/com/owlmaddie/chat/EntityChatDataLight.java @@ -19,6 +19,7 @@ public class EntityChatDataLight { public ChatDataManager.ChatSender sender; public Map players; public Long death; + public Long lastMessage; public String entityName; public String entityType; @@ -30,6 +31,7 @@ public EntityChatDataLight(EntityChatData fullData, String playerName) { this.status = fullData.status; this.sender = fullData.sender; this.death = fullData.death; + this.lastMessage = fullData.lastMessage; this.entityName = fullData.entityName; this.entityType = fullData.entityType; diff --git a/src/main/java/com/owlmaddie/network/ServerPackets.java b/src/main/java/com/owlmaddie/network/ServerPackets.java index 2e900c2d..bfd67954 100644 --- a/src/main/java/com/owlmaddie/network/ServerPackets.java +++ b/src/main/java/com/owlmaddie/network/ServerPackets.java @@ -489,6 +489,7 @@ public static void BroadcastEntityMessage(EntityChatData chatData) { buffer.writeUtf(chatData.status.toString()); buffer.writeUtf(chatData.sender.toString()); writePlayerDataMap(buffer, chatData.players); + buffer.writeLong(chatData.lastMessage != null ? chatData.lastMessage : 0L); // Send message to player PacketHelper.send(player, PACKET_S2C_ENTITY_MESSAGE, buffer); From c4f19c272a439ce4481d7b0f920f7248968f6421 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 24 Aug 2025 19:44:53 -0500 Subject: [PATCH 23/26] Adding deterministic random stickers to pages (based on UUID and page #). Stickers are still WIP. --- CHANGELOG.md | 1 + .../java/com/owlmaddie/ui/BookScreen.java | 123 +++++++++++++++++- .../textures/ui/book/stickers/apple.png | Bin 0 -> 510 bytes .../textures/ui/book/stickers/blue flower.png | Bin 0 -> 409 bytes .../textures/ui/book/stickers/blue grass.png | Bin 0 -> 295 bytes .../textures/ui/book/stickers/cloud 1.png | Bin 0 -> 437 bytes .../textures/ui/book/stickers/clouds 1.png | Bin 0 -> 253 bytes .../ui/book/stickers/cresent moon 2.png | Bin 0 -> 280 bytes .../ui/book/stickers/cresent moon.png | Bin 0 -> 205 bytes .../textures/ui/book/stickers/crop.py | 117 +++++++++++++++++ .../ui/book/stickers/crystal yellow.png | Bin 0 -> 296 bytes .../textures/ui/book/stickers/dandelion.png | Bin 0 -> 569 bytes .../ui/book/stickers/dark flower .png | Bin 0 -> 465 bytes .../textures/ui/book/stickers/diam 2.png | Bin 0 -> 349 bytes .../ui/book/stickers/diamond pickaxe.png | Bin 0 -> 332 bytes .../ui/book/stickers/diamond sword.png | Bin 0 -> 463 bytes .../textures/ui/book/stickers/diamonds.png | Bin 0 -> 492 bytes .../textures/ui/book/stickers/eclipse.png | Bin 0 -> 260 bytes .../ui/book/stickers/enchant table.png | Bin 0 -> 653 bytes .../textures/ui/book/stickers/feather .png | Bin 0 -> 249 bytes .../textures/ui/book/stickers/grass 2.png | Bin 0 -> 402 bytes .../textures/ui/book/stickers/grass block.png | Bin 0 -> 300 bytes .../ui/book/stickers/grass flowers.png | Bin 0 -> 383 bytes .../textures/ui/book/stickers/ink blot.png | Bin 0 -> 359 bytes .../textures/ui/book/stickers/moon.png | Bin 0 -> 259 bytes .../textures/ui/book/stickers/moon2.png | Bin 0 -> 214 bytes .../ui/book/stickers/portal swirls.png | Bin 0 -> 303 bytes .../textures/ui/book/stickers/potion1.png | Bin 0 -> 269 bytes .../textures/ui/book/stickers/potion2.png | Bin 0 -> 497 bytes .../textures/ui/book/stickers/pumpkin .png | Bin 0 -> 393 bytes .../ui/book/stickers/purple blue flower.png | Bin 0 -> 467 bytes .../textures/ui/book/stickers/red flower.png | Bin 0 -> 470 bytes .../ui/book/stickers/ruined_portal.png | Bin 0 -> 645 bytes .../ui/book/stickers/star sparkles.png | Bin 0 -> 228 bytes .../textures/ui/book/stickers/sun 2.png | Bin 0 -> 474 bytes .../textures/ui/book/stickers/sun 3.png | Bin 0 -> 346 bytes .../textures/ui/book/stickers/sun.png | Bin 0 -> 156 bytes .../textures/ui/book/stickers/sunflower.png | Bin 0 -> 405 bytes .../textures/ui/book/stickers/tnt.png | Bin 0 -> 548 bytes .../textures/ui/book/stickers/tnt1.png | Bin 0 -> 542 bytes 40 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/apple.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/blue flower.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/blue grass.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/cloud 1.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/clouds 1.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/cresent moon 2.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/cresent moon.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/crop.py create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/crystal yellow.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/dandelion.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/dark flower .png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/diam 2.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/diamond pickaxe.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/diamond sword.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/diamonds.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/eclipse.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/enchant table.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/feather .png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/grass 2.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/grass block.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/grass flowers.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/ink blot.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/moon.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/moon2.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/portal swirls.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/potion1.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/potion2.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/pumpkin .png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/purple blue flower.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/red flower.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/ruined_portal.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/star sparkles.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/sun 2.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/sun 3.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/sun.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/sunflower.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/tnt.png create mode 100644 src/main/resources/assets/creaturechat/textures/ui/book/stickers/tnt1.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 7232873b..2545e3b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to **CreatureChat™** are documented in this file. The form - Adding entityName into chat data, for dead mobs - Remember book screen state when exiting (so book resumes exactly where you left it) - Adding top buttons + hover states - integrated hover assets and new arrow buttons + - Adding deterministic random stickers to pages (based on UUID and page #) ## Unreleased diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index a5ccf6d0..b5db1dd8 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -27,9 +27,12 @@ import net.minecraft.world.entity.player.Player; import java.time.Instant; import java.time.ZoneId; +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.server.packs.resources.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @@ -118,6 +121,53 @@ private static class MessageChunk { } } + private static class Sticker { + final ResourceLocation tex; + final int w; + final int h; + Sticker(ResourceLocation t, int w, int h) { + this.tex = t; + this.w = w; + this.h = h; + } + } + + private static class Rect { + final int x, y, w, h; + Rect(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + boolean intersects(Rect o, int pad) { + return x < o.x + o.w + pad && x + w + pad > o.x && y < o.y + o.h + pad && y + h + pad > o.y; + } + } + + private static final Rect LEFT_TOP_AREA_REL = new Rect(117, 43, 30, 24); + private static final Rect LEFT_BOTTOM_AREA_REL = new Rect(44, 154, 100, 27); + private static final Rect RIGHT_TOP_AREA_REL = new Rect(240, 42, 30, 24); + private static final Rect RIGHT_BOTTOM_AREA_REL = new Rect(156, 154, 100, 27); + + private static final List STICKERS = new ArrayList<>(); + + private static void loadStickers() { + if (!STICKERS.isEmpty()) return; + var rm = Minecraft.getInstance().getResourceManager(); + try { + rm.listResources("textures/ui/book/stickers", loc -> loc.getPath().endsWith(".png")) + .forEach((loc, res) -> { + try (var in = res.open()) { + NativeImage img = NativeImage.read(in); + STICKERS.add(new Sticker(loc, img.getWidth(), img.getHeight())); + } catch (IOException ignored) { + } + }); + } catch (Exception ignored) { + } + } + public BookScreen() { super(Component.literal("Creature Log")); ChatDataManager mgr = ChatDataManager.getClientInstance(); @@ -589,8 +639,9 @@ protected void renderContent(net.minecraft.client.gui.GuiGraphics ctx, int mouse renderSummaryPage(ctx, bgX + PAGE1_X, bgY + PAGE1_Y, summaryIndex, mouseX, mouseY); renderSummaryPage(ctx, bgX + PAGE2_X, bgY + PAGE2_Y, summaryIndex + SUMMARY_ROWS_PER_PAGE, mouseX, mouseY); } else if (detailEntity != null) { - renderDetailPage(ctx, bgX + PAGE1_X, bgY + PAGE1_Y, detailPage); - renderDetailPage(ctx, bgX + PAGE2_X, bgY + PAGE2_Y, detailPage + 1); + Set used = new HashSet<>(); + renderDetailPage(ctx, bgX + PAGE1_X, bgY + PAGE1_Y, detailPage, used); + renderDetailPage(ctx, bgX + PAGE2_X, bgY + PAGE2_Y, detailPage + 1, used); } renderTopButtons(ctx, mouseX, mouseY); @@ -672,10 +723,76 @@ private void renderSummaryPage(net.minecraft.client.gui.GuiGraphics ctx, int x, ctx.disableScissor(); } - private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, int pageIndex) { + private void renderStickers(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, int pageIndex, Set used) { + loadStickers(); + if (STICKERS.isEmpty() || detailEntity == null) return; + + UUID uuid; + try { + uuid = UUID.fromString(detailEntity.entityId); + } catch (Exception e) { + return; + } + long seed = uuid.getMostSignificantBits() ^ uuid.getLeastSignificantBits() ^ (long) (pageIndex + 1); + Random rand = new Random(seed); + + List placed = new ArrayList<>(); + List blocked = List.of( + new Rect(bgX + PREV_X, bgY + PREV_Y, PREV_W, PREV_H), + new Rect(bgX + NEXT_X, bgY + NEXT_Y, NEXT_W, NEXT_H), + new Rect(bgX + INDEX_BTN_X, bgY + INDEX_BTN_Y, INDEX_BTN_W, INDEX_BTN_H), + new Rect(bgX + SEARCH_BTN_X, bgY + SEARCH_BTN_Y, SEARCH_BTN_W, SEARCH_BTN_H), + new Rect(bgX + EXIT_BTN_X, bgY + EXIT_BTN_Y, EXIT_BTN_W, EXIT_BTN_H) + ); + + Rect content = new Rect(x, y, PAGE_CONTENT_W, PAGE_CONTENT_H); + Rect[] areaRels = (pageIndex % 2 == 0) + ? new Rect[]{LEFT_TOP_AREA_REL, LEFT_BOTTOM_AREA_REL} + : new Rect[]{RIGHT_TOP_AREA_REL, RIGHT_BOTTOM_AREA_REL}; + + for (Rect rel : areaRels) { + Rect area = new Rect(bgX + rel.x, bgY + rel.y, rel.w, rel.h); + int count = rand.nextInt(2); // 0-1 sticker per area + for (int i = 0; i < count; i++) { + Sticker st = null; + for (int attempt = 0; attempt < 20 && st == null; attempt++) { + Sticker cand = STICKERS.get(rand.nextInt(STICKERS.size())); + if (!used.contains(cand.tex)) { + st = cand; + } + } + if (st == null || st.w > area.w || st.h > area.h) continue; + boolean done = false; + for (int attempt = 0; attempt < 20 && !done; attempt++) { + int sx = area.x + rand.nextInt(area.w - st.w + 1); + int sy = area.y + rand.nextInt(area.h - st.h + 1); + Rect r = new Rect(sx, sy, st.w, st.h); + boolean collide = false; + for (Rect p : placed) { + if (r.intersects(p, 4)) { collide = true; break; } + } + if (!collide) { + for (Rect b : blocked) { + if (r.intersects(b, 0)) { collide = true; break; } + } + } + if (!collide && !(r.x >= content.x && r.x + r.w <= content.x + content.w && r.y >= content.y && r.y + r.h <= content.y + content.h)) { + RenderPipelineHelper.blitGuiTexture(ctx, st.tex, sx, sy, 0, 0, st.w, st.h, st.w, st.h); + placed.add(r); + used.add(st.tex); + done = true; + } + } + } + } + } + + private void renderDetailPage(net.minecraft.client.gui.GuiGraphics ctx, int x, int y, int pageIndex, Set used) { if (pageIndex >= detailTotalPages) return; if (detailEntity == null) return; + renderStickers(ctx, x, y, pageIndex, used); + Player player = Minecraft.getInstance().player; String playerName = player != null ? player.getDisplayName().getString() : ""; PlayerData pData = detailEntity.getPlayerData(playerName); diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/apple.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/apple.png new file mode 100644 index 0000000000000000000000000000000000000000..3eaeb2007cc0b2f9e4995aaf0f74b861bc380555 GIT binary patch literal 510 zcmVXod>*rx3Cxr0sx*{K|BTK1c3RYwo)o4A zEim6^Ss$VU&ys-9h7-T(nze4(ghkVV13)+xMmQB#0M5Rug7@$vOCNrl4Ss$pfeyHF z*-*|7wj!=}K}m@`R&tE3ExF$99M|A4SPD3;#Eja1iG0xug-npQJ8U1Q27+1>#VO&ZenST5+eL*JI=`20Wd-h_c4kAE`!c>EJZEOmMAf0DusW)M1n8PsAq6(vF#vBerJ z=G?6-{{M5ALD;hDSB6JZxEavZ!VQB3HZu4%=PtuPcNqqzoloFmFg{Fep5sM^ zN&EE~e$Bbdu=8jKBO`LGbuH3B4TGIe7)q9Yf%AK!c^Hsg$|1{+VjvSSu|^2wIbOt! z01jDpq7A^NmZC7z*SNrNx`Y=V2{>aD7HjCS35#W%v574Wp&JDA8Z71rX7rll+5gE6 zLkz?0B_S5hj-1ShEx=(J9v8sK=wX)s8D=B#%i?apGxfzA1`G@gcS$eLb{_3u{59t; zJpI{AGU5#{tP!yLLC$|yg(-~Wl(Y=k3}c`sAlEX1^@s6%>pGE0004eNkl?8-EYCnR(wJhIpfpP=@BsN>UiV&$ zJ_53l`fdnD=`)uXN(HZ~g*`0?#DH{NI81V{;=ui3Cpz?=Iue*#rPM5x56Hpm?JBvR zlwSAV0i4Oqqe!ei*)G|+aQh<|<)v1W>*ds6K z3Y>daly?DSgV-xMS7BsqsyFFKavEuh*Aiqq_DJU+CdHP=GO2f=%Ah$sa_hr8&8cLp zPUdPIiK^XY?sC2nQWdTUacCW@M$lubs}+=>amnvLi;af!o8q$h00000NkvXXu0mjf DgHmV! literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/cresent moon 2.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/cresent moon 2.png new file mode 100644 index 0000000000000000000000000000000000000000..e3776b528aa19e15c902bd975c3eb24ffe69c562 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1r0#pVIEGZ*N=`YzIB8PXzsHki zo3HN|oNb}3=lrT_wWI_P%<77kOi6tjc*UmfkHG4MJuldk7<~k(E;(BsiIM$TZ|A2ydKKu#->HAn=h^$@Rn? zXAVRN>&we0G_o3Patq=)V8CPf>;kV-f9#&bHob;5Gew#~HqUr=_I2`|EjhBTf(+h! zDwG*!?#;YuwPq*7;n@b)6S*sxlYRFHHqBW&BeCiEdHMW5Uw-*F@7*oi#+LM1?X4HD c*J)#h>5r>5xxbss0=>%M>FVdQ&MBb@0MZ6<0ssI2 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/cresent moon.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/cresent moon.png new file mode 100644 index 0000000000000000000000000000000000000000..588f28456ea0a7d6dd01699fefe5c6f4b2da3546 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv@!3HGlXG}j1q^5YfIEGZ*N=`Yz7-cQ?@3?As ztl;?$-#LQQ6)(QD;{gHh^t{9iW`S;j#b3|Fu4yw`B+X*XBAH+;kfyPz(Wix{X{XRL zsgwn|i+Y4)fLtY(3A`yvVZwUX6`9q%Vp`u>sj+D_O-za?b+vhXX=b#B5zDlS&N9h$ zF%1uWy<*zuES)jIn<<7(XtsG~cve8vJVu5S%VXs7rpc`Vx{txr)z4*}Q$iB}whBV7 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/crop.py b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/crop.py new file mode 100644 index 00000000..27eb9aed --- /dev/null +++ b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/crop.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +import argparse, os, sys +from PIL import Image + +def iter_pngs(paths, recursive): + for p in paths: + if os.path.isdir(p): + if recursive: + for root, _, files in os.walk(p): + for name in files: + if name.lower().endswith(".png"): + yield os.path.join(root, name) + else: + for name in os.listdir(p): + full = os.path.join(p, name) + if os.path.isfile(full) and name.lower().endswith(".png"): + yield full + else: + if p.lower().endswith(".png"): + yield p + +def expand_box(bbox, w, h, pad): + if not bbox or pad <= 0: + return bbox + l, t, r, b = bbox + return (max(0, l - pad), max(0, t - pad), min(w, r + pad), min(h, b + pad)) + +def crop_png(path, out_path, threshold=0, pad=0, dry_run=False): + try: + with Image.open(path) as im: + # Ensure alpha channel is present (handles P with tRNS too) + if im.mode != "RGBA": + im = im.convert("RGBA") + w, h = im.size + alpha = im.getchannel("A") + # Build a binary mask of "visible" pixels by alpha threshold + if threshold > 0: + mask = alpha.point(lambda p: 255 if p > threshold else 0, mode="L") + else: + mask = alpha + bbox = mask.getbbox() # (left, upper, right, lower) or None + + if bbox is None: + print(f"SKIP (fully transparent): {path}") + return False + + bbox = expand_box(bbox, w, h, pad) + + if bbox == (0, 0, w, h): + print(f"OK (already tight): {path} [{w}x{h}]") + # Still write if out_path differs (e.g., copying to out dir) + if out_path and os.path.abspath(out_path) != os.path.abspath(path): + if not dry_run: + os.makedirs(os.path.dirname(out_path), exist_ok=True) + im.save(out_path, format="PNG", optimize=True) + return False + + cropped = im.crop(bbox) + cw, ch = cropped.size + msg = f"{path} [{w}x{h}] -> [{cw}x{ch}]" + if dry_run: + print("DRY RUN:", msg) + return True + + os.makedirs(os.path.dirname(out_path), exist_ok=True) + # Save atomically when overwriting + tmp = out_path + ".tmp" + cropped.save(tmp, format="PNG", optimize=True) + os.replace(tmp, out_path) + print("TRIM:", msg) + return True + except Exception as e: + print(f"ERROR: {path}: {e}", file=sys.stderr) + return False + +def main(): + ap = argparse.ArgumentParser( + description="Trim transparent margins from PNG files." + ) + ap.add_argument("paths", nargs="+", help="Files or directories to process") + ap.add_argument("-r", "--recursive", action="store_true", + help="Recurse into subdirectories") + ap.add_argument("-o", "--out-dir", default=None, + help="Write results under this directory instead of overwriting files") + ap.add_argument("-t", "--threshold", type=int, default=0, + help="Alpha threshold 0-255 (default 0: any nonzero alpha is kept)") + ap.add_argument("-p", "--pad", type=int, default=0, + help="Extra padding (pixels) to keep around content") + ap.add_argument("-n", "--dry-run", action="store_true", + help="Show what would change without writing files") + args = ap.parse_args() + + # If a single directory is provided and an out-dir is used, preserve structure + base_dir_for_rel = None + if args.out_dir and len(args.paths) == 1 and os.path.isdir(args.paths[0]): + base_dir_for_rel = os.path.abspath(args.paths[0]) + + changed = 0 + total = 0 + for src in iter_pngs(args.paths, args.recursive): + total += 1 + if args.out_dir: + if base_dir_for_rel and os.path.abspath(src).startswith(base_dir_for_rel + os.sep): + rel = os.path.relpath(src, start=base_dir_for_rel) + out_path = os.path.join(args.out_dir, rel) + else: + out_path = os.path.join(args.out_dir, os.path.basename(src)) + else: + out_path = src + + if crop_png(src, out_path, threshold=args.threshold, pad=args.pad, dry_run=args.dry_run): + changed += 1 + + print(f"\nDone. Examined {total} file(s). Trimmed {changed} file(s).") + +if __name__ == "__main__": + main() diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/crystal yellow.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/crystal yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..81277a9cb51de5763f56449f86fc11e6dedd3165 GIT binary patch literal 296 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv}!3HE>9xS;9q~3eFIEGZ*N={h7P&0eN*ZGZ7 z4{o=t`TyU3{V)Fu4o+MNYzGZ|Y?aMg=bYr@x&K@);epnSw$OQ3{o5O#9%W}^ixp*S zJ6$dT0{iAy9Xil*R)Gx&&ZV&L(fgyU$Y9RFHe-V%3v&yP3%kz_9;M?BYBg*VvlNcZ z&Zs-i(DGy=?_seSOQg2^d1LLul3*&(?HI#)S)x9FSCf)i{x8+GCr7m=G`P)X1^@s6JPCf300062Nkl0LWSu&QQUBs?4??>}(``8uI7-W0h{ZPBwV}02hXXuU8XcpZ((w0EF*5 zdz5SU$yK>7fNTZS`utw-?O#PUMI5<4*qljhUbbj&zC=2ZOq%u!z&+@Un|!S^ZUP2q zYrcf6DKjz;J{^9e@>5;^|9trqfCKBDkpnWJa}&r z>nap)wqgc^BBi)0bRh*pg-UPWA`~Jna*A#|LRTKaRgRENPLYKq5VCNaWd$V#aUr-W zB1M=k+L6iR%_QoB5Hf#$-+S+yBn(7&&Xmi~Q8r_^GAtdu<9g`09w~T}007)QbaBda z2%%eo6gcj;#!a3xv&_)8Lh_V^8`$Jx=7R&_KnhXGMqjwGfX&jayLEtkn|ML7;*|%KpXaNDFLIy3@ zJEU>Q?Ea9CUB?JdWEC00000NkvXX Hu0mjfIAq5R literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/diam 2.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/diam 2.png new file mode 100644 index 0000000000000000000000000000000000000000..8abdef1b404855ec021e811c9564a3902eea1f2e GIT binary patch literal 349 zcmV-j0iyniP)`UEZx#p#xS0gEr--)=N01gj+W}A9WUYzkKvyVl(1=b*)rk-1NNz4f! zO_z+O%g)dB!d$B^sA*?)76WpD>GhG_*;*D+ARP^osv^tFwih$x%d9^X7HlmGQb}|) vsI#-sAvL#0mX~dom#w}Rp-r$W?n?RwEd+qTyf;R800000NkvXXu0mjf_6wBm literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/diamond pickaxe.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/diamond pickaxe.png new file mode 100644 index 0000000000000000000000000000000000000000..4b8110caa635a4720e1cd385faead7ebb96c8312 GIT binary patch literal 332 zcmV-S0ki&zP)~hBo}nwDSg7^_ zg)T!kW?YmRGig%mgDmo1e&2+b2&pG@n*)tUwv&0z5HCMpY?-3f7}3x z|3C+?gMJX%-CUl&yEqH`!+9aRJKO)on?k_Cc4OJzN)OjT15EirB+{zZ^k}wh6v3jB e?BBI11o#0=nu3hOG;A*b0000_>eIlNdqR?q0Uoz`Ng2{if%7Ul8 zJ}Il1T0+*#%sNS-QikU+9coOr(zVdHP%46ny)q_S9IvmEgGZ?dCT1ogd@un1GAt4v z9kMJeHD&6!3;;~Kh3NB<9PbH8#E~Adi_#TRwZcw9n1E=*jax-iX1^@s6F&7Tg0005ANklY7v4c~$cD8iXr9=M$8&|h1O2{At zgqzHwUS^Yqtr_(P96DJ4-zGwNijc+*r9AU3V2P6bF* z%wo<&Le*$WXPF0;f$@zj`s(`@fC$DW1OV{5BNW5eitIZ!nv0JG^rH)`gHa~Oxfd)8 z(;qMK{nO2yGh(gCR%Xtns(~n^(k{rQP}L%`mdYJ+FrR21b&}~(krlqA-04s{in*)2 iuj>1*hlB27-u?mT^ZQ`z&aq|y0000Guz*Cu>ec!yD z>@s9@++XIf*7Yq*^)@-Pu`2Esm{YJO1d#b~I!BDh{pw&oh=qWZ834AMoqHRrAcb~` zcr5?`q|h){xzRZa3IQpxZz)Ou8mpM3q;VSZkTqkFeom5w)T96a-t{e$LVp%%rRXzW z&l~&SIJIf~6pOVFZ$}E9G}B!XAcB;xm=@9pBq2U*oQ6d$G?U&KyJ11k+~|=20000< KMNUMnLSTY@ZE30i literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/enchant table.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/enchant table.png new file mode 100644 index 0000000000000000000000000000000000000000..84050433af498a4928a6c2dc14b80266ed8c9ad3 GIT binary patch literal 653 zcmV;80&@L{P)X1^@s6+90*i00071NklecYQ54+QRe=e*zlf9|{IMZpqLU#Nv3 zo|IlFz0?RJ>I=1g|7qHO8n9iy_WF@Y)FBe6XEW5Z8GQ|qD5i+|LajtGrRS|R07ij6 z5qxAaEL@*Eeb9DKzmDf@90xtbvGXT-DHl7pwrX|SOfTi@dBfzlt4xo3Nmo=;;q7M2 zKAP(O-aoUyEdYmH$6EmJj*cP|Ih>3D@afiy6zWw=0Bo+$*-uMgtA)=kK)9zi_PYaO$1;zpQl=A;PJbK(7}Nre*F60{rCsAY6P}wx>Jwe&HTbD zUbQ53D}`@1ccdcKLW5^hF&#QMFl4H+y6)(FV%Vjx`&XaSYdHAN(41O3u)Way)BSM{ zUQd}`tZbWPp&dctY0*UP#9sE)ffYJ>;kKd=k?1eV*ooGSHs&&?8dU#qiQI@pjPOp- nws4tp8xLrTHQDUH;%PU24V55j58 zs?!iIUk{=}5@G=*M1)F+2au2=v`4&vhM4!rQ%wwTPm}hLl*WU205y??)Bqx6-HTe_ z=htzw+#`JWQ6Ph?M=RoCq;DkZP_@^!L;&CozIu^bvOinb00000NkvXXu0mjf*KuaC literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/grass 2.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/grass 2.png new file mode 100644 index 0000000000000000000000000000000000000000..2019feffbb723480dbf565dcb4fbf9efb7107ecb GIT binary patch literal 402 zcmV;D0d4+?P)VO~?#XZ)BJy zq-Lu_A&O|Px&FMCr>-yeK6&r1Ie{jQ4z7K(FNe^ z`QA4HTJ0vwaI-U5usq+7id0yIej8nCVP19zV9G^jGLLoP3>Eg85OvqL{Pf6q(V5I~JyH7RjjUwy z2&#ku@c8cKj+qQ~ysV26^8dNKdde(N&noX`M|Y=bP$pEk)o!wOHz^(^b07*qoM6N<$g4lqohX4Qo literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/grass block.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/grass block.png new file mode 100644 index 0000000000000000000000000000000000000000..77fbac6518dbfe674dcfbf7d5accaaaae431da14 GIT binary patch literal 300 zcmeAS@N?(olHy`uVBq!ia0vp^qChOd!3HFgyvvM%)Mrl@$B>F!$tem9G1=*V-JiYB z^GjA)(mruQB3IP(z$5Z8msK+?lsC35WUgs5o|GkF2qd#5><&jJw6#V$Jxab>=r)O` z`Q_`GMf`fln350hC|@hsk$BNY<5Br`-(Tk!8GUDC27<$LQ+OBg98Z;ay}LMIn#iH0 zEj;avciIdSs^(94*uShm*o0^K!W}&7#|)a<7D@m6Bl~e>Q)eSvgIb%>p-b0h?K@i` zDp%Ka|F>{;t-)cFB`2mgg204_KX{T85)uNwok~t|ImdZo(u=mmyG3RvT6EjRv>oO- tnl5qBN$PGvNyAy7xsQxw<|i63FnF@m?oalK*8+N3_Fbjs#jTpXP81G?DFq3D=^e?X9mt7BTaIOt$(5cD6Ut7ItzoVvLg=#UVC z;85saIHxPgm)w1I=sSkvdEWc@aso^i<*fpMcqsq??^iDZJLG@^>iB(yL2ryQa}@wA zrq@=1;DEYmvlQ`1~1fXGx({Sj`+%Gp9Y{`?GTe0Gpdr&@JeiwH^_L z9d5n=0MAbs&2>9^U7;?}Kq|N#JOXf9Kz5H!f--X<(}D%EKL{1<3S_}l@Vj2&Kato| z_68Pt5-I?Ad-$*({9fBZdd*s39h1NWrofZoooGBloPdH&K;yG%5@;|7G=fb68WWHO dn*GSk#3K@2} z-gf!_{=CArE|lYgN`P`e$7w#!ZtFr#4Oq$~82`8b%8ae=hGTySA+#lt^IP4+J5`fFV zYq{CE04)mF^wI>xjWWxc-bJ-^oaSAdEz6N-k5+C%Hp+;t-e78F6BCz~S|#4rg_v}>uKCKop0I?-aQEO_*-0TrHOe*e zfR)HutfW=fQ_QLL^e6(f$dP*Ikhh1axjXe;ntCkd*cZAT(a}nVMymh-002ovPDHLk FV1oK~r40Z8 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/moon.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/moon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a9f9edc717b07eac57f27a8ba627a134220f2e0 GIT binary patch literal 259 zcmV+e0sQ`nP))Yt@^>(pCNR1%d0{gz;l&gIh4(zD>ijqorBu;i?-$ogn!NtjlXf674b9`X0ODo#fN zL-V?leoFZ~dXy7T^5^)7@sUzzGZ%q1n|Nev%;N;vt#Njry#Quvh2@K16XpN_002ov JPDHLkV1m@Uaz6k7 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/moon2.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/moon2.png new file mode 100644 index 0000000000000000000000000000000000000000..09ad40e042bf78ea88fa7bef9f06c137f7804525 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2e8xt=bLAr-fhQw}gjS&RL17hk=u zBmKy-X{^(qy(p2_Ii*SAU2k7qi~m(|p!|IoWqjW&6Y_F+AMXdv?ya^NEFl*)K6o`L`Fy6?0002?NklUE01<|)bY+&^Vrm#lpngs1#xO^muvt4002ovPDHLkV1iPx BeX9Tf literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/potion1.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/potion1.png new file mode 100644 index 0000000000000000000000000000000000000000..3ae4345ec5b3d623cd78536fdac4df886556f1b7 GIT binary patch literal 269 zcmV+o0rLKdP)Cels)1Q|Criq1tNTB=q0|SF&?0*(y(_rkw`xi6XI>!EA zylw&mm!L3XmWl6w467f!fBXO2gslJHCS?7G@fjEx7#_TT`ybgKaFfIq)}k1)ODBrK zz*v(ZHmHE%@czY&7@jU!@)$*f+m&S)iZMb7S<|cdW`@OIzoYmK9!eL^UPmZg_6ovS zmX0;F;GtB;#Ev1^_3tMG0|O_D7$Yo{imTHZc2#tuNM7a@!0-bjws=5}4|FjAzD8Zx T;-?he00000NkvXXu0mjfEO~Wg literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/potion2.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/potion2.png new file mode 100644 index 0000000000000000000000000000000000000000..b5aea7674ba418ad33618d58ee89b1eb1a6253bc GIT binary patch literal 497 zcmVYfW*`7-rUY8?{MF7xT@1LV~)x{oG8h$cd)mSrf! zOWP4T;AA|DXd**(h;CAv5xn3+6+)73&=E%KE}ffg_TEY6>!Z)gO-3p)k9 zXM1aZ+(WDs&}`JrLeg(qrX+fXe@MAw%|@Le5i13{?tgy+;L!agH7DfNBAl)kL(**2 zAyx_$e0g^O02ovpR1ade62At9DR^mO1I9OEQlf_hHHCOlEz4XYy=-9lWCfeC9jZn~ n%Dhj*n9&Gk2zvP!txx(J2RO1!tle(^00000NkvXXu0mjf6g}3a literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/pumpkin .png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/pumpkin .png new file mode 100644 index 0000000000000000000000000000000000000000..fd44b2cf894a676fa7898b30a2382e5ef6ef6dce GIT binary patch literal 393 zcmV;40e1e0P)h3Bd!HwC;f1)l71f6iwxHvRT1O^i8 z(u5JmI^bQewFQa(rhC`B@BQBOJ&h&mR!>8*Zav&-?rx+(@|$5S*V}Z48$iWa>|fSRfG`QwR8YHFlHi-( z2!KaFGEgx>Ov?bkF6IE}IAaOalc24mefpDm=DUnMA3&aIH%qc2E$OH~nKSZyOv{iL zmC~f^G>5UjDYBWCLF4>D0fPZ$>p0`JA?m}6UeiSvcbu`zlI*jr*e>QcyN#MwOOBHpz(sX$rL(8k58MEHUdlMB ns#~>C473<_fN~*_ZHZW74iP@#jHbaqn^x@B-F4uVMM=q#b&ATC1P1R-wDrMQGrsDm8@ z30>T}h*%sn9rS*a@J&POO?RK;{d@23{s$shAzgM0>9QLoHx>n~Ug}Wjj%GdfXFvjL zy>dNuKOJB{9q{UFS`Lb33$=`S zHTjso<57L#w;8eyb67#Lod*Dz)e4NGc3w#gS?5}^;Am_zajw^XIzY0W$NcFN0HEDm zVp$zSy1E4btW*aVZ(oOzy1s)stcaWPA7iH|A8Gg~FRNqFxOlrv(u`(3#=K4O-c&Dj z(A@QT@~Ja2>h{^khKV+S=qQ#A0;eX>S&(YzrWmr$=Zt_>s>5T&vbn~gyL`OKC7*Pq z(V(3U=7m0A!30Cr(L(o!B5Yw^WGkf0?xeAGCyj07SNp7Az&8k_`jb(h%p?E+002ov JPDHLkV1k~7(e(fT literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/red flower.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/red flower.png new file mode 100644 index 0000000000000000000000000000000000000000..82c7ef8dba76e074aad80290aa0e6ff21f8d39a0 GIT binary patch literal 470 zcmV;{0V)28P)p_x6&@ymcK%zxjIXGL<&ekSa*_pz?rSh-~5e|z$DitB!fn5}= z&mu(34+w$4rjS|oKGZYY?dy*msZA%}ui_ABcCIn7IR zC2jh2KwKVYIEvW!M(S8L`OU6yu!2_B002HpqFOA#8IGiF{k)C{tBM=z&A%!M1~4SN zuNDhZ2Ntp2%VFHAQ3T4~IkMBrHI{i7>4}N&-dhByipzDUFpMwzi!8W?>4}MD-rdx_ zSb*9w;Z}1!!m-+;A3tk(c>nYct*qhf;Z{a^^81DS%M7m<=Uah}WfL*b^$4aR;Ch6+ z&Lx#({|ndsATylHZE(kF~E{fBN+6FOR>A1poj5 M07*qoM6N<$f`Mw(wEzGB literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/ruined_portal.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/ruined_portal.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4697803858c51de9ec7379e9924f3b8adacc0f GIT binary patch literal 645 zcmV;00($+4P)4`p##B-*2CCfJM@xc@zle9f`=W3UOe_9B21YF zJrxEOT%jTe4MST#hcJBN7`0^R|8x?5n zh-2%BSSaiWB+_cuxD9GWS*&1v^Z(w;B{M9S%rI&n<+FXgbIA<*8=uG3Y8~oq&=k72 zau)!wUplZk)7u@$3o)!cdSJHR$))X>Xxv$20FeIaJ-#!6fNdDoH&?k9jwPhGI{*NA zA?ErWBn}}RwR7Kp9iCGnB?o}*vp!mi|pbuUL2~f z&L7?$o@oNTpHae=&(E#5&-!rVP=Qv+*Zp|@`JLc;lv^DFK=jiaZeT`5C~l2e1;+l* f;3RThLO1*aHvR!s0TG`500000NkvXXu0mjf(X=Kg literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/star sparkles.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/star sparkles.png new file mode 100644 index 0000000000000000000000000000000000000000..133af7becf41e9e97af6b1b98d1e5fe6525f35bd GIT binary patch literal 228 zcmVX1^@s6-qmI800021NklJ|=A4@RSC5lWiKu`w?cC=vxDO+%7r2!n0&fDr|W zh$M_Kc*F%pN$t4nndasG!MyC}9x$zAlC{g7hZ=QcEHh+x{S4OxRR~17>_%TfBMdia zfcbS6zd$jc=OkRwB22;AbX6x_C(~**E$Y7Vx}d%35>Op}75;etuxugk-mJ1vLQDF8 endbiQx!3^pgOd-U^VW|50000X5&$ z0vTbQj#ME5Z{BA{knl|BU?Z#-$P$QUyu9vDoR?VIaF!h0qLfL(v8h#Hhc%&fy+UBBWpWTRV_;# z%qR58UU%PXQu7IGa+dC-5%XA$7)qw>GBy?@$DJ*VO9YC_|NMU=B~hRF0cIQ?H_NA~ QMF0Q*07*qoM6N<$f>mjD0& literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/sun 3.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/sun 3.png new file mode 100644 index 0000000000000000000000000000000000000000..2acef8294e4406edd3e01a00ae71c02f7e1f49cc GIT binary patch literal 346 zcmV-g0j2(lP)a3aVM%%e+l$>zRYgo6m`9}v=)+Cjh@6u z@@tM54HzvX@{EZRFb9p(V3t04$AkoGC(?iv856Qla*#GHs*~F4-de-8fEZ1<=Q>Co ssX^YTBd;s#=@5R*{MS>?vsx^A1GPL+FY~#)761SM07*qoM6N<$f}`P)82|tP literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/sun.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/sun.png new file mode 100644 index 0000000000000000000000000000000000000000..f7be93b5e4f526a1f024ae6e5df00b715a199bd0 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2^So-U3d6}OTT4lw?c&;H%--PhSD z)Nnp!&!_upDSz8F8xm6rB@&KgEn3Ir^x%^$o7-wFw>fo(E*t=Y1*@ALnuyI{*p>O? z&rzqIq#f}KvSu2v=-+n??L6wA(kl1#qs!$K7KYkYzJJuCKDq;~WAJqKb6Mw<&;$VN CPd(TG literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/sunflower.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/sunflower.png new file mode 100644 index 0000000000000000000000000000000000000000..9118a95840df9293a708cd8b0108a583a27578f6 GIT binary patch literal 405 zcmV;G0c!qy;0ls~rIa!1vFW zKJPQeA%#ui)D_^lu+PAAOW*^niBp~wi0lf9faEMbD}HlKl&Z_VuTol}ufKzy_U(R4 zU+7!uq>mIV*%CNeX0Xi6t1(o-l8)2wR%HwL{jEu-oda;WHJL24lH?KF??Pj-P1HPy z4714Yv~w=ECYM`D)Ee|mUFJc=VwKjchvw$W1ZLYy=bynJ|&o z3Vq_K%juePIK$x#r)y3;b*n{0-#OK)mTinB=l|L$T?15okk-4}^x1<2j0chV-y9R= z*b8EaM3DuwRoc7o+yyIe9z>WHTe59audeeSE+@JM$cu)v00000NkvXXu0mjfA-=h} literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/creaturechat/textures/ui/book/stickers/tnt.png b/src/main/resources/assets/creaturechat/textures/ui/book/stickers/tnt.png new file mode 100644 index 0000000000000000000000000000000000000000..93e7429863996fbe41f48cc12a9a5621d789ea7d GIT binary patch literal 548 zcmV+<0^9wGP)Qv}l3s00xa1rfnuBdFL|3YtRX5F-|1 zC4wL+A|wdvp~NORD-{c?ScJK3_I5WZ{BUsW-n{)gGdF-nXVm0ts5L7}6Y45buHq=QEbF za4sP4#1}AXauts+NZrY*U$-3yww5d4crpwC+QKNS{5BGCTy6M!0RYC}5Ta%p0N}kp zWeqhp4bb1&|fVI^%u2-=y>+~OX-L}HGhgPU;qHJ$%F&1Cr{9i&z^4ga2Q_Msf;ELL}{Qs z5%m1}0Y1z2U9R#H@m7lxHPe7u1?Mvs@{{3;`?(DV^!fVXfI5pM*u|25Vn@xiz6Mc@Lp^D7?@g+nsXfX4_1|-o%ivHUzoMJJ?_TUPl&MO)3lU{j zUl?hyHYcF}29;v@Oj76j`AkyhQ}gBe4NjkZff!p_?n5~gvLq2@R<}zU@+;E5?F@Z^ z7^4NgKUBz@-x|KDXZ#P`;|{s0zwA_8>z@+!`GG9v|<6GYgKy9xtu zA1J1)#Hi~N0MJ%j0|4-zY#R^8*ObeRa)tGiwTka!>YIRYu&QMEfQg~~)Mfd^RHzk{h3r#Jh88{EwVV>U3(k^d? zg8+c2nuaX#*vqqRB7!XMa!0oh0FX_@4ZK`=dOPvd*dRWKT{9~HfSd`UG0-(0y1IFU zdCJ=H+0RMhEp19vO#^BP&Yk9w3kQqo$L9^`>E_XZ>h2$)-9I=VUr{w}B%@K7=hWI< zUxX-?>`lpMl6uA5k@SXaB96V3YW-_mtu87eNDUcmx65XTPAA46tTLDfWsq<-UHUIzs07*qoM6N<$f)+#c0RR91 literal 0 HcmV?d00001 From cbcfe909854850b6d738a696263ee4970698284d Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 25 Aug 2025 12:34:10 -0500 Subject: [PATCH 24/26] Fixing build error for datagen providers - after merging develop --- .../java/com/owlmaddie/datagen/CreatureChatDataGenerator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java b/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java index f41a9f83..baddb47d 100644 --- a/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java +++ b/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java @@ -22,7 +22,6 @@ public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) { // Load the client-side model provider reflectively so the common // sources don't depend on client-only classes at compile time. pack.addProvider((FabricDataOutput out) -> createModelProvider(out)); - pack.addProvider(CreatureChatEnglishLangProvider::new); } private DataProvider createModelProvider(FabricDataOutput output) { From 66034badcf7ab38a17c0aedbbdf85c97ef752819 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 25 Aug 2025 14:21:56 -0500 Subject: [PATCH 25/26] Clean messages (remove behaviors), and better support death timestamps --- CHANGELOG.md | 1 + .../com/owlmaddie/network/ClientPackets.java | 2 + .../java/com/owlmaddie/ui/BookScreen.java | 38 +++++++++++++++---- .../com/owlmaddie/network/ServerPackets.java | 1 + 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93cc8d66..26f3ea9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to **CreatureChat™** are documented in this file. The form - Remember book screen state when exiting (so book resumes exactly where you left it) - Adding top buttons + hover states - integrated hover assets and new arrow buttons - Adding deterministic random stickers to pages (based on UUID and page #) + - Clean recent messages (remove behaviors) ## Unreleased diff --git a/src/client/java/com/owlmaddie/network/ClientPackets.java b/src/client/java/com/owlmaddie/network/ClientPackets.java index ecd66d86..3fd52621 100644 --- a/src/client/java/com/owlmaddie/network/ClientPackets.java +++ b/src/client/java/com/owlmaddie/network/ClientPackets.java @@ -126,6 +126,7 @@ public static void register() { ChatDataManager.ChatSender sender = ChatDataManager.ChatSender.valueOf(sender_name); Map players = readPlayerDataMap(buffer); long lastTs = buffer.readLong(); + long deathTs = buffer.readLong(); // Update the chat data manager on the client-side client.execute(() -> { // Make sure to run on the client thread @@ -148,6 +149,7 @@ public static void register() { chatData.sender = sender; chatData.players = players; chatData.lastMessage = lastTs; + chatData.death = deathTs != 0L ? deathTs : null; // Play sound with volume based on distance (from player or entity) Mob entity = ClientEntityFinder.getEntityByUUID(client.level, entityId); diff --git a/src/client/java/com/owlmaddie/ui/BookScreen.java b/src/client/java/com/owlmaddie/ui/BookScreen.java index b5db1dd8..162a9aa0 100644 --- a/src/client/java/com/owlmaddie/ui/BookScreen.java +++ b/src/client/java/com/owlmaddie/ui/BookScreen.java @@ -13,6 +13,7 @@ import com.owlmaddie.render.RenderPipelineHelper; import com.owlmaddie.utils.ClientEntityFinder; import com.owlmaddie.utils.EntityCreationHelper; +import com.owlmaddie.message.MessageParser; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.components.Button; @@ -526,7 +527,23 @@ private void buildDetailContent() { } } } else { - detailMessages.addAll(filtered); + List last = new ArrayList<>(); + ChatDataManager.ChatSender expected = null; + for (int i = filtered.size() - 1; i >= 0 && last.size() < 4; i--) { + ChatMessage m = filtered.get(i); + if (last.isEmpty()) { + if (m.sender != ChatDataManager.ChatSender.ASSISTANT) continue; + expected = ChatDataManager.ChatSender.USER; + last.add(m); + } else { + if (m.sender != expected) continue; + last.add(m); + expected = expected == ChatDataManager.ChatSender.USER + ? ChatDataManager.ChatSender.ASSISTANT + : ChatDataManager.ChatSender.USER; + } + } + detailMessages.addAll(last); } } @@ -599,7 +616,8 @@ private void paginateMessages() { List page = new ArrayList<>(); int remaining = available; for (ChatMessage m : detailMessages) { - List lines = wrapLines(safe(m.message), PAGE_CONTENT_W - 4, 0.9f); + String cleaned = MessageParser.parseMessage(safe(m.message)).getCleanedMessage(); + List lines = wrapLines(cleaned, PAGE_CONTENT_W - 4, 0.9f); boolean first = true; int idx = 0; while (idx < lines.size()) { @@ -870,11 +888,17 @@ private int renderMessagesPage(net.minecraft.client.gui.GuiGraphics ctx, int x, if (speaker == null || speaker.isBlank()) speaker = "Unknown"; long ts = mc.msg.timestamp == null ? 0L : mc.msg.timestamp; String ago = friendlyTime(System.currentTimeMillis() - ts) + " ago"; - int timeW = this.font.width(ago); - speaker = this.font.plainSubstrByWidth(speaker, PAGE_CONTENT_W - timeW - 2); - ctx.drawString(this.font, speaker, x, lineY, LABEL_COLOR, false); - ctx.drawString(this.font, ago, x + PAGE_CONTENT_W - timeW, lineY, LABEL_COLOR, false); - lineY += this.font.lineHeight + 1; + float metaScale = 0.8f; + int timeW = Math.round(this.font.width(ago) * metaScale); + int availSpeaker = (int)Math.floor((PAGE_CONTENT_W - timeW - 2) / metaScale); + speaker = this.font.plainSubstrByWidth(speaker, availSpeaker); + PoseHelper.push(ctx.pose()); + PoseHelper.translate(ctx.pose(), x, lineY); + PoseHelper.scale(ctx.pose(), metaScale, metaScale); + ctx.drawString(this.font, speaker, 0, 0, LABEL_COLOR, false); + ctx.drawString(this.font, ago, Math.round(PAGE_CONTENT_W / metaScale) - this.font.width(ago), 0, LABEL_COLOR, false); + PoseHelper.pop(ctx.pose()); + lineY += Math.round(this.font.lineHeight * metaScale) + 1; } lineY = drawLines(ctx, mc.lines, x + 4, lineY, 0.9f, BODY_COLOR); lineY += 4; diff --git a/src/main/java/com/owlmaddie/network/ServerPackets.java b/src/main/java/com/owlmaddie/network/ServerPackets.java index e3d1bb0c..ff43fc91 100644 --- a/src/main/java/com/owlmaddie/network/ServerPackets.java +++ b/src/main/java/com/owlmaddie/network/ServerPackets.java @@ -503,6 +503,7 @@ public static void BroadcastEntityMessage(EntityChatData chatData) { buffer.writeUtf(chatData.sender.toString()); writePlayerDataMap(buffer, chatData.players); buffer.writeLong(chatData.lastMessage != null ? chatData.lastMessage : 0L); + buffer.writeLong(chatData.death != null ? chatData.death : 0L); // Send message to player PacketHelper.send(player, PACKET_S2C_ENTITY_MESSAGE, buffer); From 78039be13ead1653e6fdda4991e1e994c90c3289 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 28 Aug 2025 17:39:49 -0500 Subject: [PATCH 26/26] Merging Creature Book into existing translations --- .../datagen/CreatureChatDataGenerator.java | 16 ------------- .../CreatureChatEnglishLangProvider.java | 23 ------------------- src/main/java/com/owlmaddie/i18n/CCText.java | 5 +++- .../assets/creaturechat/lang/de_de.json | 1 + .../assets/creaturechat/lang/es_es.json | 1 + .../assets/creaturechat/lang/es_mx.json | 1 + .../assets/creaturechat/lang/fr_fr.json | 1 + .../assets/creaturechat/lang/hi_in.json | 1 + .../assets/creaturechat/lang/id_id.json | 1 + .../assets/creaturechat/lang/ja_jp.json | 1 + .../assets/creaturechat/lang/ko_kr.json | 1 + .../assets/creaturechat/lang/nl_nl.json | 1 + .../assets/creaturechat/lang/pl_pl.json | 1 + .../assets/creaturechat/lang/pt_br.json | 1 + .../assets/creaturechat/lang/pt_pt.json | 1 + .../assets/creaturechat/lang/ru_ru.json | 1 + .../assets/creaturechat/lang/sv_se.json | 1 + .../assets/creaturechat/lang/tr_tr.json | 1 + .../assets/creaturechat/lang/uk_ua.json | 1 + .../assets/creaturechat/lang/zh_cn.json | 1 + .../assets/creaturechat/lang/zh_tw.json | 1 + 21 files changed, 22 insertions(+), 40 deletions(-) delete mode 100644 src/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java diff --git a/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java b/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java index 412bd563..3e0332ad 100644 --- a/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java +++ b/src/main/java/com/owlmaddie/datagen/CreatureChatDataGenerator.java @@ -18,22 +18,6 @@ public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) { pack.addProvider(CreatureChatLootTableProvider::new); pack.addProvider(CreatureChatAdvancementProvider::new); pack.addProvider(CreatureChatLangProvider::new); - pack.addProvider(CreatureChatEnglishLangProvider::new); - - // Load the client-side model provider reflectively so the common - // sources don't depend on client-only classes at compile time. - pack.addProvider((FabricDataOutput out) -> createModelProvider(out)); - } - - private DataProvider createModelProvider(FabricDataOutput output) { - try { - Class clazz = Class.forName("com.owlmaddie.datagen.CreatureChatModelProvider"); - return (DataProvider) clazz - .getConstructor(FabricDataOutput.class) - .newInstance(output); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to load model provider", e); - } } } diff --git a/src/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java b/src/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java deleted file mode 100644 index 44083341..00000000 --- a/src/main/java/com/owlmaddie/datagen/CreatureChatEnglishLangProvider.java +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2025 owlmaddie LLC -// SPDX-License-Identifier: GPL-3.0-or-later -// Assets CC-BY-NC-SA-4.0; CreatureChat™ trademark © owlmaddie LLC - unauthorized use prohibited -package com.owlmaddie.datagen; - -import com.owlmaddie.items.ModItems; -import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; -import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider; - -/** - * Generates the English language translations for the mod. - */ -public class CreatureChatEnglishLangProvider extends FabricLanguageProvider { - public CreatureChatEnglishLangProvider(FabricDataOutput dataOutput) { - super(dataOutput, "en_us"); - } - - @Override - public void generateTranslations(TranslationBuilder builder) { - builder.add(ModItems.BOOK, "Creature Book"); - } -} - diff --git a/src/main/java/com/owlmaddie/i18n/CCText.java b/src/main/java/com/owlmaddie/i18n/CCText.java index 1b386777..526de3fd 100644 --- a/src/main/java/com/owlmaddie/i18n/CCText.java +++ b/src/main/java/com/owlmaddie/i18n/CCText.java @@ -12,9 +12,12 @@ public class CCText { // UI text public static final TR UI_CHAT_TITLE = new TR("ui.chat_title", "CreatureChat"); public static final TR UI_ENTER_MESSAGE = new TR("ui.enter_message", "Enter your message:"); + public static final TR UI_CREATURE_BOOK = new TR("ui.chat_book_item", "Creature Book"); + public static final List UI_TEXT = List.of( UI_CHAT_TITLE, - UI_ENTER_MESSAGE + UI_ENTER_MESSAGE, + UI_CREATURE_BOOK ); // Configuration command text diff --git a/src/main/resources/assets/creaturechat/lang/de_de.json b/src/main/resources/assets/creaturechat/lang/de_de.json index 7adf6df7..5333f17e 100644 --- a/src/main/resources/assets/creaturechat/lang/de_de.json +++ b/src/main/resources/assets/creaturechat/lang/de_de.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Lösung: Serverfehler, später erneut versuchen", "creaturechat.solution.try_again": "Lösung: Versuche es später erneut", "creaturechat.solution.verify_url": "Lösung: Überprüfe die API-URL", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Gib deine Nachricht ein:" } diff --git a/src/main/resources/assets/creaturechat/lang/es_es.json b/src/main/resources/assets/creaturechat/lang/es_es.json index 6019e1ca..eed3843e 100644 --- a/src/main/resources/assets/creaturechat/lang/es_es.json +++ b/src/main/resources/assets/creaturechat/lang/es_es.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Solución: Error del servidor, intenta de nuevo más tarde", "creaturechat.solution.try_again": "Solución: Intenta de nuevo más tarde", "creaturechat.solution.verify_url": "Solución: Verifica la URL de la API", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Escribe tu mensaje:" } diff --git a/src/main/resources/assets/creaturechat/lang/es_mx.json b/src/main/resources/assets/creaturechat/lang/es_mx.json index 0b1abd91..07946e96 100644 --- a/src/main/resources/assets/creaturechat/lang/es_mx.json +++ b/src/main/resources/assets/creaturechat/lang/es_mx.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Solución: Error del servidor, intenta de nuevo más tarde", "creaturechat.solution.try_again": "Solución: Intenta de nuevo más tarde", "creaturechat.solution.verify_url": "Solución: Verifica la URL de la API", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Escribe tu mensaje:" } diff --git a/src/main/resources/assets/creaturechat/lang/fr_fr.json b/src/main/resources/assets/creaturechat/lang/fr_fr.json index 9b519d67..9f3e1761 100644 --- a/src/main/resources/assets/creaturechat/lang/fr_fr.json +++ b/src/main/resources/assets/creaturechat/lang/fr_fr.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Solution : Erreur serveur, réessayez plus tard", "creaturechat.solution.try_again": "Solution : Réessayez plus tard", "creaturechat.solution.verify_url": "Solution : Vérifiez l’URL de l’API", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Entrez votre message :" } diff --git a/src/main/resources/assets/creaturechat/lang/hi_in.json b/src/main/resources/assets/creaturechat/lang/hi_in.json index 0fbb411d..f47dc4a6 100644 --- a/src/main/resources/assets/creaturechat/lang/hi_in.json +++ b/src/main/resources/assets/creaturechat/lang/hi_in.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "समाधान: सर्वर त्रुटि, बाद में कोशिश करें", "creaturechat.solution.try_again": "समाधान: बाद में फिर कोशिश करें", "creaturechat.solution.verify_url": "समाधान: API URL जांचें", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "अपना संदेश लिखें:" } diff --git a/src/main/resources/assets/creaturechat/lang/id_id.json b/src/main/resources/assets/creaturechat/lang/id_id.json index 659c1be4..c7332bbf 100644 --- a/src/main/resources/assets/creaturechat/lang/id_id.json +++ b/src/main/resources/assets/creaturechat/lang/id_id.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Solusi: Error server, coba lagi nanti", "creaturechat.solution.try_again": "Solusi: Coba lagi nanti", "creaturechat.solution.verify_url": "Solusi: Periksa URL API", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Tulis pesanmu:" } diff --git a/src/main/resources/assets/creaturechat/lang/ja_jp.json b/src/main/resources/assets/creaturechat/lang/ja_jp.json index 2301cfc9..17c0a750 100644 --- a/src/main/resources/assets/creaturechat/lang/ja_jp.json +++ b/src/main/resources/assets/creaturechat/lang/ja_jp.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "解決策: サーバーエラー、後で再試行してください", "creaturechat.solution.try_again": "解決策: 後でもう一度試してください", "creaturechat.solution.verify_url": "解決策: APIのURLを確認してください", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "メッセージを入力してください:" } diff --git a/src/main/resources/assets/creaturechat/lang/ko_kr.json b/src/main/resources/assets/creaturechat/lang/ko_kr.json index 9d55f089..e1dae584 100644 --- a/src/main/resources/assets/creaturechat/lang/ko_kr.json +++ b/src/main/resources/assets/creaturechat/lang/ko_kr.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "해결 방법: 서버 오류, 나중에 다시 시도하세요", "creaturechat.solution.try_again": "해결 방법: 나중에 다시 시도하세요", "creaturechat.solution.verify_url": "해결 방법: API URL을 확인하세요", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "메시지를 입력하세요:" } diff --git a/src/main/resources/assets/creaturechat/lang/nl_nl.json b/src/main/resources/assets/creaturechat/lang/nl_nl.json index 4cba9d5a..54f88d55 100644 --- a/src/main/resources/assets/creaturechat/lang/nl_nl.json +++ b/src/main/resources/assets/creaturechat/lang/nl_nl.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Oplossing: Serverfout, probeer het later opnieuw", "creaturechat.solution.try_again": "Oplossing: Probeer het later opnieuw", "creaturechat.solution.verify_url": "Oplossing: Controleer de API-URL", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Voer je bericht in:" } diff --git a/src/main/resources/assets/creaturechat/lang/pl_pl.json b/src/main/resources/assets/creaturechat/lang/pl_pl.json index 0fdc500e..98b96f25 100644 --- a/src/main/resources/assets/creaturechat/lang/pl_pl.json +++ b/src/main/resources/assets/creaturechat/lang/pl_pl.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Rozwiązanie: Błąd serwera, spróbuj ponownie później", "creaturechat.solution.try_again": "Rozwiązanie: Spróbuj ponownie później", "creaturechat.solution.verify_url": "Rozwiązanie: Sprawdź adres URL API", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Wpisz swoją wiadomość:" } diff --git a/src/main/resources/assets/creaturechat/lang/pt_br.json b/src/main/resources/assets/creaturechat/lang/pt_br.json index faab1082..fada856c 100644 --- a/src/main/resources/assets/creaturechat/lang/pt_br.json +++ b/src/main/resources/assets/creaturechat/lang/pt_br.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Solução: Erro do servidor, tente novamente mais tarde", "creaturechat.solution.try_again": "Solução: Tente novamente mais tarde", "creaturechat.solution.verify_url": "Solução: Verifique a URL da API", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Digite sua mensagem:" } diff --git a/src/main/resources/assets/creaturechat/lang/pt_pt.json b/src/main/resources/assets/creaturechat/lang/pt_pt.json index d1bb1184..6fcfbd6a 100644 --- a/src/main/resources/assets/creaturechat/lang/pt_pt.json +++ b/src/main/resources/assets/creaturechat/lang/pt_pt.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Solução: Erro no servidor, tente novamente mais tarde", "creaturechat.solution.try_again": "Solução: Tente novamente mais tarde", "creaturechat.solution.verify_url": "Solução: Verifique a URL da API", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Escreve a tua mensagem:" } diff --git a/src/main/resources/assets/creaturechat/lang/ru_ru.json b/src/main/resources/assets/creaturechat/lang/ru_ru.json index be2ed2de..bde6abaa 100644 --- a/src/main/resources/assets/creaturechat/lang/ru_ru.json +++ b/src/main/resources/assets/creaturechat/lang/ru_ru.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Решение: Ошибка сервера, попробуйте позже", "creaturechat.solution.try_again": "Решение: Попробуйте позже", "creaturechat.solution.verify_url": "Решение: Проверьте URL API", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Введите сообщение:" } diff --git a/src/main/resources/assets/creaturechat/lang/sv_se.json b/src/main/resources/assets/creaturechat/lang/sv_se.json index 304b7257..d06af1d3 100644 --- a/src/main/resources/assets/creaturechat/lang/sv_se.json +++ b/src/main/resources/assets/creaturechat/lang/sv_se.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Lösning: Serverfel, försök igen senare", "creaturechat.solution.try_again": "Lösning: Försök igen senare", "creaturechat.solution.verify_url": "Lösning: Verifiera API-URL:en", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Skriv ditt meddelande:" } diff --git a/src/main/resources/assets/creaturechat/lang/tr_tr.json b/src/main/resources/assets/creaturechat/lang/tr_tr.json index 244c2b9a..df1500d7 100644 --- a/src/main/resources/assets/creaturechat/lang/tr_tr.json +++ b/src/main/resources/assets/creaturechat/lang/tr_tr.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Çözüm: Sunucu hatası, daha sonra tekrar dene", "creaturechat.solution.try_again": "Çözüm: Daha sonra tekrar dene", "creaturechat.solution.verify_url": "Çözüm: API URL\u0027sini kontrol et", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Mesajını yaz:" } diff --git a/src/main/resources/assets/creaturechat/lang/uk_ua.json b/src/main/resources/assets/creaturechat/lang/uk_ua.json index d37e00b9..c17b04a8 100644 --- a/src/main/resources/assets/creaturechat/lang/uk_ua.json +++ b/src/main/resources/assets/creaturechat/lang/uk_ua.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "Рішення: Помилка сервера, спробуй пізніше", "creaturechat.solution.try_again": "Рішення: Спробуй пізніше", "creaturechat.solution.verify_url": "Рішення: Перевір API-URL", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "Введи своє повідомлення:" } diff --git a/src/main/resources/assets/creaturechat/lang/zh_cn.json b/src/main/resources/assets/creaturechat/lang/zh_cn.json index cd9f9ee8..17c6147f 100644 --- a/src/main/resources/assets/creaturechat/lang/zh_cn.json +++ b/src/main/resources/assets/creaturechat/lang/zh_cn.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "解决方法: 服务器错误,请稍后再试", "creaturechat.solution.try_again": "解决方法: 稍后再试", "creaturechat.solution.verify_url": "解决方法: 验证 API URL", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "输入你的消息:" } diff --git a/src/main/resources/assets/creaturechat/lang/zh_tw.json b/src/main/resources/assets/creaturechat/lang/zh_tw.json index b013edc0..577f9f59 100644 --- a/src/main/resources/assets/creaturechat/lang/zh_tw.json +++ b/src/main/resources/assets/creaturechat/lang/zh_tw.json @@ -109,6 +109,7 @@ "creaturechat.solution.server_error": "解決方法: 伺服器錯誤,稍後再試", "creaturechat.solution.try_again": "解決方法: 稍後再試", "creaturechat.solution.verify_url": "解決方法: 驗證 API URL", + "creaturechat.ui.chat_book_item": "Creature Book", "creaturechat.ui.chat_title": "CreatureChat", "creaturechat.ui.enter_message": "輸入你的訊息:" }