From ca349a9add18a76d7397d4101a87d379612abfe1 Mon Sep 17 00:00:00 2001 From: evanbones <74276616+evanbones@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:59:16 -0800 Subject: [PATCH] Rework data format to support resource packs and datapacks separately, closes #3 --- .../client/data/CategoryVisual.java | 19 + .../fieldguide/client/data/EntryVisual.java | 18 + .../gui/screens/FieldGuideEntryScreen.java | 6 +- .../client/gui/screens/FieldGuideScreen.java | 56 +- .../client/gui/util/EntryRenderHelper.java | 86 ++- .../client/gui/widget/TabButton.java | 38 +- .../com/evandev/fieldguide/data/Category.java | 113 +--- .../fieldguide/data/CategoryEntry.java | 23 +- .../data/FieldGuideDataManager.java | 620 ++++++++---------- .../network/SyncCategoriesPacket.java | 55 ++ .../fieldguide/server/LootTableHelper.java | 2 +- .../server/ServerFieldGuideManager.java | 97 +++ .../server/command/FieldGuideCommand.java | 6 +- .../assets/fieldguide/categories/flora.json | 11 - .../assets/fieldguide/categories/hostile.json | 11 - .../assets/fieldguide/categories/passive.json | 11 - .../fieldguide/visuals/categories/flora.json | 4 + .../visuals/categories/hostile.json | 4 + .../visuals/categories/passive.json | 4 + .../fieldguide/categories/flora.json | 9 + .../fieldguide/categories/hostile.json | 9 + .../fieldguide/categories/passive.json | 9 + .../fieldguide/FieldGuideFabricClient.java | 11 + .../com/evandev/fieldguide/FieldGuideMod.java | 27 +- .../platform/FabricNetworkHelper.java | 5 + .../com/evandev/fieldguide/FieldGuideMod.java | 27 +- .../platform/ForgeNetworkHelper.java | 4 +- 27 files changed, 688 insertions(+), 597 deletions(-) create mode 100644 common/src/main/java/com/evandev/fieldguide/client/data/CategoryVisual.java create mode 100644 common/src/main/java/com/evandev/fieldguide/client/data/EntryVisual.java create mode 100644 common/src/main/java/com/evandev/fieldguide/network/SyncCategoriesPacket.java create mode 100644 common/src/main/java/com/evandev/fieldguide/server/ServerFieldGuideManager.java delete mode 100644 common/src/main/resources/assets/fieldguide/categories/flora.json delete mode 100644 common/src/main/resources/assets/fieldguide/categories/hostile.json delete mode 100644 common/src/main/resources/assets/fieldguide/categories/passive.json create mode 100644 common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/flora.json create mode 100644 common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/hostile.json create mode 100644 common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/passive.json create mode 100644 common/src/main/resources/data/fieldguide/fieldguide/categories/flora.json create mode 100644 common/src/main/resources/data/fieldguide/fieldguide/categories/hostile.json create mode 100644 common/src/main/resources/data/fieldguide/fieldguide/categories/passive.json diff --git a/common/src/main/java/com/evandev/fieldguide/client/data/CategoryVisual.java b/common/src/main/java/com/evandev/fieldguide/client/data/CategoryVisual.java new file mode 100644 index 0000000..1d32915 --- /dev/null +++ b/common/src/main/java/com/evandev/fieldguide/client/data/CategoryVisual.java @@ -0,0 +1,19 @@ +package com.evandev.fieldguide.client.data; + +import net.minecraft.resources.ResourceLocation; + +public class CategoryVisual { + public static final CategoryVisual DEFAULT = new CategoryVisual(); + + public String color = "#FFFFFF"; + public ResourceLocation icon = new ResourceLocation("minecraft:book"); + + public int getColorInt() { + try { + String hex = color.startsWith("#") ? color.substring(1) : color; + return Integer.parseInt(hex, 16); + } catch (Exception e) { + return 0xFFFFFF; + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/evandev/fieldguide/client/data/EntryVisual.java b/common/src/main/java/com/evandev/fieldguide/client/data/EntryVisual.java new file mode 100644 index 0000000..cfaabfd --- /dev/null +++ b/common/src/main/java/com/evandev/fieldguide/client/data/EntryVisual.java @@ -0,0 +1,18 @@ +package com.evandev.fieldguide.client.data; + +public class EntryVisual { + // Global defaults + public float scale = 1.0f; + public float yOffset = 0.0f; + public float xOffset = 0.0f; + + // Grid Overrides + public Float gridScale = null; + public Float gridYOffset = null; + public Float gridXOffset = null; + + // Page Overrides + public Float pageScale = null; + public Float pageYOffset = null; + public Float pageXOffset = null; +} \ No newline at end of file diff --git a/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideEntryScreen.java b/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideEntryScreen.java index 38ce90e..5b135fc 100644 --- a/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideEntryScreen.java +++ b/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideEntryScreen.java @@ -94,12 +94,12 @@ public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, flo if (entry instanceof EntityType && renderedEntity instanceof LivingEntity living) { if (unlocked) { - EntryRenderHelper.renderEntityNormalized(guiGraphics, living, xPos, yPos, 100, 100, 80, false); + EntryRenderHelper.renderEntityNormalized(guiGraphics, living, xPos, yPos, 100, 100, 80, false, 0, true); } else { - EntryRenderHelper.renderEntityNormalized(guiGraphics, living, xPos, yPos, 100, 100, 80, true, Constants.DETAILS_SILHOUETTE_COLOR); + EntryRenderHelper.renderEntityNormalized(guiGraphics, living, xPos, yPos, 100, 100, 80, true, Constants.DETAILS_SILHOUETTE_COLOR, true); } } else if (entry instanceof Block block) { - EntryRenderHelper.renderBlock(guiGraphics, block, xPos, yPos, 30.0F, !unlocked); + EntryRenderHelper.renderBlock(guiGraphics, block, xPos, yPos, 30.0F, !unlocked, true); } // Description diff --git a/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideScreen.java b/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideScreen.java index bb7c3a1..07f1fbd 100644 --- a/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideScreen.java +++ b/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideScreen.java @@ -78,7 +78,8 @@ public FieldGuideScreen(Category initialCategory, String searchQuery) { } public static int getPageForEntry(Category category, Object entry) { - int index = category.getEntries().indexOf(entry); + List entries = FieldGuideDataManager.getInstance().getEntriesForCategory(category); + int index = entries.indexOf(entry); if (index < 0) return 0; if (index < ITEMS_PER_PAGE) return 0; return 1 + (index - ITEMS_PER_PAGE) / ITEMS_PER_VIEW; @@ -97,8 +98,13 @@ protected void init() { super.init(); this.sortedCategories.clear(); - this.sortedCategories.addAll(FieldGuideDataManager.getCategories().values()); - this.sortedCategories.sort(Comparator.comparingInt(Category::getTabIndex) + for (Category cat : FieldGuideDataManager.getCategories().values()) { + List entries = FieldGuideDataManager.getInstance().getEntriesForCategory(cat); + if (entries != null && !entries.isEmpty()) { + this.sortedCategories.add(cat); + } + } + this.sortedCategories.sort(Comparator.comparingInt(Category::getSortIndex) .thenComparing(c -> c.getId().getPath())); if (this.selectedCategory == null) { @@ -112,7 +118,7 @@ protected void init() { } if (this.selectedCategory != null) { - this.currentEntries = this.selectedCategory.getEntries(); + this.currentEntries = FieldGuideDataManager.getInstance().getEntriesForCategory(this.selectedCategory); lastOpenedCategory = this.selectedCategory.getId(); int totalSpreads = getTotalSpreads(); @@ -158,7 +164,7 @@ private void onSearchChanged(String query) { if (!isSearching) { if (this.selectedCategory != null) { - this.currentEntries = this.selectedCategory.getEntries(); + this.currentEntries = FieldGuideDataManager.getInstance().getEntriesForCategory(this.selectedCategory); } } else { this.currentPage = 0; @@ -197,7 +203,7 @@ public void selectCategory(Category category) { if (this.selectedCategory == category) return; this.selectedCategory = category; this.currentPage = 0; - this.currentEntries = category.getEntries(); + this.currentEntries = FieldGuideDataManager.getInstance().getEntriesForCategory(category); lastOpenedCategory = this.selectedCategory.getId(); lastOpenedPage = this.currentPage; @@ -245,13 +251,8 @@ private void nextPage() { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (this.searchBox != null) { - this.searchBox.setFocused(this.searchBox.isMouseOver(mouseX, mouseY)); - } - - if (super.mouseClicked(mouseX, mouseY, button)) { - return true; - } + if (this.searchBox != null) this.searchBox.setFocused(this.searchBox.isMouseOver(mouseX, mouseY)); + if (super.mouseClicked(mouseX, mouseY, button)) return true; for (int i = 0; i < ITEMS_PER_VIEW; i++) { int globalSlotIndex = currentPage * ITEMS_PER_VIEW + i; @@ -259,9 +260,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { int itemIndex = getItemIndexForSlot(i); if (itemIndex >= 0 && itemIndex < currentEntries.size()) { Object entry = currentEntries.get(itemIndex); - if (FieldGuideDataManager.isNew(entry)) { - FieldGuideDataManager.markAsSeen(entry); - } + if (FieldGuideDataManager.isNew(entry)) FieldGuideDataManager.markAsSeen(entry); Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); Minecraft.getInstance().setScreen(new FieldGuideEntryScreen(this, entry)); return true; @@ -274,27 +273,18 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (this.searchBox.isFocused()) { - if (keyCode == GLFW.GLFW_KEY_ESCAPE) { - return super.keyPressed(keyCode, scanCode, modifiers); - } - - if (this.searchBox.keyPressed(keyCode, scanCode, modifiers)) { - return true; - } - + if (keyCode == GLFW.GLFW_KEY_ESCAPE) return super.keyPressed(keyCode, scanCode, modifiers); + if (this.searchBox.keyPressed(keyCode, scanCode, modifiers)) return true; return true; } - return super.keyPressed(keyCode, scanCode, modifiers); } @Override public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { this.renderBackground(guiGraphics); - RenderSystem.setShaderTexture(0, Constants.BOOK_TEXTURE); guiGraphics.blit(Constants.BOOK_TEXTURE, this.bounds.left(), this.bounds.top(), 0, 0, this.bounds.width(), this.bounds.height(), this.bounds.width(), this.bounds.height()); - super.render(guiGraphics, mouseX, mouseY, partialTick); if (!isSearching && selectedCategory != null && currentPage == 0) { @@ -303,9 +293,7 @@ public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, flo renderSearchTab(guiGraphics); if (currentEntries.isEmpty()) { Component noResults = Component.translatable("gui.fieldguide.no_results"); - int textX = this.leftPageBounds.x_center() - this.font.width(noResults) / 2; - int textY = this.leftPageBounds.y_center(); - guiGraphics.drawString(this.font, noResults, textX, textY, Constants.TEXT_MUTED_COLOR, false); + guiGraphics.drawString(this.font, noResults, this.leftPageBounds.x_center() - this.font.width(noResults) / 2, this.leftPageBounds.y_center(), Constants.TEXT_MUTED_COLOR, false); } } @@ -367,9 +355,7 @@ private void renderCategoryInfo(GuiGraphics guiGraphics) { } private int getItemIndexForSlot(int slotIndex) { - if (isSearching) { - return (currentPage * ITEMS_PER_VIEW) + slotIndex; - } + if (isSearching) return (currentPage * ITEMS_PER_VIEW) + slotIndex; if (currentPage == 0) { if (slotIndex < ITEMS_PER_PAGE) return -1; return slotIndex - ITEMS_PER_PAGE; @@ -482,11 +468,11 @@ private void renderEntryInGrid(GuiGraphics guiGraphics, Object entry, int x, int } if (entity instanceof LivingEntity living) { - EntryRenderHelper.renderEntityNormalized(guiGraphics, living, x, y, CELL_SIZE - 8, CELL_SIZE - 8, scale, !unlocked); + EntryRenderHelper.renderEntityNormalized(guiGraphics, living, x, y, CELL_SIZE - 8, CELL_SIZE - 8, scale, !unlocked, Constants.LIST_SILHOUETTE_COLOR, false); } } } else if (entry instanceof Block block) { - EntryRenderHelper.renderBlock(guiGraphics, block, x, y, 15.0F, !unlocked); + EntryRenderHelper.renderBlock(guiGraphics, block, x, y, 15.0F, !unlocked, false); } } } \ No newline at end of file diff --git a/common/src/main/java/com/evandev/fieldguide/client/gui/util/EntryRenderHelper.java b/common/src/main/java/com/evandev/fieldguide/client/gui/util/EntryRenderHelper.java index 0e4aa38..1690002 100644 --- a/common/src/main/java/com/evandev/fieldguide/client/gui/util/EntryRenderHelper.java +++ b/common/src/main/java/com/evandev/fieldguide/client/gui/util/EntryRenderHelper.java @@ -1,6 +1,8 @@ package com.evandev.fieldguide.client.gui.util; import com.evandev.fieldguide.Constants; +import com.evandev.fieldguide.client.data.EntryVisual; +import com.evandev.fieldguide.data.FieldGuideDataManager; import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; @@ -32,15 +34,17 @@ public class EntryRenderHelper { - private static final Map> OVERRIDE_CACHE = new HashMap<>(); + private static final Map> OVERRIDE_CACHE = new HashMap<>(); public static void clearCache() { OVERRIDE_CACHE.clear(); } - private static Optional getOverride(Object entry) { - if (OVERRIDE_CACHE.containsKey(entry)) { - return OVERRIDE_CACHE.get(entry); + private static Optional getOverride(Object entry, boolean isPage) { + String key = entry.toString() + (isPage ? "_page" : "_grid"); + + if (OVERRIDE_CACHE.containsKey(key)) { + return OVERRIDE_CACHE.get(key); } ResourceLocation id = null; @@ -51,19 +55,30 @@ private static Optional getOverride(Object entry) { } if (id != null) { - ResourceLocation texture = new ResourceLocation(id.getNamespace(), "textures/fieldguide/entries/" + id.getPath() + ".png"); - if (Minecraft.getInstance().getResourceManager().getResource(texture).isPresent()) { - OVERRIDE_CACHE.put(entry, Optional.of(texture)); - return Optional.of(texture); + // Define paths to check + ResourceLocation specificLoc = new ResourceLocation(id.getNamespace(), + "textures/fieldguide/entries/" + id.getPath() + (isPage ? "_page.png" : "_grid.png")); + + ResourceLocation defaultLoc = new ResourceLocation(id.getNamespace(), + "textures/fieldguide/entries/" + id.getPath() + ".png"); + + // Check specific first (e.g., pig_page.png), then default (pig.png) + if (Minecraft.getInstance().getResourceManager().getResource(specificLoc).isPresent()) { + OVERRIDE_CACHE.put(key, Optional.of(specificLoc)); + return Optional.of(specificLoc); + } else if (Minecraft.getInstance().getResourceManager().getResource(defaultLoc).isPresent()) { + OVERRIDE_CACHE.put(key, Optional.of(defaultLoc)); + return Optional.of(defaultLoc); } } - OVERRIDE_CACHE.put(entry, Optional.empty()); + OVERRIDE_CACHE.put(key, Optional.empty()); return Optional.empty(); } - private static boolean tryRenderOverride(GuiGraphics guiGraphics, Object entry, int x, int y, int width, int height, boolean silhouette, int color) { - Optional override = getOverride(entry); + private static boolean tryRenderOverride(GuiGraphics guiGraphics, Object entry, int x, int y, int width, int height, boolean silhouette, int color, boolean isPage) { + Optional override = getOverride(entry, isPage); + if (override.isPresent()) { ResourceLocation texture = override.get(); int drawX = x - width / 2; @@ -163,32 +178,40 @@ private static float getScaleFactorForEntity(LivingEntity entity) { } } - /** - * Renders an entity normalized to fit within a standard widget box. - * Defaults to the standard sepia silhouette color. - */ - public static void renderEntityNormalized(GuiGraphics guiGraphics, LivingEntity entity, int x, int y, int maxWidth, int maxHeight, float baseScale, boolean silhouette) { - renderEntityNormalized(guiGraphics, entity, x, y, maxWidth, maxHeight, baseScale, silhouette, Constants.LIST_SILHOUETTE_COLOR); - } - - /** - * Renders an entity normalized to fit within a standard widget box with a specific silhouette color. - */ - public static void renderEntityNormalized(GuiGraphics guiGraphics, LivingEntity entity, int x, int y, int maxWidth, int maxHeight, float baseScale, boolean silhouette, int color) { - if (tryRenderOverride(guiGraphics, entity.getType(), x, y, maxWidth, maxHeight, silhouette, color)) { + public static void renderEntityNormalized(GuiGraphics guiGraphics, LivingEntity entity, int x, int y, int maxWidth, int maxHeight, float baseScale, boolean silhouette, int color, boolean isPage) { + if (tryRenderOverride(guiGraphics, entity.getType(), x, y, maxWidth, maxHeight, silhouette, color, isPage)) { return; } + ResourceLocation id = FieldGuideDataManager.getEntryId(entity.getType()); + EntryVisual visual = FieldGuideDataManager.getInstance().getEntryVisual(id); + + float visualScale = visual.scale; + float yOff = visual.yOffset; + if (isPage) { + if (visual.pageScale != null) visualScale = visual.pageScale; + if (visual.pageYOffset != null) yOff = visual.pageYOffset; + } else { + if (visual.gridScale != null) visualScale = visual.gridScale; + if (visual.gridYOffset != null) yOff = visual.gridYOffset; + } + float dynamicFactor = getScaleFactorForEntity(entity); - float finalScale = (baseScale * 0.32F) * dynamicFactor; + float finalScale; + + if (visualScale == 1.0f && visual.gridScale == null && visual.pageScale == null) { + finalScale = (baseScale * 0.32F) * dynamicFactor; + } else { + finalScale = baseScale * visualScale; + } float entityHeight = entity.getBbHeight(); if (entityHeight * finalScale > maxHeight * 0.9f) { finalScale = (maxHeight * 0.9f) / entityHeight; } - // Standardize Y offset - int feetY = (int) (y + (entityHeight * finalScale / 2.0f)); + int feetY = (int) (y + (entityHeight * finalScale / 2.0f) + yOff); + int minX = x - maxWidth / 2; int minY = y - maxHeight / 2; int maxX = x + maxWidth / 2; @@ -216,7 +239,6 @@ public static void renderEntityStatic(GuiGraphics guiGraphics, LivingEntity enti float bodyYAngle = 30; Quaternionf cameraOrientation = Axis.XP.rotationDegrees(cameraXAngle); - Quaternionf bodyOrientation = Axis.YP.rotationDegrees(-bodyYAngle); pose.mulPose(cameraOrientation); @@ -268,9 +290,10 @@ public static void renderEntityStatic(GuiGraphics guiGraphics, LivingEntity enti Lighting.setupForFlatItems(); } - public static void renderBlock(GuiGraphics guiGraphics, Block block, int x, int y, float scale, boolean silhouette) { + public static void renderBlock(GuiGraphics guiGraphics, Block block, int x, int y, float scale, boolean silhouette, boolean isPage) { int estimatedSize = (int) (scale * 2); - if (tryRenderOverride(guiGraphics, block, x, y, estimatedSize, estimatedSize, silhouette, Constants.LIST_SILHOUETTE_COLOR)) { + + if (tryRenderOverride(guiGraphics, block, x, y, estimatedSize, estimatedSize, silhouette, Constants.LIST_SILHOUETTE_COLOR, isPage)) { return; } @@ -288,7 +311,6 @@ public static void renderBlock(GuiGraphics guiGraphics, Block block, int x, int state = stateWithMaxPropertyValue(state, "flower_amount"); state = stateWithMaxPropertyValue(state, "pickles"); - // Rendering float cameraXAngle = 30; float cameraYAngle = 210; @@ -320,8 +342,6 @@ public static void renderBlock(GuiGraphics guiGraphics, Block block, int x, int } else { // Render multiple blocks vertically Collection values = verticalProp.getPossibleValues(); - - // Sort values in correct order from bottom -> top if (!values.isEmpty() && values.iterator().next() instanceof Comparable) { @SuppressWarnings("unchecked") Collection> sorted = (Collection>) values; diff --git a/common/src/main/java/com/evandev/fieldguide/client/gui/widget/TabButton.java b/common/src/main/java/com/evandev/fieldguide/client/gui/widget/TabButton.java index 615957a..c7cca32 100644 --- a/common/src/main/java/com/evandev/fieldguide/client/gui/widget/TabButton.java +++ b/common/src/main/java/com/evandev/fieldguide/client/gui/widget/TabButton.java @@ -1,65 +1,51 @@ package com.evandev.fieldguide.client.gui.widget; import com.evandev.fieldguide.Constants; +import com.evandev.fieldguide.client.data.CategoryVisual; import com.evandev.fieldguide.client.gui.screens.FieldGuideScreen; import com.evandev.fieldguide.data.Category; -import com.mojang.blaze3d.systems.RenderSystem; +import com.evandev.fieldguide.data.FieldGuideDataManager; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.ImageButton; import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; public class TabButton extends ImageButton { private final Category category; private final FieldGuideScreen parent; -// private final ItemStack iconStack; private final float r, g, b; + private final CategoryVisual visual; public TabButton(int x, int y, int width, int height, Category category, FieldGuideScreen parent) { super(x, y, width, height, 0, 0, 0, Constants.TAB_TEXTURE, 24, 40, (btn) -> parent.selectCategory(category)); this.category = category; this.parent = parent; - int colorInt = parseColor(category.getTabColor()); + this.visual = FieldGuideDataManager.getInstance().getCategoryVisual(category.getId()); + + int colorInt = visual.getColorInt(); this.r = ((colorInt >> 16) & 0xFF) / 255.0F; this.g = ((colorInt >> 8) & 0xFF) / 255.0F; this.b = (colorInt & 0xFF) / 255.0F; -// ItemStack stack = new ItemStack(BuiltInRegistries.ITEM.get(category.getTabIcon())); -// this.iconStack = stack.isEmpty() ? new ItemStack(Items.BARRIER) : stack; - this.setTooltip(Tooltip.create(Component.translatable("category.fieldguide." + category.getId().getPath()))); } @Override public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { boolean isSelected = (category == parent.getSelectedCategory()); - -// RenderSystem.setShaderColor(r, g, b, 1.0F); - int vOffset = isSelected ? 24 : 0; + // Tab tinting + // guiGraphics.setColor(r, g, b, 1.0F); + guiGraphics.blit(Constants.TAB_TEXTURE, this.getX(), this.getY(), 0, vOffset, this.width, this.height, 24, 48); -// RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + guiGraphics.setColor(1.0F, 1.0F, 1.0F, 1.0F); int iconX = this.getX() + 4; int iconY = this.getY() + 3; - if (isSelected) { - iconY = iconY - 1; - } - guiGraphics.blit(category.getTabIcon(), iconX, iconY, 0, 0, 16, 16, 16, 16); -// guiGraphics.renderItem(iconStack, iconX, iconY); - } + if (isSelected) iconY = iconY - 1; - private int parseColor(String hex) { - try { - if (hex.startsWith("#")) hex = hex.substring(1); - return Integer.parseInt(hex, 16); - } catch (NumberFormatException e) { - return 0xFFFFFF; - } + guiGraphics.blit(visual.icon, iconX, iconY, 0, 0, 16, 16, 16, 16); } } \ No newline at end of file diff --git a/common/src/main/java/com/evandev/fieldguide/data/Category.java b/common/src/main/java/com/evandev/fieldguide/data/Category.java index d1ccd0c..b75b73b 100644 --- a/common/src/main/java/com/evandev/fieldguide/data/Category.java +++ b/common/src/main/java/com/evandev/fieldguide/data/Category.java @@ -1,22 +1,14 @@ package com.evandev.fieldguide.data; -import com.evandev.fieldguide.config.ModConfig; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.MobCategory; -import net.minecraft.world.item.SpawnEggItem; -import net.minecraft.world.level.block.*; -import java.util.*; +import java.util.ArrayList; +import java.util.List; public class Category { - final List entries = new ArrayList<>(); private final ResourceLocation id; - String tabColor = "#FFFFFF"; - ResourceLocation tabIcon = new ResourceLocation("minecraft:barrier"); - int tabIndex = 0; - private List resolvedEntries = new ArrayList<>(); + private final List entries = new ArrayList<>(); + private int sortIndex = 0; public Category(ResourceLocation id) { this.id = id; @@ -26,100 +18,19 @@ public ResourceLocation getId() { return id; } - public String getTabColor() { - return tabColor; + public int getSortIndex() { + return sortIndex; } - public ResourceLocation getTabIcon() { - return tabIcon; + public void setSortIndex(int sortIndex) { + this.sortIndex = sortIndex; } - public int getTabIndex() { - return tabIndex; + public List getEntries() { + return entries; } - public List getEntries() { - return resolvedEntries; - } - - public void resolveEntries() { - Set foundEntries = new LinkedHashSet<>(); - ModConfig config = ModConfig.get(); - - for (CategoryEntry entry : entries) { - if (entry.type == FieldGuideDataManager.EntryType.ENTRY) { - Optional> entityType = BuiltInRegistries.ENTITY_TYPE.getOptional(entry.id); - if (entityType.isPresent()) { - if (isValidEntity(entityType.get(), config)) { - foundEntries.add(entityType.get()); - } - } else { - Optional block = BuiltInRegistries.BLOCK.getOptional(entry.id); - if (block.isPresent()) { - if (isValidBlock(block.get(), config)) { - foundEntries.add(block.get()); - } - } - } - } else if (entry.type == FieldGuideDataManager.EntryType.AUTO_POPULATE) { - List autoEntries = getEntriesForStrategy(entry.strategy); - for (Object obj : autoEntries) { - if (obj instanceof EntityType type) { - if (isValidEntity(type, config)) foundEntries.add(type); - } else if (obj instanceof Block block) { - if (isValidBlock(block, config)) foundEntries.add(block); - } - } - } - } - this.resolvedEntries = new ArrayList<>(foundEntries); - } - - private boolean isValidEntity(EntityType type, ModConfig config) { - ResourceLocation id = BuiltInRegistries.ENTITY_TYPE.getKey(type); - return type.canSummon() && !config.isEntityBlacklisted(id); - } - - private boolean isValidBlock(Block block, ModConfig config) { - ResourceLocation id = BuiltInRegistries.BLOCK.getKey(block); - return !config.isEntityBlacklisted(id); - } - - private List getEntriesForStrategy(String strategy) { - List results = new ArrayList<>(); - - if ("flora".equalsIgnoreCase(strategy)) { - results.addAll(BuiltInRegistries.BLOCK.stream() - .filter(block -> block instanceof BushBlock || - block instanceof LeavesBlock || - block instanceof VineBlock || - block instanceof CactusBlock || - block instanceof SugarCaneBlock || - block instanceof WaterlilyBlock || - block instanceof StemBlock) - .sorted(Comparator.comparing(block -> BuiltInRegistries.BLOCK.getKey(block).toString())) - .toList()); - return results; - } - - results.addAll(BuiltInRegistries.ENTITY_TYPE.stream() - .filter(type -> { - if ("hostile".equalsIgnoreCase(strategy)) { - return type.getCategory() == MobCategory.MONSTER; - } else if ("passive".equalsIgnoreCase(strategy)) { - if (type.getCategory() == MobCategory.MONSTER) { - return false; - } - if (type.getCategory() == MobCategory.MISC) { - return SpawnEggItem.byId(type) != null; - } - return true; - } - return false; - }) - .sorted(Comparator.comparing(type -> BuiltInRegistries.ENTITY_TYPE.getKey(type).toString())) - .toList()); - - return results; + public void addEntry(CategoryEntry entry) { + this.entries.add(entry); } } \ No newline at end of file diff --git a/common/src/main/java/com/evandev/fieldguide/data/CategoryEntry.java b/common/src/main/java/com/evandev/fieldguide/data/CategoryEntry.java index ad47d1b..9f8a446 100644 --- a/common/src/main/java/com/evandev/fieldguide/data/CategoryEntry.java +++ b/common/src/main/java/com/evandev/fieldguide/data/CategoryEntry.java @@ -3,13 +3,26 @@ import net.minecraft.resources.ResourceLocation; public class CategoryEntry { - FieldGuideDataManager.EntryType type; - ResourceLocation id; - String strategy; - - CategoryEntry(FieldGuideDataManager.EntryType type, ResourceLocation id, String strategy) { + private final Type type; + private final ResourceLocation id; + private final String strategy; + public CategoryEntry(Type type, ResourceLocation id, String strategy) { this.type = type; this.id = id; this.strategy = strategy; } + + public Type getType() { + return type; + } + + public ResourceLocation getId() { + return id; + } + + public String getStrategy() { + return strategy; + } + + public enum Type {ENTRY, AUTO_POPULATE} } \ No newline at end of file diff --git a/common/src/main/java/com/evandev/fieldguide/data/FieldGuideDataManager.java b/common/src/main/java/com/evandev/fieldguide/data/FieldGuideDataManager.java index 2ee1d8a..6db917a 100644 --- a/common/src/main/java/com/evandev/fieldguide/data/FieldGuideDataManager.java +++ b/common/src/main/java/com/evandev/fieldguide/data/FieldGuideDataManager.java @@ -1,6 +1,8 @@ package com.evandev.fieldguide.data; import com.evandev.fieldguide.Constants; +import com.evandev.fieldguide.client.data.CategoryVisual; +import com.evandev.fieldguide.client.data.EntryVisual; import com.evandev.fieldguide.client.gui.toasts.FieldGuideToast; import com.evandev.fieldguide.client.gui.util.EntryRenderHelper; import com.evandev.fieldguide.config.ModConfig; @@ -19,16 +21,21 @@ import net.minecraft.util.GsonHelper; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MobCategory; import net.minecraft.world.entity.projectile.ProjectileUtil; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.item.SpawnEggItem; import net.minecraft.world.level.ClipContext; -import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.*; +import org.jetbrains.annotations.NotNull; -import java.io.*; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.Reader; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; @@ -37,13 +44,23 @@ public class FieldGuideDataManager implements ResourceManagerReloadListener { private static final Gson GSON = new GsonBuilder().create(); private static final FieldGuideDataManager INSTANCE = new FieldGuideDataManager(); private static final int FADE_DURATION = 10; - private final Map categories = new LinkedHashMap<>(); + + // Data Structures + private final Map syncedCategories = new LinkedHashMap<>(); + private final Map> resolvedCategoryEntries = new HashMap<>(); + + // Visuals + private final Map categoryVisuals = new HashMap<>(); + private final Map entryVisuals = new HashMap<>(); + + // Progress private final Set unlockedEntries = new HashSet<>(); private final Set seenEntries = new HashSet<>(); private final Map> dropCache = new HashMap<>(); private final Set requestedDrops = new HashSet<>(); private Path currentSavePath = null; - private List flattenedEntryCache = null; + + // Scanning State private long lastUnlockTime = 0; private Object lastUnlockedEntry = null; private Object scanningTarget = null; @@ -51,8 +68,8 @@ public class FieldGuideDataManager implements ResourceManagerReloadListener { private BlockPos scanningPos = null; private Object fadingTarget = null; private int fadeTicks = 0; - private BlockPos fadingPos = null; private int prevScanTicks = 0; + private BlockPos fadingPos = null; private FieldGuideDataManager() { } @@ -61,26 +78,27 @@ public static FieldGuideDataManager getInstance() { return INSTANCE; } - public static List getValidEntries() { - if (INSTANCE.flattenedEntryCache == null) { - INSTANCE.flattenedEntryCache = INSTANCE.categories.values().stream() - .flatMap(cat -> cat.getEntries().stream()) - .distinct() - .collect(Collectors.toList()); - } - return INSTANCE.flattenedEntryCache; + public static void clearCache() { + INSTANCE.dropCache.clear(); + INSTANCE.resolvedCategoryEntries.clear(); + EntryRenderHelper.clearCache(); + INSTANCE.resolveAllEntries(); } public static Map getCategories() { - return INSTANCE.categories; + return INSTANCE.syncedCategories; + } + + public static List getValidEntries() { + return INSTANCE.resolvedCategoryEntries.values().stream() + .flatMap(List::stream) + .distinct() + .collect(Collectors.toList()); } public static ResourceLocation getEntryId(Object entry) { - if (entry instanceof EntityType type) { - return BuiltInRegistries.ENTITY_TYPE.getKey(type); - } else if (entry instanceof Block block) { - return BuiltInRegistries.BLOCK.getKey(block); - } + if (entry instanceof EntityType type) return BuiltInRegistries.ENTITY_TYPE.getKey(type); + if (entry instanceof Block block) return BuiltInRegistries.BLOCK.getKey(block); return null; } @@ -91,230 +109,192 @@ public static boolean isUnlocked(Object entry) { public static boolean isNew(Object entry) { ResourceLocation id = getEntryId(entry); - if (id == null) return false; - String key = id.toString(); - return INSTANCE.unlockedEntries.contains(key) && !INSTANCE.seenEntries.contains(key); + return id != null && INSTANCE.unlockedEntries.contains(id.toString()) && !INSTANCE.seenEntries.contains(id.toString()); } public static void markAsSeen(Object entry) { ResourceLocation id = getEntryId(entry); - if (id != null && INSTANCE.seenEntries.add(id.toString())) { - INSTANCE.saveProgress(); - } + if (id != null && INSTANCE.seenEntries.add(id.toString())) INSTANCE.saveProgress(); } + // --- Logic & Resolving --- + public static String getEntryDescription(Object entry) { ResourceLocation id = getEntryId(entry); if (id == null) return ""; - String overrideKey = "fieldguide." + id.getNamespace() + "." + id.getPath() + ".description"; + String fallbackKey = (entry instanceof EntityType) ? "entity." + id.getNamespace() + "." + id.getPath() + ".description" : "lore." + id.getNamespace() + "." + id.getPath(); + return I18n.exists(overrideKey) ? I18n.get(overrideKey) : (I18n.exists(fallbackKey) ? I18n.get(fallbackKey) : I18n.get("fieldguide.description.missing")); + } - String fallbackKey; + /** + * Called when the client receives the SyncCategoriesPacket from the server. + */ + public void updateCategoriesFromServer(List categories) { + this.syncedCategories.clear(); + categories.sort(Comparator.comparingInt(Category::getSortIndex).thenComparing(Category::getId)); - if (entry instanceof EntityType) { - fallbackKey = "entity." + id.getNamespace() + "." + id.getPath() + ".description"; - } else { - fallbackKey = "lore." + id.getNamespace() + "." + id.getPath(); + for (Category cat : categories) { + this.syncedCategories.put(cat.getId(), cat); } + resolveAllEntries(); + } - if (I18n.exists(overrideKey)) { - return I18n.get(overrideKey); - } else if (I18n.exists(fallbackKey)) { - return I18n.get(fallbackKey); - } + public CategoryVisual getCategoryVisual(ResourceLocation categoryId) { + return categoryVisuals.getOrDefault(categoryId, CategoryVisual.DEFAULT); + } - return I18n.get("fieldguide.description.missing"); + public EntryVisual getEntryVisual(ResourceLocation entryId) { + return entryVisuals.getOrDefault(entryId, new EntryVisual()); } - public static void clearCache() { - INSTANCE.flattenedEntryCache = null; - INSTANCE.dropCache.clear(); + @Override + public void onResourceManagerReload(@NotNull ResourceManager resourceManager) { + categoryVisuals.clear(); + entryVisuals.clear(); EntryRenderHelper.clearCache(); - for (Category category : INSTANCE.categories.values()) { - category.resolveEntries(); - } - } - private static boolean isMatch(Object entry, String processedQuery, ResourceLocation id) { - boolean match = false; - if (processedQuery.startsWith("@")) { - String modQuery = processedQuery.substring(1); - if (id.getNamespace().contains(modQuery)) { - match = true; - } - } else { - String name = ""; - if (entry instanceof EntityType type) { - name = type.getDescription().getString(); - } else if (entry instanceof Block block) { - name = block.getName().getString(); - } + loadVisuals(resourceManager, "visuals/categories", (id, json) -> { + CategoryVisual visual = new CategoryVisual(); + if (json.has("color")) visual.color = GsonHelper.getAsString(json, "color"); + if (json.has("icon")) visual.icon = new ResourceLocation(GsonHelper.getAsString(json, "icon")); + categoryVisuals.put(id, visual); + }); - if (name.toLowerCase(Locale.ROOT).contains(processedQuery) || id.getPath().contains(processedQuery)) { - match = true; - } - } - return match; - } + loadVisuals(resourceManager, "visuals/entries", (id, json) -> { + EntryVisual visual = new EntryVisual(); - /** - * Gets drops. If missing, requests from server. - */ - public List getDrops(Object entry) { - if (dropCache.containsKey(entry)) return dropCache.get(entry); + // Base + if (json.has("scale")) visual.scale = GsonHelper.getAsFloat(json, "scale"); + if (json.has("y_offset")) visual.yOffset = GsonHelper.getAsFloat(json, "y_offset"); + if (json.has("x_offset")) visual.xOffset = GsonHelper.getAsFloat(json, "x_offset"); - if (!requestedDrops.contains(entry)) { - ResourceLocation id = getEntryId(entry); - if (id != null) { - requestedDrops.add(entry); - Services.NETWORK.sendToServer(new RequestDropsPacket(id)); - } - } + // Grid Overrides + if (json.has("grid_scale")) visual.gridScale = GsonHelper.getAsFloat(json, "grid_scale"); + if (json.has("grid_y_offset")) visual.gridYOffset = GsonHelper.getAsFloat(json, "grid_y_offset"); + if (json.has("grid_x_offset")) visual.gridXOffset = GsonHelper.getAsFloat(json, "grid_x_offset"); - return Collections.emptyList(); - } + // Page Overrides + if (json.has("page_scale")) visual.pageScale = GsonHelper.getAsFloat(json, "page_scale"); + if (json.has("page_y_offset")) visual.pageYOffset = GsonHelper.getAsFloat(json, "page_y_offset"); + if (json.has("page_x_offset")) visual.pageXOffset = GsonHelper.getAsFloat(json, "page_x_offset"); - /** - * Called by the Packet Handler to update the Client's cache. - */ - public void setDrops(ResourceLocation entryId, List drops) { - Optional foundEntry = getValidEntries().stream() - .filter(e -> Objects.equals(getEntryId(e), entryId)) - .findFirst(); + entryVisuals.put(id, visual); + }); + resolveAllEntries(); - foundEntry.ifPresent(o -> dropCache.put(o, drops)); + Constants.LOG.info("Loaded {} category visuals and {} entry visuals.", categoryVisuals.size(), entryVisuals.size()); } - /** - * Calculates the drops for an entry using the Server's ResourceManager. - */ - public List serverCalculateDrops(ResourceManager serverResourceManager, Object entry) { - List drops = new ArrayList<>(); - ResourceLocation lootTableId = null; - - if (entry instanceof EntityType type) { - lootTableId = type.getDefaultLootTable(); - } else if (entry instanceof Block block) { - lootTableId = block.getLootTable(); - } + private void loadVisuals(ResourceManager mgr, String folder, java.util.function.BiConsumer processor) { + Map> resources = mgr.listResourceStacks("fieldguide/" + folder, + id -> id.getPath().endsWith(".json")); - if (lootTableId != null && !lootTableId.toString().equals("minecraft:empty")) { - ResourceLocation fileId = new ResourceLocation(lootTableId.getNamespace(), "loot_tables/" + lootTableId.getPath() + ".json"); + for (Map.Entry> entry : resources.entrySet()) { + ResourceLocation fileId = entry.getKey(); + String path = fileId.getPath(); + String idPath = path.substring(("fieldguide/" + folder + "/").length(), path.length() - ".json".length()); + ResourceLocation targetId = new ResourceLocation(fileId.getNamespace(), idPath); - Optional resource = serverResourceManager.getResource(fileId); - if (resource.isPresent()) { - try (Reader reader = resource.get().openAsReader()) { - JsonObject json = GsonHelper.parse(reader); - collectItemsFromLootTable(json, drops); + for (Resource resource : entry.getValue()) { + try (Reader reader = resource.openAsReader()) { + processor.accept(targetId, GsonHelper.parse(reader)); } catch (Exception e) { - Constants.LOG.error("Failed to load loot table: {}", fileId, e); + Constants.LOG.error("Error loading field guide visual: {}", fileId, e); } } } + } - // Deduplicate items - List distinctDrops = new ArrayList<>(); - Set seenItems = new HashSet<>(); - for (ItemStack stack : drops) { - if (seenItems.add(stack.getItem())) { - distinctDrops.add(stack); - } + // --- Helper Accessors --- + + private void resolveAllEntries() { + resolvedCategoryEntries.clear(); + for (Category cat : syncedCategories.values()) { + resolveCategory(cat); } - return distinctDrops; } - private void collectItemsFromLootTable(JsonElement element, List drops) { - if (element.isJsonObject()) { - JsonObject obj = element.getAsJsonObject(); - if (obj.has("type") && "minecraft:item".equals(GsonHelper.getAsString(obj, "type"))) { - if (obj.has("name")) { - String name = GsonHelper.getAsString(obj, "name"); - ResourceLocation itemId = new ResourceLocation(name); - BuiltInRegistries.ITEM.getOptional(itemId).ifPresent(item -> drops.add(new ItemStack(item))); + private void resolveCategory(Category category) { + Set foundEntries = new LinkedHashSet<>(); + ModConfig config = ModConfig.get(); + + for (CategoryEntry entry : category.getEntries()) { + if (entry.getType() == CategoryEntry.Type.ENTRY) { + if (entry.getId() == null) continue; + Optional> entityType = BuiltInRegistries.ENTITY_TYPE.getOptional(entry.getId()); + if (entityType.isPresent()) { + if (isValidEntity(entityType.get(), config)) foundEntries.add(entityType.get()); + } else { + Optional block = BuiltInRegistries.BLOCK.getOptional(entry.getId()); + if (block.isPresent() && isValidBlock(block.get(), config)) foundEntries.add(block.get()); } - } - for (Map.Entry entry : obj.entrySet()) { - collectItemsFromLootTable(entry.getValue(), drops); - } - } else if (element.isJsonArray()) { - for (JsonElement e : element.getAsJsonArray()) { - collectItemsFromLootTable(e, drops); + } else if (entry.getType() == CategoryEntry.Type.AUTO_POPULATE) { + foundEntries.addAll(getEntriesForStrategy(entry.getStrategy(), config)); } } + resolvedCategoryEntries.put(category.getId(), new ArrayList<>(foundEntries)); } - private int getScanDuration() { - return (int) (ModConfig.get().scanSpeed * 20); - } + // --- Progression --- - public long getLastUnlockTime() { - return lastUnlockTime; + public List getEntriesForCategory(Category category) { + return resolvedCategoryEntries.getOrDefault(category.getId(), Collections.emptyList()); } - public Object getLastUnlockedEntry() { - return lastUnlockedEntry; - } - - public Object getScanningTarget() { - return scanningTarget; - } - - public BlockPos getScanningPos() { - return scanningPos; - } - - public Entity getScanningEntity() { - return scanningTarget instanceof Entity ? (Entity) scanningTarget : null; - } - - public float getScanProgress(float partialTicks) { - float lerped = (float) prevScanTicks + ((float) scanTicks - (float) prevScanTicks) * partialTicks; - return Math.min(1.0F, lerped / (float) getScanDuration()); - } - - public Entity getFadingEntity() { - return fadingTarget instanceof Entity ? (Entity) fadingTarget : null; - } - - public BlockPos getFadingPos() { - return fadingPos; + private List getEntriesForStrategy(String strategy, ModConfig config) { + List results = new ArrayList<>(); + if ("flora".equalsIgnoreCase(strategy)) { + results.addAll(BuiltInRegistries.BLOCK.stream() + .filter(block -> block instanceof BushBlock || block instanceof LeavesBlock || block instanceof VineBlock || block instanceof CactusBlock || block instanceof SugarCaneBlock || block instanceof WaterlilyBlock || block instanceof StemBlock) + .sorted(Comparator.comparing(block -> BuiltInRegistries.BLOCK.getKey(block).toString())) + .toList()); + } else { + results.addAll(BuiltInRegistries.ENTITY_TYPE.stream() + .filter(type -> { + if ("hostile".equalsIgnoreCase(strategy)) return type.getCategory() == MobCategory.MONSTER; + if ("passive".equalsIgnoreCase(strategy)) + return type.getCategory() != MobCategory.MONSTER && (type.getCategory() != MobCategory.MISC || SpawnEggItem.byId(type) != null); + return false; + }) + .sorted(Comparator.comparing(type -> BuiltInRegistries.ENTITY_TYPE.getKey(type).toString())) + .toList()); + } + return results; } - public Object getFadingTarget() { - return fadingTarget; + private boolean isValidEntity(EntityType type, ModConfig config) { + ResourceLocation id = BuiltInRegistries.ENTITY_TYPE.getKey(type); + return type.canSummon() && !config.isEntityBlacklisted(id); } - public float getFadeProgress() { - return (float) fadeTicks / (float) FADE_DURATION; + private boolean isValidBlock(Block block, ModConfig config) { + ResourceLocation id = BuiltInRegistries.BLOCK.getKey(block); + return !config.isEntityBlacklisted(id); } public Category getCategoryForEntry(Object entry) { - for (Category category : categories.values()) { - if (category.getEntries().contains(entry)) { - return category; - } + for (Map.Entry> cat : resolvedCategoryEntries.entrySet()) { + if (cat.getValue().contains(entry)) return syncedCategories.get(cat.getKey()); } return null; } - public void onWorldLoad(Path worldSaveDir) { - this.unlockedEntries.clear(); - this.seenEntries.clear(); - if (worldSaveDir != null) { - this.currentSavePath = worldSaveDir.resolve("fieldguide.dat"); - loadProgress(); - } else { - this.currentSavePath = null; - } - } + public List searchEntries(String query) { + String processedQuery = query.toLowerCase(Locale.ROOT).trim(); + List results = new ArrayList<>(); + if (processedQuery.isEmpty()) return results; + for (Object entry : getValidEntries()) { + if (!isUnlocked(entry) && !ModConfig.get().showUndiscoveredNames) continue; + ResourceLocation id = getEntryId(entry); + if (id == null) continue; - public void onWorldUnload() { - if (this.currentSavePath != null) { - saveProgress(); + String name = (entry instanceof EntityType type) ? type.getDescription().getString() : ((Block) entry).getName().getString(); + boolean match = name.toLowerCase(Locale.ROOT).contains(processedQuery) || id.getPath().contains(processedQuery); + + if (match) results.add(entry); } - this.currentSavePath = null; - this.unlockedEntries.clear(); - this.seenEntries.clear(); + return results; } public void onClientTick(Minecraft minecraft) { @@ -394,7 +374,7 @@ public void onClientTick(Minecraft minecraft) { this.scanningPos = blockHit.getBlockPos(); } - if (scanTicks >= getScanDuration()) { + if (scanTicks >= (int) (ModConfig.get().scanSpeed * 20)) { unlock(targetKey); minecraft.player.playSound(SoundEvents.VILLAGER_WORK_CARTOGRAPHER, 1.0F, 1.0F); minecraft.player.playSound(SoundEvents.EXPERIENCE_ORB_PICKUP, 1.0F, 1.0F); @@ -458,207 +438,137 @@ public void onClientTick(Minecraft minecraft) { } } + public Object getScanningTarget() { + return scanningTarget; + } + + public BlockPos getScanningPos() { + return scanningPos; + } + + public Entity getScanningEntity() { + return scanningTarget instanceof Entity ? (Entity) scanningTarget : null; + } + + public float getScanProgress(float partialTicks) { + float lerped = (float) prevScanTicks + ((float) scanTicks - (float) prevScanTicks) * partialTicks; + return Math.min(1.0F, lerped / (int) (ModConfig.get().scanSpeed * 20)); + } + + public Object getFadingTarget() { + return fadingTarget; + } + + public BlockPos getFadingPos() { + return fadingPos; + } + + public Entity getFadingEntity() { + return fadingTarget instanceof Entity ? (Entity) fadingTarget : null; + } + + public float getFadeProgress() { + return (float) fadeTicks / (float) FADE_DURATION; + } + public void unlock(Object entry) { unlock(entry, true); } public void unlock(Object entry, boolean showToast) { - if (!getValidEntries().contains(entry)) { - return; - } - ResourceLocation id = getEntryId(entry); if (id != null && unlockedEntries.add(id.toString())) { this.lastUnlockedEntry = entry; this.lastUnlockTime = System.currentTimeMillis(); - - if (showToast) { - Minecraft.getInstance().getToasts().addToast(new FieldGuideToast(entry)); - } + if (showToast) Minecraft.getInstance().getToasts().addToast(new FieldGuideToast(entry)); saveProgress(); } } - public void revoke(Object entry) { - ResourceLocation id = getEntryId(entry); - if (id != null && unlockedEntries.remove(id.toString())) { - seenEntries.remove(id.toString()); - saveProgress(); - } + public long getLastUnlockTime() { + return lastUnlockTime; } - public void revokeAll() { - unlockedEntries.clear(); - seenEntries.clear(); - saveProgress(); + public Object getLastUnlockedEntry() { + return lastUnlockedEntry; } - private void loadProgress() { - if (currentSavePath == null) return; - File file = currentSavePath.toFile(); - - if (file.exists()) { - try (FileReader reader = new FileReader(file)) { - JsonObject json = GSON.fromJson(reader, JsonObject.class); - if (json != null) { - if (json.has("unlocked")) { - JsonArray array = json.getAsJsonArray("unlocked"); - for (JsonElement e : array) { - unlockedEntries.add(e.getAsString()); - } - } - - if (json.has("seen")) { - JsonArray array = json.getAsJsonArray("seen"); - for (JsonElement e : array) { - seenEntries.add(e.getAsString()); - } - } - } - } catch (Exception e) { - Constants.LOG.error("Failed to load field guide progress", e); + // Drops Logic + public List getDrops(Object entry) { + if (dropCache.containsKey(entry)) return dropCache.get(entry); + if (!requestedDrops.contains(entry)) { + ResourceLocation id = getEntryId(entry); + if (id != null) { + requestedDrops.add(entry); + Services.NETWORK.sendToServer(new RequestDropsPacket(id)); } } + return Collections.emptyList(); } - private void saveProgress() { - if (currentSavePath == null) return; - File file = currentSavePath.toFile(); - - try { - JsonObject json = new JsonObject(); - - JsonArray unlockedArray = new JsonArray(); - for (String id : unlockedEntries) { - unlockedArray.add(id); - } - json.add("unlocked", unlockedArray); - - JsonArray seenArray = new JsonArray(); - for (String id : seenEntries) { - seenArray.add(id); - } - json.add("seen", seenArray); - - File parent = file.getParentFile(); - if (parent != null && !parent.exists()) { - if (!parent.mkdirs()) { - throw new IOException("Failed to create directories: " + parent); - } - } - - try (FileWriter writer = new FileWriter(file)) { - GSON.toJson(json, writer); - } - } catch (IOException e) { - Constants.LOG.error("Failed to save field guide progress", e); - } + public void setDrops(ResourceLocation entryId, List drops) { + getValidEntries().stream().filter(e -> Objects.equals(getEntryId(e), entryId)) + .findFirst().ifPresent(o -> dropCache.put(o, drops)); } - @Override - public void onResourceManagerReload(ResourceManager resourceManager) { - categories.clear(); - flattenedEntryCache = null; - dropCache.clear(); - EntryRenderHelper.clearCache(); - - Map> resources = - resourceManager.listResourceStacks( - "categories", - id -> id.getNamespace().equals(Constants.MOD_ID) - && id.getPath().endsWith(".json") - ); - - List sortedIds = new ArrayList<>(resources.keySet()); - sortedIds.sort(Comparator.comparing(ResourceLocation::getPath)); - - for (ResourceLocation fileId : sortedIds) { - String path = fileId.getPath(); - String idPath = path.substring( - "categories/".length(), - path.length() - ".json".length() - ); - - ResourceLocation categoryId = - new ResourceLocation(Constants.MOD_ID, idPath); - - Category category = new Category(categoryId); - - for (Resource resource : resources.get(fileId)) { - try (Reader reader = resource.openAsReader()) { - JsonObject json = GsonHelper.parse(reader); - loadCategoryData(category, json); - } catch (Exception e) { - Constants.LOG.error("Failed to load field guide category: {}", fileId, e); - } - } - - category.resolveEntries(); - - if (!category.getEntries().isEmpty()) { - categories.put(categoryId, category); - } - } - - Constants.LOG.info("Loaded {} field guide categories.", categories.size()); + // Save/Load + public void onWorldLoad(Path worldSaveDir) { + this.unlockedEntries.clear(); + this.seenEntries.clear(); + this.currentSavePath = worldSaveDir != null ? worldSaveDir.resolve("fieldguide.dat") : null; + loadProgress(); } - private void loadCategoryData(Category category, JsonObject json) { - if (GsonHelper.getAsBoolean(json, "replace", false)) { - category.entries.clear(); - } + public void onWorldUnload() { + if (this.currentSavePath != null) saveProgress(); + this.currentSavePath = null; + this.unlockedEntries.clear(); + this.seenEntries.clear(); + } - if (json.has("tab_color")) { - category.tabColor = GsonHelper.getAsString(json, "tab_color"); - } - if (json.has("tab_icon")) { - category.tabIcon = new ResourceLocation(GsonHelper.getAsString(json, "tab_icon")); - } - if (json.has("tab_index")) { - category.tabIndex = GsonHelper.getAsInt(json, "tab_index"); + private void loadProgress() { + if (currentSavePath == null || !currentSavePath.toFile().exists()) return; + try (FileReader reader = new FileReader(currentSavePath.toFile())) { + JsonObject json = GSON.fromJson(reader, JsonObject.class); + if (json.has("unlocked")) + for (JsonElement e : json.getAsJsonArray("unlocked")) unlockedEntries.add(e.getAsString()); + if (json.has("seen")) for (JsonElement e : json.getAsJsonArray("seen")) seenEntries.add(e.getAsString()); + } catch (Exception e) { + Constants.LOG.error("Failed to load progress", e); } + } - if (json.has("contents")) { - JsonArray contents = GsonHelper.getAsJsonArray(json, "contents"); - for (JsonElement element : contents) { - JsonObject entryObj = element.getAsJsonObject(); - String type = GsonHelper.getAsString(entryObj, "type"); - - if ("entry".equals(type)) { - ResourceLocation entityId = new ResourceLocation(GsonHelper.getAsString(entryObj, "id")); - category.entries.add(new CategoryEntry(EntryType.ENTRY, entityId, null)); - } else if ("auto_populate".equals(type)) { - String strategy = GsonHelper.getAsString(entryObj, "strategy"); - category.entries.add(new CategoryEntry(EntryType.AUTO_POPULATE, null, strategy)); - } + private void saveProgress() { + if (currentSavePath == null) return; + try { + JsonObject json = new JsonObject(); + JsonArray uArr = new JsonArray(); + unlockedEntries.forEach(uArr::add); + json.add("unlocked", uArr); + JsonArray sArr = new JsonArray(); + seenEntries.forEach(sArr::add); + json.add("seen", sArr); + File file = currentSavePath.toFile(); + if (file.getParentFile() != null) file.getParentFile().mkdirs(); + try (FileWriter w = new FileWriter(file)) { + GSON.toJson(json, w); } + } catch (Exception e) { + Constants.LOG.error("Failed to save progress", e); } } - /** - * Searches for entries matching the query. - * Supports @modid filtering and unlocked entry filtering. - */ - public List searchEntries(String query) { - String processedQuery = query.toLowerCase(Locale.ROOT).trim(); - List results = new ArrayList<>(); - - if (processedQuery.isEmpty()) return results; - - for (Object entry : getValidEntries()) { - if (!isUnlocked(entry) && !ModConfig.get().showUndiscoveredNames) continue; - - ResourceLocation id = getEntryId(entry); - if (id == null) continue; - - boolean match = isMatch(entry, processedQuery, id); - - if (match) { - results.add(entry); - } + public void revoke(Object entry) { + ResourceLocation id = getEntryId(entry); + if (id != null && unlockedEntries.remove(id.toString())) { + seenEntries.remove(id.toString()); + saveProgress(); } - return results; } - public enum EntryType {ENTRY, AUTO_POPULATE} + public void revokeAll() { + unlockedEntries.clear(); + seenEntries.clear(); + saveProgress(); + } } \ No newline at end of file diff --git a/common/src/main/java/com/evandev/fieldguide/network/SyncCategoriesPacket.java b/common/src/main/java/com/evandev/fieldguide/network/SyncCategoriesPacket.java new file mode 100644 index 0000000..44e0046 --- /dev/null +++ b/common/src/main/java/com/evandev/fieldguide/network/SyncCategoriesPacket.java @@ -0,0 +1,55 @@ +package com.evandev.fieldguide.network; + +import com.evandev.fieldguide.data.Category; +import com.evandev.fieldguide.data.CategoryEntry; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +import java.util.ArrayList; +import java.util.List; + +public class SyncCategoriesPacket { + private final List categories; + + public SyncCategoriesPacket(List categories) { + this.categories = categories; + } + + public SyncCategoriesPacket(FriendlyByteBuf buf) { + this.categories = buf.readCollection(ArrayList::new, b -> { + ResourceLocation id = b.readResourceLocation(); + Category cat = new Category(id); + cat.setSortIndex(b.readInt()); + + int entryCount = b.readInt(); + for (int i = 0; i < entryCount; i++) { + CategoryEntry.Type type = b.readEnum(CategoryEntry.Type.class); + ResourceLocation entryId = b.readBoolean() ? b.readResourceLocation() : null; + String strategy = b.readBoolean() ? b.readUtf() : null; + cat.addEntry(new CategoryEntry(type, entryId, strategy)); + } + return cat; + }); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeCollection(categories, (b, cat) -> { + b.writeResourceLocation(cat.getId()); + b.writeInt(cat.getSortIndex()); + + b.writeInt(cat.getEntries().size()); + for (CategoryEntry entry : cat.getEntries()) { + b.writeEnum(entry.getType()); + b.writeBoolean(entry.getId() != null); + if (entry.getId() != null) b.writeResourceLocation(entry.getId()); + + b.writeBoolean(entry.getStrategy() != null); + if (entry.getStrategy() != null) b.writeUtf(entry.getStrategy()); + } + }); + } + + public List getCategories() { + return categories; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/evandev/fieldguide/server/LootTableHelper.java b/common/src/main/java/com/evandev/fieldguide/server/LootTableHelper.java index 965ca2d..9beb770 100644 --- a/common/src/main/java/com/evandev/fieldguide/server/LootTableHelper.java +++ b/common/src/main/java/com/evandev/fieldguide/server/LootTableHelper.java @@ -39,7 +39,7 @@ public static List getDrops(ResourceManager resourceManager, Object e JsonObject json = GsonHelper.parse(reader); collectItemsFromLootTable(json, drops); } catch (Exception e) { - Constants.LOG.error("Failed to load loot table: " + fileId, e); + Constants.LOG.error("Failed to load loot table: {}", fileId, e); } } } diff --git a/common/src/main/java/com/evandev/fieldguide/server/ServerFieldGuideManager.java b/common/src/main/java/com/evandev/fieldguide/server/ServerFieldGuideManager.java new file mode 100644 index 0000000..6b1e787 --- /dev/null +++ b/common/src/main/java/com/evandev/fieldguide/server/ServerFieldGuideManager.java @@ -0,0 +1,97 @@ +package com.evandev.fieldguide.server; + +import com.evandev.fieldguide.Constants; +import com.evandev.fieldguide.data.Category; +import com.evandev.fieldguide.data.CategoryEntry; +import com.evandev.fieldguide.network.SyncCategoriesPacket; +import com.evandev.fieldguide.platform.Services; +import com.google.gson.*; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimplePreparableReloadListener; +import net.minecraft.util.GsonHelper; +import net.minecraft.util.profiling.ProfilerFiller; +import org.jetbrains.annotations.NotNull; + +import java.io.Reader; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ServerFieldGuideManager extends SimplePreparableReloadListener> { + private static final Gson GSON = new GsonBuilder().create(); + private static final ServerFieldGuideManager INSTANCE = new ServerFieldGuideManager(); + + private Map categories = new LinkedHashMap<>(); + + public static ServerFieldGuideManager getInstance() { + return INSTANCE; + } + + public void syncToPlayer(ServerPlayer player) { + List categoryList = new ArrayList<>(categories.values()); + Services.NETWORK.sendToPlayer(new SyncCategoriesPacket(categoryList), player); + } + + @Override + protected @NotNull Map prepare(ResourceManager resourceManager, @NotNull ProfilerFiller profiler) { + Map map = new LinkedHashMap<>(); + + Map> resources = resourceManager.listResourceStacks( + "fieldguide/categories", + id -> id.getPath().endsWith(".json") + ); + + for (Map.Entry> entry : resources.entrySet()) { + ResourceLocation fileId = entry.getKey(); + String path = fileId.getPath(); + String idPath = path.substring("fieldguide/categories/".length(), path.length() - ".json".length()); + ResourceLocation categoryId = new ResourceLocation(fileId.getNamespace(), idPath); + + Category category = new Category(categoryId); + + for (Resource resource : entry.getValue()) { + try (Reader reader = resource.openAsReader()) { + JsonObject json = GsonHelper.parse(reader); + if (GsonHelper.getAsBoolean(json, "replace", false)) { + category.getEntries().clear(); + } + + if (json.has("sort_index")) { + category.setSortIndex(GsonHelper.getAsInt(json, "sort_index")); + } + + if (json.has("contents")) { + JsonArray contents = GsonHelper.getAsJsonArray(json, "contents"); + for (JsonElement el : contents) { + JsonObject obj = el.getAsJsonObject(); + String typeStr = GsonHelper.getAsString(obj, "type"); + + if ("entry".equals(typeStr)) { + ResourceLocation id = new ResourceLocation(GsonHelper.getAsString(obj, "id")); + category.addEntry(new CategoryEntry(CategoryEntry.Type.ENTRY, id, null)); + } else if ("auto_populate".equals(typeStr)) { + String strategy = GsonHelper.getAsString(obj, "strategy"); + category.addEntry(new CategoryEntry(CategoryEntry.Type.AUTO_POPULATE, null, strategy)); + } + } + } + } catch (Exception e) { + Constants.LOG.error("Failed to load category: {}", fileId, e); + } + } + map.put(categoryId, category); + } + + return map; + } + + @Override + protected void apply(@NotNull Map object, @NotNull ResourceManager resourceManager, @NotNull ProfilerFiller profiler) { + this.categories = object; + Constants.LOG.info("Server loaded {} Field Guide categories.", categories.size()); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/evandev/fieldguide/server/command/FieldGuideCommand.java b/common/src/main/java/com/evandev/fieldguide/server/command/FieldGuideCommand.java index 4527b55..5746456 100644 --- a/common/src/main/java/com/evandev/fieldguide/server/command/FieldGuideCommand.java +++ b/common/src/main/java/com/evandev/fieldguide/server/command/FieldGuideCommand.java @@ -86,10 +86,11 @@ private static int grantCategory(CommandSourceStack source, ResourceLocation cat } int count = 0; - for (Object entry : category.getEntries()) { + for (Object entry : FieldGuideDataManager.getInstance().getEntriesForCategory(category)) { FieldGuideDataManager.getInstance().unlock(entry, false); count++; } + int finalCount = count; source.sendSuccess(() -> Component.translatable("commands.fieldguide.grant.category.success", categoryId, finalCount), true); return count; @@ -121,10 +122,11 @@ private static int revokeCategory(CommandSourceStack source, ResourceLocation ca } int count = 0; - for (Object entry : category.getEntries()) { + for (Object entry : FieldGuideDataManager.getInstance().getEntriesForCategory(category)) { FieldGuideDataManager.getInstance().revoke(entry); count++; } + int finalCount = count; source.sendSuccess(() -> Component.translatable("commands.fieldguide.revoke.category.success", categoryId, finalCount), true); return count; diff --git a/common/src/main/resources/assets/fieldguide/categories/flora.json b/common/src/main/resources/assets/fieldguide/categories/flora.json deleted file mode 100644 index 883400c..0000000 --- a/common/src/main/resources/assets/fieldguide/categories/flora.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tab_color": "#52C83F", - "tab_icon": "fieldguide:textures/gui/icons/azalea.png", - "tab_index": 3, - "contents": [ - { - "type": "auto_populate", - "strategy": "flora" - } - ] -} \ No newline at end of file diff --git a/common/src/main/resources/assets/fieldguide/categories/hostile.json b/common/src/main/resources/assets/fieldguide/categories/hostile.json deleted file mode 100644 index 1d95586..0000000 --- a/common/src/main/resources/assets/fieldguide/categories/hostile.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tab_color": "#FF0000", - "tab_icon": "fieldguide:textures/gui/icons/creeper.png", - "tab_index": 1, - "contents": [ - { - "type": "auto_populate", - "strategy": "hostile" - } - ] -} \ No newline at end of file diff --git a/common/src/main/resources/assets/fieldguide/categories/passive.json b/common/src/main/resources/assets/fieldguide/categories/passive.json deleted file mode 100644 index 42ff89f..0000000 --- a/common/src/main/resources/assets/fieldguide/categories/passive.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tab_color": "#52C83F", - "tab_icon": "fieldguide:textures/gui/icons/bee.png", - "tab_index": 2, - "contents": [ - { - "type": "auto_populate", - "strategy": "passive" - } - ] -} \ No newline at end of file diff --git a/common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/flora.json b/common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/flora.json new file mode 100644 index 0000000..8ebbcb4 --- /dev/null +++ b/common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/flora.json @@ -0,0 +1,4 @@ +{ + "color": "#52C83F", + "icon": "fieldguide:textures/gui/icons/azalea.png" +} \ No newline at end of file diff --git a/common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/hostile.json b/common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/hostile.json new file mode 100644 index 0000000..48446cb --- /dev/null +++ b/common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/hostile.json @@ -0,0 +1,4 @@ +{ + "color": "#FF0000", + "icon": "fieldguide:textures/gui/icons/creeper.png" +} \ No newline at end of file diff --git a/common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/passive.json b/common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/passive.json new file mode 100644 index 0000000..34a67cc --- /dev/null +++ b/common/src/main/resources/assets/fieldguide/fieldguide/visuals/categories/passive.json @@ -0,0 +1,4 @@ +{ + "color": "#52C83F", + "icon": "fieldguide:textures/gui/icons/bee.png" +} \ No newline at end of file diff --git a/common/src/main/resources/data/fieldguide/fieldguide/categories/flora.json b/common/src/main/resources/data/fieldguide/fieldguide/categories/flora.json new file mode 100644 index 0000000..c325ed0 --- /dev/null +++ b/common/src/main/resources/data/fieldguide/fieldguide/categories/flora.json @@ -0,0 +1,9 @@ +{ + "sort_index": 3, + "contents": [ + { + "type": "auto_populate", + "strategy": "flora" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/data/fieldguide/fieldguide/categories/hostile.json b/common/src/main/resources/data/fieldguide/fieldguide/categories/hostile.json new file mode 100644 index 0000000..e0d7360 --- /dev/null +++ b/common/src/main/resources/data/fieldguide/fieldguide/categories/hostile.json @@ -0,0 +1,9 @@ +{ + "sort_index": 1, + "contents": [ + { + "type": "auto_populate", + "strategy": "hostile" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/data/fieldguide/fieldguide/categories/passive.json b/common/src/main/resources/data/fieldguide/fieldguide/categories/passive.json new file mode 100644 index 0000000..8bf223e --- /dev/null +++ b/common/src/main/resources/data/fieldguide/fieldguide/categories/passive.json @@ -0,0 +1,9 @@ +{ + "sort_index": 2, + "contents": [ + { + "type": "auto_populate", + "strategy": "passive" + } + ] +} \ No newline at end of file diff --git a/fabric/src/main/java/com/evandev/fieldguide/FieldGuideFabricClient.java b/fabric/src/main/java/com/evandev/fieldguide/FieldGuideFabricClient.java index cf0e8f9..b0f6272 100644 --- a/fabric/src/main/java/com/evandev/fieldguide/FieldGuideFabricClient.java +++ b/fabric/src/main/java/com/evandev/fieldguide/FieldGuideFabricClient.java @@ -2,6 +2,7 @@ import com.evandev.fieldguide.client.FieldGuideClient; import com.evandev.fieldguide.data.FieldGuideDataManager; +import com.evandev.fieldguide.network.SyncCategoriesPacket; import com.evandev.fieldguide.network.SyncDropsPacket; import com.evandev.fieldguide.platform.FabricNetworkHelper; import com.evandev.fieldguide.server.command.FieldGuideCommand; @@ -48,6 +49,16 @@ public void onResourceManagerReload(@NotNull ResourceManager resourceManager) { FieldGuideCommand.register(dispatcher); }); + ClientPlayNetworking.registerGlobalReceiver(FabricNetworkHelper.SYNC_DROPS_CHANNEL, (client, handler, buf, responseSender) -> { + SyncDropsPacket packet = new SyncDropsPacket(buf); + client.execute(() -> FieldGuideDataManager.getInstance().setDrops(packet.getEntryId(), packet.getDrops())); + }); + + ClientPlayNetworking.registerGlobalReceiver(FabricNetworkHelper.SYNC_CATEGORIES_CHANNEL, (client, handler, buf, responseSender) -> { + SyncCategoriesPacket packet = new SyncCategoriesPacket(buf); + client.execute(() -> FieldGuideDataManager.getInstance().updateCategoriesFromServer(packet.getCategories())); + }); + ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { Path saveDir = null; if (client.hasSingleplayerServer() && client.getSingleplayerServer() != null) { diff --git a/fabric/src/main/java/com/evandev/fieldguide/FieldGuideMod.java b/fabric/src/main/java/com/evandev/fieldguide/FieldGuideMod.java index 667c435..6a405f6 100644 --- a/fabric/src/main/java/com/evandev/fieldguide/FieldGuideMod.java +++ b/fabric/src/main/java/com/evandev/fieldguide/FieldGuideMod.java @@ -5,16 +5,26 @@ import com.evandev.fieldguide.platform.FabricNetworkHelper; import com.evandev.fieldguide.platform.Services; import com.evandev.fieldguide.server.LootTableHelper; +import com.evandev.fieldguide.server.ServerFieldGuideManager; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; +import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; public class FieldGuideMod implements ModInitializer { @@ -22,12 +32,27 @@ public class FieldGuideMod implements ModInitializer { public void onInitialize() { CommonClass.init(); + ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new IdentifiableResourceReloadListener() { + @Override + public ResourceLocation getFabricId() { + return new ResourceLocation(Constants.MOD_ID, "server_data"); + } + + @Override + public @NotNull CompletableFuture reload(@NotNull PreparationBarrier barrier, @NotNull ResourceManager manager, @NotNull ProfilerFiller prepareProfiler, @NotNull ProfilerFiller applyProfiler, @NotNull Executor prepareExecutor, @NotNull Executor applyExecutor) { + return ServerFieldGuideManager.getInstance().reload(barrier, manager, prepareProfiler, applyProfiler, prepareExecutor, applyExecutor); + } + }); + + ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { + ServerFieldGuideManager.getInstance().syncToPlayer(handler.player); + }); + ServerPlayNetworking.registerGlobalReceiver(FabricNetworkHelper.REQUEST_DROPS_CHANNEL, (server, player, handler, buf, responseSender) -> { RequestDropsPacket packet = new RequestDropsPacket(buf); server.execute(() -> { ResourceLocation id = packet.getEntryId(); Object entry = null; - Optional> type = BuiltInRegistries.ENTITY_TYPE.getOptional(id); if (type.isPresent()) entry = type.get(); else { diff --git a/fabric/src/main/java/com/evandev/fieldguide/platform/FabricNetworkHelper.java b/fabric/src/main/java/com/evandev/fieldguide/platform/FabricNetworkHelper.java index dff9359..9c3d8c1 100644 --- a/fabric/src/main/java/com/evandev/fieldguide/platform/FabricNetworkHelper.java +++ b/fabric/src/main/java/com/evandev/fieldguide/platform/FabricNetworkHelper.java @@ -2,6 +2,7 @@ import com.evandev.fieldguide.Constants; import com.evandev.fieldguide.network.RequestDropsPacket; +import com.evandev.fieldguide.network.SyncCategoriesPacket; import com.evandev.fieldguide.network.SyncDropsPacket; import com.evandev.fieldguide.platform.services.INetworkHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; @@ -14,6 +15,7 @@ public class FabricNetworkHelper implements INetworkHelper { public static final ResourceLocation REQUEST_DROPS_CHANNEL = new ResourceLocation(Constants.MOD_ID, "request_drops"); public static final ResourceLocation SYNC_DROPS_CHANNEL = new ResourceLocation(Constants.MOD_ID, "sync_drops"); + public static final ResourceLocation SYNC_CATEGORIES_CHANNEL = new ResourceLocation(Constants.MOD_ID, "sync_categories"); @Override public void sendToServer(Object packet) { @@ -30,6 +32,9 @@ public void sendToPlayer(Object packet, ServerPlayer player) { if (packet instanceof SyncDropsPacket sync) { sync.encode(buf); ServerPlayNetworking.send(player, SYNC_DROPS_CHANNEL, buf); + } else if (packet instanceof SyncCategoriesPacket syncCat) { + syncCat.encode(buf); + ServerPlayNetworking.send(player, SYNC_CATEGORIES_CHANNEL, buf); } } } \ No newline at end of file diff --git a/forge/src/main/java/com/evandev/fieldguide/FieldGuideMod.java b/forge/src/main/java/com/evandev/fieldguide/FieldGuideMod.java index c8c8529..f880333 100644 --- a/forge/src/main/java/com/evandev/fieldguide/FieldGuideMod.java +++ b/forge/src/main/java/com/evandev/fieldguide/FieldGuideMod.java @@ -4,10 +4,12 @@ import com.evandev.fieldguide.config.ClothConfigIntegration; import com.evandev.fieldguide.data.FieldGuideDataManager; import com.evandev.fieldguide.network.RequestDropsPacket; +import com.evandev.fieldguide.network.SyncCategoriesPacket; import com.evandev.fieldguide.network.SyncDropsPacket; import com.evandev.fieldguide.platform.ForgeNetworkHelper; import com.evandev.fieldguide.platform.Services; import com.evandev.fieldguide.server.LootTableHelper; +import com.evandev.fieldguide.server.ServerFieldGuideManager; import com.evandev.fieldguide.server.command.FieldGuideCommand; import net.minecraft.client.Minecraft; import net.minecraft.core.registries.BuiltInRegistries; @@ -22,8 +24,10 @@ import net.minecraftforge.client.event.RegisterClientReloadListenersEvent; import net.minecraftforge.client.event.RegisterKeyMappingsEvent; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.AddReloadListenerEvent; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.ModList; @@ -88,7 +92,7 @@ public static void handleRequest(RequestDropsPacket packet, Supplier contextSupplier) { + public static void handleSyncDrops(SyncDropsPacket packet, Supplier contextSupplier) { NetworkEvent.Context context = contextSupplier.get(); context.enqueueWork(() -> { FieldGuideDataManager.getInstance().setDrops(packet.getEntryId(), packet.getDrops()); @@ -96,6 +100,14 @@ public static void handleSync(SyncDropsPacket packet, Supplier contextSupplier) { + NetworkEvent.Context context = contextSupplier.get(); + context.enqueueWork(() -> { + FieldGuideDataManager.getInstance().updateCategoriesFromServer(packet.getCategories()); + }); + context.setPacketHandled(true); + } + private void commonSetup(final FMLCommonSetupEvent event) { ForgeNetworkHelper.register(); } @@ -109,6 +121,12 @@ public void registerKeyMappings(RegisterKeyMappingsEvent event) { event.register(FieldGuideClient.OPEN_GUIDE_KEY); } + @SubscribeEvent + public void onAddReloadListeners(AddReloadListenerEvent event) { + // Register server data loader + event.addListener(ServerFieldGuideManager.getInstance()); + } + @SubscribeEvent public void onRegisterCommands(RegisterCommandsEvent event) { FieldGuideCommand.register(event.getDispatcher()); @@ -133,6 +151,13 @@ public void onClientPlayerLogin(ClientPlayerNetworkEvent.LoggingIn event) { FieldGuideDataManager.getInstance().onWorldLoad(saveDir); } + @SubscribeEvent + public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { + if (event.getEntity() instanceof ServerPlayer player) { + ServerFieldGuideManager.getInstance().syncToPlayer(player); + } + } + @SubscribeEvent public void onClientPlayerLogout(ClientPlayerNetworkEvent.LoggingOut event) { FieldGuideDataManager.getInstance().onWorldUnload(); diff --git a/forge/src/main/java/com/evandev/fieldguide/platform/ForgeNetworkHelper.java b/forge/src/main/java/com/evandev/fieldguide/platform/ForgeNetworkHelper.java index 6fb87d5..62199c5 100644 --- a/forge/src/main/java/com/evandev/fieldguide/platform/ForgeNetworkHelper.java +++ b/forge/src/main/java/com/evandev/fieldguide/platform/ForgeNetworkHelper.java @@ -2,6 +2,7 @@ import com.evandev.fieldguide.Constants; import com.evandev.fieldguide.network.RequestDropsPacket; +import com.evandev.fieldguide.network.SyncCategoriesPacket; import com.evandev.fieldguide.network.SyncDropsPacket; import com.evandev.fieldguide.platform.services.INetworkHelper; import net.minecraft.resources.ResourceLocation; @@ -22,7 +23,8 @@ public class ForgeNetworkHelper implements INetworkHelper { public static void register() { int id = 0; CHANNEL.registerMessage(id++, RequestDropsPacket.class, RequestDropsPacket::encode, RequestDropsPacket::new, com.evandev.fieldguide.FieldGuideMod::handleRequest); - CHANNEL.registerMessage(id++, SyncDropsPacket.class, SyncDropsPacket::encode, SyncDropsPacket::new, com.evandev.fieldguide.FieldGuideMod::handleSync); + CHANNEL.registerMessage(id++, SyncDropsPacket.class, SyncDropsPacket::encode, SyncDropsPacket::new, com.evandev.fieldguide.FieldGuideMod::handleSyncDrops); + CHANNEL.registerMessage(id++, SyncCategoriesPacket.class, SyncCategoriesPacket::encode, SyncCategoriesPacket::new, com.evandev.fieldguide.FieldGuideMod::handleSyncCategories); } @Override