From e245bde9bf2b39972c19e92cc0dff6f245789496 Mon Sep 17 00:00:00 2001 From: Kli Kli Date: Thu, 13 Jun 2024 19:01:28 +0200 Subject: [PATCH] feat: allow books or individual categories to use "index" mode (patchouli style books) (#214) * feat: first experiments with index-based books * feat: add basic category index view * chore: refactor structure and rename classes to unified scheme * chore: add missing annotations * chore: adjust wrong renames * feat: add display mode property to book and category * chore: state management refactor * chore: run datagen * chore: state management refactor * chore: remove test code * chore: state management refactor * feat: re-add open entry and category link entry functionality * chore: refactor closing handling * chore: rename onEsc to closeScreenStack to better reflect its role * chore: minor code cleanup * fix: book always reopens entry/category even if not closed with esc * feat: set up new direct-to-entry navigation * chore: remove completed todo * chore: reorganize methods in gui manager and add index mode open logic * feat: add index book state loading/saving * feat: extend index mode handling * fix: each category displays all book entries * feat: add background rendering handling for index categories in node parents * feat: add behaviour handling for index categories in node parents * chore: code cleanup * feat: add descriptions to book and category * chore: restructure book package * chore: re-establish reuse compliance * feat: add layered gui for fabric * feat: add search button to index parent screen * feat: add read all button * chore: remove complete todo comments * fix: commands on fabric * feat: add sort number handling to datagen --- .../modonomicon/api/ModonomiconConstants.java | 4 + .../api/datagen/BookContextHelper.java | 8 + .../modonomicon/api/datagen/BookProvider.java | 108 +++- .../api/datagen/CategoryProvider.java | 15 +- .../api/datagen/book/BookCategoryModel.java | 48 ++ .../api/datagen/book/BookEntryModel.java | 19 + .../api/datagen/book/BookModel.java | 48 ++ .../api/events/EntryClickedEvent.java | 2 +- .../com/klikli_dev/modonomicon/book/Book.java | 44 +- .../modonomicon/book/BookCategory.java | 36 +- .../modonomicon/book/BookDisplayMode.java | 42 ++ .../modonomicon/book/BookProvider.java | 9 + .../context/BookConditionPageContext.java | 4 +- ...ntBookEntry.java => BookContentEntry.java} | 21 +- .../modonomicon/book/entries/BookEntry.java | 23 +- .../book/entries/CategoryLinkBookEntry.java | 11 +- .../book/error/BookErrorManager.java | 4 +- .../modonomicon/book/page/BookEntityPage.java | 4 +- .../book/page/BookMultiblockPage.java | 4 +- .../modonomicon/book/page/BookPage.java | 10 +- .../modonomicon/book/page/BookRecipePage.java | 7 +- .../book/page/BookSpotlightPage.java | 4 +- .../bookstate/BookVisualStates.java | 1 + .../bookstate/visual/BookVisualState.java | 16 +- .../bookstate/visual/CategoryVisualState.java | 12 +- .../client/gui/BookGuiManager.java | 463 +++++++++++++---- .../client/gui/book/BookAddress.java | 53 ++ .../client/gui/book/BookCategoryScreen.java | 466 +----------------- .../client/gui/book/BookHistoryEntry.java | 23 - .../client/gui/book/BookPaginatedScreen.java | 36 +- .../client/gui/book/BookParentScreen.java | 29 ++ .../gui/book/BookScreenWithButtons.java | 6 +- .../client/gui/book/button/BackButton.java | 4 +- .../client/gui/book/button/BookButton.java | 4 +- .../gui/book/button/CategoryButton.java | 15 +- .../gui/book/button/CategoryListButton.java | 88 ++++ .../gui/book/button/EntryListButton.java | 14 +- .../client/gui/book/button/ReadAllButton.java | 10 +- .../client/gui/book/button/SearchButton.java | 12 +- .../gui/book/button/SmallArrowButton.java | 4 +- .../gui/book/button/VisualizeButton.java | 4 +- .../BookEntryScreen.java} | 89 ++-- .../{ => entry}/EntryConnectionRenderer.java | 9 +- .../book/{ => entry}/EntryDisplayState.java | 2 +- .../index/BookCategoryIndexOnNodeScreen.java | 62 +++ .../book/index/BookCategoryIndexScreen.java | 307 ++++++++++++ .../gui/book/index/BookParentIndexScreen.java | 372 ++++++++++++++ .../gui/book/node/BookCategoryNodeScreen.java | 421 ++++++++++++++++ .../BookParentNodeScreen.java} | 134 +++-- .../node/DummyBookCategoryNodeScreen.java | 19 + .../book/{ => search}/BookSearchScreen.java | 70 ++- .../page/BookCraftingRecipePageRenderer.java | 6 +- .../render/page/BookEntityPageRenderer.java | 16 +- .../render/page/BookImagePageRenderer.java | 16 +- .../page/BookMultiblockPageRenderer.java | 22 +- .../client/render/page/BookPageRenderer.java | 14 +- .../BookProcessingRecipePageRenderer.java | 6 +- .../render/page/BookRecipePageRenderer.java | 10 +- .../page/BookSmithingRecipePageRenderer.java | 6 +- .../page/BookSpotlightPageRenderer.java | 14 +- .../render/page/BookTextPageRenderer.java | 10 +- .../command/ResetBookUnlocksCommand.java | 1 - .../modonomicon/data/BookDataManager.java | 6 +- .../modonomicon/data/LoaderRegistry.java | 2 +- .../data/MultiblockDataManager.java | 1 - .../modonomicon/datagen/EnUsProvider.java | 5 +- .../datagen/book/DemoBookProvider.java | 45 +- .../book/FeaturesCategoryProvider.java | 3 + .../ModonomiconJeiIntegrationImpl.java | 3 +- .../modonomicon/item/ModonomiconItem.java | 7 +- .../networking/SaveBookStateMessage.java | 5 + .../networking/SaveCategoryStateMessage.java | 5 + .../networking/SaveEntryStateMessage.java | 7 +- .../SyncBookUnlockStatesMessage.java | 4 +- .../platform/services/GuiHelper.java | 2 + .../modonomicon/registry/CommandRegistry.java | 2 +- .../main/resources/modonomicon.accesswidener | 4 +- .../13ce76d12c64f3a9c0874320845bdcbe00b11956 | 62 +-- .../3eecf17e0b3542fa44b02bae7aa93622bb4a80e9 | 4 +- .../e6530a5ab7a2c0d5d93ba510671319215bfabee3 | 6 +- .../fbe7fdca8a3d7d4724f001a760fe02aeb76403e5 | 6 +- .../assets/modonomicon/lang/en_us.json | 3 + .../modonomicon/books/demo/book.json | 2 + .../books/demo/categories/conditional.json | 4 +- .../books/demo/categories/features.json | 4 +- .../books/demo/categories/formatting.json | 6 +- .../books/demo/categories/hidden.json | 4 +- .../books/demo/categories/other.json | 6 +- .../entries/conditional/always_locked.json | 1 + .../books/demo/entries/features/command.json | 1 + .../features/condition_advancement.json | 1 + .../entries/features/condition_level_1.json | 1 + .../entries/features/condition_level_2.json | 1 + .../demo/entries/features/condition_root.json | 1 + .../demo/entries/features/custom_icon.json | 1 + .../books/demo/entries/features/empty.json | 1 + .../books/demo/entries/features/entity.json | 1 + .../books/demo/entries/features/image.json | 1 + .../demo/entries/features/multiblock.json | 1 + .../books/demo/entries/features/recipe.json | 1 + .../books/demo/entries/features/redirect.json | 1 + .../demo/entries/features/spotlight.json | 1 + .../demo/entries/features/two_parents.json | 1 + .../demo/entries/formatting/advanced.json | 1 + .../entries/formatting/always_locked.json | 1 + .../books/demo/entries/formatting/basic.json | 1 + .../books/demo/entries/formatting/link.json | 1 + .../demo/entries/hidden/always_locked.json | 1 + .../books/demo/entries/other/a.json | 1 + .../books/demo/entries/other/b.json | 1 + .../books/demo/entries/other/root.json | 1 + .../modonomicon/gui/FabricGuiHelper.java | 61 ++- .../gui/FabricMultiLayerScreen.java | 278 +++++++++++ .../modonomicon/mixin/MixinGameRenderer.java | 23 + .../registry/FabricClientCommandRegistry.java | 4 +- .../modonomicon/mixin/MixinGui.java | 4 + .../1a31259611d3b927433f60e0500eb1623e6ee72d | 60 +-- .../c622617f6fabf890a00b9275cd5f643584a8a2c8 | 4 +- .../assets/modonomicon/lang/en_us.json | 3 + .../modonomicon/books/demo/book.json | 2 + .../books/demo/categories/conditional.json | 2 + .../books/demo/categories/features.json | 2 + .../books/demo/categories/formatting.json | 2 + .../books/demo/categories/hidden.json | 2 + .../books/demo/categories/other.json | 2 + .../entries/conditional/always_locked.json | 1 + .../books/demo/entries/features/command.json | 1 + .../features/condition_advancement.json | 1 + .../entries/features/condition_level_1.json | 1 + .../entries/features/condition_level_2.json | 1 + .../demo/entries/features/condition_root.json | 1 + .../demo/entries/features/custom_icon.json | 1 + .../books/demo/entries/features/empty.json | 1 + .../books/demo/entries/features/entity.json | 1 + .../books/demo/entries/features/image.json | 1 + .../demo/entries/features/multiblock.json | 1 + .../books/demo/entries/features/recipe.json | 1 + .../books/demo/entries/features/redirect.json | 1 + .../demo/entries/features/spotlight.json | 1 + .../demo/entries/features/two_parents.json | 1 + .../demo/entries/formatting/advanced.json | 1 + .../entries/formatting/always_locked.json | 1 + .../books/demo/entries/formatting/basic.json | 1 + .../books/demo/entries/formatting/link.json | 1 + .../demo/entries/hidden/always_locked.json | 1 + .../books/demo/entries/other/a.json | 1 + .../books/demo/entries/other/b.json | 1 + .../books/demo/entries/other/root.json | 1 + .../modonomicon/gui/NeoGuiHelper.java | 5 + 149 files changed, 2992 insertions(+), 1057 deletions(-) create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/book/BookDisplayMode.java create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/book/BookProvider.java rename common/src/main/java/com/klikli_dev/modonomicon/book/entries/{ContentBookEntry.java => BookContentEntry.java} (89%) create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookAddress.java delete mode 100644 common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookHistoryEntry.java create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookParentScreen.java create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/CategoryListButton.java rename common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/{BookContentScreen.java => entry/BookEntryScreen.java} (94%) rename common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/{ => entry}/EntryConnectionRenderer.java (97%) rename common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/{ => entry}/EntryDisplayState.java (73%) create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/index/BookCategoryIndexOnNodeScreen.java create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/index/BookCategoryIndexScreen.java create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/index/BookParentIndexScreen.java create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/node/BookCategoryNodeScreen.java rename common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/{BookOverviewScreen.java => node/BookParentNodeScreen.java} (79%) create mode 100644 common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/node/DummyBookCategoryNodeScreen.java rename common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/{ => search}/BookSearchScreen.java (78%) create mode 100644 fabric/src/main/java/com/klikli_dev/modonomicon/gui/FabricMultiLayerScreen.java diff --git a/common/src/main/java/com/klikli_dev/modonomicon/api/ModonomiconConstants.java b/common/src/main/java/com/klikli_dev/modonomicon/api/ModonomiconConstants.java index a060ab208..41d6f30eb 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/api/ModonomiconConstants.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/api/ModonomiconConstants.java @@ -161,6 +161,10 @@ public static class Gui { public static final String SEARCH_INFO_TEXT = PREFIX + "search.info"; public static final String SEARCH_ENTRY_LIST_TITLE = PREFIX + "search.entry_list_title"; + public static final String BOOK_INDEX_LIST_TITLE = PREFIX + "book.index_list_title"; + public static final String CATEGORY_INDEX_LIST_TITLE = PREFIX + "category.index_list_title"; + + public static final String OPEN_SEARCH = PREFIX + "open_search"; public static final String RECIPE_PAGE_RECIPE_MISSING = PREFIX + "recipe_page.recipe_missing"; diff --git a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/BookContextHelper.java b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/BookContextHelper.java index ff10eef49..a96671cc5 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/BookContextHelper.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/BookContextHelper.java @@ -55,6 +55,10 @@ public String bookName() { return this.book() + ".name"; } + public String bookDescription() { + return this.book() + ".description"; + } + public String bookTooltip() { return this.book() + ".tooltip"; } @@ -87,6 +91,10 @@ public String categoryName() { return this.category() + ".name"; } + public String categoryDescription() { + return this.category() + ".description"; + } + public String categoryCondition(String conditionName) { return this.category() + ".condition." + conditionName; } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/BookProvider.java b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/BookProvider.java index 4c8accd00..cbeb27082 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/BookProvider.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/BookProvider.java @@ -22,6 +22,7 @@ import org.slf4j.Logger; import java.nio.file.Path; +import java.text.MessageFormat; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collector; @@ -30,8 +31,6 @@ public abstract class BookProvider implements DataProvider { - protected static final Logger LOGGER = LogUtils.getLogger(); - public static final Collector> mapMaker = Collector., Object2ObjectOpenHashMap>of( Object2ObjectOpenHashMap::new, @@ -46,6 +45,7 @@ public abstract class BookProvider implements DataProvider { protected final ModonomiconLanguageProvider lang; protected final Map translations; protected final Map bookModels; + protected BookModel bookModel; protected final String modid; protected String bookId; protected BookContextHelper context; @@ -53,9 +53,10 @@ public abstract class BookProvider implements DataProvider { protected Map defaultMacros; protected ConditionHelper conditionHelper; + protected int currentSortIndex; /** - * @param defaultLang The LanguageProvider to fill with this book provider. IMPORTANT: the Languag Provider needs to be added to the DataGenerator AFTER the BookProvider. + * @param defaultLang The LanguageProvider to fill with this book provider. IMPORTANT: the Language Provider needs to be added to the DataGenerator AFTER the BookProvider. */ public BookProvider(String bookId, PackOutput packOutput, CompletableFuture registries, String modid, ModonomiconLanguageProvider defaultLang, ModonomiconLanguageProvider... translations) { this.modid = modid; @@ -63,6 +64,7 @@ public BookProvider(String bookId, PackOutput packOutput, CompletableFuture(); + this.bookModel = null; this.translations = Stream.concat(Arrays.stream(translations), Stream.of(defaultLang)) .collect(mapMaker); @@ -70,6 +72,7 @@ public BookProvider(String bookId, PackOutput packOutput, CompletableFuture(); this.conditionHelper = new ConditionHelper(); + this.currentSortIndex = 0; } protected ModonomiconLanguageProvider lang() { @@ -92,18 +95,6 @@ protected ConditionHelper condition() { return this.conditionHelper; } - /** - * Call registerMacro() here to make macros (= simple string.replace() of macro -> value) available to all category providers of this book. - */ - protected abstract void registerDefaultMacros(); - - /** - * Override this to generate your book. - * Each BookProvider should generate only one book. - * Context already is set to the book id provided in the constructor. - */ - protected abstract BookModel generateBook(); - /** * Register a macro (= simple string.replace() of macro -> value) to be used in all category providers of this book. */ @@ -122,15 +113,25 @@ protected Map defaultMacros() { * Only override if you know what you are doing. * Generally you should not. */ - protected void generate() { - this.context.book(this.bookId); - this.add(this.generateBook()); + public void generate() { + this.context().book(this.bookId); + this.bookModel = this.generateBook(); + this.generateCategories(); + this.add(this.bookModel); } protected ResourceLocation modLoc(String name) { return ResourceLocation.fromNamespaceAndPath(this.modid, name); } + protected BookCategoryModel add(BookCategoryModel category) { + if(category.getSortNumber() == -1){ + category.withSortNumber(this.currentSortIndex++); + } + this.bookModel.withCategory(category); + return category; + } + protected BookModel add(BookModel bookModel) { if (this.bookModels.containsKey(bookModel.getId())) throw new IllegalStateException("Duplicate book " + bookModel.getId()); @@ -176,6 +177,59 @@ protected Path getPath(Path dataFolder, BookEntryModel bookEntryModel) { .resolve(id.getPath() + ".json"); } + /** + * Add translation to the default translation provider. + * This will apply all macros registered in this category provider and its parent book provider. + */ + protected void add(String key, String value) { + this.lang().add(key, this.macro(value)); + } + + /** + * Adds translation to the default translation provider with a pattern and arguments, internally using MessageFormat to format the pattern. + * This will apply all macros registered in this category provider and its parent book provider. + */ + protected void add(String key, String pattern, Object... args) { + this.add(key, this.format(pattern, args)); + } + + /** + * Add translation to the given translation provider. + * This will apply all macros registered in this category provider and its parent book provider. + *

+ * Sample usage: this.add(this.lang("ru_ru"), "category", "Text"); + */ + protected void add(ModonomiconLanguageProvider translation, String key, String value) { + translation.add(key, this.macro(value)); + } + + /** + * Adds translation to the given translation provider with a pattern and arguments, internally using MessageFormat to format the pattern. + * This will apply all macros registered in this category provider and its parent book provider. + *

+ * Sample usage: this.add(this.lang("ru_ru"), "category", "pattern", "arg1"); + */ + protected void add(ModonomiconLanguageProvider translation, String key, String pattern, Object... args) { + this.add(translation, key, this.format(pattern, args)); + } + + /** + * Apply all macros of this book provider to the input string. + */ + protected String macro(String input) { + for (var entry : this.defaultMacros().entrySet()) { + input = input.replace(entry.getKey(), entry.getValue()); + } + return input; + } + + /** + * Format a string with the given arguments using MessageFormat.format() + */ + protected String format(String pattern, Object... arguments) { + return MessageFormat.format(pattern, arguments); + } + @Override public CompletableFuture run(CachedOutput cache) { return this.registries.thenCompose(registries -> { @@ -215,4 +269,22 @@ public String getName() { return "Books: " + this.modid; } + /** + * Call registerMacro() here to make macros (= simple string.replace() of macro -> value) available to all category providers of this book. + */ + protected abstract void registerDefaultMacros(); + + /** + * Implement this and return your book. + * Categories should not be added here, instead call .add() in generateCategories(). + * Context already is set to this book. + */ + protected abstract BookModel generateBook(); + + /** + * Implement this and in it generate and .add() your categories. + * Context already is set to this book. + */ + protected abstract void generateCategories(); + } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/CategoryProvider.java b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/CategoryProvider.java index c4e0dbe7d..810d038bd 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/CategoryProvider.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/CategoryProvider.java @@ -35,6 +35,8 @@ public abstract class CategoryProvider { protected ConditionHelper conditionHelper; protected BookCategoryModel category; + protected int currentSortIndex; + public CategoryProvider(BookProvider parent, String categoryId) { this.parent = parent; @@ -43,6 +45,7 @@ public CategoryProvider(BookProvider parent, String categoryId) { this.macros = new Object2ObjectOpenHashMap<>(); this.conditionHelper = new ConditionHelper(); this.category = null; + this.currentSortIndex = 0; } public String categoryId() { @@ -260,7 +263,7 @@ protected void add(String key, String value) { *

* Sample usage: this.add(this.lang("ru_ru"), "category", "Text"); */ - protected void add(AbstractModonomiconLanguageProvider translation, String key, String value) { + protected void add(ModonomiconLanguageProvider translation, String key, String value) { translation.add(key, this.macro(value)); } @@ -278,16 +281,24 @@ protected void add(String key, String pattern, Object... args) { *

* Sample usage: this.add(this.lang("ru_ru"), "category", "pattern", "arg1"); */ - protected void add(AbstractModonomiconLanguageProvider translation, String key, String pattern, Object... args) { + protected void add(ModonomiconLanguageProvider translation, String key, String pattern, Object... args) { this.add(translation, key, this.format(pattern, args)); } protected BookEntryModel add(BookEntryModel entry) { + if(entry.getSortNumber() == -1){ + entry.withSortNumber(this.currentSortIndex++); + } this.category.withEntry(entry); return entry; } protected List add(List entries) { + for (var entry : entries) { + if(entry.getSortNumber() == -1){ + entry.withSortNumber(this.currentSortIndex++); + } + } this.category.withEntries(entries); return entries; } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookCategoryModel.java b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookCategoryModel.java index 46c7ff547..47ebd7993 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookCategoryModel.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookCategoryModel.java @@ -10,10 +10,13 @@ import com.google.gson.JsonObject; import com.klikli_dev.modonomicon.api.ModonomiconConstants.Data.Category; import com.klikli_dev.modonomicon.api.datagen.book.condition.BookConditionModel; +import com.klikli_dev.modonomicon.api.datagen.book.page.BookTextPageModel; import com.klikli_dev.modonomicon.book.BookCategoryBackgroundParallaxLayer; +import com.klikli_dev.modonomicon.book.BookDisplayMode; import com.klikli_dev.modonomicon.registry.ItemRegistry; import com.mojang.serialization.JsonOps; import net.minecraft.core.HolderLookup; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.ItemLike; import org.jetbrains.annotations.Nullable; @@ -26,7 +29,17 @@ public class BookCategoryModel { protected ResourceLocation id; protected String name; + /** + * The description to (optionally) display on the first page of the category. + */ + protected BookTextHolderModel description = new BookTextHolderModel(""); protected BookIconModel icon = BookIconModel.create(ItemRegistry.MODONOMICON_PURPLE.get()); + + /** + * The display mode - node based (thaumonomicon style) or index based (lexica botania / patchouli style) + */ + protected BookDisplayMode displayMode = BookDisplayMode.NODE; + protected int sortNumber = -1; protected ResourceLocation background = ResourceLocation.parse(Category.DEFAULT_BACKGROUND); protected int backgroundWidth = Category.DEFAULT_BACKGROUND_WIDTH; @@ -89,7 +102,9 @@ public BookModel getBook() { public JsonObject toJson(HolderLookup.Provider provider) { JsonObject json = new JsonObject(); json.addProperty("name", this.name); + json.add("description", this.description.toJson(provider)); json.add("icon", this.icon.toJson(provider)); + json.addProperty("display_mode", this.displayMode.getSerializedName()); json.addProperty("sort_number", this.sortNumber); json.addProperty("background", this.background.toString()); json.addProperty("background_width", this.backgroundWidth); @@ -120,10 +135,18 @@ public String getName() { return this.name; } + public BookTextHolderModel getDescription() { + return this.description; + } + public BookIconModel getIcon() { return this.icon; } + public BookDisplayMode getDisplayMode() { + return this.displayMode; + } + public int getSortNumber() { return this.sortNumber; } @@ -148,6 +171,22 @@ public ResourceLocation getEntryTextures() { return this.entryTextures; } + /** + * The description to (optionally) display on the first page of the category. + */ + public BookCategoryModel withDescription(String title) { + this.description = new BookTextHolderModel(title); + return this; + } + + /** + * The description to (optionally) display on the first page of the category. + */ + public BookCategoryModel withDescription(Component title) { + this.description = new BookTextHolderModel(title); + return this; + } + /** * Sets the category's icon. * @@ -181,6 +220,15 @@ public BookCategoryModel withIcon(ItemLike item) { return this; } + /** + * Sets the display mode - node based (thaumonomicon style) or index based (lexica botania / patchouli style) + */ + public BookCategoryModel withDisplayMode(BookDisplayMode displayMode) { + this.displayMode = displayMode; + return this; + } + + /** * Sets the category's sort number. * Categories with a lower sort number will be displayed first. diff --git a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookEntryModel.java b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookEntryModel.java index 5fa013153..18a6760cc 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookEntryModel.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookEntryModel.java @@ -39,6 +39,8 @@ public class BookEntryModel { protected ResourceLocation categoryToOpen; protected ResourceLocation commandToRunOnFirstRead; + protected int sortNumber = -1; + protected BookEntryModel(ResourceLocation id, String name) { this.id = id; this.name = name; @@ -93,6 +95,8 @@ public JsonObject toJson(HolderLookup.Provider provider) { json.addProperty("command_to_run_on_first_read", this.commandToRunOnFirstRead.toString()); } + json.addProperty("sort_number", this.sortNumber); + return json; } @@ -160,6 +164,10 @@ public boolean showWhenAnyParentUnlocked() { return this.showWhenAnyParentUnlocked; } + public int getSortNumber() { + return this.sortNumber; + } + public List getPages() { return this.pages; } @@ -345,6 +353,17 @@ public BookEntryModel showWhenAnyParentUnlocked(boolean showWhenAnyParentUnlocke return this; } + /** + * Sets the entry's sort number. Only used if the parent category is in index mode. + * Entries with a lower sort number will be displayed first in the index list. + * + * If no sort number is provided the CategoryProvider will add sort numbers in the order the entries are added. + */ + public BookEntryModel withSortNumber(int sortNumber) { + this.sortNumber = sortNumber; + return this; + } + /** * Replaces the entry's pages with the given list. */ diff --git a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookModel.java b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookModel.java index 8eef75bce..6ca526588 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookModel.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/api/datagen/book/BookModel.java @@ -9,9 +9,11 @@ import com.google.gson.JsonObject; import com.klikli_dev.modonomicon.api.ModonomiconConstants.Data; import com.klikli_dev.modonomicon.api.ModonomiconConstants.Data.Book; +import com.klikli_dev.modonomicon.book.BookDisplayMode; import com.klikli_dev.modonomicon.book.BookFrameOverlay; import com.mojang.serialization.JsonOps; import net.minecraft.core.HolderLookup; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.Nullable; @@ -22,6 +24,10 @@ public class BookModel { protected ResourceLocation id; protected String name; + /** + * The description to (optionally) display on the first page of the category. + */ + protected BookTextHolderModel description = new BookTextHolderModel(""); protected String tooltip = ""; protected ResourceLocation creativeTab = ResourceLocation.parse("modonomicon:modonomicon"); @@ -29,6 +35,13 @@ public class BookModel { protected ResourceLocation bookOverviewTexture = ResourceLocation.parse(Data.Book.DEFAULT_OVERVIEW_TEXTURE); protected ResourceLocation font = ResourceLocation.parse(Book.DEFAULT_FONT); + /** + * The display mode - node based (thaumonomicon style) or index based (lexica botania / patchouli style) + * If the book is in index mode then all categories will also be shown in index mode. If the book is in node mode, then individual categories can be in index mode. + * If in index mode, the frame textures will be ignored, instead bookContentTexture will be used. + */ + protected BookDisplayMode displayMode = BookDisplayMode.NODE; + protected ResourceLocation frameTexture = ResourceLocation.parse(Book.DEFAULT_FRAME_TEXTURE); protected BookFrameOverlay topFrameOverlay = Data.Book.DEFAULT_TOP_FRAME_OVERLAY; protected BookFrameOverlay bottomFrameOverlay = Data.Book.DEFAULT_BOTTOM_FRAME_OVERLAY; @@ -119,6 +132,10 @@ public String getName() { return this.name; } + public BookTextHolderModel getDescription() { + return this.description; + } + public String getTooltip() { return this.tooltip; } @@ -167,11 +184,17 @@ public int getBookTextOffsetWidth() { return this.bookTextOffsetWidth; } + public BookDisplayMode getDisplayMode() { + return this.displayMode; + } + public JsonObject toJson(HolderLookup.Provider provider) { JsonObject json = new JsonObject(); json.addProperty("name", this.name); + json.add("description", this.description.toJson(provider)); json.addProperty("tooltip", this.tooltip); json.addProperty("model", this.model.toString()); + json.addProperty("display_mode", this.displayMode.getSerializedName()); json.addProperty("creative_tab", this.creativeTab.toString()); json.addProperty("book_overview_texture", this.bookOverviewTexture.toString()); json.addProperty("font", this.font.toString()); @@ -201,6 +224,22 @@ public JsonObject toJson(HolderLookup.Provider provider) { return json; } + /** + * The description to (optionally) display on the first page of the category. + */ + public BookModel withDescription(String title) { + this.description = new BookTextHolderModel(title); + return this; + } + + /** + * The description to (optionally) display on the first page of the category. + */ + public BookModel withDescription(Component title) { + this.description = new BookTextHolderModel(title); + return this; + } + public BookModel withTooltip(String tooltip) { this.tooltip = tooltip; return this; @@ -287,6 +326,15 @@ public BookModel withModel(ResourceLocation model) { return this; } + /** + * Sets the display mode - node based (thaumonomicon style) or index based (lexica botania / patchouli style) + * If in index mode, the frame textures will be ignored, instead bookContentTexture will be used + */ + public BookModel withDisplayMode(BookDisplayMode displayMode) { + this.displayMode = displayMode; + return this; + } + public BookModel withGenerateBookItem(boolean generateBookItem) { this.generateBookItem = generateBookItem; return this; diff --git a/common/src/main/java/com/klikli_dev/modonomicon/api/events/EntryClickedEvent.java b/common/src/main/java/com/klikli_dev/modonomicon/api/events/EntryClickedEvent.java index 79b671c1b..83ff4e8ec 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/api/events/EntryClickedEvent.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/api/events/EntryClickedEvent.java @@ -4,7 +4,7 @@ package com.klikli_dev.modonomicon.api.events; -import com.klikli_dev.modonomicon.client.gui.book.EntryDisplayState; +import com.klikli_dev.modonomicon.client.gui.book.entry.EntryDisplayState; import net.minecraft.resources.ResourceLocation; public class EntryClickedEvent extends ModonomiconEvent { diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/Book.java b/common/src/main/java/com/klikli_dev/modonomicon/book/Book.java index b51e1d95e..4b4986640 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/Book.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/Book.java @@ -12,8 +12,11 @@ import com.klikli_dev.modonomicon.book.entries.BookEntry; import com.klikli_dev.modonomicon.book.error.BookErrorManager; import com.klikli_dev.modonomicon.client.gui.book.markdown.BookTextRenderer; +import com.klikli_dev.modonomicon.util.BookGsonHelper; +import com.mojang.serialization.JsonOps; import net.minecraft.core.HolderLookup; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.GsonHelper; import net.minecraft.world.level.Level; @@ -28,9 +31,11 @@ public class Book { protected ResourceLocation id; protected String name; + protected BookTextHolder description; protected String tooltip; protected String creativeTab; + protected ResourceLocation model; protected ResourceLocation bookOverviewTexture; protected ResourceLocation frameTexture; @@ -56,6 +61,13 @@ public class Book { protected ResourceLocation font; + /** + * The display mode - node based (thaumonomicon style) or index based (lexica botania / patchouli style) + * If the book is in index mode then all categories will also be shown in index mode. If the book is in node mode, then individual categories can be in index mode. + * If in index mode, the frame textures will be ignored, instead bookContentTexture will be used. + */ + protected BookDisplayMode displayMode; + /** * When rendering book text holders, add this offset to the x position (basically, create a left margin). * Will be automatically subtracted from the width to avoid overflow. @@ -79,7 +91,7 @@ public class Book { protected int searchButtonYOffset; protected int readAllButtonYOffset; - public Book(ResourceLocation id, String name, String tooltip, ResourceLocation model, boolean generateBookItem, + public Book(ResourceLocation id, String name, BookTextHolder description, String tooltip, ResourceLocation model, BookDisplayMode displayMode, boolean generateBookItem, ResourceLocation customBookItem, String creativeTab, ResourceLocation font, ResourceLocation bookOverviewTexture, ResourceLocation frameTexture, BookFrameOverlay topFrameOverlay, BookFrameOverlay bottomFrameOverlay, BookFrameOverlay leftFrameOverlay, BookFrameOverlay rightFrameOverlay, ResourceLocation bookContentTexture, ResourceLocation craftingTexture, ResourceLocation turnPageSound, @@ -88,8 +100,10 @@ public Book(ResourceLocation id, String name, String tooltip, ResourceLocation m ) { this.id = id; this.name = name; + this.description = description; this.tooltip = tooltip; this.model = model; + this.displayMode = displayMode; this.generateBookItem = generateBookItem; this.customBookItem = customBookItem; this.creativeTab = creativeTab; @@ -122,9 +136,11 @@ public Book(ResourceLocation id, String name, String tooltip, ResourceLocation m public static Book fromJson(ResourceLocation id, JsonObject json, HolderLookup.Provider provider) { var name = GsonHelper.getAsString(json, "name"); + var description = BookGsonHelper.getAsBookTextHolder(json, "description", BookTextHolder.EMPTY, provider); var tooltip = GsonHelper.getAsString(json, "tooltip", ""); var model = ResourceLocation.parse(GsonHelper.getAsString(json, "model", Data.Book.DEFAULT_MODEL)); var generateBookItem = GsonHelper.getAsBoolean(json, "generate_book_item", true); + var displayMode = BookDisplayMode.byName(GsonHelper.getAsString(json, "display_mode", BookDisplayMode.NODE.getSerializedName())); var customBookItem = json.has("custom_book_item") ? ResourceLocation.parse(GsonHelper.getAsString(json, "custom_book_item")) : null; @@ -167,7 +183,7 @@ public static Book fromJson(ResourceLocation id, JsonObject json, HolderLookup.P var searchButtonYOffset = GsonHelper.getAsInt(json, "search_button_y_offset", 0); var readAllButtonYOffset = GsonHelper.getAsInt(json, "read_all_button_y_offset", 0); - return new Book(id, name, tooltip, model, generateBookItem, customBookItem, creativeTab, font, bookOverviewTexture, + return new Book(id, name, description, tooltip, model, displayMode, generateBookItem, customBookItem, creativeTab, font, bookOverviewTexture, frameTexture, topFrameOverlay, bottomFrameOverlay, leftFrameOverlay, rightFrameOverlay, bookContentTexture, craftingTexture, turnPageSound, defaultTitleColor, categoryButtonIconScale, autoAddReadConditions, bookTextOffsetX, bookTextOffsetY, bookTextOffsetWidth, categoryButtonXOffset, categoryButtonYOffset, searchButtonXOffset, searchButtonYOffset, readAllButtonYOffset); @@ -175,10 +191,13 @@ public static Book fromJson(ResourceLocation id, JsonObject json, HolderLookup.P @SuppressWarnings("deprecation") - public static Book fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) { + public static Book fromNetwork(ResourceLocation id, RegistryFriendlyByteBuf buffer) { var name = buffer.readUtf(); + var description = BookTextHolder.fromNetwork(buffer); var tooltip = buffer.readUtf(); var model = buffer.readResourceLocation(); + var displayMode = BookDisplayMode.byId(buffer.readByte()); + var generateBookItem = buffer.readBoolean(); var customBookItem = buffer.readBoolean() ? buffer.readResourceLocation() : null; var creativeTab = buffer.readUtf(); @@ -210,7 +229,7 @@ public static Book fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) { var searchButtonYOffset = (int) buffer.readShort(); var readAllButtonYOffset = (int) buffer.readShort(); - return new Book(id, name, tooltip, model, generateBookItem, customBookItem, creativeTab, font, bookOverviewTexture, + return new Book(id, name, description, tooltip, model, displayMode, generateBookItem, customBookItem, creativeTab, font, bookOverviewTexture, frameTexture, topFrameOverlay, bottomFrameOverlay, leftFrameOverlay, rightFrameOverlay, bookContentTexture, craftingTexture, turnPageSound, defaultTitleColor, categoryButtonIconScale, autoAddReadConditions, bookTextOffsetX, bookTextOffsetY, bookTextOffsetWidth, categoryButtonXOffset, categoryButtonYOffset, searchButtonXOffset, searchButtonYOffset, readAllButtonYOffset); @@ -243,6 +262,10 @@ public void build(Level level) { * Called after build() (after loading the book jsons) to render markdown and store any errors */ public void prerenderMarkdown(BookTextRenderer textRenderer) { + if (!this.description.hasComponent()) { + this.description = new RenderedBookTextHolder(this.description, textRenderer.render(this.description.getString())); + } + for (var category : this.categories.values()) { BookErrorManager.get().getContextHelper().categoryId = category.getId(); category.prerenderMarkdown(textRenderer); @@ -251,10 +274,13 @@ public void prerenderMarkdown(BookTextRenderer textRenderer) { } @SuppressWarnings("deprecation") - public void toNetwork(FriendlyByteBuf buffer) { + public void toNetwork(RegistryFriendlyByteBuf buffer) { buffer.writeUtf(this.name); + this.description.toNetwork(buffer); buffer.writeUtf(this.tooltip); buffer.writeResourceLocation(this.model); + buffer.writeByte(this.displayMode.ordinal()); + buffer.writeBoolean(this.generateBookItem); buffer.writeBoolean(this.customBookItem != null); if (this.customBookItem != null) { @@ -354,6 +380,10 @@ public String getName() { return this.name; } + public BookTextHolder getDescription() { + return this.description; + } + public String getTooltip() { return this.tooltip; } @@ -407,6 +437,10 @@ public ResourceLocation getModel() { return this.model; } + public BookDisplayMode getDisplayMode() { + return this.displayMode; + } + public boolean generateBookItem() { return this.generateBookItem; } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/BookCategory.java b/common/src/main/java/com/klikli_dev/modonomicon/book/BookCategory.java index abb203afb..130c67f60 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/BookCategory.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/BookCategory.java @@ -13,6 +13,7 @@ import com.klikli_dev.modonomicon.book.entries.BookEntry; import com.klikli_dev.modonomicon.book.error.BookErrorManager; import com.klikli_dev.modonomicon.client.gui.book.markdown.BookTextRenderer; +import com.klikli_dev.modonomicon.util.BookGsonHelper; import net.minecraft.core.HolderLookup; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf; @@ -32,6 +33,11 @@ public class BookCategory { protected Book book; protected String name; protected BookIcon icon; + protected BookTextHolder description; + /** + * The display mode - node based (thaumonomicon style) or index based (lexica botania / patchouli style) + */ + protected BookDisplayMode displayMode; protected int sortNumber; protected ResourceLocation background; protected int backgroundWidth; @@ -44,10 +50,8 @@ public class BookCategory { protected List backgroundParallaxLayers; protected ResourceLocation entryTextures; protected ConcurrentMap entries; - protected BookCondition condition; protected boolean showCategoryButton; - /** * The entry to open when this category is opened. * If null, no entry will be opened. @@ -58,14 +62,15 @@ public class BookCategory { * If false, the entryToOpen will be opened every time the category is opened. */ protected boolean openEntryToOpenOnlyOnce; - - public BookCategory(ResourceLocation id, String name, int sortNumber, BookCondition condition, boolean showCategoryButton, BookIcon icon, ResourceLocation background, int backgroundWidth, int backgroundHeight, float backgroundTextureZoomMultiplier, List backgroundParallaxLayers, ResourceLocation entryTextures, ResourceLocation entryToOpen, boolean openEntryOnlyOnce) { + public BookCategory(ResourceLocation id, String name, BookTextHolder description, int sortNumber, BookCondition condition, boolean showCategoryButton, BookIcon icon, BookDisplayMode displayMode, ResourceLocation background, int backgroundWidth, int backgroundHeight, float backgroundTextureZoomMultiplier, List backgroundParallaxLayers, ResourceLocation entryTextures, ResourceLocation entryToOpen, boolean openEntryOnlyOnce) { this.id = id; this.name = name; + this.description = description; this.sortNumber = sortNumber; this.condition = condition; this.showCategoryButton = showCategoryButton; this.icon = icon; + this.displayMode = displayMode; this.background = background; this.backgroundWidth = backgroundWidth; this.backgroundHeight = backgroundHeight; @@ -79,8 +84,10 @@ public BookCategory(ResourceLocation id, String name, int sortNumber, BookCondit public static BookCategory fromJson(ResourceLocation id, JsonObject json, HolderLookup.Provider provider) { var name = GsonHelper.getAsString(json, "name"); + var description = BookGsonHelper.getAsBookTextHolder(json, "description", BookTextHolder.EMPTY, provider); var sortNumber = GsonHelper.getAsInt(json, "sort_number", -1); var icon = BookIcon.fromJson(json.get("icon")); + var displayMode = BookDisplayMode.byName(GsonHelper.getAsString(json, "display_mode", BookDisplayMode.NODE.getSerializedName())); var background = ResourceLocation.parse(GsonHelper.getAsString(json, "background", Category.DEFAULT_BACKGROUND)); var backgroundWidth = GsonHelper.getAsInt(json, "background_width", Category.DEFAULT_BACKGROUND_WIDTH); var backgroundHeight = GsonHelper.getAsInt(json, "background_height", Category.DEFAULT_BACKGROUND_HEIGHT); @@ -103,13 +110,15 @@ public static BookCategory fromJson(ResourceLocation id, JsonObject json, Holder } boolean openEntryOnlyOnce = GsonHelper.getAsBoolean(json, "open_entry_to_open_only_once", true); - return new BookCategory(id, name, sortNumber, condition, showCategoryButton, icon, background, backgroundWidth, backgroundHeight, backgroundTextureZoomMultiplier, backgroundParallaxLayers, entryTextures, entryToOpen, openEntryOnlyOnce); + return new BookCategory(id, name, description, sortNumber, condition, showCategoryButton, icon, displayMode, background, backgroundWidth, backgroundHeight, backgroundTextureZoomMultiplier, backgroundParallaxLayers, entryTextures, entryToOpen, openEntryOnlyOnce); } public static BookCategory fromNetwork(ResourceLocation id, RegistryFriendlyByteBuf buffer) { var name = buffer.readUtf(); + var description = BookTextHolder.fromNetwork(buffer); var sortNumber = buffer.readInt(); var icon = BookIcon.fromNetwork(buffer); + var displayMode = BookDisplayMode.byId(buffer.readByte()); var background = buffer.readResourceLocation(); var backgroundWidth = buffer.readVarInt(); var backgroundHeight = buffer.readVarInt(); @@ -120,15 +129,16 @@ public static BookCategory fromNetwork(ResourceLocation id, RegistryFriendlyByte var showCategoryButton = buffer.readBoolean(); var entryToOpen = buffer.readNullable(FriendlyByteBuf::readResourceLocation); var openEntryOnlyOnce = buffer.readBoolean(); - return new BookCategory(id, name, sortNumber, condition, showCategoryButton, icon, background, backgroundWidth, backgroundHeight, + return new BookCategory(id, name, description, sortNumber, condition, showCategoryButton, icon, displayMode, background, backgroundWidth, backgroundHeight, backgroundTextureZoomMultiplier, backgroundParallaxLayers, entryTextures, entryToOpen, openEntryOnlyOnce); } - public void toNetwork(RegistryFriendlyByteBuf buffer) { buffer.writeUtf(this.name); + this.description.toNetwork(buffer); buffer.writeInt(this.sortNumber); this.icon.toNetwork(buffer); + buffer.writeByte(this.displayMode.ordinal()); buffer.writeResourceLocation(this.background); buffer.writeVarInt(this.backgroundWidth); buffer.writeVarInt(this.backgroundHeight); @@ -165,6 +175,10 @@ public void build(Level level, Book book) { * Called after build() (after loading the book jsons) to render markdown and store any errors */ public void prerenderMarkdown(BookTextRenderer textRenderer) { + if (!this.description.hasComponent()) { + this.description = new RenderedBookTextHolder(this.description, textRenderer.render(this.description.getString())); + } + for (var entry : this.entries.values()) { BookErrorManager.get().getContextHelper().entryId = entry.getId(); try { @@ -189,6 +203,10 @@ public String getName() { return this.name; } + public BookTextHolder getDescription() { + return this.description; + } + public int getSortNumber() { return this.sortNumber; } @@ -197,6 +215,10 @@ public BookIcon getIcon() { return this.icon; } + public BookDisplayMode getDisplayMode() { + return this.displayMode; + } + public ResourceLocation getBackground() { return this.background; } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/BookDisplayMode.java b/common/src/main/java/com/klikli_dev/modonomicon/book/BookDisplayMode.java new file mode 100644 index 000000000..bda91ec73 --- /dev/null +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/BookDisplayMode.java @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 klikli-dev +// +// SPDX-License-Identifier: MIT + + +package com.klikli_dev.modonomicon.book; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.util.ByIdMap; +import net.minecraft.util.StringRepresentable; +import org.jetbrains.annotations.NotNull; + +import java.util.function.IntFunction; + +public enum BookDisplayMode implements StringRepresentable { + NODE("node"), + INDEX("index"); + + public static final StringRepresentable.EnumCodec CODEC = StringRepresentable.fromEnum(BookDisplayMode::values); + private static final IntFunction BY_ID = ByIdMap.continuous(Enum::ordinal, values(), ByIdMap.OutOfBoundsStrategy.WRAP); + public static final StreamCodec STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, BookDisplayMode::ordinal); + private final String name; + + BookDisplayMode(String name) { + this.name = name; + } + + public static BookDisplayMode byName(String pName) { + return CODEC.byName(pName); + } + + public static BookDisplayMode byId(int pId) { + return BY_ID.apply(pId); + } + + @Override + public @NotNull String getSerializedName() { + return this.name; + } +} diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/BookProvider.java b/common/src/main/java/com/klikli_dev/modonomicon/book/BookProvider.java new file mode 100644 index 000000000..ccf4eeeb9 --- /dev/null +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/BookProvider.java @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 klikli-dev +// +// SPDX-License-Identifier: MIT + +package com.klikli_dev.modonomicon.book; + +public interface BookProvider { + Book getBook(); +} diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/conditions/context/BookConditionPageContext.java b/common/src/main/java/com/klikli_dev/modonomicon/book/conditions/context/BookConditionPageContext.java index 0b78e6ac8..0b7266419 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/conditions/context/BookConditionPageContext.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/conditions/context/BookConditionPageContext.java @@ -7,7 +7,7 @@ package com.klikli_dev.modonomicon.book.conditions.context; import com.klikli_dev.modonomicon.book.Book; -import com.klikli_dev.modonomicon.book.entries.ContentBookEntry; +import com.klikli_dev.modonomicon.book.entries.BookContentEntry; import com.klikli_dev.modonomicon.book.page.BookPage; public class BookConditionPageContext extends BookConditionContext { @@ -27,7 +27,7 @@ public String toString() { '}'; } - public ContentBookEntry getEntry() { + public BookContentEntry getEntry() { return this.page.getParentEntry(); } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/entries/ContentBookEntry.java b/common/src/main/java/com/klikli_dev/modonomicon/book/entries/BookContentEntry.java similarity index 89% rename from common/src/main/java/com/klikli_dev/modonomicon/book/entries/ContentBookEntry.java rename to common/src/main/java/com/klikli_dev/modonomicon/book/entries/BookContentEntry.java index 2cc7683de..f235bdb49 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/entries/ContentBookEntry.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/entries/BookContentEntry.java @@ -12,8 +12,8 @@ import com.klikli_dev.modonomicon.book.error.BookErrorManager; import com.klikli_dev.modonomicon.book.page.BookPage; import com.klikli_dev.modonomicon.bookstate.BookUnlockStateManager; -import com.klikli_dev.modonomicon.client.gui.book.BookCategoryScreen; -import com.klikli_dev.modonomicon.client.gui.book.BookContentScreen; +import com.klikli_dev.modonomicon.client.gui.BookGuiManager; +import com.klikli_dev.modonomicon.client.gui.book.BookAddress; import com.klikli_dev.modonomicon.client.gui.book.markdown.BookTextRenderer; import com.klikli_dev.modonomicon.data.LoaderRegistry; import net.minecraft.core.HolderLookup; @@ -27,16 +27,16 @@ import java.util.ArrayList; import java.util.List; -public class ContentBookEntry extends BookEntry { +public class BookContentEntry extends BookEntry { protected List pages; - public ContentBookEntry(ResourceLocation id, BookEntryData data, ResourceLocation commandToRunOnFirstReadId, List pages) { + public BookContentEntry(ResourceLocation id, BookEntryData data, ResourceLocation commandToRunOnFirstReadId, List pages) { super(id, data, commandToRunOnFirstReadId); this.pages = pages; } - public static ContentBookEntry fromJson(ResourceLocation id, JsonObject json, boolean autoAddReadConditions, HolderLookup.Provider provider) { + public static BookContentEntry fromJson(ResourceLocation id, JsonObject json, boolean autoAddReadConditions, HolderLookup.Provider provider) { BookEntryData data = BookEntryData.fromJson(json, autoAddReadConditions, provider); ResourceLocation commandToRunOnFirstReadId = null; @@ -57,10 +57,10 @@ public static ContentBookEntry fromJson(ResourceLocation id, JsonObject json, bo } } - return new ContentBookEntry(id, data, commandToRunOnFirstReadId, pages); + return new BookContentEntry(id, data, commandToRunOnFirstReadId, pages); } - public static ContentBookEntry fromNetwork(RegistryFriendlyByteBuf buffer) { + public static BookContentEntry fromNetwork(RegistryFriendlyByteBuf buffer) { var id = buffer.readResourceLocation(); BookEntryData data = BookEntryData.fromNetwork(buffer); ResourceLocation commandToRunOnFirstReadId = buffer.readNullable(FriendlyByteBuf::readResourceLocation); @@ -74,7 +74,7 @@ public static ContentBookEntry fromNetwork(RegistryFriendlyByteBuf buffer) { pages.add(page); } - return new ContentBookEntry(id, data, commandToRunOnFirstReadId, pages); + return new BookContentEntry(id, data, commandToRunOnFirstReadId, pages); } @Override @@ -160,7 +160,8 @@ public boolean matchesQuery(String query) { return false; } - public BookContentScreen openEntry(BookCategoryScreen categoryScreen) { - return categoryScreen.openContentEntry(this); + @Override + public void openEntry(BookAddress address) { + BookGuiManager.get().openContentEntry(this, address); } } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/entries/BookEntry.java b/common/src/main/java/com/klikli_dev/modonomicon/book/entries/BookEntry.java index f68013911..cd9ad3d92 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/entries/BookEntry.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/entries/BookEntry.java @@ -15,8 +15,7 @@ import com.klikli_dev.modonomicon.book.conditions.BookNoneCondition; import com.klikli_dev.modonomicon.book.error.BookErrorManager; import com.klikli_dev.modonomicon.book.page.BookPage; -import com.klikli_dev.modonomicon.client.gui.book.BookCategoryScreen; -import com.klikli_dev.modonomicon.client.gui.book.BookContentScreen; +import com.klikli_dev.modonomicon.client.gui.book.BookAddress; import com.klikli_dev.modonomicon.client.gui.book.markdown.BookTextRenderer; import com.klikli_dev.modonomicon.data.LoaderRegistry; import net.minecraft.core.HolderLookup; @@ -60,7 +59,7 @@ public int getY() { public abstract ResourceLocation getType(); - public abstract BookContentScreen openEntry(BookCategoryScreen categoryScreen); + public abstract void openEntry(BookAddress address); /** * Called after build() (after loading the book jsons) to render markdown and store any errors @@ -172,6 +171,10 @@ public ResourceLocation getCategoryId() { return this.data.categoryId; } + public int getSortNumber() { + return this.data.sortNumber; + } + public abstract void toNetwork(RegistryFriendlyByteBuf buf); /** @@ -182,7 +185,7 @@ public ResourceLocation getCategoryId() { */ public record BookEntryData(ResourceLocation categoryId, List parents, int x, int y, String name, String description, BookIcon icon, int entryBackgroundUIndex, int entryBackgroundVIndex, - BookCondition condition, boolean hideWhileLocked, boolean showWhenAnyParentUnlocked) { + BookCondition condition, boolean hideWhileLocked, boolean showWhenAnyParentUnlocked, int sortNumber) { public static BookEntryData fromJson(JsonObject json, boolean autoAddReadConditions, HolderLookup.Provider provider) { var categoryId = ResourceLocation.parse(GsonHelper.getAsString(json, "category")); @@ -233,7 +236,9 @@ public static BookEntryData fromJson(JsonObject json, boolean autoAddReadConditi */ var showWhenAnyParentUnlocked = GsonHelper.getAsBoolean(json, "show_when_any_parent_unlocked", false); - return new BookEntryData(categoryId, parents, x, y, name, description, icon, entryBackgroundUIndex, entryBackgroundVIndex, condition, hideWhileLocked, showWhenAnyParentUnlocked); + var sortNumber = GsonHelper.getAsInt(json, "sort_number", -1); + + return new BookEntryData(categoryId, parents, x, y, name, description, icon, entryBackgroundUIndex, entryBackgroundVIndex, condition, hideWhileLocked, showWhenAnyParentUnlocked, sortNumber); } public static BookEntryData fromNetwork(RegistryFriendlyByteBuf buffer) { @@ -255,7 +260,9 @@ public static BookEntryData fromNetwork(RegistryFriendlyByteBuf buffer) { parentEntries.add(BookEntryParent.fromNetwork(buffer)); } - return new BookEntryData(categoryId, parentEntries, x, y, name, description, icon, entryBackgroundUIndex, entryBackgroundVIndex, condition, hideWhileLocked, showWhenAnyParentUnlocked); + var sortNumber = buffer.readVarInt(); + + return new BookEntryData(categoryId, parentEntries, x, y, name, description, icon, entryBackgroundUIndex, entryBackgroundVIndex, condition, hideWhileLocked, showWhenAnyParentUnlocked, sortNumber); } public void toNetwork(RegistryFriendlyByteBuf buffer) { @@ -277,8 +284,8 @@ public void toNetwork(RegistryFriendlyByteBuf buffer) { for (var parent : this.parents) { parent.toNetwork(buffer); } - } + buffer.writeVarInt(this.sortNumber); + } } - } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/entries/CategoryLinkBookEntry.java b/common/src/main/java/com/klikli_dev/modonomicon/book/entries/CategoryLinkBookEntry.java index 0f0fa7da7..0b49625ba 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/entries/CategoryLinkBookEntry.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/entries/CategoryLinkBookEntry.java @@ -10,8 +10,8 @@ import com.klikli_dev.modonomicon.api.ModonomiconConstants; import com.klikli_dev.modonomicon.book.BookCategory; import com.klikli_dev.modonomicon.book.error.BookErrorManager; -import com.klikli_dev.modonomicon.client.gui.book.BookCategoryScreen; -import com.klikli_dev.modonomicon.client.gui.book.BookContentScreen; +import com.klikli_dev.modonomicon.client.gui.BookGuiManager; +import com.klikli_dev.modonomicon.client.gui.book.BookAddress; import net.minecraft.core.HolderLookup; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf; @@ -88,9 +88,10 @@ public BookCategory getCategoryToOpen() { return this.categoryToOpen; } - public BookContentScreen openEntry(BookCategoryScreen categoryScreen) { - categoryScreen.getBookOverviewScreen().changeCategory(this.getCategoryToOpen()); - return null; + @Override + public void openEntry(BookAddress address) { + //we don't have any use for the address here + BookGuiManager.get().openCategoryLinkEntry(this); } } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/error/BookErrorManager.java b/common/src/main/java/com/klikli_dev/modonomicon/book/error/BookErrorManager.java index 1674922c5..e5ecfcd81 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/error/BookErrorManager.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/error/BookErrorManager.java @@ -7,7 +7,7 @@ package com.klikli_dev.modonomicon.book.error; import com.klikli_dev.modonomicon.Modonomicon; -import com.klikli_dev.modonomicon.book.entries.ContentBookEntry; +import com.klikli_dev.modonomicon.book.entries.BookContentEntry; import com.klikli_dev.modonomicon.book.page.BookPage; import net.minecraft.resources.ResourceLocation; import org.slf4j.helpers.MessageFormatter; @@ -108,7 +108,7 @@ public void setTo(BookPage page) { BookErrorManager.get().getContextHelper().pageNumber = page.getPageNumber(); } - public void setTo(ContentBookEntry entry) { + public void setTo(BookContentEntry entry) { BookErrorManager.get().setCurrentBookId(entry.getBook().getId()); BookErrorManager.get().getContextHelper().reset(); BookErrorManager.get().getContextHelper().categoryId = entry.getCategoryId(); diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookEntityPage.java b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookEntityPage.java index a9dae2a76..91207e107 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookEntityPage.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookEntityPage.java @@ -8,7 +8,7 @@ import com.google.gson.JsonObject; import com.klikli_dev.modonomicon.api.ModonomiconConstants.Data.Page; -import com.klikli_dev.modonomicon.book.entries.ContentBookEntry; +import com.klikli_dev.modonomicon.book.entries.BookContentEntry; import com.klikli_dev.modonomicon.book.BookTextHolder; import com.klikli_dev.modonomicon.book.RenderedBookTextHolder; import com.klikli_dev.modonomicon.book.conditions.BookCondition; @@ -111,7 +111,7 @@ public ResourceLocation getType() { } @Override - public void build(Level level, ContentBookEntry parentEntry, int pageNum) { + public void build(Level level, BookContentEntry parentEntry, int pageNum) { super.build(level, parentEntry, pageNum); if (this.entityName.isEmpty()) { diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookMultiblockPage.java b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookMultiblockPage.java index 1afd95d94..b2ab444b1 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookMultiblockPage.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookMultiblockPage.java @@ -10,7 +10,7 @@ import com.google.gson.JsonObject; import com.klikli_dev.modonomicon.api.ModonomiconConstants.Data.Page; import com.klikli_dev.modonomicon.api.multiblock.Multiblock; -import com.klikli_dev.modonomicon.book.entries.ContentBookEntry; +import com.klikli_dev.modonomicon.book.entries.BookContentEntry; import com.klikli_dev.modonomicon.book.BookTextHolder; import com.klikli_dev.modonomicon.book.RenderedBookTextHolder; import com.klikli_dev.modonomicon.book.conditions.BookCondition; @@ -87,7 +87,7 @@ public ResourceLocation getType() { } @Override - public void build(Level level, ContentBookEntry parentEntry, int pageNum) { + public void build(Level level, BookContentEntry parentEntry, int pageNum) { super.build(level, parentEntry, pageNum); this.multiblock = MultiblockDataManager.get().getMultiblock(this.multiblockId); diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookPage.java b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookPage.java index b370b2dde..43034d1f1 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookPage.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookPage.java @@ -7,7 +7,7 @@ package com.klikli_dev.modonomicon.book.page; import com.klikli_dev.modonomicon.book.Book; -import com.klikli_dev.modonomicon.book.entries.ContentBookEntry; +import com.klikli_dev.modonomicon.book.entries.BookContentEntry; import com.klikli_dev.modonomicon.book.conditions.BookCondition; import com.klikli_dev.modonomicon.client.gui.book.markdown.BookTextRenderer; import net.minecraft.network.RegistryFriendlyByteBuf; @@ -17,7 +17,7 @@ public abstract class BookPage { protected Book book; - protected ContentBookEntry parentEntry; + protected BookContentEntry parentEntry; protected int pageNumber; protected String anchor; @@ -41,7 +41,7 @@ public BookCondition getCondition() { /** * call after loading the book jsons to finalize. */ - public void build(Level level, ContentBookEntry parentEntry, int pageNum) { + public void build(Level level, BookContentEntry parentEntry, int pageNum) { this.parentEntry = parentEntry; this.pageNumber = pageNum; this.book = this.parentEntry.getBook(); @@ -63,11 +63,11 @@ public void toNetwork(RegistryFriendlyByteBuf buffer) { BookCondition.toNetwork(this.condition, buffer); } - public ContentBookEntry getParentEntry() { + public BookContentEntry getParentEntry() { return this.parentEntry; } - public void setParentEntry(ContentBookEntry parentEntry) { + public void setParentEntry(BookContentEntry parentEntry) { this.parentEntry = parentEntry; } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookRecipePage.java b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookRecipePage.java index 404908266..d9f8f0bc1 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookRecipePage.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookRecipePage.java @@ -8,7 +8,7 @@ import com.google.gson.JsonObject; import com.klikli_dev.modonomicon.Modonomicon; -import com.klikli_dev.modonomicon.book.entries.ContentBookEntry; +import com.klikli_dev.modonomicon.book.entries.BookContentEntry; import com.klikli_dev.modonomicon.book.BookTextHolder; import com.klikli_dev.modonomicon.book.RenderedBookTextHolder; import com.klikli_dev.modonomicon.book.conditions.BookCondition; @@ -16,7 +16,6 @@ import com.klikli_dev.modonomicon.util.BookGsonHelper; import net.minecraft.core.HolderLookup; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; @@ -109,7 +108,7 @@ public BookTextHolder getText() { return this.text; } - protected RecipeHolder loadRecipe(Level level, ContentBookEntry entry, ResourceLocation recipeId) { + protected RecipeHolder loadRecipe(Level level, BookContentEntry entry, ResourceLocation recipeId) { if (recipeId == null) { return null; } @@ -132,7 +131,7 @@ private RecipeHolder getRecipe(Level level, ResourceLocation id) { } @Override - public void build(Level level, ContentBookEntry parentEntry, int pageNum) { + public void build(Level level, BookContentEntry parentEntry, int pageNum) { super.build(level, parentEntry, pageNum); this.recipe1 = this.loadRecipe(level, parentEntry, this.recipeId1); diff --git a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookSpotlightPage.java b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookSpotlightPage.java index d1b400e6e..f6ae42aa9 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookSpotlightPage.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/book/page/BookSpotlightPage.java @@ -8,7 +8,7 @@ import com.google.gson.JsonObject; import com.klikli_dev.modonomicon.api.ModonomiconConstants.Data.Page; -import com.klikli_dev.modonomicon.book.entries.ContentBookEntry; +import com.klikli_dev.modonomicon.book.entries.BookContentEntry; import com.klikli_dev.modonomicon.book.BookTextHolder; import com.klikli_dev.modonomicon.book.RenderedBookTextHolder; import com.klikli_dev.modonomicon.book.conditions.BookCondition; @@ -83,7 +83,7 @@ public ResourceLocation getType() { } @Override - public void build(Level level, ContentBookEntry parentEntry, int pageNum) { + public void build(Level level, BookContentEntry parentEntry, int pageNum) { super.build(level, parentEntry, pageNum); if (this.title.isEmpty()) { diff --git a/common/src/main/java/com/klikli_dev/modonomicon/bookstate/BookVisualStates.java b/common/src/main/java/com/klikli_dev/modonomicon/bookstate/BookVisualStates.java index 0e8c2733c..201ea9489 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/bookstate/BookVisualStates.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/bookstate/BookVisualStates.java @@ -27,6 +27,7 @@ public class BookVisualStates { Codecs.concurrentMap(ResourceLocation.CODEC, BookVisualState.CODEC).fieldOf("bookStates").forGetter((s) -> s.bookStates) ).apply(instance, BookVisualStates::new)); + //TODO: make proper stream codec public static final StreamCodec STREAM_CODEC = ByteBufCodecs.fromCodec(CODEC); diff --git a/common/src/main/java/com/klikli_dev/modonomicon/bookstate/visual/BookVisualState.java b/common/src/main/java/com/klikli_dev/modonomicon/bookstate/visual/BookVisualState.java index 3e33e7215..580379504 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/bookstate/visual/BookVisualState.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/bookstate/visual/BookVisualState.java @@ -18,20 +18,28 @@ public class BookVisualState { public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance.group( - Codecs.mutableMap(ResourceLocation.CODEC, CategoryVisualState.CODEC).fieldOf("categoryStates").forGetter((state) -> state.categoryStates), - ResourceLocation.CODEC.optionalFieldOf("openCategory").forGetter((state) -> Optional.ofNullable(state.openCategory))).apply(instance, BookVisualState::new)); + Codecs.mutableMap(ResourceLocation.CODEC, CategoryVisualState.CODEC).fieldOf("categoryStates").forGetter((state) -> state.categoryStates), + ResourceLocation.CODEC.optionalFieldOf("openCategory").forGetter((state) -> Optional.ofNullable(state.openCategory)), + Codec.INT.fieldOf("openPagesIndex").forGetter((state) -> state.openPagesIndex) + ).apply(instance, BookVisualState::new)); public Map categoryStates; public ResourceLocation openCategory; + /** + * For books in index mode + */ + public int openPagesIndex; + public BookVisualState() { - this(Object2ObjectMaps.emptyMap(), Optional.empty()); + this(Object2ObjectMaps.emptyMap(), Optional.empty(), 0); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - public BookVisualState(Map categoryStates, Optional openCategory) { + public BookVisualState(Map categoryStates, Optional openCategory, int openPagesIndex) { this.categoryStates = new Object2ObjectOpenHashMap<>(categoryStates); this.openCategory = openCategory.orElse(null); + this.openPagesIndex = openPagesIndex; } } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/bookstate/visual/CategoryVisualState.java b/common/src/main/java/com/klikli_dev/modonomicon/bookstate/visual/CategoryVisualState.java index 613503198..d390fe51a 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/bookstate/visual/CategoryVisualState.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/bookstate/visual/CategoryVisualState.java @@ -22,7 +22,8 @@ public class CategoryVisualState { Codec.FLOAT.fieldOf("scrollX").forGetter((state) -> state.scrollX), Codec.FLOAT.fieldOf("scrollY").forGetter((state) -> state.scrollY), Codec.FLOAT.fieldOf("targetZoom").forGetter((state) -> state.targetZoom), - ResourceLocation.CODEC.optionalFieldOf("openEntry").forGetter((state) -> Optional.ofNullable(state.openEntry)) + ResourceLocation.CODEC.optionalFieldOf("openEntry").forGetter((state) -> Optional.ofNullable(state.openEntry)), + Codec.INT.fieldOf("openPagesIndex").forGetter((state) -> state.openPagesIndex) ).apply(instance, CategoryVisualState::new)); public Map entryStates; @@ -33,12 +34,17 @@ public class CategoryVisualState { public ResourceLocation openEntry; + /** + * For categories in index mode + */ + public int openPagesIndex; + public CategoryVisualState() { - this(new Object2ObjectOpenHashMap<>(), 0, 0, 0.7f, Optional.empty()); + this(new Object2ObjectOpenHashMap<>(), 0, 0, 0.7f, Optional.empty(), 0); } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - public CategoryVisualState(Map entryStates, float scrollX, float scrollY, float targetZoom, Optional openEntry) { + public CategoryVisualState(Map entryStates, float scrollX, float scrollY, float targetZoom, Optional openEntry, int openPagesIndex) { this.entryStates = entryStates; this.scrollX = scrollX; this.scrollY = scrollY; diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/BookGuiManager.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/BookGuiManager.java index 09b70b876..0eed0eb4e 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/BookGuiManager.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/BookGuiManager.java @@ -6,14 +6,37 @@ package com.klikli_dev.modonomicon.client.gui; -import com.klikli_dev.modonomicon.book.*; -import com.klikli_dev.modonomicon.book.entries.*; +import com.klikli_dev.modonomicon.book.Book; +import com.klikli_dev.modonomicon.book.BookCategory; +import com.klikli_dev.modonomicon.book.BookDisplayMode; +import com.klikli_dev.modonomicon.book.entries.BookContentEntry; +import com.klikli_dev.modonomicon.book.entries.BookEntry; +import com.klikli_dev.modonomicon.book.entries.CategoryLinkBookEntry; import com.klikli_dev.modonomicon.book.error.BookErrorManager; -import com.klikli_dev.modonomicon.client.gui.book.*; +import com.klikli_dev.modonomicon.bookstate.BookUnlockStateManager; +import com.klikli_dev.modonomicon.bookstate.BookVisualStateManager; +import com.klikli_dev.modonomicon.client.gui.book.BookAddress; +import com.klikli_dev.modonomicon.client.gui.book.BookCategoryScreen; +import com.klikli_dev.modonomicon.client.gui.book.BookErrorScreen; +import com.klikli_dev.modonomicon.client.gui.book.entry.BookEntryScreen; +import com.klikli_dev.modonomicon.client.gui.book.index.BookCategoryIndexOnNodeScreen; +import com.klikli_dev.modonomicon.client.gui.book.index.BookCategoryIndexScreen; +import com.klikli_dev.modonomicon.client.gui.book.index.BookParentIndexScreen; +import com.klikli_dev.modonomicon.client.gui.book.node.BookCategoryNodeScreen; +import com.klikli_dev.modonomicon.client.gui.book.node.BookParentNodeScreen; +import com.klikli_dev.modonomicon.client.gui.book.node.DummyBookCategoryNodeScreen; +import com.klikli_dev.modonomicon.client.gui.book.BookParentScreen; import com.klikli_dev.modonomicon.data.BookDataManager; +import com.klikli_dev.modonomicon.networking.BookEntryReadMessage; +import com.klikli_dev.modonomicon.networking.SaveBookStateMessage; +import com.klikli_dev.modonomicon.networking.SaveCategoryStateMessage; +import com.klikli_dev.modonomicon.networking.SaveEntryStateMessage; import com.klikli_dev.modonomicon.platform.ClientServices; +import com.klikli_dev.modonomicon.platform.Services; import net.minecraft.client.Minecraft; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.Stack; @@ -22,17 +45,14 @@ public class BookGuiManager { private static final BookGuiManager instance = new BookGuiManager(); - private final Stack history = new Stack<>(); + private final Stack history = new Stack<>(); - public Book currentBook; - public BookCategory currentCategory; - public BookEntry currentEntry; - - public BookOverviewScreen currentOverviewScreen; - public BookCategoryScreen currentCategoryScreen; - public BookContentScreen currentContentScreen; - - public BookOverviewScreen openOverviewScreen; + /** + * The currently open screen. Used for unlock state sync to immediately update the open screen. + */ + public BookParentScreen openBookParentScreen; + public BookCategoryScreen openBookCategoryScreen; + public BookEntryScreen openBookEntryScreen; private BookGuiManager() { @@ -42,7 +62,7 @@ public static BookGuiManager get() { return instance; } - public boolean showErrorScreen(ResourceLocation bookId) { + protected boolean showErrorScreen(ResourceLocation bookId) { if (BookErrorManager.get().hasErrors(bookId)) { var book = BookDataManager.get().getBook(bookId); Minecraft.getInstance().setScreen(new BookErrorScreen(book)); @@ -51,7 +71,7 @@ public boolean showErrorScreen(ResourceLocation bookId) { return false; } - public void safeguardBooksBuilt() { + protected void safeguardBooksBuilt() { if (!BookDataManager.get().areBooksBuilt()) { //This is a workaround/fallback for cases like https://github.com/klikli-dev/modonomicon/issues/48 //Generally it should never happen, because client builds books on UpdateRecipesPacket @@ -62,52 +82,277 @@ public void safeguardBooksBuilt() { } } - public void openBook(ResourceLocation bookId) { + protected Player player() { + return Minecraft.getInstance().player; + } + + protected BookCategory getSavedOrAddressedCategory(Book book, BookAddress address) { + if (address.categoryId() != null) + return book.getCategory(address.categoryId()); + + if(address.ignoreSavedCategory()) + return null; + + var state = BookVisualStateManager.get().getBookStateFor(this.player(), book); + if (state != null && state.openCategory != null) { + return book.getCategory(state.openCategory); + } + return null; + } + + protected BookCategory getSavedOrAddressedCategoryOrDefault(Book book, BookAddress address) { + var savedCategory = this.getSavedOrAddressedCategory(book, address); + if (savedCategory == null) { + return book.getCategoriesSorted().getFirst(); + } + return savedCategory; + } + + protected BookEntry getSavedOrAddressedEntry(BookCategory category, BookAddress address) { + if(address.entryId() != null) + return category.getEntry(address.entryId()); + + if(address.ignoreSavedEntry()) + return null; + + var state = BookVisualStateManager.get().getCategoryStateFor(this.player(), category); + if (state != null && state.openEntry != null) { + var openEntry = category.getEntry(state.openEntry); + //we skip link entries, they would lead to categories not being opened because it instantly jumps to the linked one + //they should not be in the history in the first place, but just to be sure + if (openEntry != null && !(openEntry instanceof CategoryLinkBookEntry)) { + //no need to load history here, will be handled by book content screen + return openEntry; + } + } + return null; + } + + protected BookEntry getSavedOrAddressedEntryOrDefault(BookCategory category, BookAddress address) { + var savedEntry = this.getSavedOrAddressedEntry(category, address); + //If we do not have a saved entry, check if we have an entry to open specified in the category definition + if (savedEntry == null && category.getEntryToOpen() != null) { + var entryToOpen = category.getEntry(category.getEntryToOpen()); + if (!category.openEntryToOpenOnlyOnce() || !BookUnlockStateManager.get().isReadFor(Minecraft.getInstance().player, entryToOpen)) { + return entryToOpen; + } + } + + return savedEntry; + } + + public void openBook(BookAddress address) { this.safeguardBooksBuilt(); - if (this.showErrorScreen(bookId)) { + if (this.showErrorScreen(address.bookId())) { return; } - var book = BookDataManager.get().getBook(bookId); + var book = BookDataManager.get().getBook(address.bookId()); - if (this.currentBook == book && this.currentOverviewScreen != null) { - Minecraft.getInstance().setScreen(this.currentOverviewScreen); - this.currentOverviewScreen.onDisplay(); - } else { - this.currentBook = book; - this.currentOverviewScreen = new BookOverviewScreen(this.currentBook); - Minecraft.getInstance().setScreen(this.currentOverviewScreen); - this.currentOverviewScreen.onDisplay(); + var displayMode = book.getDisplayMode(); + if (displayMode == BookDisplayMode.INDEX) { + this.openBookInIndexMode(book, address); + } else if (displayMode == BookDisplayMode.NODE) { + this.openBookInNodeMode(book, address); } } - public void openEntry(ResourceLocation bookId, ResourceLocation entryId, int page) { - var book = BookDataManager.get().getBook(bookId); - var entry = book.getEntry(entryId); - this.openEntry(bookId, entry.getCategoryId(), entryId, page); + protected void openBookInNodeMode(Book book, BookAddress address) { + var openBookParentScreen = new BookParentNodeScreen(book); + this.openBookParentScreen = openBookParentScreen; + + var state = BookVisualStateManager.get().getBookStateFor(this.player(), book); + if (state != null) { + openBookParentScreen.loadState(state); + } + + //Call after restoring state, to ensure the correct page etc display right away + Minecraft.getInstance().setScreen(openBookParentScreen); + + //run additional init logic (e.g. unlock state determination) + openBookParentScreen.onDisplay(); + + var openCategory = this.getSavedOrAddressedCategoryOrDefault(book, address); + this.openCategory(openCategory, address); + } + + protected void openBookInIndexMode(Book book, BookAddress address) { + var openBookParentScreen = new BookParentIndexScreen(book); + this.openBookParentScreen = openBookParentScreen; + + var state = BookVisualStateManager.get().getBookStateFor(this.player(), book); + if (state != null) { + openBookParentScreen.loadState(state); + } + + //Call after restoring state, to ensure the correct page etc display right away + Minecraft.getInstance().setScreen(openBookParentScreen); + + //run additional init logic (e.g. unlock state determination) + openBookParentScreen.onDisplay(); + + //Index mode does NOT use default categories, so we only get saved/address + var openCategory = this.getSavedOrAddressedCategory(book, address); + if(openCategory == null) + return; + + this.openCategory(openCategory, address); + } + + @ApiStatus.Internal + public void openCategory(BookCategory category, BookAddress address) { + if (this.openBookCategoryScreen != null) { + //skip if the category is already open + if (this.openBookCategoryScreen.getCategory() == category) + return; + + BookGuiManager.get().closeCategoryScreen(this.openBookCategoryScreen); + } + + var displayMode = category.getDisplayMode(); + //if the book is in index mode, force all categories into index mode too! + if (displayMode == BookDisplayMode.INDEX || category.getBook().getDisplayMode() == BookDisplayMode.INDEX) { + this.openCategoryInIndexMode(category, address); + } else if (displayMode == BookDisplayMode.NODE) { + this.openCategoryInNodeMode(category, address); + } + + //We now need to clear the open category in the book state, so that the closing handling can work properly + //Closing handling struggles with setting it to null (without lots of extra logic) + //So we reset it here, and on close just set it when needed + var bookState = BookVisualStateManager.get().getBookStateFor(this.player(), this.openBookParentScreen.getBook()); + bookState.openCategory = null; + } + + protected void openCategoryInNodeMode(BookCategory category, BookAddress address) { + //this is only possible if the book is in node mode + if (category.getBook().getDisplayMode() != BookDisplayMode.NODE || + !(this.openBookParentScreen instanceof BookParentNodeScreen bookParentNodeScreen)) { + throw new IllegalStateException("Cannot open category in node mode if book is not in node mode."); + } + + var openBookCategoryScreen = new BookCategoryNodeScreen(bookParentNodeScreen, category); + this.openBookCategoryScreen = openBookCategoryScreen; + + var state = BookVisualStateManager.get().getCategoryStateFor(this.player(), category); + if (state != null) { + openBookCategoryScreen.loadState(state); + } + + //if the parent screen is a node screen, we can need to set the category on it as it needs this for rendering + bookParentNodeScreen.setCurrentCategoryScreen(openBookCategoryScreen); + openBookCategoryScreen.onDisplay(); + + var openEntry = this.getSavedOrAddressedEntryOrDefault(category, address); + if (openEntry == null) + return; + + this.openEntry(openEntry, address); + } + + protected void openCategoryInIndexMode(BookCategory category, BookAddress address) { + //this is possible if the book is in node or in index mode, and we need different behaviour for each + //node mode needs an additional "default" screen on the book screen, that we display the category over + + var openBookCategoryScreen = new BookCategoryIndexScreen(this.openBookParentScreen, category); + + if (category.getBook().getDisplayMode() == BookDisplayMode.NODE && this.openBookParentScreen instanceof BookParentNodeScreen bookParentNodeScreen) { + //place a default screen on the parent node screen so we have a background to render + bookParentNodeScreen.setCurrentCategoryScreen(new DummyBookCategoryNodeScreen(bookParentNodeScreen, category)); + + //then use a special category index screen + openBookCategoryScreen = new BookCategoryIndexOnNodeScreen(bookParentNodeScreen, category); + } + + this.openBookCategoryScreen = openBookCategoryScreen; + + var state = BookVisualStateManager.get().getCategoryStateFor(this.player(), category); + if (state != null) { + openBookCategoryScreen.loadState(state); + } + + //call after restoring state, to ensure the correct page etc display right away + ClientServices.GUI.pushGuiLayer(openBookCategoryScreen); + + openBookCategoryScreen.onDisplay(); + + var openEntry = this.getSavedOrAddressedEntryOrDefault(category, address); + if (openEntry == null) + return; + + this.openEntry(openEntry, address); + } + + /** + * Should only be called from BookEntry#openEntry() + */ + @ApiStatus.Internal + public void openContentEntry(BookContentEntry entry, BookAddress address) { + var openBookEntryScreen = new BookEntryScreen(this.openBookParentScreen, entry); + this.openBookEntryScreen = openBookEntryScreen; + + if (address.page() != -1) + openBookEntryScreen.goToPage(address.page(), false); + else { + var state = BookVisualStateManager.get().getEntryStateFor(this.player(), entry); + if (state != null) { + openBookEntryScreen.loadState(state); + if (address.ignoreSavedPage()) + openBookEntryScreen.setOpenPagesIndex(0); + } + } + + //do this after the page setup, because init() sets up the rendering for the correct page + ClientServices.GUI.pushGuiLayer(openBookEntryScreen); + + //We now need to clear the open entry in the category state, so that the closing handling can work properly + //Closing handling struggles with setting it to null (without lots of extra logic) + //So we reset it here, and on close just set it when needed + var categoryState = BookVisualStateManager.get().getCategoryStateFor(this.player(), this.openBookCategoryScreen.getCategory()); + categoryState.openEntry = null; + } + + /** + * Should only be called from BookEntry#openEntry() + */ + @ApiStatus.Internal + public void openCategoryLinkEntry(CategoryLinkBookEntry entry) { + var category = entry.getCategoryToOpen(); + + + //Opening a category link means we already opened a book, and a category. These all need tob e closed before reopening, so we just use our default opening method + this.openBook(BookAddress.defaultFor(category)); + } + + @ApiStatus.Internal + public void openEntry(BookEntry entry, BookAddress address) { + if (!BookUnlockStateManager.get().isReadFor(this.player(), entry)) { + Services.NETWORK.sendToServer(new BookEntryReadMessage(entry.getBook().getId(), entry.getId())); + } + + entry.openEntry(address); //visitor pattern that will call openContentEntry or openCategoryLinkEntry } public void pushHistory(ResourceLocation bookId, @Nullable ResourceLocation entryId, int page) { var book = BookDataManager.get().getBook(bookId); var entry = book.getEntry(entryId); - this.history.push(new BookHistoryEntry(bookId, entry.getCategoryId(), entryId, page)); + this.history.push(BookAddress.of(bookId, entry.getCategoryId(), entryId, page)); } public void pushHistory(ResourceLocation bookId, @Nullable ResourceLocation categoryId, @Nullable ResourceLocation entryId, int page) { - this.history.push(new BookHistoryEntry(bookId, categoryId, entryId, page)); + this.history.push(BookAddress.of(bookId, categoryId, entryId, page)); } - - public void pushHistory(BookHistoryEntry entry) { + public void pushHistory(BookAddress entry) { this.history.push(entry); } - public BookHistoryEntry popHistory() { + public BookAddress popHistory() { return this.history.pop(); } - public BookHistoryEntry peekHistory() { + public BookAddress peekHistory() { return this.history.peek(); } @@ -119,6 +364,16 @@ public void resetHistory() { this.history.clear(); } + /** + * Opens the book at the given location. Will open as far as possible (meaning, if category and entry are null, it + * will not open those obviously). + */ + public void openEntry(ResourceLocation bookId, ResourceLocation entryId, int page) { + var book = BookDataManager.get().getBook(bookId); + var entry = book.getEntry(entryId); + this.openEntry(bookId, entry.getCategoryId(), entryId, page); + } + /** * Opens the book at the given location. Will open as far as possible (meaning, if category and entry are null, it * will not open those obviously). @@ -131,76 +386,98 @@ public void openEntry(ResourceLocation bookId, @Nullable ResourceLocation catego } if (this.showErrorScreen(bookId)) { - return; } - if (!BookDataManager.get().areBooksBuilt()) { - //This is a workaround/fallback for cases like https://github.com/klikli-dev/modonomicon/issues/48 - //Generally it should never happen, because client builds books on UpdateRecipesPacket - //If that packet for some reason is not handled clientside, we build books here and hope for the best :) - //Why don't we generally do it lazily like that? Because then markdown prerender errors only show in log if a book is actually opened - BookDataManager.get().tryBuildBooks(Minecraft.getInstance().level); - BookDataManager.get().prerenderMarkdown(Minecraft.getInstance().level.registryAccess()); - } + //First close the current book and preserve -state + if (this.openBookEntryScreen != null) + this.closeScreenStack(this.openBookEntryScreen); + else if (this.openBookCategoryScreen != null) + this.closeScreenStack(this.openBookCategoryScreen); + else if (this.openBookParentScreen != null) + this.closeScreenStack(this.openBookParentScreen); - var book = BookDataManager.get().getBook(bookId); - if (this.currentBook != book) { - this.currentBook = book; - } - - if (this.currentOverviewScreen == null || this.currentOverviewScreen.getBook() != book) { - this.currentOverviewScreen = new BookOverviewScreen(book); - } + //then open the given address + this.openBook(BookAddress.ignoreSaved(bookId, categoryId, entryId, page)); + } - Minecraft.getInstance().setScreen(this.currentOverviewScreen); + /** + * Call this when you want to close *just* the entry screen, but not the category and parent. + * E.g. from the "close"/"x" button. + */ + public void closeEntryScreen(BookEntryScreen screen) { + this.closeEntryScreen(screen, false); + } - if (categoryId == null) { - //if no category is provided, just open the book and exit. - return; - } + /** + * Call this when you want to close *just* the entry screen, but not the category and parent. + * E.g. from the "close"/"x" button. + */ + public void closeEntryScreen(BookEntryScreen screen, boolean overrideStoreLastOpenPageWhenClosingEntry) { + //close the entry screen + if (ClientServices.GUI.getCurrentScreen() == screen) + ClientServices.GUI.popGuiLayer(); + this.openBookEntryScreen = null; + + var state = BookVisualStateManager.get().getEntryStateFor(this.player(), screen.getEntry()); + //if we close "normally" without Esc we respect the config setting + //for ESC closing we always save the page (see below #onEsc()) + screen.saveState(state, overrideStoreLastOpenPageWhenClosingEntry || ClientServices.CLIENT_CONFIG.storeLastOpenPageWhenClosingEntry()); + Services.NETWORK.sendToServer(new SaveEntryStateMessage(screen.getEntry(), state)); + } - var category = book.getCategory(categoryId); - if (this.currentCategory != category) { - this.currentCategory = category; - } + /** + * Call this when you want to close *just* the category screen, but not the parent. + * E.g. from the "close"/"x" button. + */ + public void closeCategoryScreen(BookCategoryScreen screen) { + //close the category screen + //this check handles the case of node categories that are not real screens + //thus no gui layer can be popped -> otherwise we already remove our parent screen! + if (ClientServices.GUI.getCurrentScreen() == screen) + ClientServices.GUI.popGuiLayer(); + this.openBookCategoryScreen = null; + + var state = BookVisualStateManager.get().getCategoryStateFor(this.player(), screen.getCategory()); + screen.saveState(state); + Services.NETWORK.sendToServer(new SaveCategoryStateMessage(screen.getCategory(), state)); + } - if (this.currentCategoryScreen == null || this.currentCategoryScreen.getCategory() != category) { - this.currentOverviewScreen.changeCategory(category); - this.currentCategoryScreen = this.currentOverviewScreen.getCurrentCategoryScreen(); - } + /** + * Call this when you want to close the parent screen naturally, without esc. + * E.g. from the "close"/"x" button. + */ + public void closeParentScreen(BookParentScreen screen) { + Minecraft.getInstance().setScreen(null); + this.openBookParentScreen = null; - if (entryId == null) { - //if no entry is provided, just open the book and category and exit. - return; - } + var state = BookVisualStateManager.get().getBookStateFor(this.player(), screen.getBook()); + screen.saveState(state); + Services.NETWORK.sendToServer(new SaveBookStateMessage(screen.getBook(), state)); - var entry = book.getEntry(entryId); - if (this.currentEntry != entry) { - this.currentEntry = entry; - } + this.resetHistory(); + } - if (this.currentContentScreen == null || this.currentContentScreen.getEntry() != entry) { - this.currentContentScreen = this.currentCategoryScreen.openEntry(entry); - } else { - //we are clearing the gui layers above, so we have to restore here if we do not call openentry - //we check if the content screen was already added, e.g. by the book category screen - if (!this.isEntryAlreadyDisplayed(entry)) - ClientServices.GUI.pushGuiLayer(this.currentContentScreen); + public void closeScreenStack(BookParentScreen screen) { + //We don't need to do any additional state saving here, the other closeScreenStack overloads already handle that for us + this.closeParentScreen(screen); + } - //to ensure the current open entry is tracked on the category, we manually set it - //this is necessary to ensure state is tracked and saved when closing again. Fixes #196 - this.currentCategoryScreen.setOpenEntry(entry.getId()); - } + public void closeScreenStack(BookCategoryScreen screen) { + this.closeCategoryScreen(screen); - //we need to check for null, because this.currentCategoryScreen.openEntry(entry); returns null for "redirect" entries that open categories - because there is no content to show. - if(this.currentContentScreen != null) { - //we don't need to manually check for the current page because the content screen will do that for us - this.currentContentScreen.goToPage(page, false); - } - //TODO: play sound here? could just make this a client config + //set the open category on the book state, so that closeParentScreen sends it along. + var bookState = BookVisualStateManager.get().getBookStateFor(this.player(), screen.getCategory().getBook()); + bookState.openCategory = screen.getCategory().getId(); + this.closeScreenStack(this.openBookParentScreen); } - public boolean isEntryAlreadyDisplayed(BookEntry entry) { - return Minecraft.getInstance().screen instanceof BookContentScreen bookContentScreen && bookContentScreen.getEntry().equals(entry); + public void closeScreenStack(BookEntryScreen screen) { + //close entry screen with forced saving of last page + this.closeEntryScreen(screen, true); + + //set the open entry on the category state, so that closeCategoryScreen sends it along. + var categoryState = BookVisualStateManager.get().getCategoryStateFor(this.player(), this.openBookCategoryScreen.getCategory()); + categoryState.openEntry = screen.getEntry().getId(); + this.closeScreenStack(this.openBookCategoryScreen); //will then bubble down to close the parent screen } } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookAddress.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookAddress.java new file mode 100644 index 000000000..3bcde554d --- /dev/null +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookAddress.java @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2022 klikli-dev + * + * SPDX-License-Identifier: MIT + */ + +package com.klikli_dev.modonomicon.client.gui.book; + +import com.klikli_dev.modonomicon.book.Book; +import com.klikli_dev.modonomicon.book.BookCategory; +import com.klikli_dev.modonomicon.book.entries.BookEntry; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an address in a book, consisting of the book, category, entry and page. + * Used to navigate to a specific page in a book and to store such a state + */ +public record BookAddress(@NotNull ResourceLocation bookId, + ResourceLocation categoryId, boolean ignoreSavedCategory, + ResourceLocation entryId, boolean ignoreSavedEntry, + int page, boolean ignoreSavedPage +) { + public static BookAddress defaultFor(@NotNull BookCategory category) { + return of(category.getBook().getId(), category.getId(), null, -1); + } + + public static BookAddress defaultFor(@NotNull BookEntry entry) { + return of(entry.getBook().getId(), entry.getCategory().getId(), entry.getId(), -1); + } + + public static BookAddress defaultFor(@NotNull Book book) { + return defaultFor(book.getId()); + } + + public static BookAddress defaultFor(@NotNull ResourceLocation bookId) { + return of(bookId, null, null, -1); + } + + public static BookAddress of(@NotNull ResourceLocation bookId, + ResourceLocation categoryId, + ResourceLocation entryId, + int page) { + return new BookAddress(bookId, categoryId, false, entryId, false, page, false); + } + + public static BookAddress ignoreSaved(@NotNull ResourceLocation bookId, + ResourceLocation categoryId, + ResourceLocation entryId, + int page) { + return new BookAddress(bookId, categoryId, true, entryId, true, page, true); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookCategoryScreen.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookCategoryScreen.java index f5a7d4877..c5e911ff8 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookCategoryScreen.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookCategoryScreen.java @@ -1,460 +1,24 @@ -/* - * SPDX-FileCopyrightText: 2022 klikli-dev - * SPDX-FileCopyrightText: 2021 Authors of Arcana - * - * SPDX-License-Identifier: MIT - */ -package com.klikli_dev.modonomicon.client.gui.book; - -import com.klikli_dev.modonomicon.api.events.EntryClickedEvent; -import com.klikli_dev.modonomicon.book.*; -import com.klikli_dev.modonomicon.book.entries.*; -import com.klikli_dev.modonomicon.book.conditions.context.BookConditionEntryContext; -import com.klikli_dev.modonomicon.bookstate.BookUnlockStateManager; -import com.klikli_dev.modonomicon.bookstate.BookVisualStateManager; -import com.klikli_dev.modonomicon.client.gui.BookGuiManager; -import com.klikli_dev.modonomicon.events.ModonomiconEvents; -import com.klikli_dev.modonomicon.networking.BookEntryReadMessage; -import com.klikli_dev.modonomicon.networking.SaveCategoryStateMessage; -import com.klikli_dev.modonomicon.platform.ClientServices; -import com.klikli_dev.modonomicon.platform.Services; -import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import org.jetbrains.annotations.*; - -import java.util.ArrayList; -import java.util.Optional; - - -public class BookCategoryScreen { - - public static final int ENTRY_GRID_SCALE = 30; - public static final int ENTRY_GAP = 2; - public static final int MAX_SCROLL = 512; - - public static final int ENTRY_HEIGHT = 26; - public static final int ENTRY_WIDTH = 26; - - private final BookOverviewScreen bookOverviewScreen; - private final BookCategory category; - private final EntryConnectionRenderer connectionRenderer; - private float scrollX = 0; - private float scrollY = 0; - private boolean isScrolling; - private float targetZoom; - private float currentZoom; - - private ResourceLocation openEntry; - - public BookCategoryScreen(BookOverviewScreen bookOverviewScreen, BookCategory category) { - this.bookOverviewScreen = bookOverviewScreen; - this.category = category; - - this.connectionRenderer = new EntryConnectionRenderer(category.getEntryTextures()); - - this.targetZoom = 0.7f; - this.currentZoom = this.targetZoom; - } - - public BookCategory getCategory() { - return this.category; - } - - public float getXOffset() { - return ((this.bookOverviewScreen.getInnerWidth() / 2f) * (1 / this.currentZoom)) - this.scrollX / 2; - } - - public float getYOffset() { - return ((this.bookOverviewScreen.getInnerHeight() / 2f) * (1 / this.currentZoom)) - this.scrollY / 2; - } - - public void render(GuiGraphics guiGraphics, int pMouseX, int pMouseY, float pPartialTick) { - if (ClientServices.CLIENT_CONFIG.enableSmoothZoom()) { - float diff = this.targetZoom - this.currentZoom; - this.currentZoom = this.currentZoom + Math.min(pPartialTick * (2 / 3f), 1) * diff; - } else - this.currentZoom = this.targetZoom; - - //GL Scissors to the inner frame area so entries do not stick out - int innerX = this.bookOverviewScreen.getInnerX(); - int innerY = this.bookOverviewScreen.getInnerY(); - int innerWidth = this.bookOverviewScreen.getInnerWidth(); - int innerHeight = this.bookOverviewScreen.getInnerHeight(); - //the -1 are magic numbers to avoid an overflow of 1px. - guiGraphics.enableScissor(innerX, innerY, innerX + innerWidth - 1, innerY + innerHeight - 1); - this.renderEntries(guiGraphics, pMouseX, pMouseY); - guiGraphics.disableScissor(); - } - - public void zoom(double delta) { - float step = 1.2f; - if ((delta < 0 && this.targetZoom > 0.5) || (delta > 0 && this.targetZoom < 1)) - this.targetZoom *= delta > 0 ? step : 1 / step; - if (this.targetZoom > 1f) - this.targetZoom = 1f; - } - - public boolean mouseDragged(double pMouseX, double pMouseY, int pButton, double pDragX, double pDragY) { - //Based on advancementsscreen - if (pButton != 0) { - this.isScrolling = false; - return false; - } else { - if (!this.isScrolling) { - this.isScrolling = true; - } else { - this.scroll(pDragX * 1.5, pDragY * 1.5); - } - return true; - } - } - - public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - - float xOffset = this.getXOffset(); - float yOffset = this.getYOffset(); - for (var entry : this.category.getEntries().values()) { - var displayStyle = this.getEntryDisplayState(entry); - - if (this.isEntryHovered(entry, xOffset, yOffset, (int) pMouseX, (int) pMouseY)) { - - var event = new EntryClickedEvent(this.category.getBook().getId(), entry.getId(), pMouseX, pMouseY, pButton, displayStyle); - //if event is canceled -> click was handled and we do not open the entry. - if (ModonomiconEvents.client().entryClicked(event)) { - return true; - } - - //only if the entry is unlocked we open it - if (displayStyle == EntryDisplayState.UNLOCKED) { - this.openEntry(entry); - return true; - } - } - } - - return false; - } - - public @Nullable BookContentScreen openEntry(BookEntry entry) { - if (!BookUnlockStateManager.get().isReadFor(Minecraft.getInstance().player, entry)) { - Services.NETWORK.sendToServer(new BookEntryReadMessage(entry.getBook().getId(), entry.getId())); - } - - if(!(entry instanceof CategoryLinkBookEntry)) - this.openEntry = entry.getId(); //do not store link entries in history, otherwise we always jump to its target when opening categories - - return entry.openEntry(this); - } - - public @Nullable BookContentScreen openContentEntry(ContentBookEntry entry) { - //we check if the content screen was already added, e.g. by the book gui manager - if (BookGuiManager.get().isEntryAlreadyDisplayed(entry)) - return (BookContentScreen) Minecraft.getInstance().screen; - - var bookContentScreen = new BookContentScreen(this.bookOverviewScreen.getCurrentCategoryScreen().bookOverviewScreen, entry); - ClientServices.GUI.pushGuiLayer(bookContentScreen); - - this.openEntry = entry.getId(); - - return bookContentScreen; - } - - public void renderBackground(GuiGraphics guiGraphics) { - //based on the frame's total width and its thickness, calculate where the inner area starts - int innerX = this.bookOverviewScreen.getInnerX(); - int innerY = this.bookOverviewScreen.getInnerY(); - - //then calculate the corresponding inner area width/height so we don't draw out of the frame - int innerWidth = this.bookOverviewScreen.getInnerWidth(); - int innerHeight = this.bookOverviewScreen.getInnerHeight(); - - //we do not use our static max_scroll here because it makes some issues, so we use the tex instead. - int backgroundWidth = this.category.getBackgroundWidth(); - int backgroundHeight = this.category.getBackgroundHeight(); - final int MAX_SCROLL = Math.max(backgroundWidth, backgroundHeight); - float backgroundTextureZoomMultiplier = this.category.getBackgroundTextureZoomMultiplier(); - - - // Adjust scale calculations to take into account actual texture size - float xScale = MAX_SCROLL * 2.0f / ((float) MAX_SCROLL + this.bookOverviewScreen.getFrameThicknessW() - this.bookOverviewScreen.getFrameWidth()); - float yScale = MAX_SCROLL * 2.0f / ((float) MAX_SCROLL + this.bookOverviewScreen.getFrameThicknessH() - this.bookOverviewScreen.getFrameHeight()); - float scale = Math.max(xScale, yScale); - float xOffset = xScale == scale ? 0 : (MAX_SCROLL - (innerWidth + MAX_SCROLL * 2.0f / scale)) / 2; - float yOffset = yScale == scale ? 0 : (MAX_SCROLL - (innerHeight + MAX_SCROLL * 2.0f / scale)) / 2; - - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - RenderSystem.setShader(GameRenderer::getPositionTexShader); - - //note we cannot translate -z here because even -1 immediately pushes us behind the scene -> not visible - if (!this.category.getBackgroundParallaxLayers().isEmpty()) { - this.category.getBackgroundParallaxLayers().forEach(layer -> { - this.renderBackgroundParallaxLayer(guiGraphics, layer, innerX, innerY, innerWidth, innerHeight, this.scrollX, this.scrollY, scale, xOffset, yOffset, this.currentZoom, backgroundWidth, backgroundHeight, backgroundTextureZoomMultiplier); - }); - } else { - //for some reason on this one blit overload tex width and height are switched. It does correctly call the followup though, so we have to go along - //force offset to int here to reduce difference to entry rendering which is pos based and thus int precision only - guiGraphics.blit(this.category.getBackground(), innerX, innerY, - (this.scrollX + MAX_SCROLL) / scale + xOffset, - (this.scrollY + MAX_SCROLL) / scale + yOffset, - innerWidth, innerHeight, (int)(backgroundHeight * backgroundTextureZoomMultiplier), (int)(backgroundWidth * backgroundTextureZoomMultiplier)); - - } - } - - public void renderBackgroundParallaxLayer(GuiGraphics guiGraphics, BookCategoryBackgroundParallaxLayer layer, int x, int y, int width, int height, float scrollX, float scrollY, float parallax, float xOffset, float yOffset, float zoom, int backgroundWidth, int backgroundHeight, float backgroundTextureZoomMultiplier) { - float parallax1 = parallax / layer.getSpeed(); - RenderSystem.setShaderTexture(0, layer.getBackground()); - - if (layer.getVanishZoom() == -1 || layer.getVanishZoom() > zoom) { - //for some reason on this one blit overload tex width and height are switched. It does correctly call the followup though, so we have to go along - guiGraphics.blit(layer.getBackground(), x, y, - (scrollX + MAX_SCROLL) / parallax1 + xOffset, - (scrollY + MAX_SCROLL) / parallax1 + yOffset, - width, height,(int)(backgroundHeight * backgroundTextureZoomMultiplier), (int)(backgroundWidth * backgroundTextureZoomMultiplier)); - } - - } - +// SPDX-FileCopyrightText: 2023 klikli-dev +// +// SPDX-License-Identifier: MIT - private EntryDisplayState getEntryDisplayState(BookEntry entry) { - var player = this.bookOverviewScreen.getMinecraft().player; - - var isEntryUnlocked = BookUnlockStateManager.get().isUnlockedFor(player, entry); - - var anyParentsUnlocked = false; - var allParentsUnlocked = true; - for (var parent : entry.getParents()) { - if (!BookUnlockStateManager.get().isUnlockedFor(player, parent.getEntry())) { - allParentsUnlocked = false; - } else { - anyParentsUnlocked = true; - } - } - - if (entry.showWhenAnyParentUnlocked() && !anyParentsUnlocked) - return EntryDisplayState.HIDDEN; - - if (!entry.showWhenAnyParentUnlocked() && !allParentsUnlocked) - return EntryDisplayState.HIDDEN; - - if (!isEntryUnlocked) - return entry.hideWhileLocked() ? EntryDisplayState.HIDDEN : EntryDisplayState.LOCKED; - - return EntryDisplayState.UNLOCKED; - } - - private void renderEntries(GuiGraphics guiGraphics, int mouseX, int mouseY) { - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - RenderSystem.setShader(GameRenderer::getPositionTexShader); - - //calculate the render offset - float xOffset = this.getXOffset(); - float yOffset = this.getYOffset(); - - guiGraphics.pose().pushPose(); - guiGraphics.pose().scale(this.currentZoom, this.currentZoom, 1.0f); - - for (var entry : this.category.getEntries().values()) { - var displayState = this.getEntryDisplayState(entry); - var isHovered = this.isEntryHovered(entry, xOffset, yOffset, mouseX, mouseY); - - if (displayState == EntryDisplayState.HIDDEN) - continue; - - int texX = entry.getEntryBackgroundVIndex() * ENTRY_HEIGHT; - int texY = entry.getEntryBackgroundUIndex() * ENTRY_WIDTH; - - guiGraphics.pose().pushPose(); - //we translate instead of applying the offset to the entry x/y to avoid jittering when moving - guiGraphics.pose().translate(xOffset, yOffset, 0); - - - //we apply a z offset to push the entries before the connection arrows - guiGraphics.pose().translate(0, 0, 10); - - //As of 1.20 this is not necessary, in fact it causes the entry to render behind the bg - //guiGraphics.pose().translate(0, 0, -10); //push the whole entry behind the frame - - - if (displayState == EntryDisplayState.LOCKED) { - //Draw locked entries greyed out - RenderSystem.setShaderColor(0.2F, 0.2F, 0.2F, 1.0F); - } else if (isHovered) { - //Draw hovered entries slightly greyed out - RenderSystem.setShaderColor(0.8F, 0.8F, 0.8F, 1.0F); - } - //render entry background - guiGraphics.blit(this.category.getEntryTextures(), entry.getX() * ENTRY_GRID_SCALE + ENTRY_GAP, entry.getY() * ENTRY_GRID_SCALE + ENTRY_GAP, texX, texY, ENTRY_WIDTH, ENTRY_HEIGHT); - - guiGraphics.pose().pushPose(); - - //render icon - entry.getIcon().render(guiGraphics, entry.getX() * ENTRY_GRID_SCALE + ENTRY_GAP + 5, entry.getY() * ENTRY_GRID_SCALE + ENTRY_GAP + 5); - - guiGraphics.pose().popPose(); - - //render unread icon - if (displayState == EntryDisplayState.UNLOCKED && !BookUnlockStateManager.get().isReadFor(this.bookOverviewScreen.getMinecraft().player, entry)) { - final int U = 350; - final int V = 19; - final int width = 11; - final int height = 11; - - RenderSystem.setShader(GameRenderer::getPositionTexShader); - //RenderSystem.setShaderColor(1F, 1F, 1F, 1F); - - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - - //testing - guiGraphics.pose().pushPose(); - guiGraphics.pose().translate(0, 0, 11); //and push the unread icon in front of the background and icon (they are at Z 10) - //if focused we go to the right of our normal button (instead of down, like mc buttons do) - BookContentScreen.drawFromTexture(guiGraphics, this.bookOverviewScreen.getBook(), - entry.getX() * ENTRY_GRID_SCALE + ENTRY_GAP + 16 + 2, - entry.getY() * ENTRY_GRID_SCALE + ENTRY_GAP - 2, U + (isHovered ? width : 0), V, width, height); - guiGraphics.pose().popPose(); - } - - guiGraphics.pose().popPose(); - - //reset color to avoid greyed out carrying over - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - - this.renderConnections(guiGraphics, entry, xOffset, yOffset); - } - guiGraphics.pose().popPose(); - } - - public void renderEntryTooltips(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { - //calculate the render offset - float xOffset = this.getXOffset(); - float yOffset = this.getYOffset(); - - for (var entry : this.category.getEntries().values()) { - var displayState = this.getEntryDisplayState(entry); - if (displayState == EntryDisplayState.HIDDEN) - continue; - - this.renderTooltip(guiGraphics, entry, displayState, xOffset, yOffset, mouseX, mouseY); - } - } - - private boolean isEntryHovered(BookEntry entry, float xOffset, float yOffset, int mouseX, int mouseY) { - int x = (int) ((entry.getX() * ENTRY_GRID_SCALE + xOffset + 2) * this.currentZoom); - int y = (int) ((entry.getY() * ENTRY_GRID_SCALE + yOffset + 2) * this.currentZoom); - int innerX = this.bookOverviewScreen.getInnerX(); - int innerY = this.bookOverviewScreen.getInnerY(); - int innerWidth = this.bookOverviewScreen.getInnerWidth(); - int innerHeight = this.bookOverviewScreen.getInnerHeight(); - return mouseX >= x && mouseX <= x + (ENTRY_WIDTH * this.currentZoom) - && mouseY >= y && mouseY <= y + (ENTRY_HEIGHT * this.currentZoom) - && mouseX >= innerX && mouseX <= innerX + innerWidth - && mouseY >= innerY && mouseY <= innerY + innerHeight; - } - - private void renderTooltip(GuiGraphics guiGraphics, BookEntry entry, EntryDisplayState displayState, float xOffset, float yOffset, int mouseX, int mouseY) { - //hovered? - if (this.isEntryHovered(entry, xOffset, yOffset, mouseX, mouseY)) { - - var tooltip = new ArrayList(); - - if (displayState == EntryDisplayState.LOCKED) { - tooltip.addAll(entry.getCondition().getTooltip(this.bookOverviewScreen.getMinecraft().player, BookConditionEntryContext.of(this.bookOverviewScreen.getBook(), entry))); - } else if (displayState == EntryDisplayState.UNLOCKED) { - //add name in bold - tooltip.add(Component.translatable(entry.getName()).withStyle(ChatFormatting.BOLD)); - //add description - if (!entry.getDescription().isEmpty()) { - tooltip.add(Component.translatable(entry.getDescription())); - } - } - - //draw description - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - guiGraphics.renderTooltip(Minecraft.getInstance().font, tooltip, Optional.empty(), mouseX, mouseY); - } - } - - private void renderConnections(GuiGraphics guiGraphics, BookEntry entry, float xOffset, float yOffset) { - //our arrows are aliased and need blending - RenderSystem.enableBlend(); - - for (var parent : entry.getParents()) { - var parentDisplayState = this.getEntryDisplayState(parent.getEntry()); - if (parentDisplayState == EntryDisplayState.HIDDEN) - continue; - - int blitOffset = 0; //note: any negative blit offset will move it behind our category background - this.connectionRenderer.setBlitOffset(blitOffset); - guiGraphics.pose().pushPose(); - guiGraphics.pose().translate(xOffset, yOffset, 0); - this.connectionRenderer.render(guiGraphics, entry, parent); - guiGraphics.pose().popPose(); - } - - RenderSystem.disableBlend(); - } +package com.klikli_dev.modonomicon.client.gui.book; - private void scroll(double pDragX, double pDragY) { - this.scrollX = (float) Mth.clamp(this.scrollX - pDragX, -MAX_SCROLL, MAX_SCROLL); - this.scrollY = (float) Mth.clamp(this.scrollY - pDragY, -MAX_SCROLL, MAX_SCROLL); - } +import com.klikli_dev.modonomicon.book.BookCategory; +import com.klikli_dev.modonomicon.bookstate.visual.CategoryVisualState; - private void loadCategoryState() { - var state = BookVisualStateManager.get().getCategoryStateFor(this.bookOverviewScreen.getMinecraft().player, this.category); - BookGuiManager.get().currentCategory = this.category; - BookGuiManager.get().currentCategoryScreen = this; - if (state != null) { - this.scrollX = state.scrollX; - this.scrollY = state.scrollY; - this.targetZoom = state.targetZoom; - this.currentZoom = state.targetZoom; - if (state.openEntry != null) { - var openEntry = this.category.getEntry(state.openEntry); - //also check for link entries here if they pollute old history -> they are not allowed to be stored, otherwise opening one category auto-jumps to the next - if (openEntry != null && !(openEntry instanceof CategoryLinkBookEntry)) { - //no need to load history here, will be handled by book content screen - this.openEntry(openEntry); - } - } - } - } +/** + * A screen that represents a book. It usually manages other screens for categories and entries. + */ +public interface BookCategoryScreen { - public void onDisplay() { - this.loadCategoryState(); + void onDisplay(); - //handle the entryToOpen - if(this.openEntry == null && this.category.getEntryToOpen() != null){ - var entryToOpen = this.category.getEntry(this.category.getEntryToOpen()); - //the entry is by default only opened if it was not read yet. - //however, the book author can override this with openEntryToOpenOnlyOnce = false - if(!this.category.openEntryToOpenOnlyOnce() || !BookUnlockStateManager.get().isReadFor(Minecraft.getInstance().player, entryToOpen)){ - this.openEntry(entryToOpen); - } - } - } + void onClose(); - public void onClose() { - Services.NETWORK.sendToServer(new SaveCategoryStateMessage(this.category, this.scrollX, this.scrollY, this.currentZoom, this.openEntry)); - } + void loadState(CategoryVisualState state); - public void onCloseEntry(BookContentScreen screen) { - this.openEntry = null; - } - - public BookOverviewScreen getBookOverviewScreen() { - return this.bookOverviewScreen; - } + void saveState(CategoryVisualState state); - public void setOpenEntry(ResourceLocation openEntry) { - this.openEntry = openEntry; - } + BookCategory getCategory(); } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookHistoryEntry.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookHistoryEntry.java deleted file mode 100644 index ccd7a2e44..000000000 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookHistoryEntry.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 klikli-dev - * - * SPDX-License-Identifier: MIT - */ - -package com.klikli_dev.modonomicon.client.gui.book; - -import net.minecraft.resources.ResourceLocation; - -public class BookHistoryEntry { - public ResourceLocation bookId; - public ResourceLocation categoryId; - public ResourceLocation entryId; - public int page; - - public BookHistoryEntry(ResourceLocation bookId, ResourceLocation categoryId, ResourceLocation entryId, int page) { - this.bookId = bookId; - this.categoryId = categoryId; - this.entryId = entryId; - this.page = page; - } -} diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookPaginatedScreen.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookPaginatedScreen.java index ec9e53087..ad516c5a6 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookPaginatedScreen.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookPaginatedScreen.java @@ -6,9 +6,11 @@ package com.klikli_dev.modonomicon.client.gui.book; +import com.klikli_dev.modonomicon.book.page.BookPage; import com.klikli_dev.modonomicon.client.gui.BookGuiManager; import com.klikli_dev.modonomicon.client.gui.book.button.ArrowButton; import com.klikli_dev.modonomicon.client.gui.book.button.ExitButton; +import com.klikli_dev.modonomicon.client.gui.book.entry.BookEntryScreen; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; @@ -21,16 +23,20 @@ public abstract class BookPaginatedScreen extends Screen implements BookScreenWi public static final int BOOK_BACKGROUND_WIDTH = 272; public static final int BOOK_BACKGROUND_HEIGHT = 178; - - protected final BookOverviewScreen parentScreen; - + protected int bookLeft; protected int bookTop; - - public BookPaginatedScreen(Component component, BookOverviewScreen parentScreen) { + + protected boolean addExitButton; + + public BookPaginatedScreen(Component component) { + this(component, true); + } + + public BookPaginatedScreen(Component component, boolean addExitButton) { super(component); - - this.parentScreen = parentScreen; + + this.addExitButton = addExitButton; } @Override @@ -42,7 +48,9 @@ protected void init() { this.addRenderableWidget(new ArrowButton(this, this.bookLeft - 4, this.bookTop + FULL_HEIGHT - 6, true, () -> this.canSeeArrowButton(true), this::handleArrowButton)); this.addRenderableWidget(new ArrowButton(this, this.bookLeft + FULL_WIDTH - 14, this.bookTop + FULL_HEIGHT - 6, false, () -> this.canSeeArrowButton(false), this::handleArrowButton)); - this.addRenderableWidget(new ExitButton(this, this.bookLeft + FULL_WIDTH - 10, this.bookTop - 2, this::handleExitButton)); + if(this.addExitButton){ + this.addRenderableWidget(new ExitButton(this, this.bookLeft + FULL_WIDTH - 10, this.bookTop - 2, this::handleExitButton)); + } } public void handleExitButton(Button button) { @@ -62,10 +70,10 @@ public void handleArrowButton(Button button) { protected abstract void flipPage(boolean left, boolean playSound); protected boolean isClickOutsideEntry(double pMouseX, double pMouseY) { - return pMouseX < this.bookLeft - BookContentScreen.CLICK_SAFETY_MARGIN - || pMouseX > this.bookLeft + BookContentScreen.FULL_WIDTH + BookContentScreen.CLICK_SAFETY_MARGIN - || pMouseY < this.bookTop - BookContentScreen.CLICK_SAFETY_MARGIN - || pMouseY > this.bookTop + BookContentScreen.FULL_HEIGHT + BookContentScreen.CLICK_SAFETY_MARGIN; + return pMouseX < this.bookLeft - BookEntryScreen.CLICK_SAFETY_MARGIN + || pMouseX > this.bookLeft + BookEntryScreen.FULL_WIDTH + BookEntryScreen.CLICK_SAFETY_MARGIN + || pMouseY < this.bookTop - BookEntryScreen.CLICK_SAFETY_MARGIN + || pMouseY > this.bookTop + BookEntryScreen.FULL_HEIGHT + BookEntryScreen.CLICK_SAFETY_MARGIN; } @Override @@ -89,7 +97,7 @@ public void handleBackButton(Button button) { public void back() { if (BookGuiManager.get().getHistorySize() > 0) { var lastPage = BookGuiManager.get().popHistory(); - BookGuiManager.get().openEntry(lastPage.bookId, lastPage.categoryId, lastPage.entryId, lastPage.page); + BookGuiManager.get().openEntry(lastPage.bookId(), lastPage.categoryId(), lastPage.entryId(), lastPage.page()); } else { this.onClose(); } @@ -101,7 +109,7 @@ public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { this.onClose(); return true; //need to return, otherwise a right click outside the entry causes a double-close (the whole book, due to calling .back() below) } - + if (pButton == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { this.back(); return true; diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookParentScreen.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookParentScreen.java new file mode 100644 index 000000000..665dafbec --- /dev/null +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookParentScreen.java @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 klikli-dev +// +// SPDX-License-Identifier: MIT + +package com.klikli_dev.modonomicon.client.gui.book; + +import com.klikli_dev.modonomicon.book.BookProvider; +import com.klikli_dev.modonomicon.bookstate.visual.BookVisualState; +import com.klikli_dev.modonomicon.networking.SyncBookUnlockStatesMessage; + +/** + * A screen that represents a book. It usually manages other screens for categories and entries. + */ +public interface BookParentScreen extends BookProvider { + + void onDisplay(); + + /** + * This is provided by any vanilla screen, and usually overridden by Modonomicon screens. + * Making it available in this interface allows various book child screens to close the entire book. + */ + void onClose(); + + void loadState(BookVisualState state); + + void saveState(BookVisualState state); + + void onSyncBookUnlockCapabilityMessage(SyncBookUnlockStatesMessage message); +} diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookScreenWithButtons.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookScreenWithButtons.java index aec8a0a2c..42439d55b 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookScreenWithButtons.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookScreenWithButtons.java @@ -4,13 +4,11 @@ package com.klikli_dev.modonomicon.client.gui.book; -import com.klikli_dev.modonomicon.book.Book; +import com.klikli_dev.modonomicon.book.BookProvider; import net.minecraft.network.chat.Component; import java.util.List; -public interface BookScreenWithButtons { +public interface BookScreenWithButtons extends BookProvider { void setTooltip(List tooltip); - - Book getBook(); } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/BackButton.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/BackButton.java index 979f5bca7..385668e21 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/BackButton.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/BackButton.java @@ -7,7 +7,7 @@ package com.klikli_dev.modonomicon.client.gui.book.button; import com.klikli_dev.modonomicon.api.ModonomiconConstants.I18n.Gui; -import com.klikli_dev.modonomicon.client.gui.book.BookContentScreen; +import com.klikli_dev.modonomicon.client.gui.book.entry.BookEntryScreen; import net.minecraft.network.chat.Component; public class BackButton extends BookButton { @@ -17,7 +17,7 @@ public class BackButton extends BookButton { public static final int HEIGHT = 9; public static final int WIDTH = 18; - public BackButton(BookContentScreen parent, int x, int y) { + public BackButton(BookEntryScreen parent, int x, int y) { super(parent, x, y, U, V, WIDTH, HEIGHT, parent::canSeeBackButton, Component.translatable(Gui.BUTTON_BACK), parent::handleBackButton, diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/BookButton.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/BookButton.java index 35fb243ac..82aefef44 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/BookButton.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/BookButton.java @@ -6,7 +6,7 @@ package com.klikli_dev.modonomicon.client.gui.book.button; -import com.klikli_dev.modonomicon.client.gui.book.BookContentScreen; +import com.klikli_dev.modonomicon.client.gui.book.entry.BookEntryScreen; import com.klikli_dev.modonomicon.client.gui.book.BookScreenWithButtons; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.gui.GuiGraphics; @@ -53,7 +53,7 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float RenderSystem.enableDepthTest(); //if focused we go to the right of our normal button (instead of down, like mc buttons do) - BookContentScreen.drawFromTexture(guiGraphics, this.parent.getBook(), this.getX(), this.getY(), this.u + (this.isHovered() ? this.width : 0), this.v, this.width, this.height); + BookEntryScreen.drawFromTexture(guiGraphics, this.parent.getBook(), this.getX(), this.getY(), this.u + (this.isHovered() ? this.width : 0), this.v, this.width, this.height); if (this.isHovered()) { this.parent.setTooltip(this.tooltip); } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/CategoryButton.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/CategoryButton.java index f297907a9..f160aa011 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/CategoryButton.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/CategoryButton.java @@ -7,7 +7,8 @@ package com.klikli_dev.modonomicon.client.gui.book.button; import com.klikli_dev.modonomicon.book.BookCategory; -import com.klikli_dev.modonomicon.client.gui.book.BookOverviewScreen; +import com.klikli_dev.modonomicon.client.gui.BookGuiManager; +import com.klikli_dev.modonomicon.client.gui.book.node.BookParentNodeScreen; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; @@ -16,26 +17,20 @@ public class CategoryButton extends Button { - private final BookOverviewScreen parent; + private final BookParentNodeScreen parent; private final BookCategory category; - private final int categoryIndex; - public CategoryButton(BookOverviewScreen parent, BookCategory category, int categoryIndex, int pX, int pY, int width, int height, OnPress pOnPress, Tooltip tooltip) { + public CategoryButton(BookParentNodeScreen parent, BookCategory category, int pX, int pY, int width, int height, OnPress pOnPress, Tooltip tooltip) { super(pX, pY, width, height, Component.literal(""), pOnPress, Button.DEFAULT_NARRATION); this.setTooltip(tooltip); this.parent = parent; this.category = category; - this.categoryIndex = categoryIndex; } public BookCategory getCategory() { return this.category; } - public int getCategoryIndex() { - return this.categoryIndex; - } - @Override public void renderWidget(GuiGraphics guiGraphics, int pMouseX, int pMouseY, float pPartialTicks) { if (this.visible) { @@ -49,7 +44,7 @@ public void renderWidget(GuiGraphics guiGraphics, int pMouseX, int pMouseY, floa int renderX = this.getX(); int renderWidth = this.width; - if (this.categoryIndex == this.parent.getCurrentCategory()) { + if (BookGuiManager.get().openBookCategoryScreen != null && this.category == BookGuiManager.get().openBookCategoryScreen.getCategory()) { renderX -= 3; renderWidth += 3; } else if (this.isHovered()) { diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/CategoryListButton.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/CategoryListButton.java new file mode 100644 index 000000000..ec2bce85e --- /dev/null +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/CategoryListButton.java @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2022 klikli-dev + * SPDX-FileCopyrightText: 2021 Authors of Patchouli + * + * SPDX-License-Identifier: MIT + */ + + +package com.klikli_dev.modonomicon.client.gui.book.button; + +import com.klikli_dev.modonomicon.api.ModonomiconConstants.I18n.Gui; +import com.klikli_dev.modonomicon.book.BookCategory; +import com.klikli_dev.modonomicon.bookstate.BookUnlockStateManager; +import com.klikli_dev.modonomicon.client.ClientTicks; +import com.klikli_dev.modonomicon.client.gui.book.entry.BookEntryScreen; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.sounds.SoundManager; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class CategoryListButton extends Button { + + private static final int ANIM_TIME = 5; + + private final BookCategory category; + private float timeHovered; + + public CategoryListButton(BookCategory category, int pX, int pY, OnPress pOnPress) { + super(pX, pY, BookEntryScreen.PAGE_WIDTH, 10, Component.translatable(category.getName()), pOnPress, Button.DEFAULT_NARRATION); + + this.category = category; + } + + public BookCategory getCategory() { + return this.category; + } + + private int getEntryColor() { + return 0x000000; + } + + @Override + public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { + if (this.active) { + if (this.isHovered()) { + this.timeHovered = Math.min(ANIM_TIME, this.timeHovered + ClientTicks.delta); + } else { + this.timeHovered = Math.max(0, this.timeHovered - ClientTicks.delta); + } + + float time = Math.max(0, Math.min(ANIM_TIME, this.timeHovered + (this.isHovered() ? partialTicks : -partialTicks))); + float widthFract = time / ANIM_TIME; + boolean locked = !BookUnlockStateManager.get().isUnlockedFor(Minecraft.getInstance().player, this.category); + + guiGraphics.pose().scale(0.5F, 0.5F, 0.5F); + guiGraphics.fill(this.getX() * 2, this.getY() * 2, (this.getX() + (int) ((float) this.width * widthFract)) * 2, (this.getY() + this.height) * 2, 0x22000000); + RenderSystem.enableBlend(); + + if (locked) { + RenderSystem.setShaderColor(1F, 1F, 1F, 0.7F); + BookEntryScreen.drawLock(guiGraphics, this.category.getBook(), this.getX() * 2 + 2, this.getY() * 2 + 2); + } else { + this.category.getIcon().render(guiGraphics, this.getX() * 2 + 2, this.getY() * 2 + 2); + } + + guiGraphics.pose().scale(2F, 2F, 2F); + + MutableComponent name; + if (locked) { + name = Component.translatable(Gui.SEARCH_ENTRY_LOCKED); + } else { + name = Component.translatable(this.category.getName()); + } + + guiGraphics.drawString(Minecraft.getInstance().font, name, this.getX() + 12, this.getY(), this.getEntryColor(), false); + } + } + + @Override + public void playDownSound(SoundManager soundHandlerIn) { + if (this.category != null) { + //TODO: play flip sound + } + } +} diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/EntryListButton.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/EntryListButton.java index db0250e8e..d190f0bf2 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/EntryListButton.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/EntryListButton.java @@ -9,11 +9,10 @@ package com.klikli_dev.modonomicon.client.gui.book.button; import com.klikli_dev.modonomicon.api.ModonomiconConstants.I18n.Gui; -import com.klikli_dev.modonomicon.book.entries.*; +import com.klikli_dev.modonomicon.book.entries.BookEntry; import com.klikli_dev.modonomicon.bookstate.BookUnlockStateManager; import com.klikli_dev.modonomicon.client.ClientTicks; -import com.klikli_dev.modonomicon.client.gui.book.BookContentScreen; -import com.klikli_dev.modonomicon.client.gui.book.BookSearchScreen; +import com.klikli_dev.modonomicon.client.gui.book.entry.BookEntryScreen; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; @@ -26,14 +25,12 @@ public class EntryListButton extends Button { private static final int ANIM_TIME = 5; - private final BookSearchScreen parent; private final BookEntry entry; private float timeHovered; - public EntryListButton(BookSearchScreen parent, BookEntry entry, int pX, int pY, OnPress pOnPress) { - super(pX, pY, BookContentScreen.PAGE_WIDTH, 10, Component.translatable(entry.getName()), pOnPress, Button.DEFAULT_NARRATION); + public EntryListButton(BookEntry entry, int pX, int pY, OnPress pOnPress) { + super(pX, pY, BookEntryScreen.PAGE_WIDTH, 10, Component.translatable(entry.getName()), pOnPress, Button.DEFAULT_NARRATION); - this.parent = parent; this.entry = entry; } @@ -64,7 +61,7 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float if (locked) { RenderSystem.setShaderColor(1F, 1F, 1F, 0.7F); - BookContentScreen.drawLock(guiGraphics, this.parent.getParentScreen().getBook(), this.getX() * 2 + 2, this.getY() * 2 + 2); + BookEntryScreen.drawLock(guiGraphics, this.entry.getBook(), this.getX() * 2 + 2, this.getY() * 2 + 2); } else { this.entry.getIcon().render(guiGraphics, this.getX() * 2 + 2, this.getY() * 2 + 2); } @@ -78,7 +75,6 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float name = Component.translatable(this.entry.getName()); } - //TODO: if we ever add a font style setting to the book, use it here guiGraphics.drawString(Minecraft.getInstance().font, name, this.getX() + 12, this.getY(), this.getEntryColor(), false); } } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/ReadAllButton.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/ReadAllButton.java index 8087a197c..e6ff50d2e 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/ReadAllButton.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/ReadAllButton.java @@ -7,7 +7,9 @@ package com.klikli_dev.modonomicon.client.gui.book.button; import com.klikli_dev.modonomicon.api.ModonomiconConstants.I18n.Gui; -import com.klikli_dev.modonomicon.client.gui.book.BookOverviewScreen; +import com.klikli_dev.modonomicon.client.gui.book.BookParentScreen; +import com.klikli_dev.modonomicon.client.gui.book.node.BookParentNodeScreen; +import com.klikli_dev.modonomicon.platform.ClientServices; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.Util; import net.minecraft.client.Minecraft; @@ -32,7 +34,7 @@ public class ReadAllButton extends Button { public static final int HEIGHT = 14; - private final BookOverviewScreen parent; + private final BookParentScreen parent; private final MutableComponent tooltipReadUnlocked; private final MutableComponent tooltipReadAll; @@ -47,7 +49,7 @@ public class ReadAllButton extends Button { private long hoveredStartTime; - public ReadAllButton(BookOverviewScreen parent, int x, int y, Supplier hasUnreadUnlockedEntries, Supplier displayCondition, OnPress onPress) { + public ReadAllButton(BookParentScreen parent, int x, int y, Supplier hasUnreadUnlockedEntries, Supplier displayCondition, OnPress onPress) { super(x, y, WIDTH, HEIGHT, Component.translatable(Gui.BUTTON_READ_ALL), onPress, Button.DEFAULT_NARRATION @@ -111,7 +113,7 @@ private void updateCustomTooltip() { if (flag && Util.getMillis() - this.hoveredStartTime > (long) this.tooltipMsDelay) { var tooltip = this.getCustomTooltip(); - Screen screen = Minecraft.getInstance().screen; + Screen screen = ClientServices.GUI.getCurrentScreen(); if (screen != null) { screen.setTooltipForNextRenderPass(Tooltip.create(tooltip), DefaultTooltipPositioner.INSTANCE, this.isHovered()); } diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/SearchButton.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/SearchButton.java index d58ec6057..f851adc7e 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/SearchButton.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/SearchButton.java @@ -6,19 +6,21 @@ package com.klikli_dev.modonomicon.client.gui.book.button; -import com.klikli_dev.modonomicon.client.gui.book.BookOverviewScreen; +import com.klikli_dev.modonomicon.client.gui.book.BookParentScreen; +import com.klikli_dev.modonomicon.client.gui.book.node.BookParentNodeScreen; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; public class SearchButton extends Button { - private final BookOverviewScreen parent; + private final BookParentScreen parent; private final int scissorX; - public SearchButton(BookOverviewScreen parent, int pX, int pY, int scissorX, int width, int height, OnPress pOnPress, Tooltip tooltip) { + public SearchButton(BookParentScreen parent, int pX, int pY, int scissorX, int width, int height, OnPress pOnPress, Tooltip tooltip) { super(pX, pY, width, height, Component.literal(""), pOnPress, Button.DEFAULT_NARRATION); this.setTooltip(tooltip); this.scissorX = scissorX; @@ -38,7 +40,7 @@ public void renderWidget(GuiGraphics guiGraphics, int pMouseX, int pMouseY, floa int renderX = this.getX(); int scissorWidth = this.width + (this.getX() - this.scissorX); - int scissorY = (this.parent.height - this.getY() - this.height - 1); //from the bottom up + int scissorY = (((Screen)this.parent).height - this.getY() - this.height - 1); //from the bottom up if (this.isHovered()) { renderX += 1; @@ -52,7 +54,7 @@ public void renderWidget(GuiGraphics guiGraphics, int pMouseX, int pMouseY, floa guiGraphics.enableScissor(scissorX, scissorY, scissorX + scissorWidth, scissorY + 1000); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - guiGraphics.blit(this.parent.getBookOverviewTexture(), renderX, this.getY(), texX, texY, this.width, this.height, 256, 256); + guiGraphics.blit(this.parent.getBook().getBookOverviewTexture(), renderX, this.getY(), texX, texY, this.width, this.height, 256, 256); guiGraphics.disableScissor(); diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/SmallArrowButton.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/SmallArrowButton.java index c4b9a373f..55138e3fa 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/SmallArrowButton.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/SmallArrowButton.java @@ -7,7 +7,7 @@ package com.klikli_dev.modonomicon.client.gui.book.button; import com.klikli_dev.modonomicon.api.ModonomiconConstants.I18n.Gui; -import com.klikli_dev.modonomicon.client.gui.book.BookContentScreen; +import com.klikli_dev.modonomicon.client.gui.book.entry.BookEntryScreen; import net.minecraft.network.chat.Component; import java.util.function.Supplier; @@ -21,7 +21,7 @@ public class SmallArrowButton extends BookButton { public final boolean left; - public SmallArrowButton(BookContentScreen parent, int x, int y, boolean left, Supplier displayCondition, OnPress onPress) { + public SmallArrowButton(BookEntryScreen parent, int x, int y, boolean left, Supplier displayCondition, OnPress onPress) { super(parent, x, y, U, left ? V + HEIGHT : V, WIDTH, HEIGHT, displayCondition, Component.translatable(left ? Gui.BUTTON_PREVIOUS : Gui.BUTTON_NEXT), onPress, diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/VisualizeButton.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/VisualizeButton.java index e9c8e3ff2..49f9f5518 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/VisualizeButton.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/button/VisualizeButton.java @@ -7,7 +7,7 @@ package com.klikli_dev.modonomicon.client.gui.book.button; import com.klikli_dev.modonomicon.api.ModonomiconConstants.I18n.Gui; -import com.klikli_dev.modonomicon.client.gui.book.BookContentScreen; +import com.klikli_dev.modonomicon.client.gui.book.entry.BookEntryScreen; import net.minecraft.client.gui.components.Button; import net.minecraft.network.chat.Component; @@ -19,7 +19,7 @@ public class VisualizeButton extends BookButton { public static final int HEIGHT = 7; public static final int WIDTH = 11; - public VisualizeButton(BookContentScreen parent, int x, int y, Button.OnPress onPress) { + public VisualizeButton(BookEntryScreen parent, int x, int y, Button.OnPress onPress) { super(parent, x, y, U, V, WIDTH, HEIGHT, Component.translatable(Gui.BUTTON_VISUALIZE), onPress, diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookContentScreen.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/entry/BookEntryScreen.java similarity index 94% rename from common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookContentScreen.java rename to common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/entry/BookEntryScreen.java index e3fe7b99f..f09143faa 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/BookContentScreen.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/entry/BookEntryScreen.java @@ -4,30 +4,33 @@ * SPDX-License-Identifier: MIT */ -package com.klikli_dev.modonomicon.client.gui.book; +package com.klikli_dev.modonomicon.client.gui.book.entry; import com.klikli_dev.modonomicon.Modonomicon; import com.klikli_dev.modonomicon.api.ModonomiconConstants.I18n.Gui; -import com.klikli_dev.modonomicon.book.*; -import com.klikli_dev.modonomicon.book.entries.*; +import com.klikli_dev.modonomicon.book.Book; +import com.klikli_dev.modonomicon.book.BookLink; +import com.klikli_dev.modonomicon.book.CommandLink; +import com.klikli_dev.modonomicon.book.PatchouliLink; +import com.klikli_dev.modonomicon.book.entries.BookContentEntry; import com.klikli_dev.modonomicon.book.page.BookPage; import com.klikli_dev.modonomicon.bookstate.BookUnlockStateManager; -import com.klikli_dev.modonomicon.bookstate.BookVisualStateManager; +import com.klikli_dev.modonomicon.bookstate.visual.EntryVisualState; import com.klikli_dev.modonomicon.client.ClientTicks; import com.klikli_dev.modonomicon.client.gui.BookGuiManager; +import com.klikli_dev.modonomicon.client.gui.book.BookPaginatedScreen; import com.klikli_dev.modonomicon.client.gui.book.button.BackButton; import com.klikli_dev.modonomicon.client.gui.book.markdown.ItemLinkRenderer; +import com.klikli_dev.modonomicon.client.gui.book.BookParentScreen; import com.klikli_dev.modonomicon.client.render.page.BookPageRenderer; import com.klikli_dev.modonomicon.client.render.page.PageRendererRegistry; import com.klikli_dev.modonomicon.data.BookDataManager; import com.klikli_dev.modonomicon.fluid.FluidHolder; import com.klikli_dev.modonomicon.integration.ModonomiconJeiIntegration; import com.klikli_dev.modonomicon.networking.ClickCommandLinkMessage; -import com.klikli_dev.modonomicon.networking.SaveEntryStateMessage; import com.klikli_dev.modonomicon.platform.ClientServices; import com.klikli_dev.modonomicon.platform.Services; import com.klikli_dev.modonomicon.platform.services.FluidHelper; -import com.mojang.blaze3d.platform.InputConstants; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.brigadier.StringReader; import net.minecraft.ChatFormatting; @@ -57,7 +60,7 @@ import java.util.Collection; import java.util.List; -public class BookContentScreen extends BookPaginatedScreen { +public class BookEntryScreen extends BookPaginatedScreen { public static final int TOP_PADDING = 15; public static final int LEFT_PAGE_X = 12; @@ -70,11 +73,11 @@ public class BookContentScreen extends BookPaginatedScreen { public static final int CLICK_SAFETY_MARGIN = 20; private static long lastTurnPageSoundTime; - private final ContentBookEntry entry; + protected final BookParentScreen parentScreen; + private final BookContentEntry entry; private final ResourceLocation bookContentTexture; private final ItemParser itemParser; public int ticksInBook; - public boolean simulateEscClosing; private List unlockedPages; private BookPage leftPage; private BookPage rightPage; @@ -89,8 +92,10 @@ public class BookContentScreen extends BookPaginatedScreen { private FluidHolder tooltipFluidStack; private boolean isHoveringItemLink; - public BookContentScreen(BookOverviewScreen parentScreen, ContentBookEntry entry) { - super(Component.literal(""), parentScreen); + public BookEntryScreen(BookParentScreen parentScreen, BookContentEntry entry) { + super(Component.literal("")); + + this.parentScreen = parentScreen; this.minecraft = Minecraft.getInstance(); this.itemParser = new ItemParser(this.minecraft.level.registryAccess()); @@ -99,7 +104,8 @@ public BookContentScreen(BookOverviewScreen parentScreen, ContentBookEntry entry this.bookContentTexture = this.parentScreen.getBook().getBookContentTexture(); - this.loadEntryState(); + //We're doing that here to ensure unlockedPages is available for state modification during loading + this.unlockedPages = this.entry.getUnlockedPagesFor(this.minecraft.player); } public static void drawFromTexture(GuiGraphics guiGraphics, Book book, int x, int y, int u, int v, int w, int h) { @@ -142,14 +148,16 @@ public Minecraft getMinecraft() { return this.minecraft; } - public ContentBookEntry getEntry() { + public BookContentEntry getEntry() { return this.entry; } + @Override public Book getBook() { return this.entry.getBook(); } + @Override public boolean canSeeArrowButton(boolean left) { return left ? this.openPagesIndex > 0 : (this.openPagesIndex + 2) < this.unlockedPages.size(); } @@ -158,6 +166,7 @@ public void setTooltip(Component... strings) { this.setTooltip(List.of(strings)); } + @Override public void setTooltip(List tooltip) { this.resetTooltip(); this.tooltip = tooltip; @@ -274,6 +283,10 @@ private int getOpenPagesIndexForPage(int pageIndex) { return 0; } + public void setOpenPagesIndex(int openPagesIndex) { + this.openPagesIndex = openPagesIndex; + } + /** * Will change to the specified page, if not open already */ @@ -413,15 +426,12 @@ protected void resetTooltip() { this.tooltipFluidStack = null; } - private void loadEntryState() { - var state = BookVisualStateManager.get().getEntryStateFor(this.parentScreen.getMinecraft().player, this.entry); - - BookGuiManager.get().currentEntry = this.entry; - BookGuiManager.get().currentContentScreen = this; + public void loadState(EntryVisualState state) { + this.openPagesIndex = state.openPagesIndex; + } - if (state != null) { - this.openPagesIndex = state.openPagesIndex; - } + public void saveState(EntryVisualState state, boolean savePage) { + state.openPagesIndex = savePage ? this.openPagesIndex : 0; } @Override @@ -458,22 +468,19 @@ public void render(GuiGraphics guiGraphics, int pMouseX, int pMouseY, float pPar } @Override - public void onClose() { - if (this.simulateEscClosing || InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), GLFW.GLFW_KEY_ESCAPE)) { - Services.NETWORK.sendToServer(new SaveEntryStateMessage(this.entry, this.openPagesIndex)); - - super.onClose(); - this.parentScreen.onClose(); - - this.simulateEscClosing = false; - } else { - Services.NETWORK.sendToServer(new SaveEntryStateMessage(this.entry, - ClientServices.CLIENT_CONFIG.storeLastOpenPageWhenClosingEntry() ? this.openPagesIndex : 0)); - - this.parentScreen.getCurrentCategoryScreen().onCloseEntry(this); - - ClientServices.GUI.popGuiLayer(); //instead of super.onClose() to restore our parent screen + public boolean keyPressed(int key, int scanCode, int modifiers) { + if (key == GLFW.GLFW_KEY_ESCAPE) { + BookGuiManager.get().closeScreenStack(this); + return true; } + return super.keyPressed(key, scanCode, modifiers); + } + + @Override + public void onClose() { + //do not call super, as it would close the screen stack + //In most cases closeEntryScreen should be called directly, but if our parent BookPaginatedScreen wants us to close we need to handle that + BookGuiManager.get().closeEntryScreen(this); } /** @@ -710,10 +717,8 @@ public boolean handleComponentClicked(@Nullable Style pStyle) { if (PatchouliLink.isPatchouliLink(event.getValue())) { var link = PatchouliLink.from(event.getValue()); if (link.bookId != null) { + BookGuiManager.get().closeScreenStack(this.parentScreen); //will cause the book to close entirely, and save the open page //the integration class handles class loading guards if patchouli is not present - this.simulateEscClosing = true; - //this.onClose(); - Services.PATCHOULI.openEntry(link.bookId, link.entryId, link.pageNumber); return true; } @@ -735,7 +740,7 @@ public boolean handleComponentClicked(@Nullable Style pStyle) { return true; } - this.onClose(); //we have to do this before showing JEI, because super.onClose() clears Gui Layers, and thus would kill JEIs freshly spawned gui + BookGuiManager.get().closeScreenStack(this); //will cause the book to close entirely, and save the open page if (Screen.hasShiftDown()) { ModonomiconJeiIntegration.get().showUses(itemStack); @@ -743,10 +748,6 @@ public boolean handleComponentClicked(@Nullable Style pStyle) { ModonomiconJeiIntegration.get().showRecipe(itemStack); } - if (!ModonomiconJeiIntegration.get().isJEIRecipesGuiOpen()) { - ClientServices.GUI.pushGuiLayer(this); - } - //TODO: Consider adding logic to restore content screen after JEI gui close // currently only the overview screen is restored (because JEI does not use Forges Gui Stack, only vanilla screen, thus only saves one parent screen) // we could fix that by listening to the Closing event from forge, and in that set the closing time diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/EntryConnectionRenderer.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/entry/EntryConnectionRenderer.java similarity index 97% rename from common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/EntryConnectionRenderer.java rename to common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/entry/EntryConnectionRenderer.java index c3205c3cb..8ef16bf6c 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/EntryConnectionRenderer.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/entry/EntryConnectionRenderer.java @@ -5,10 +5,11 @@ * SPDX-License-Identifier: MIT */ -package com.klikli_dev.modonomicon.client.gui.book; +package com.klikli_dev.modonomicon.client.gui.book.entry; import com.klikli_dev.modonomicon.book.*; import com.klikli_dev.modonomicon.book.entries.*; +import com.klikli_dev.modonomicon.client.gui.book.node.BookCategoryNodeScreen; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.resources.ResourceLocation; @@ -173,7 +174,7 @@ public void render(GuiGraphics guiGraphics, BookEntry entry, BookEntryParent par } } - protected void setBlitOffset(int blitOffset) { + public void setBlitOffset(int blitOffset) { this.blitOffset = blitOffset; } @@ -181,14 +182,14 @@ protected void setBlitOffset(int blitOffset) { * Scales from grid coordinates (1, 2, 3, ... ) to screen coordinates (30, 60, 90) */ protected int screenX(int x) { - return x * BookCategoryScreen.ENTRY_GRID_SCALE; + return x * BookCategoryNodeScreen.ENTRY_GRID_SCALE; } /** * Scales from grid coordinates (1, 2, 3, ... ) to screen coordinates (30, 60, 90) */ protected int screenY(int y) { - return y * BookCategoryScreen.ENTRY_GRID_SCALE; + return y * BookCategoryNodeScreen.ENTRY_GRID_SCALE; } protected void blit(GuiGraphics guiGraphics, int pX, int pY, float pUOffset, float pVOffset, int pUWidth, int pVHeight) { diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/EntryDisplayState.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/entry/EntryDisplayState.java similarity index 73% rename from common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/EntryDisplayState.java rename to common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/entry/EntryDisplayState.java index a5f25187c..9a81c83dc 100644 --- a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/EntryDisplayState.java +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/entry/EntryDisplayState.java @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT */ -package com.klikli_dev.modonomicon.client.gui.book; +package com.klikli_dev.modonomicon.client.gui.book.entry; public enum EntryDisplayState { HIDDEN, diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/index/BookCategoryIndexOnNodeScreen.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/index/BookCategoryIndexOnNodeScreen.java new file mode 100644 index 000000000..a821305ec --- /dev/null +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/index/BookCategoryIndexOnNodeScreen.java @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2022 klikli-dev + * SPDX-FileCopyrightText: 2021 Authors of Patchouli + * + * SPDX-License-Identifier: MIT + */ + +package com.klikli_dev.modonomicon.client.gui.book.index; + +import com.klikli_dev.modonomicon.book.BookCategory; +import com.klikli_dev.modonomicon.client.gui.BookGuiManager; +import com.klikli_dev.modonomicon.client.gui.book.node.BookParentNodeScreen; +import com.klikli_dev.modonomicon.client.gui.book.BookParentScreen; +import net.minecraft.client.gui.GuiGraphics; + +/** + * A special version of the BookCategoryIndexScreen that is intended to be rendered on top of a parent node screen (instead of a parent index screen) + */ +public class BookCategoryIndexOnNodeScreen extends BookCategoryIndexScreen { + + public BookCategoryIndexOnNodeScreen(BookParentScreen parentScreen, BookCategory category) { + super(parentScreen, category, false); + } + + @Override + public void render(GuiGraphics guiGraphics, int pMouseX, int pMouseY, float pPartialTick) { + super.render(guiGraphics, pMouseX, pMouseY, pPartialTick); + + if (BookGuiManager.get().openBookParentScreen instanceof BookParentNodeScreen parentScreen) { + parentScreen.renderMouseXOverride = pMouseX; + parentScreen.renderMouseYOverride = pMouseY; + } + } + + @Override + public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + //we prevent the default background rendering because we still need to see the underlying parent node screen + } + + @Override + public void onClose() { + //on close might come from paginated screen, and in our parent would ask the gui manager to close us + //but this special index screen can only be closed together with the parent screen -> on esc. + //so we simply don't do anything here. + } + + @Override + public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { + //if we click outside, we don't close like the parent would, instead we let the click unhandled so the parent can handle it + if (this.isClickOutsideEntry(pMouseX, pMouseY)) { + if (BookGuiManager.get().openBookParentScreen instanceof BookParentNodeScreen parentScreen) { + return parentScreen.mouseClicked(pMouseX, pMouseY, pButton); + } + + return false; + } + + //with the "outside closing" prevented we can let our parents do the rest + return super.mouseClicked(pMouseX, pMouseY, pButton); + } + +} diff --git a/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/index/BookCategoryIndexScreen.java b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/index/BookCategoryIndexScreen.java new file mode 100644 index 000000000..4de319806 --- /dev/null +++ b/common/src/main/java/com/klikli_dev/modonomicon/client/gui/book/index/BookCategoryIndexScreen.java @@ -0,0 +1,307 @@ +/* + * SPDX-FileCopyrightText: 2022 klikli-dev + * SPDX-FileCopyrightText: 2021 Authors of Patchouli + * + * SPDX-License-Identifier: MIT + */ + +package com.klikli_dev.modonomicon.client.gui.book.index; + +import com.klikli_dev.modonomicon.api.ModonomiconConstants.I18n.Gui; +import com.klikli_dev.modonomicon.book.Book; +import com.klikli_dev.modonomicon.book.BookCategory; +import com.klikli_dev.modonomicon.book.entries.BookEntry; +import com.klikli_dev.modonomicon.bookstate.BookUnlockStateManager; +import com.klikli_dev.modonomicon.bookstate.visual.CategoryVisualState; +import com.klikli_dev.modonomicon.client.gui.BookGuiManager; +import com.klikli_dev.modonomicon.client.gui.book.BookPaginatedScreen; +import com.klikli_dev.modonomicon.client.gui.book.button.EntryListButton; +import com.klikli_dev.modonomicon.client.gui.book.BookCategoryScreen; +import com.klikli_dev.modonomicon.client.gui.book.entry.BookEntryScreen; +import com.klikli_dev.modonomicon.client.gui.book.BookParentScreen; +import com.klikli_dev.modonomicon.client.render.page.BookPageRenderer; +import com.klikli_dev.modonomicon.util.GuiGraphicsExt; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.resources.language.I18n; +import net.minecraft.network.chat.Component; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class BookCategoryIndexScreen extends BookPaginatedScreen implements BookCategoryScreen { + public static final int ENTRIES_PER_PAGE = 13; + public static final int ENTRIES_IN_FIRST_PAGE = 11; + protected final List