diff --git a/src/generated/resources/assets/thavma/lang/en_us.json b/src/generated/resources/assets/thavma/lang/en_us.json index bddd0d24..08a4e39f 100644 --- a/src/generated/resources/assets/thavma/lang/en_us.json +++ b/src/generated/resources/assets/thavma/lang/en_us.json @@ -85,7 +85,7 @@ "item.thavma.basic_amulet": "Basic Amulet", "item.thavma.basic_belt": "Basic Belt", "item.thavma.basic_ring": "Basic Ring", - "item.thavma.book": "Elements of Thavma", + "item.thavma.book": "Elements", "item.thavma.charm_of_the_dawn": "Charm of the Dawn", "item.thavma.eye_of_warden": "Eye of Warden", "item.thavma.fabric": "Infused Fabric", @@ -169,15 +169,16 @@ "research/category.thavma.thavma": "Thavma", "research/entry.thavma.alchemy.alchemy": "Alchemy", "research/entry.thavma.story.story1": "A Courtesy Call", - "research/entry.thavma.story.story1.page0.paragraph0": "Lorem ipsum %s 1 sit amet,", - "research/entry.thavma.story.story1.page0.paragraph1": "this story a great meaning haveth.", - "research/entry.thavma.story.story1.page0.title": "A Courtesy Call 1", - "research/entry.thavma.story.story1.page1.paragraph0": "Lorem dolor 2 sit amet,", - "research/entry.thavma.story.story1.page1.paragraph1": "this story a great meaning haveth.", - "research/entry.thavma.story.story1.page1.title": "A Courtesy Call 2", - "research/entry.thavma.story.story1.page2.paragraph0": "Lorem lotrumatum dolor 3 sit amet,", - "research/entry.thavma.story.story1.page2.paragraph1": "this story a great meaning haveth.", - "research/entry.thavma.story.story1.page2.title": "A Courtesy Call 3", + "research/entry.thavma.story.story1.figure_feature0": "What follows is generic lorem ipsum so the text looks natural. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque dapibus mattis lectus, quis aliquet ex. In hac habitasse platea dictumst. Praesent dignissim urna at feugiat pulvinar. Suspendisse laoreet lorem ut velit venenatis gravida. Donec posuere diam est, ac malesuada libero fermentum sed. Phasellus ac cursus nibh, eget pharetra leo. Maecenas scelerisque velit massa, sit amet tincidunt nulla dictum non. Sed egestas congue bibendum. Aenean facilisis nunc vitae purus tincidunt, sit amet dignissim libero gravida. Mauris vel tortor elit. Curabitur sit amet nisi sagittis, ullamcorper diam sed, condimentum est. Etiam blandit ac magna sit amet luctus. Duis nec mi tincidunt nunc.", + "research/entry.thavma.story.story1.paragraph_feature0": "This is a sample paragraph that does not start a page.", + "research/entry.thavma.story.story1.paragraph_feature1": "Another paragraph just to make the content on the page stretch out a little longer. Possible double trimIndent() in the LanguageProvider does not cause any trouble. ", + "research/entry.thavma.story.story1.paragraph_feature2": "This paragraph should start a new page always. It has the mustStartPage property set to true.", + "research/entry.thavma.story.story1.paragraph_feature3": "Just another random little paragraph :D", + "research/entry.thavma.story.story1.paragraph_feature4": "This paragraph is inserted into the middle without it being an image caption (pre-set page number)", + "research/entry.thavma.story.story1.title_feature0": "A courtesy call starts the page", + "research/entry.thavma.story.story1.title_feature1": "This title might appear in the middle", + "research/entry.thavma.story.story1.title_feature2": "(start of page)", + "research/entry.thavma.story.story1.title_feature3": "This is page number 1!", "research/entry.thavma.thavma.alchemy": "Alchemy", "research/entry.thavma.thavma.arcane_lens": "The Arcane Lens", "research/entry.thavma.thavma.arcane_lens.page0.paragraph0": "The part of the book I can read describes an arcane tool that \"allows the user to see\", whatever that might mean. I have a feeling that crafting it could assist my work in unsealing the other pages.", @@ -191,7 +192,7 @@ "research/entry.thavma.thavma.technology": "Technology", "research/entry.thavma.thavma.thavma": "Thavma", "research/entry.thavma.thavma.thavma.page0.paragraph0": "I was merely toying with that wand -if it can even be called that- when this tome flew into my hands! I can sense great power within it.", - "research/entry.thavma.thavma.thavma.page0.paragraph1": "The cover reads \"Elements of Thavma\", but a lot of its pages appear blank, sealed by some magic.", + "research/entry.thavma.thavma.thavma.page0.paragraph1": "The cover reads \"Elements\", but a lot of its pages appear blank, sealed by some magic.", "research/entry.thavma.thavma.thavma.page0.paragraph2": "To read them, I will first need to break that seal. It won't be easy... but I have a feeling it will be worth my efforts.", "research/entry.thavma.thavma.thavma.page0.title": "Thavma", "research/entry.thavma.thavma.thavma.page1.paragraph0": "I will document all my findings inside the book, so that I can recall them later.", diff --git a/src/generated/resources/data/thavma/thavma/research/entry/alchemy/alchemy.json b/src/generated/resources/data/thavma/thavma/research/entry/alchemy/alchemy.json index e5abb074..4c760f17 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/alchemy/alchemy.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/alchemy/alchemy.json @@ -26,7 +26,7 @@ "count": 1, "id": "thavma:crucible" }, - "pages": [], + "pageFeatures": [], "position": [ 0, 0 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/story/story1.json b/src/generated/resources/data/thavma/thavma/research/entry/story/story1.json index 0cdc3892..cbcb95e1 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/story/story1.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/story/story1.json @@ -7,56 +7,104 @@ "count": 1, "id": "minecraft:turtle_helmet" }, - "pages": [ + "pageFeatures": [ { - "type": "thavma:text", - "paragraphs": [ - { - "translate": "research/entry.thavma.story.story1.page0.paragraph0" - }, - { - "translate": "research/entry.thavma.story.story1.page0.paragraph1" - } - ], - "title": { + "type": "thavma:title", + "starts_page": true, + "text": { "bold": true, - "translate": "research/entry.thavma.story.story1.page0.title" + "translate": "research/entry.thavma.story.story1.title_feature0" } }, { - "type": "thavma:text", - "paragraphs": [ - { - "translate": "research/entry.thavma.story.story1.page1.paragraph0" - }, - { - "translate": "research/entry.thavma.story.story1.page1.paragraph1" - } - ], - "title": { + "type": "thavma:paragraph", + "text": { + "translate": "research/entry.thavma.story.story1.paragraph_feature0" + } + }, + { + "type": "thavma:paragraph", + "text": { + "translate": "research/entry.thavma.story.story1.paragraph_feature1" + } + }, + { + "type": "thavma:figure", + "caption": { + "color": "dark_aqua", + "italic": true, + "translate": "research/entry.thavma.story.story1.figure_feature0" + }, + "image": { + "canvas_height": 101, + "canvas_width": 180, + "height": 101, + "location": "thavma:textures/gui/images/haybales.png", + "width": 180 + } + }, + { + "type": "thavma:title", + "text": { + "bold": true, + "translate": "research/entry.thavma.story.story1.title_feature1" + } + }, + { + "type": "thavma:paragraph", + "starts_page": true, + "text": { + "translate": "research/entry.thavma.story.story1.paragraph_feature2" + } + }, + { + "type": "thavma:title", + "starts_page": true, + "text": { "bold": true, - "translate": "research/entry.thavma.story.story1.page1.title" + "translate": "research/entry.thavma.story.story1.title_feature2" } }, { - "type": "thavma:text", - "paragraphs": [ - { - "translate": "research/entry.thavma.story.story1.page2.paragraph0" - }, - { - "translate": "research/entry.thavma.story.story1.page2.paragraph1" - } - ], - "title": { + "type": "thavma:title", + "has_set_page": true, + "preferred_page": 0, + "starts_page": true, + "text": { "bold": true, - "translate": "research/entry.thavma.story.story1.page2.title" + "translate": "research/entry.thavma.story.story1.title_feature3" + } + }, + { + "type": "thavma:paragraph", + "text": { + "translate": "research/entry.thavma.story.story1.paragraph_feature3" + } + }, + { + "type": "thavma:figure", + "has_set_page": true, + "image": { + "canvas_height": 77, + "canvas_width": 87, + "height": 77, + "location": "thavma:textures/gui/images/smileyface.png", + "width": 87 + }, + "preferred_page": 5 + }, + { + "type": "thavma:paragraph", + "has_set_page": true, + "preferred_page": 5, + "text": { + "translate": "research/entry.thavma.story.story1.paragraph_feature4" } } ], "position": [ 0, - -3 + 0 ], "preferX": false, "title": { diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/alchemy.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/alchemy.json index cf144174..9af0e3d1 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/alchemy.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/alchemy.json @@ -26,7 +26,7 @@ "count": 1, "id": "thavma:crucible" }, - "pages": [], + "pageFeatures": [], "position": [ -2, 2 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/arcane_lens.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/arcane_lens.json index b1cf4745..33183a96 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/arcane_lens.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/arcane_lens.json @@ -36,26 +36,7 @@ "count": 1, "id": "thavma:arcane_lens" }, - "pages": [ - { - "type": "thavma:text", - "paragraphs": [ - { - "translate": "research/entry.thavma.thavma.arcane_lens.page0.paragraph0" - }, - { - "translate": "research/entry.thavma.thavma.arcane_lens.page0.paragraph1" - }, - { - "translate": "research/entry.thavma.thavma.arcane_lens.page0.paragraph2" - } - ], - "title": { - "bold": true, - "translate": "research/entry.thavma.thavma.arcane_lens.page0.title" - } - } - ], + "pageFeatures": [], "position": [ 2, -2 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/infusion.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/infusion.json index ca774a8a..fa0d7078 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/infusion.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/infusion.json @@ -26,7 +26,7 @@ "count": 1, "id": "thavma:infusion_matrix" }, - "pages": [], + "pageFeatures": [], "position": [ 2, 2 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/ores.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/ores.json index 2dee4be2..ea1a35f3 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/ores.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/ores.json @@ -28,7 +28,7 @@ "count": 1, "id": "thavma:aqua_shard" }, - "pages": [], + "pageFeatures": [], "position": [ 2, -4 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/research_proficiency.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/research_proficiency.json index 382dde7e..36d325ae 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/research_proficiency.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/research_proficiency.json @@ -26,7 +26,7 @@ "count": 1, "id": "thavma:research_table" }, - "pages": [], + "pageFeatures": [], "position": [ -1, -1 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/research_table.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/research_table.json index 1edfaf76..928ed808 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/research_table.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/research_table.json @@ -32,12 +32,7 @@ "count": 1, "id": "thavma:research_table" }, - "pages": [ - { - "type": "thavma:crafting", - "recipeRL": "minecraft:chest" - } - ], + "pageFeatures": [], "position": [ 0, 0 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/technology.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/technology.json index 03f96e16..e7999939 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/technology.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/technology.json @@ -26,7 +26,7 @@ "count": 1, "id": "thavma:goggles" }, - "pages": [], + "pageFeatures": [], "position": [ 2, 4 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/thavma.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/thavma.json index 8c91c2af..a4906b9e 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/thavma.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/thavma.json @@ -29,34 +29,7 @@ "count": 1, "id": "thavma:book" }, - "pages": [ - { - "type": "thavma:text", - "paragraphs": [ - { - "translate": "research/entry.thavma.thavma.thavma.page0.paragraph0" - }, - { - "translate": "research/entry.thavma.thavma.thavma.page0.paragraph1" - }, - { - "translate": "research/entry.thavma.thavma.thavma.page0.paragraph2" - } - ], - "title": { - "bold": true, - "translate": "research/entry.thavma.thavma.thavma.page0.title" - } - }, - { - "type": "thavma:text", - "paragraphs": [ - { - "translate": "research/entry.thavma.thavma.thavma.page1.paragraph0" - } - ] - } - ], + "pageFeatures": [], "position": [ 0, -6 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/trees.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/trees.json index 1d03df95..b47d0111 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/trees.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/trees.json @@ -28,7 +28,7 @@ "count": 1, "id": "thavma:greatwood_log" }, - "pages": [], + "pageFeatures": [], "position": [ 0, -3 diff --git a/src/generated/resources/data/thavma/thavma/research/entry/thavma/wands.json b/src/generated/resources/data/thavma/thavma/research/entry/thavma/wands.json index 77ccd882..6dff452d 100644 --- a/src/generated/resources/data/thavma/thavma/research/entry/thavma/wands.json +++ b/src/generated/resources/data/thavma/thavma/research/entry/thavma/wands.json @@ -26,7 +26,7 @@ "count": 1, "id": "thavma:thavmite_silverwood_wand" }, - "pages": [], + "pageFeatures": [], "position": [ -2, 4 diff --git a/src/main/java/me/alegian/thavma/impl/Thavma.kt b/src/main/java/me/alegian/thavma/impl/Thavma.kt index b14253ac..d97a950a 100644 --- a/src/main/java/me/alegian/thavma/impl/Thavma.kt +++ b/src/main/java/me/alegian/thavma/impl/Thavma.kt @@ -36,6 +36,7 @@ object Thavma { T7Attributes.REGISTRAR.register(KFF_MOD_BUS) T7GlobalLootModifierSerializers.REGISTRAR.register(KFF_MOD_BUS) PageTypes.REGISTRAR.register(KFF_MOD_BUS) + PageFeatureTypes.REGISTRAR.register(KFF_MOD_BUS) T7Features.REGISTRAR.register(KFF_MOD_BUS) registerCommonModEvents() diff --git a/src/main/java/me/alegian/thavma/impl/client/event/RegisterPageFeatureRenderersEvent.kt b/src/main/java/me/alegian/thavma/impl/client/event/RegisterPageFeatureRenderersEvent.kt new file mode 100644 index 00000000..77b10989 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/event/RegisterPageFeatureRenderersEvent.kt @@ -0,0 +1,14 @@ +package me.alegian.thavma.impl.client.event + +import me.alegian.thavma.impl.client.gui.book.PAGE_FEATURE_RENDERERS +import me.alegian.thavma.impl.client.gui.book.PageFeatureRenderer +import me.alegian.thavma.impl.common.book.PageFeature +import me.alegian.thavma.impl.common.book.PageFeatureType +import net.neoforged.bus.api.Event +import net.neoforged.fml.event.IModBusEvent + +class RegisterPageFeatureRenderersEvent : Event(), IModBusEvent { + fun register(pageFeatureType: PageFeatureType, pageFeatureRenderer: PageFeatureRenderer) { + PAGE_FEATURE_RENDERERS[pageFeatureType] = pageFeatureRenderer + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/client/event/RegisterPageRenderersEvent.kt b/src/main/java/me/alegian/thavma/impl/client/event/RegisterPageRenderersEvent.kt index 89591982..55408cc8 100644 --- a/src/main/java/me/alegian/thavma/impl/client/event/RegisterPageRenderersEvent.kt +++ b/src/main/java/me/alegian/thavma/impl/client/event/RegisterPageRenderersEvent.kt @@ -8,7 +8,7 @@ import net.neoforged.bus.api.Event import net.neoforged.fml.event.IModBusEvent class RegisterPageRenderersEvent : Event(), IModBusEvent { - fun register(pageType:PageType, pageRenderer: PageRenderer) { + fun register(pageType: PageType, pageRenderer: PageRenderer) { PAGE_RENDERERS[pageType] = pageRenderer } } \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/client/event/T7ClientModEvents.kt b/src/main/java/me/alegian/thavma/impl/client/event/T7ClientModEvents.kt index 2dddd63a..92f14e4c 100644 --- a/src/main/java/me/alegian/thavma/impl/client/event/T7ClientModEvents.kt +++ b/src/main/java/me/alegian/thavma/impl/client/event/T7ClientModEvents.kt @@ -5,8 +5,7 @@ import me.alegian.thavma.impl.client.extension.ArcaneLensItemExtensions import me.alegian.thavma.impl.client.extension.BEWLRItemExtensionFactory import me.alegian.thavma.impl.client.extension.WandItemExtensions import me.alegian.thavma.impl.client.gui.WorkbenchScreen -import me.alegian.thavma.impl.client.gui.book.CraftingPageRenderer -import me.alegian.thavma.impl.client.gui.book.TextPageRenderer +import me.alegian.thavma.impl.client.gui.book.* import me.alegian.thavma.impl.client.gui.layer.ArcaneLensLayer import me.alegian.thavma.impl.client.gui.layer.WandLayer import me.alegian.thavma.impl.client.gui.research_table.ResearchScreen @@ -42,6 +41,7 @@ import thedarkcolour.kotlinforforge.neoforge.forge.MOD_BUS as KFF_MOD_BUS private fun clientSetup(event: FMLClientSetupEvent) { ModLoader.postEvent(RegisterPageRenderersEvent()) + ModLoader.postEvent(RegisterPageFeatureRenderersEvent()) ItemProperties.register(T7Items.RESEARCH_SCROLL.get(), T7ItemProperties.COMPLETED) { stack, level, entity, seed -> val completed = stack.get(T7DataComponents.RESEARCH_STATE)?.completed ?: false if (completed) 1f else 0f @@ -226,6 +226,14 @@ private fun registerPageRenderers(event: RegisterPageRenderersEvent) { event.register(PageTypes.CRAFTING.get(), CraftingPageRenderer) } +private fun registerPageFeatureRenderers(event: RegisterPageFeatureRenderersEvent) { + event.register(PageFeatureTypes.PARAGRAPH.get(), ParagraphFeatureRenderer) + event.register(PageFeatureTypes.TITLE.get(), TitleFeatureRenderer) + event.register(PageFeatureTypes.FIGURE.get(), FigureFeatureRenderer) + event.register(PageFeatureTypes.FORMATTED.get(), FormattedTextFeatureRenderer) + event.register(PageFeatureTypes.RECIPE.get(), RecipeFeatureRenderer) +} + private fun registerKeyMappings(event: RegisterKeyMappingsEvent) { event.register(T7KeyMappings.FOCI) } @@ -251,6 +259,7 @@ fun registerClientModEvents() { KFF_MOD_BUS.addListener(::registerClientTooltipComponentFactories) KFF_MOD_BUS.addListener(::registerScreens) KFF_MOD_BUS.addListener(::registerPageRenderers) + KFF_MOD_BUS.addListener(::registerPageFeatureRenderers) KFF_MOD_BUS.addListener(::registerKeyMappings) KFF_MOD_BUS.addListener(::registerRenderBuffers) } \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/T7ContainerScreen.kt b/src/main/java/me/alegian/thavma/impl/client/gui/T7ContainerScreen.kt index c15a60fd..45a2f9ad 100644 --- a/src/main/java/me/alegian/thavma/impl/client/gui/T7ContainerScreen.kt +++ b/src/main/java/me/alegian/thavma/impl/client/gui/T7ContainerScreen.kt @@ -51,7 +51,7 @@ abstract class T7ContainerScreen(menu: T, pPlayerInventory: Inventory, height = fixed(font.lineHeight) width = grow() }) { - relativeRenderable(text(this@T7ContainerScreen.title, T7Colors.GREEN)) + relativeRenderable{ text(this@T7ContainerScreen.title, T7Colors.GREEN) } } TextureBox(bgTexture) { @@ -69,7 +69,7 @@ abstract class T7ContainerScreen(menu: T, pPlayerInventory: Inventory, width = grow() height = fixed(font.lineHeight) }) { - relativeRenderable(text(this@T7ContainerScreen.playerInventoryTitle, 0x404040)) + relativeRenderable{ text(this@T7ContainerScreen.playerInventoryTitle, 0x404040) } } Column({ diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/CraftingPageRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/CraftingPageRenderer.kt index c322e36b..c9bf90f1 100644 --- a/src/main/java/me/alegian/thavma/impl/client/gui/book/CraftingPageRenderer.kt +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/CraftingPageRenderer.kt @@ -6,6 +6,7 @@ import me.alegian.thavma.impl.client.util.drawCenteredString import me.alegian.thavma.impl.common.book.CraftingPage import me.alegian.thavma.impl.common.recipe.translationId import net.minecraft.client.Minecraft +import net.minecraft.client.gui.components.Renderable import net.minecraft.network.chat.Component import net.minecraft.world.item.crafting.CraftingRecipe import net.minecraft.world.item.crafting.RecipeType @@ -40,8 +41,10 @@ object CraftingPageRenderer : PageRenderer { Row({ height = fixed(font.lineHeight) }) { - relativeRenderable { guiGraphics, _, _, _ -> - guiGraphics.drawCenteredString(font, TITLE, size.x / 2) + relativeRenderable { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.drawCenteredString(font, TITLE, size.x / 2) + } } } } diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/DynamicRenderingHelper.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/DynamicRenderingHelper.kt new file mode 100644 index 00000000..a9657d3e --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/DynamicRenderingHelper.kt @@ -0,0 +1,277 @@ +package me.alegian.thavma.impl.client.gui.book + +import me.alegian.thavma.impl.common.book.* +import net.minecraft.client.gui.Font +import net.minecraft.util.Mth.ceil +import net.minecraft.util.Mth.floor +import kotlin.math.min + +private fun PageFeature.renderedHeight(pageWidth: Int, font: Font, scale: Float): Int { + val lineHeight = ceil((font.lineHeight * scale) + 2) + return when (this) { + is ParagraphFeature -> font.split(this.text, floor(pageWidth/scale)).size * lineHeight + is TitleFeature -> font.split(this.text, (pageWidth/scale).toInt()).size * lineHeight + 16 + is FigureFeature -> if (caption != null) font.split( + this.caption, + (pageWidth/scale).toInt() + ).size * lineHeight + this.textureHeight else this.textureHeight + is RecipeFeature -> 96 + is FormattedTextFeature -> text.size * lineHeight + else -> throw IllegalArgumentException("This PageFeature $this does not have renderedHeight() implemented yet") + } +} + +/** + * Return a list of PageFeatures containing a list of lines - FormattedCharSequence + * or possibly a Figure with just the image and its processed caption - FormattedCharSequence, + * so that they all fit on their + * respective pages (represented by list indices) + */ +fun spliceParagraphOrFigure( + input: PageFeature, maxPageHeight: Int, + currentHeight: Int, maxPageWidth: Int, font: Font, scale: Float +): List { + val result = mutableListOf() + if (input is ParagraphFeature) { + val lineHeight = ceil((font.lineHeight * scale + 2)) + val linesRemainingAtStart: Int = (maxPageHeight - currentHeight) / lineHeight + + // so that we get at least 2 lines at the start of the last page + // (making use of rounding down when dividing integers) + val numOfLinesCoveringFullPages: Int = + (input.renderedHeight( + maxPageWidth, + font, + scale + ) - linesRemainingAtStart * lineHeight) / maxPageHeight * maxPageHeight / lineHeight + + var linesCroppingOutAtEnd: Int = + (input.renderedHeight( + maxPageWidth, + font, + scale + ) - linesRemainingAtStart * lineHeight - numOfLinesCoveringFullPages * lineHeight) / lineHeight + + if (linesCroppingOutAtEnd < 0) linesCroppingOutAtEnd = 0 + + val maxLinesPerPage = maxPageHeight / lineHeight + val numOfFullPagesCovered = numOfLinesCoveringFullPages / maxLinesPerPage + val lines = font.split(input.text, (maxPageWidth/scale).toInt()) + val realLinesRemaining: Int = min(linesRemainingAtStart, lines.size) + + when { + lines.size <= linesRemainingAtStart && currentHeight + lineHeight * lines.size <= maxPageHeight -> result += FormattedTextFeature( + lines + ) + + linesRemainingAtStart != 1 && linesCroppingOutAtEnd != 1 -> { + // separate into "underhang", middle and overhang + val start = lines.slice(0 until realLinesRemaining) + result += FormattedTextFeature(start) + + for (i in 0 until numOfFullPagesCovered) { + val fullPage = + lines.slice(linesRemainingAtStart + i * maxLinesPerPage until linesRemainingAtStart + (i + 1) * maxLinesPerPage) + result += FormattedTextFeature(fullPage) + } + + val end = + lines.slice(lines.size - linesCroppingOutAtEnd until lines.size) + result += FormattedTextFeature(end) + } + + else -> { + // add an empty paragraph to current page and continue on the next + result += FormattedTextFeature(listOf()) + for (i in 0 until numOfFullPagesCovered) { + val fullPage = lines.slice(i * maxLinesPerPage until (i + 1) * maxLinesPerPage) + result += FormattedTextFeature(fullPage) + } + val finish = + lines.slice(numOfFullPagesCovered * maxLinesPerPage until lines.size) + result += FormattedTextFeature(finish) + } + } + return result + } + + if (input is FigureFeature) { + with(input) { + when { + caption == null && currentHeight + textureHeight <= maxPageHeight -> result += input + caption == null -> { + result += FormattedTextFeature(listOf()) + result += this + } + + currentHeight + textureHeight <= maxPageHeight -> { + result += this + result.addAll( + spliceParagraphOrFigure( + ParagraphFeature(caption), + maxPageHeight, + currentHeight + textureHeight, + maxPageWidth, font, scale + ) + ) + } + + else -> { + result += FormattedTextFeature(listOf()) + result += this + result.addAll( + spliceParagraphOrFigure( + ParagraphFeature(caption), + maxPageHeight, + textureHeight, + maxPageWidth, + font, + scale + ) + ) + } + } + } + return result + } + throw IllegalArgumentException("The supplied input $input is not a Paragraph or Figure") +} + +/** + * Returns a list of lists of features where every index represents a page. + * Features in the same list belong together on one page. + */ +fun pagifyFeatures(features: List, maxHeight: Int, pageWidth: Int, font: Font, scale: Float): List> { + val partition = features.partition { !it.mustOccupySetPage } + val pages = mutableListOf>() + val buffer = mutableListOf() + fun currentHeight() = buffer.sumOf { it.renderedHeight(pageWidth, font, scale) } + fun submitBufferAndClear() { + pages.add(buffer.toList()) + buffer.clear() + } + + // deal with elements without predetermined order (bulk of the logic) + for (feature in partition.first) { + with(feature) { + when { + (this !is ParagraphFeature && this !is FigureFeature) && renderedHeight( + pageWidth, + font, + scale + ) > maxHeight -> throw IllegalArgumentException( + "The size of the element ${this::class.simpleName} is too large at ${ + renderedHeight( + pageWidth, + font, + scale + ) + } while allowed $maxHeight." + ) + + coversOneWholePage -> { + if (buffer.isNotEmpty()) submitBufferAndClear() + pages += listOf(this) + } + + mustStartPage -> { + if (buffer.isNotEmpty()) submitBufferAndClear() + if (this is ParagraphFeature || this is FigureFeature) { + val processed = spliceParagraphOrFigure(this, maxHeight, currentHeight(), pageWidth, font, scale) + if (processed.size < 2) buffer += processed.first() + // only need to check this much since buffer is empty (starts page) + else if (this is ParagraphFeature) { + processed.slice(0 until processed.size - 1).forEach { pages += listOf(it) } + buffer += processed.last() + } + // we are dealing with figure features now + else { + buffer += processed.first() + buffer += processed[1] + if (processed.size > 2) { + submitBufferAndClear() + for (j in 2 until processed.size - 1) { + pages += listOf(processed[j]) + } + buffer += processed.last() + } + } + } else buffer += this + } + + (this is ParagraphFeature || this is FigureFeature) -> { + val processed = spliceParagraphOrFigure(this, maxHeight, currentHeight(), pageWidth, font, scale) + if (processed.size < 2) buffer += processed.first() + else if (this is ParagraphFeature) { + buffer += processed.first() + submitBufferAndClear() + if (processed.size > 2) processed.slice(1 until processed.size - 1).forEach { pages += listOf(it) } + buffer += processed.last() + } else { + // we are dealing with figure features now + when { + // Size 1 is already taken care of above. The result is at least size 2. + // If the first thing in a processed Figure is a FormattedTextFeature, + // it's an empty one to signify end of page. Size 2 means + // there is only this emptiness + image. + processed.first() is FormattedTextFeature && processed.size == 2 -> { + submitBufferAndClear() + buffer += processed.last() + } + // Now there are either exactly 2 elements and the first one is an image + // that fits in the page and the second one is text, + // or there are more than 2 elements and they can be anything. + else -> { + var i = 0 + // address possible page break + if (processed.first() is FormattedTextFeature) { + submitBufferAndClear() + i++ + } + // always takes the image next + buffer += processed[i] + // the check whether the start of the caption fits in the page with the + // image is done by the Paragraph half of the function, so just add + // all that remains + buffer += processed[i + 1] + if (processed.size > 2 + i) { + submitBufferAndClear() + for (j in 2 + i until processed.size - 1) { + pages += listOf(processed[j]) + } + buffer += processed.last() + } + } + } + } + } + + currentHeight() + renderedHeight(pageWidth, font, scale) <= maxHeight -> buffer += this + // the next check might be redundant: + buffer.isEmpty() -> throw IllegalArgumentException( + "The size of the element ${this::class.simpleName} is too large at ${ + renderedHeight( + pageWidth, + font, + scale + ) + } while allowed $maxHeight." + ) + + else -> { + submitBufferAndClear() + buffer += this + } + } + } + } + + // add anything left over in the buffer + if (buffer.isNotEmpty()) submitBufferAndClear() + + // finally add features with pre-determined positions (cannot be long paragraphs) + // these features have to be ordered correctly in the Research Entry builder + partition.second.groupBy { it.preferredPageIndex }.forEach { pages.add(it.key, it.value) } + + return pages +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/EntryScreen.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/EntryScreen.kt index 12eb73e2..225b398c 100644 --- a/src/main/java/me/alegian/thavma/impl/client/gui/book/EntryScreen.kt +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/EntryScreen.kt @@ -2,23 +2,36 @@ package me.alegian.thavma.impl.client.gui.book import me.alegian.thavma.impl.client.gui.layout.* import me.alegian.thavma.impl.client.texture.Texture -import me.alegian.thavma.impl.common.book.Page +import me.alegian.thavma.impl.common.book.PageFeature import me.alegian.thavma.impl.common.research.ResearchEntry +import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.screens.Screen import net.minecraft.core.Holder import net.minecraft.network.chat.Component -class EntryScreen(private val entry: Holder) : Screen(Component.literal("Book Entry")) { +class EntryScreen(entry: Holder) : Screen(Component.literal("Book Entry")) { companion object { private val BG = Texture("gui/book/background", 510, 282, 512, 512) } private var currentPage = 0 + private val fontify = Minecraft.getInstance().font + private val entry = entry.value() + + private var maxWidthCorrection = 0 + private var maxHeightCorrection = 0 + + private val scale = 1f + + // maxHeight is height of background texture minus padding (32 top 42 bottom) + var pages = listOf>() override fun init() { super.init() clearWidgets() + maxWidthCorrection = 0 + maxHeightCorrection = 0 LayoutExtensions.currScreen = this Row({ @@ -27,56 +40,97 @@ class EntryScreen(private val entry: Holder) : Screen(Component.l align = Alignment.CENTER }) { TextureBox(BG) { - Row({ + Column({ size = grow() - paddingTop = 32 - paddingX = 32 - paddingBottom = 42 - gap = 48 + gap = 8 }) { Row({ size = grow() + paddingY = PageTurningWidget.LEFT_TEXTURE.height + 8 + paddingX = 32 + paddingBottom = 42 + gap = 48 + maxWidthCorrection += 2 * paddingX.toInt() + maxHeightCorrection += gap.toInt() + paddingBottom.toInt() }) { - initPage(entry.value().pages.getOrNull(currentPage)) - if (currentPage != 0) { - Box({ - width = fixed(PageTurningWidget.LEFT_TEXTURE.width) - height = fixed(PageTurningWidget.LEFT_TEXTURE.height) + Row({ + size = grow() + }) { + Column({ + size = grow() + gap = 4 }) { - afterLayout { - addRenderableWidget(PageTurningWidget(position, false) { - // rerender the screen for the new page(s) - turnPage(false) - }) + pages = pagifyFeatures( + entry.pageFeatures, + BG.height - maxHeightCorrection, + BG.width / 2 - maxWidthCorrection, + fontify, + scale + ) + val features = pages.getOrNull(currentPage) + if (features != null) { + for (feature in features) initPageFeature(feature) } } } - } - Row({ - size = grow() - }) { - initPage(entry.value().pages.getOrNull(currentPage + 1)) - if (entry.value().pages.getOrNull(currentPage + 2) != null) { - Box({ - width = fixed(PageTurningWidget.RIGHT_TEXTURE.width) - height = fixed(PageTurningWidget.RIGHT_TEXTURE.height) + Row({ + size = grow() + }) { + Column({ + size = grow() + gap = 4 }) { - afterLayout { - addRenderableWidget(PageTurningWidget(position, true) { - // rerender the screen for the new page(s) - turnPage(true) - }) + val features = pages.getOrNull(currentPage + 1) + if (features != null) { + for (feature in features) initPageFeature(feature) } } } } + PageTurnerRow() } } } } - fun turnPage(right: Boolean){ + private fun PageTurnerRow() { + Row({ + width = grow() + paddingX = 40 + paddingY = 20 + }) { + if (currentPage != 0) { + Box({ + width = fixed(PageTurningWidget.LEFT_TEXTURE.width) + height = fixed(PageTurningWidget.LEFT_TEXTURE.height) + }) { + afterLayout { + addRenderableWidget(PageTurningWidget(position, false) { + // rerender the screen for the new page(s) + turnPage(false) + }) + } + } + } + Box({ width = grow() }) {} + if (pages.getOrNull(currentPage + 2) != null) { + Box({ + width = fixed(PageTurningWidget.RIGHT_TEXTURE.width) + height = fixed(PageTurningWidget.RIGHT_TEXTURE.height) + }) { + afterLayout { + addRenderableWidget(PageTurningWidget(position, true) { + // rerender the screen for the new page(s) + turnPage(true) + }) + } + } + } + } + } + + fun turnPage(right: Boolean) { if (right) currentPage += 2 else currentPage -= 2 init() @@ -86,11 +140,12 @@ class EntryScreen(private val entry: Holder) : Screen(Component.l renderTransparentBackground(guiGraphics) } - // wrapper around unchecked cast - private fun initPage(page: T) { - if (page == null) return - val renderer = PAGE_RENDERERS[page.type] as PageRenderer - renderer.initPage(this, page) + // wrapper around... unchecked cast + private fun initPageFeature(feature: T) { + if (feature != null) { + val renderer = PAGE_FEATURE_RENDERERS[feature.type] as PageFeatureRenderer + renderer.initPageFeature(this, feature, BG.width / 2 - maxWidthCorrection, fontify, scale) + } } override fun isPauseScreen() = false diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/FigureFeatureRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/FigureFeatureRenderer.kt new file mode 100644 index 00000000..4788b4d9 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/FigureFeatureRenderer.kt @@ -0,0 +1,25 @@ +package me.alegian.thavma.impl.client.gui.book + +import me.alegian.thavma.impl.client.gui.layout.CenteredTextureBox +import me.alegian.thavma.impl.client.gui.layout.Row +import me.alegian.thavma.impl.client.gui.layout.fixed +import me.alegian.thavma.impl.client.gui.layout.grow +import me.alegian.thavma.impl.common.book.FigureFeature +import net.minecraft.client.gui.Font + +object FigureFeatureRenderer : PageFeatureRenderer { + override fun initPageFeature( + screen: EntryScreen, + feature: FigureFeature, + maxWidth: Int, + font: Font, + scale: Float + ) { + Row({ + width = grow() + height = fixed(feature.textureHeight) + }) { + CenteredTextureBox(feature.image, maxWidth) {} + } + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/FormattedTextFeatureRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/FormattedTextFeatureRenderer.kt new file mode 100644 index 00000000..3f07e3e4 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/FormattedTextFeatureRenderer.kt @@ -0,0 +1,43 @@ +package me.alegian.thavma.impl.client.gui.book + +import me.alegian.thavma.impl.client.gui.layout.Row +import me.alegian.thavma.impl.client.gui.layout.fixed +import me.alegian.thavma.impl.client.gui.layout.grow +import me.alegian.thavma.impl.client.gui.layout.relativeRenderable +import me.alegian.thavma.impl.client.util.drawString +import me.alegian.thavma.impl.client.util.translateXY +import me.alegian.thavma.impl.client.util.usePose +import me.alegian.thavma.impl.common.book.FormattedTextFeature +import net.minecraft.client.gui.Font +import net.minecraft.client.gui.components.Renderable +import net.minecraft.util.Mth.ceil + +object FormattedTextFeatureRenderer : PageFeatureRenderer { + override fun initPageFeature( + screen: EntryScreen, + feature: FormattedTextFeature, + maxWidth: Int, + font: Font, + scale: Float + ) { + val LINE_HEIGHT = ceil((font.lineHeight * scale + 2)) + + Row({ + width = grow() + height = fixed(LINE_HEIGHT * (feature.text.size + 0.5f)) + }) { + relativeRenderable { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.pose().scale(scale, scale, 1.0f) + guiGraphics.usePose { + for (line in feature.text) { + guiGraphics.drawString(font, line) + translateXY(0, LINE_HEIGHT / scale) + } + translateXY(0, LINE_HEIGHT * 2 / 3) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/GridHelper.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/GridHelper.kt index 72f50fcc..1ae570f5 100644 --- a/src/main/java/me/alegian/thavma/impl/client/gui/book/GridHelper.kt +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/GridHelper.kt @@ -34,7 +34,7 @@ fun PoseStack.renderConnectionRecursive(dx: Int, dy: Int, guiGraphics: GuiGraphi val preference = if (preferX) -1f else 1f if (absDx + absDy <= 1f) return - else if (absDx > 2 && absDy > 2) throw IllegalStateException() + else if (absDx > 2 && absDy > 2) throw IllegalStateException("Grid does not support dx,dy both > than 2") else if (!invert && (preferX && absDx > absDy && absDy > 0 || !preferX && absDy > absDx && absDx > 0)) { translateXY(dx, dy) renderConnectionRecursive(-dx, -dy, guiGraphics, preferX, true) diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/PageFeatureRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/PageFeatureRenderer.kt new file mode 100644 index 00000000..be6cca3c --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/PageFeatureRenderer.kt @@ -0,0 +1,11 @@ +package me.alegian.thavma.impl.client.gui.book + +import me.alegian.thavma.impl.common.book.PageFeature +import me.alegian.thavma.impl.common.book.PageFeatureType +import net.minecraft.client.gui.Font + +val PAGE_FEATURE_RENDERERS = mutableMapOf, PageFeatureRenderer<*>>() + +interface PageFeatureRenderer { + fun initPageFeature(screen: EntryScreen, feature: T, maxWidth: Int, font: Font, scale: Float) +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/ParagraphFeatureRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/ParagraphFeatureRenderer.kt new file mode 100644 index 00000000..5d9bb7a1 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/ParagraphFeatureRenderer.kt @@ -0,0 +1,44 @@ +package me.alegian.thavma.impl.client.gui.book + +import me.alegian.thavma.impl.client.gui.layout.Row +import me.alegian.thavma.impl.client.gui.layout.fixed +import me.alegian.thavma.impl.client.gui.layout.grow +import me.alegian.thavma.impl.client.gui.layout.relativeRenderable +import me.alegian.thavma.impl.client.util.drawString +import me.alegian.thavma.impl.client.util.translateXY +import me.alegian.thavma.impl.client.util.usePose +import me.alegian.thavma.impl.common.book.ParagraphFeature +import net.minecraft.client.gui.Font +import net.minecraft.client.gui.components.Renderable +import net.minecraft.util.Mth.ceil + +object ParagraphFeatureRenderer : PageFeatureRenderer { + override fun initPageFeature( + screen: EntryScreen, + feature: ParagraphFeature, + maxWidth: Int, + font: Font, + scale: Float + ) { + val LINE_HEIGHT = ceil((font.lineHeight * scale + 2)) + + Row({ + val lines = font.split(feature.text, (maxWidth / scale).toInt()) + width = grow() + height = fixed(LINE_HEIGHT * (lines.size + 0.5f)) + }) { + relativeRenderable { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.pose().scale(scale, scale, 1.0f) + guiGraphics.usePose { + for (line in font.split(feature.text, (maxWidth / scale).toInt())) { + guiGraphics.drawString(font, line) + translateXY(0, LINE_HEIGHT / scale) + } + translateXY(0, LINE_HEIGHT * 2 / 3) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/RecipeFeatureRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/RecipeFeatureRenderer.kt new file mode 100644 index 00000000..fc266f1c --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/RecipeFeatureRenderer.kt @@ -0,0 +1,52 @@ +package me.alegian.thavma.impl.client.gui.book + +import me.alegian.thavma.impl.client.gui.layout.* +import me.alegian.thavma.impl.client.texture.Texture +import me.alegian.thavma.impl.client.util.drawCenteredString +import me.alegian.thavma.impl.common.book.RecipeFeature +import me.alegian.thavma.impl.common.recipe.translationId +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.Font +import net.minecraft.client.gui.components.Renderable +import net.minecraft.network.chat.Component +import net.minecraft.world.item.crafting.CraftingRecipe +import net.minecraft.world.item.crafting.RecipeType +import kotlin.jvm.optionals.getOrNull + +object RecipeFeatureRenderer : PageFeatureRenderer { + private val GRID = Texture("gui/book/crafting", 96, 96, 96, 96) + private val RESULT = Texture("gui/book/result", 32, 32, 32, 32) + private val TITLE = Component.translatable(RecipeType.CRAFTING.translationId) + private const val GAP = 12 + + override fun initPageFeature(screen: EntryScreen, feature: RecipeFeature, maxWidth: Int, font: Font, scale: Float) { + val recipe = Minecraft.getInstance().level?.recipeManager?.byKey(feature.recipeRL)?.getOrNull()?.value + if (recipe !is CraftingRecipe) return // TODO: support other recipe types + + Column({ + alignCross = Alignment.CENTER + size = grow() + gap = GAP + }) { + Title() + + TextureBox(RESULT) {} + + TextureBox(GRID) {} + } + } + + private fun Title() { + val font = Minecraft.getInstance().font + + Row({ + height = fixed(font.lineHeight) + }) { + relativeRenderable { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.drawCenteredString(font, TITLE, size.x / 2) + } + } + } + } +} diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/TextPageRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/TextPageRenderer.kt index 1de0a3a3..12b0fca2 100644 --- a/src/main/java/me/alegian/thavma/impl/client/gui/book/TextPageRenderer.kt +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/TextPageRenderer.kt @@ -8,6 +8,7 @@ import me.alegian.thavma.impl.client.util.translateXY import me.alegian.thavma.impl.client.util.usePose import me.alegian.thavma.impl.common.book.TextPage import net.minecraft.client.Minecraft +import net.minecraft.client.gui.components.Renderable import net.minecraft.network.chat.Component object TextPageRenderer : PageRenderer { @@ -28,14 +29,16 @@ object TextPageRenderer : PageRenderer { Row({ size = grow() }) { - relativeRenderable { guiGraphics, _, _, _ -> - guiGraphics.usePose { - for (paragraph in page.paragraphs) { - for (line in font.split(paragraph, size.x.toInt())) { - guiGraphics.drawString(Minecraft.getInstance().font, line) - translateXY(0, LINE_HEIGHT) + relativeRenderable { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.usePose { + for (paragraph in page.paragraphs) { + for (line in font.split(paragraph, size.x.toInt())) { + guiGraphics.drawString(Minecraft.getInstance().font, line) + translateXY(0, LINE_HEIGHT) + } + translateXY(0, LINE_HEIGHT * 2 / 3) } - translateXY(0, LINE_HEIGHT * 2 / 3) } } } @@ -59,8 +62,10 @@ object TextPageRenderer : PageRenderer { width = grow() height = fixed(font.lineHeight) }) { - relativeRenderable { guiGraphics, _, _, _ -> - guiGraphics.drawCenteredString(font, text, size.x / 2) + relativeRenderable { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.drawCenteredString(font, text, size.x / 2) + } } } } diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/TitleFeatureRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/TitleFeatureRenderer.kt new file mode 100644 index 00000000..23a1821c --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/TitleFeatureRenderer.kt @@ -0,0 +1,59 @@ +package me.alegian.thavma.impl.client.gui.book + +import me.alegian.thavma.impl.client.gui.layout.* +import me.alegian.thavma.impl.client.texture.Texture +import me.alegian.thavma.impl.client.util.drawCenteredString +import me.alegian.thavma.impl.client.util.translateXY +import me.alegian.thavma.impl.client.util.usePose +import me.alegian.thavma.impl.common.book.TitleFeature +import net.minecraft.client.gui.Font +import net.minecraft.client.gui.components.Renderable +import net.minecraft.util.Mth.ceil + +object TitleFeatureRenderer : PageFeatureRenderer { + private val SEPARATOR = Texture("gui/book/separator", 128, 16, 128, 16) + + override fun initPageFeature( + screen: EntryScreen, + feature: TitleFeature, + maxWidth: Int, + font: Font, + scale: Float + ) { + Title(feature, maxWidth, font, scale) + Separator() + } + + private fun Separator() { + Row({ + width = grow() + alignMain = Alignment.CENTER + }) { + TextureBox(SEPARATOR) {} + } + } + + private fun Title(title: TitleFeature, maxWidth: Int, font: Font, scale: Float) { + + val lines = font.split(title.text, (maxWidth / scale).toInt()) + + Row({ + width = grow() + height = fixed(ceil((font.lineHeight * scale + 2)) * lines.size) + }) { + relativeRenderable { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.pose().scale(scale, scale, 1.0f) + guiGraphics.usePose { + for ((index, line) in lines.withIndex()) { + guiGraphics.drawCenteredString( + font, line, size.x / scale / 2 + ) + if (index != lines.size - 1) translateXY(0, ceil((font.lineHeight * scale + 2)) / scale) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/layout/LayoutAPI.kt b/src/main/java/me/alegian/thavma/impl/client/gui/layout/LayoutAPI.kt index 680737e4..de77d523 100644 --- a/src/main/java/me/alegian/thavma/impl/client/gui/layout/LayoutAPI.kt +++ b/src/main/java/me/alegian/thavma/impl/client/gui/layout/LayoutAPI.kt @@ -51,7 +51,8 @@ class Props() { } var paddingX: Number - get() = throw UnsupportedOperationException() + //get() = throw UnsupportedOperationException() + get() = paddingLeft set(value) { paddingLeft = value paddingRight = value diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/layout/LayoutExtensions.kt b/src/main/java/me/alegian/thavma/impl/client/gui/layout/LayoutExtensions.kt index 13425938..b6212c7a 100644 --- a/src/main/java/me/alegian/thavma/impl/client/gui/layout/LayoutExtensions.kt +++ b/src/main/java/me/alegian/thavma/impl/client/gui/layout/LayoutExtensions.kt @@ -4,7 +4,6 @@ import com.mojang.blaze3d.systems.RenderSystem import me.alegian.thavma.impl.client.texture.Texture import me.alegian.thavma.impl.client.util.blit import me.alegian.thavma.impl.client.util.drawString -import me.alegian.thavma.impl.client.util.transformOrigin import me.alegian.thavma.impl.client.util.translateXY import me.alegian.thavma.impl.client.util.usePose import me.alegian.thavma.impl.common.menu.slot.DynamicSlot @@ -40,13 +39,13 @@ private fun renderableTexture(texture: Texture) = Renderable { guiGraphics: GuiG RenderSystem.disableBlend() } -fun relativeRenderable(renderable: Renderable) { +fun relativeRenderable(getRenderable: T7LayoutElement.() -> Renderable) { val screen = LayoutExtensions.currScreen ?: throw IllegalStateException("Thavma Exception: cannot add renderable without setting LayoutExtensions.currScreen first!") afterLayout { screen.renderables.add(Renderable { guiGraphics, mouseX, mouseY, partialTick -> guiGraphics.usePose { translateXY(position.x, position.y) - renderable.render(guiGraphics, mouseX, mouseY, partialTick) + getRenderable(this@afterLayout).render(guiGraphics, mouseX, mouseY, partialTick) } }) } @@ -57,10 +56,28 @@ fun TextureBox(texture: Texture, children: T7LayoutElement.() -> Unit) = width = fixed(texture.width) height = fixed(texture.height) }) { - relativeRenderable(renderableTexture(texture)) + relativeRenderable { renderableTexture(texture) } children() } +fun CenteredTextureBox(texture: Texture, maxWidth: Int, children: T7LayoutElement.() -> Unit) { + val screen = LayoutExtensions.currScreen ?: throw IllegalStateException("Thavma Exception: cannot add renderable without setting LayoutExtensions.currScreen first!") + Row({ + width = fixed(texture.width) + height = fixed(texture.height) + }) { + afterLayout { + screen.renderables.add(Renderable { guiGraphics, mouseX, mouseY, partialTick -> + guiGraphics.usePose { + translateXY(position.x + (maxWidth - texture.width) / 2, position.y) + renderableTexture(texture).render(guiGraphics, mouseX, mouseY, partialTick) + } + }) + } + children() + } +} + private fun T7LayoutElement.slotSetup(slot: Slot) { if (slot !is DynamicSlot<*>) return slot.actualX = position.x @@ -81,7 +98,7 @@ fun Slot(slot: Slot, texture: Texture? = null, slotSize: Int? = null) = fun Grid(rows: Int, columns: Int, elements: List, bgLayers: List = listOf(), gapSize: Int = 0, child: (T) -> Unit) = Column({ gap = gapSize }) { for (layer in bgLayers) - relativeRenderable(renderableTexture(layer)) + relativeRenderable{ renderableTexture(layer) } for (i in 0 until rows) Row({ gap = gapSize }) { diff --git a/src/main/java/me/alegian/thavma/impl/client/texture/Texture.kt b/src/main/java/me/alegian/thavma/impl/client/texture/Texture.kt index dd6eb418..ab0b28bd 100644 --- a/src/main/java/me/alegian/thavma/impl/client/texture/Texture.kt +++ b/src/main/java/me/alegian/thavma/impl/client/texture/Texture.kt @@ -1,5 +1,7 @@ package me.alegian.thavma.impl.client.texture +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import me.alegian.thavma.impl.rl import net.minecraft.resources.ResourceLocation import net.minecraft.world.phys.Vec2 @@ -8,11 +10,36 @@ import net.minecraft.world.phys.Vec2 * Width and height refer to the useful part of the texture, not its entirety. * In practice many textures (like inventories) have huge empty spaces we don't care about */ -class Texture(val location: ResourceLocation, val width: Int, val height: Int, val canvasWidth: Int, val canvasHeight: Int) { +data class Texture( + val location: ResourceLocation, + val width: Int, + val height: Int, + val canvasWidth: Int, + val canvasHeight: Int +) { constructor(path: String, width: Int, height: Int) : this(rl("textures/$path.png"), width, height, width, height) - constructor(path: String, width: Int, height: Int, canvasWidth: Int, canvasHeight: Int) : this(rl("textures/$path.png"), width, height, canvasWidth, canvasHeight) + constructor( + path: String, + width: Int, + height: Int, + canvasWidth: Int, + canvasHeight: Int + ) : this(rl("textures/$path.png"), width, height, canvasWidth, canvasHeight) val size: Vec2 get() = Vec2(width.toFloat(), height.toFloat()) + + companion object { + val CODEC: Codec = RecordCodecBuilder.create { instance -> + instance.group( + ResourceLocation.CODEC.fieldOf("location").forGetter(Texture::location), + Codec.INT.fieldOf("width").forGetter(Texture::width), + Codec.INT.fieldOf("height").forGetter(Texture::height), + Codec.INT.fieldOf("canvas_width").forGetter(Texture::canvasWidth), + Codec.INT.fieldOf("canvas_height").forGetter(Texture::canvasHeight) + ).apply(instance, ::Texture) + } + } } + diff --git a/src/main/java/me/alegian/thavma/impl/client/util/GuiGraphicsExtensions.kt b/src/main/java/me/alegian/thavma/impl/client/util/GuiGraphicsExtensions.kt index ed8fffed..ae424237 100644 --- a/src/main/java/me/alegian/thavma/impl/client/util/GuiGraphicsExtensions.kt +++ b/src/main/java/me/alegian/thavma/impl/client/util/GuiGraphicsExtensions.kt @@ -15,6 +15,10 @@ fun GuiGraphics.usePose(block: PoseStack.() -> Unit) { } fun GuiGraphics.drawCenteredString(font: Font, text: Component, centerX: Float, color: Int = 0) { + drawString(font, text, (centerX - font.width(text.visualOrderText) / 2f).toInt(), 0, color, false) +} + +fun GuiGraphics.drawCenteredString(font: Font, text: FormattedCharSequence, centerX: Float, color: Int = 0) { drawString(font, text, (centerX - font.width(text) / 2f).toInt(), 0, color, false) } diff --git a/src/main/java/me/alegian/thavma/impl/common/book/DynamicPage.kt b/src/main/java/me/alegian/thavma/impl/common/book/DynamicPage.kt new file mode 100644 index 00000000..fecf0139 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/DynamicPage.kt @@ -0,0 +1,22 @@ +package me.alegian.thavma.impl.common.book + +import com.mojang.serialization.codecs.RecordCodecBuilder +import me.alegian.thavma.impl.init.registries.deferred.PageTypes +import net.minecraft.network.chat.ComponentSerialization +import java.util.Optional + +class DynamicPage(val pageFeatures: List): Page { + // this might be a problem but for a future Tobias + override val type: PageType<*> + get() = PageTypes.TEXT.get() + + +// companion object { +// val CODEC = RecordCodecBuilder.mapCodec { builder -> +// builder.group( +// ComponentSerialization.CODEC.fieldOf("text").forGetter(ParagraphFeature::text) +// ).apply(builder, ::ParagraphFeature) +// } +// +// } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/common/book/FigureFeature.kt b/src/main/java/me/alegian/thavma/impl/common/book/FigureFeature.kt new file mode 100644 index 00000000..7fe7acd1 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/FigureFeature.kt @@ -0,0 +1,45 @@ +package me.alegian.thavma.impl.common.book + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import me.alegian.thavma.impl.client.texture.Texture +import me.alegian.thavma.impl.init.registries.deferred.PageFeatureTypes +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.ComponentSerialization +import java.util.* + +class FigureFeature( + val image: Texture, + val caption: Component?, + override val mustStartPage: Boolean = false, + override val mustOccupySetPage: Boolean = false, + override val preferredPageIndex: Int = 1 +) : PageFeature { + override val type: PageFeatureType<*> + get() = PageFeatureTypes.FIGURE.get() + + override val coversOneWholePage = false + + override fun toString(): String { + return "FigureFeature with caption $caption, mustOccupySetPage set to $mustOccupySetPage and preferred page index $preferredPageIndex" + } + + val textureHeight = image.height + + companion object { + val CODEC = RecordCodecBuilder.mapCodec { builder -> + builder.group( + Texture.CODEC.fieldOf("image").forGetter(FigureFeature::image), + ComponentSerialization.CODEC.optionalFieldOf("caption").forGetter { p -> Optional.ofNullable(p.caption) }, + Codec.BOOL.optionalFieldOf("starts_page", false).forGetter(FigureFeature::mustStartPage), + Codec.BOOL.optionalFieldOf("has_set_page", false).forGetter(FigureFeature::mustOccupySetPage), + Codec.INT.optionalFieldOf("preferred_page", 1).forGetter(FigureFeature::preferredPageIndex) + ).apply(builder) { img, cap, start, index, pref -> + FigureFeature(img, cap.orElse(null), start, index, pref) + } + } + + fun translationId(baseId: String, featureIndex: Int) = "$baseId.figure_feature$featureIndex" + } + +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/common/book/FormattedTextFeature.kt b/src/main/java/me/alegian/thavma/impl/common/book/FormattedTextFeature.kt new file mode 100644 index 00000000..1fa9a0a8 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/FormattedTextFeature.kt @@ -0,0 +1,37 @@ +package me.alegian.thavma.impl.common.book + +import com.mojang.serialization.MapCodec +import com.mojang.serialization.codecs.RecordCodecBuilder +import me.alegian.thavma.impl.init.registries.deferred.PageFeatureTypes +import net.minecraft.network.chat.ComponentSerialization +import net.minecraft.util.FormattedCharSequence + +class FormattedTextFeature(val text: List) : PageFeature { + override val coversOneWholePage: Boolean + get() = false + override val mustStartPage: Boolean + get() = false + override val mustOccupySetPage: Boolean + get() = false + + override val type: PageFeatureType<*> + get() = PageFeatureTypes.FORMATTED.get() + + override fun toString(): String { + return "FormattedTextFeature with number of lines ${text.size}" + } + + companion object { + // this thing bypasses the nonexistent native CODEC for FormattedCharSequence + // which we don't even need + val CODEC: MapCodec = RecordCodecBuilder.mapCodec { instance -> + instance.group( + ComponentSerialization.CODEC.listOf().fieldOf("text").forGetter { _ -> + emptyList() + } + ).apply(instance) { components -> + FormattedTextFeature(components.map { it.visualOrderText }) + } + } + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/common/book/PageFeature.kt b/src/main/java/me/alegian/thavma/impl/common/book/PageFeature.kt new file mode 100644 index 00000000..5268693f --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/PageFeature.kt @@ -0,0 +1,18 @@ +package me.alegian.thavma.impl.common.book + +import me.alegian.thavma.impl.init.registries.T7Registries + +interface PageFeature { + val coversOneWholePage: Boolean + val mustStartPage: Boolean + val mustOccupySetPage: Boolean + val preferredPageIndex: Int + get() = 1 + + val type: PageFeatureType<*> + + companion object { + val CODEC = + T7Registries.PAGE_FEATURE_TYPE.byNameCodec().dispatch({ pageFeature -> pageFeature.type }, { type -> type.codec }) + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/common/book/PageFeatureType.kt b/src/main/java/me/alegian/thavma/impl/common/book/PageFeatureType.kt new file mode 100644 index 00000000..df95f3c4 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/PageFeatureType.kt @@ -0,0 +1,12 @@ +package me.alegian.thavma.impl.common.book + +import com.mojang.serialization.MapCodec +import net.minecraft.resources.ResourceLocation + +class PageFeatureType(name: ResourceLocation, val codec: MapCodec) { + val stringName = name.toString() + + override fun toString(): String { + return stringName + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/common/book/ParagraphFeature.kt b/src/main/java/me/alegian/thavma/impl/common/book/ParagraphFeature.kt new file mode 100644 index 00000000..77ae7d49 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/ParagraphFeature.kt @@ -0,0 +1,37 @@ +package me.alegian.thavma.impl.common.book + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import me.alegian.thavma.impl.init.registries.deferred.PageFeatureTypes +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.ComponentSerialization + +class ParagraphFeature( + val text: Component, + override val mustStartPage: Boolean = false, + override val mustOccupySetPage: Boolean = false, + override val preferredPageIndex: Int = 1 +) : PageFeature { + override val type: PageFeatureType<*> + get() = PageFeatureTypes.PARAGRAPH.get() + + override val coversOneWholePage = false + + override fun toString(): String { + return "ParagraphFeature with text $text" + } + + companion object { + val CODEC = RecordCodecBuilder.mapCodec { builder -> + builder.group( + ComponentSerialization.CODEC.fieldOf("text").forGetter(ParagraphFeature::text), + Codec.BOOL.optionalFieldOf("starts_page", false).forGetter(ParagraphFeature::mustStartPage), + Codec.BOOL.optionalFieldOf("has_set_page", false).forGetter(ParagraphFeature::mustOccupySetPage), + Codec.INT.optionalFieldOf("preferred_page", 1).forGetter(ParagraphFeature::preferredPageIndex) + ).apply(builder, ::ParagraphFeature) + } + + fun translationId(baseId: String, featureIndex: Int) = "$baseId.paragraph_feature$featureIndex" + } + +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/common/book/RecipeFeature.kt b/src/main/java/me/alegian/thavma/impl/common/book/RecipeFeature.kt new file mode 100644 index 00000000..d49e3297 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/RecipeFeature.kt @@ -0,0 +1,32 @@ +package me.alegian.thavma.impl.common.book + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import me.alegian.thavma.impl.init.registries.deferred.PageFeatureTypes +import net.minecraft.resources.ResourceLocation + +class RecipeFeature( + val recipeRL: ResourceLocation, override val coversOneWholePage: Boolean = true, override val mustStartPage: Boolean = true, override val mustOccupySetPage: Boolean = true, + override val preferredPageIndex: Int = 1) : PageFeature { + override val type: PageFeatureType<*> + get() = PageFeatureTypes.RECIPE.get() + + //val font: Font = Minecraft.getInstance().font + +// override val renderedHeight: Int +// get() = 96 + + companion object { + val CODEC = RecordCodecBuilder.mapCodec { builder -> + builder.group( + ResourceLocation.CODEC.fieldOf("recipeRL").forGetter(RecipeFeature::recipeRL), + Codec.BOOL.optionalFieldOf("covers_whole_page", true).forGetter(RecipeFeature::coversOneWholePage), + Codec.BOOL.optionalFieldOf("starts_page", true).forGetter(RecipeFeature::mustStartPage), + Codec.BOOL.optionalFieldOf("has_set_page", true).forGetter(RecipeFeature::mustOccupySetPage), + Codec.INT.optionalFieldOf("preferred_page", 1).forGetter(RecipeFeature::preferredPageIndex) + ).apply(builder, ::RecipeFeature) + } + + fun translationId(baseId: String, featureIndex: Int) = "$baseId.figure_feature$featureIndex" + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/common/book/TitleFeature.kt b/src/main/java/me/alegian/thavma/impl/common/book/TitleFeature.kt new file mode 100644 index 00000000..48eab757 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/TitleFeature.kt @@ -0,0 +1,44 @@ +package me.alegian.thavma.impl.common.book + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import me.alegian.thavma.impl.init.registries.deferred.PageFeatureTypes +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.ComponentSerialization + +class TitleFeature(val text: Component, override val mustStartPage: Boolean = false, override val mustOccupySetPage: Boolean = false, + override val preferredPageIndex: Int = 1): PageFeature { + + override val type: PageFeatureType<*> + get() = PageFeatureTypes.TITLE.get() + + override val coversOneWholePage = false + + override fun toString(): String { + return "TitleFeature with text $text" + } + + + + //val font: Font = Minecraft.getInstance().font + // if a recalibrated font size is used, I can multiply or divide by rendering scaling factor + //val LINE_HEIGHT = font.lineHeight + 2 + //val LINE_HEIGHT = 11 + //val lines = font.splitter.splitLines(text, pageWidth, Style.EMPTY) + + //override val renderedHeight = LINE_HEIGHT * lines.size + 16 + //override val renderedHeight = 11 + 16 + + companion object { + val CODEC = RecordCodecBuilder.mapCodec { builder -> + builder.group( + ComponentSerialization.CODEC.fieldOf("text").forGetter(TitleFeature::text), + Codec.BOOL.optionalFieldOf("starts_page", false).forGetter(TitleFeature::mustStartPage), + Codec.BOOL.optionalFieldOf("has_set_page", false).forGetter(TitleFeature::mustOccupySetPage), + Codec.INT.optionalFieldOf("preferred_page", 1).forGetter(TitleFeature::preferredPageIndex) + ).apply(builder, ::TitleFeature) + } + + fun translationId(baseId: String, featureIndex: Int) = "$baseId.title_feature$featureIndex" + } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/common/event/T7CommonModEvents.kt b/src/main/java/me/alegian/thavma/impl/common/event/T7CommonModEvents.kt index c8e153a9..48736e27 100644 --- a/src/main/java/me/alegian/thavma/impl/common/event/T7CommonModEvents.kt +++ b/src/main/java/me/alegian/thavma/impl/common/event/T7CommonModEvents.kt @@ -40,6 +40,7 @@ private fun registerRegistries(event: NewRegistryEvent) { event.register(T7Registries.WAND_CORE) event.register(T7Registries.ASPECT) event.register(T7Registries.PAGE_TYPE) + event.register(T7Registries.PAGE_FEATURE_TYPE) } private fun registerDatapackRegistries(event: DataPackRegistryEvent.NewRegistry) { diff --git a/src/main/java/me/alegian/thavma/impl/common/research/ResearchEntry.kt b/src/main/java/me/alegian/thavma/impl/common/research/ResearchEntry.kt index d069b396..158674b8 100644 --- a/src/main/java/me/alegian/thavma/impl/common/research/ResearchEntry.kt +++ b/src/main/java/me/alegian/thavma/impl/common/research/ResearchEntry.kt @@ -4,6 +4,7 @@ import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder import me.alegian.thavma.impl.Thavma import me.alegian.thavma.impl.common.book.Page +import me.alegian.thavma.impl.common.book.PageFeature import me.alegian.thavma.impl.common.util.T7ExtraCodecs import me.alegian.thavma.impl.common.util.registry import me.alegian.thavma.impl.init.registries.T7DatapackRegistries @@ -20,15 +21,15 @@ import org.joml.Vector2i private val parentsMap = mutableMapOf>>() class ResearchEntry( - val category: Holder, - val position: Vector2i, - val preferX: Boolean, - val children: List>, - val pages: List, - val icon: ItemStack, - val title: Component, - val defaultResearchState: List, - val defaultKnown: Boolean + val category: Holder, + val position: Vector2i, + val preferX: Boolean, + val children: List>, + val pageFeatures: List, + val icon: ItemStack, + val title: Component, + val defaultResearchState: List, + val defaultKnown: Boolean ) { fun parents(level: Level) = parentsMap.computeIfAbsent(this) { _ -> @@ -43,9 +44,8 @@ class ResearchEntry( .forGetter(ResearchEntry::category), T7ExtraCodecs.VECTOR2I.fieldOf("position").forGetter(ResearchEntry::position), Codec.BOOL.fieldOf("preferX").forGetter(ResearchEntry::preferX), - RegistryFileCodec.create(T7DatapackRegistries.RESEARCH_ENTRY, CODEC).listOf().fieldOf("children") - .forGetter(ResearchEntry::children), - Page.CODEC.listOf().fieldOf("pages").forGetter(ResearchEntry::pages), + RegistryFileCodec.create(T7DatapackRegistries.RESEARCH_ENTRY, CODEC).listOf().fieldOf("children").forGetter(ResearchEntry::children), + PageFeature.CODEC.listOf().fieldOf("pageFeatures").forGetter(ResearchEntry::pageFeatures), ItemStack.STRICT_CODEC.fieldOf("icon").forGetter(ResearchEntry::icon), ComponentSerialization.CODEC.fieldOf("title").forGetter(ResearchEntry::title), SocketState.CODEC.listOf().fieldOf("defaultResearchState").forGetter(ResearchEntry::defaultResearchState), diff --git a/src/main/java/me/alegian/thavma/impl/init/data/providers/T7DatapackBuiltinEntriesProvider.kt b/src/main/java/me/alegian/thavma/impl/init/data/providers/T7DatapackBuiltinEntriesProvider.kt index a74a11a7..37de2e45 100644 --- a/src/main/java/me/alegian/thavma/impl/init/data/providers/T7DatapackBuiltinEntriesProvider.kt +++ b/src/main/java/me/alegian/thavma/impl/init/data/providers/T7DatapackBuiltinEntriesProvider.kt @@ -1,10 +1,16 @@ package me.alegian.thavma.impl.init.data.providers import me.alegian.thavma.impl.Thavma +import me.alegian.thavma.impl.client.texture.Texture import me.alegian.thavma.impl.common.aspect.Aspect import me.alegian.thavma.impl.common.book.CraftingPage +import me.alegian.thavma.impl.common.book.FigureFeature import me.alegian.thavma.impl.common.book.Page +import me.alegian.thavma.impl.common.book.PageFeature +import me.alegian.thavma.impl.common.book.ParagraphFeature +import me.alegian.thavma.impl.common.book.RecipeFeature import me.alegian.thavma.impl.common.book.TextPage +import me.alegian.thavma.impl.common.book.TitleFeature import me.alegian.thavma.impl.common.enchantment.ShriekResistance.LOCATION import me.alegian.thavma.impl.common.research.ResearchCategory import me.alegian.thavma.impl.common.research.ResearchEntry @@ -33,6 +39,7 @@ import net.minecraft.data.PackOutput import net.minecraft.data.worldgen.BootstrapContext import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceKey +import net.minecraft.resources.ResourceLocation import net.minecraft.tags.ItemTags import net.minecraft.world.entity.EquipmentSlotGroup import net.minecraft.world.item.ItemStack @@ -134,16 +141,19 @@ class T7DatapackBuiltinEntriesProvider(output: PackOutput, registries: Completab .defaultKnown() .build(ctx) - ResearchEntryBuilder( - ResearchEntries.Story.STORY1, - Vector2i(0, -3), - false, - Items.TURTLE_HELMET.defaultInstance - ) + ResearchEntryBuilder(ResearchEntries.Story.STORY1, Vector2i(0, 0), false, Items.TURTLE_HELMET.defaultInstance) .research() - .addPage(simpleTextPage(2, true)) - .addPage(simpleTextPage(2, true)) - .addPage(simpleTextPage(2, true)) + .addPageFeature(makeTitleFeature()) + .addPageFeature(makeParagraphFeature(false, false)) + .addPageFeature(makeParagraphFeature()) + .addPageFeature(makeFigureFeature(Texture("gui/images/haybales", 180, 101, 180, 101), true, false, false, 1, ChatFormatting.DARK_AQUA, ChatFormatting.ITALIC)) + .addPageFeature(makeTitleFeature(false)) + .addPageFeature(makeParagraphFeature(true)) + .addPageFeature(makeTitleFeature(true, false)) + .addPageFeature(makeTitleFeature(true, true, 0)) + .addPageFeature(makeParagraphFeature()) + .addPageFeature(makeFigureFeature(Texture("gui/images/smileyface", 87, 77, 87, 77), false, false, true, 5)) + .addPageFeature(makeParagraphFeature(false, true, 5)) .defaultKnown() .build(ctx) @@ -261,6 +271,7 @@ private class ResearchEntryBuilder( private val icon: ItemStack ) { private val children = mutableListOf>() + private val pageFeatures = mutableListOf() private val pages = mutableListOf() private val socketStates = mutableListOf() private var defaultKnown = false @@ -275,6 +286,11 @@ private class ResearchEntryBuilder( return this } + inline fun addPageFeature(crossinline makeFeature: (ResourceKey, Int) -> T): ResearchEntryBuilder { + pageFeatures.add(makeFeature(key, pageFeatures.filterIsInstance().size)) + return this + } + fun research(vararg states: SocketState): ResearchEntryBuilder { socketStates.addAll(states) return this @@ -297,7 +313,7 @@ private class ResearchEntryBuilder( pos, preferX, childrenHolders, - pages, + pageFeatures, icon, Component.translatable(ResearchEntry.translationId(key)).withStyle(Rarity.UNCOMMON.styleModifier), socketStates, @@ -325,6 +341,93 @@ private fun simpleTextPage(paragraphCount: Int, hasTitle: Boolean): (ResourceKey } } +private fun makeParagraphFeature( + mustStartPage: Boolean = false, + mustOccupySetPage: Boolean = false, + preferredPageIndex: Int = 1 +): (ResourceKey, Int) -> ParagraphFeature { + return { entryKey, paragraphIndex -> + val baseId = ResearchEntry.translationId(entryKey) + ParagraphFeature( + Component.translatable(ParagraphFeature.translationId(baseId, paragraphIndex)), + mustStartPage, mustOccupySetPage, preferredPageIndex + ) + } +} + +private fun makeStyledParagraphFeature( + mustStartPage: Boolean = false, + mustOccupySetPage: Boolean = false, + preferredPageIndex: Int = 1, + vararg styles: ChatFormatting? +): (ResourceKey, Int) -> ParagraphFeature { + return { entryKey, paragraphIndex -> + val baseId = ResearchEntry.translationId(entryKey) + val content = Component.translatable(ParagraphFeature.translationId(baseId, paragraphIndex)) + for (i in styles) { + content.apply { + if (i != null) { + this.withStyle(i) + } + } + } + ParagraphFeature(content, mustStartPage, mustOccupySetPage, preferredPageIndex) + } +} + +private fun makeTitleFeature( + mustStartPage: Boolean = true, + mustOccupySetPage: Boolean = false, + preferredPageIndex: Int = 1 +): (ResourceKey, Int) -> TitleFeature { + return { entryKey, titleIndex -> + val baseId = ResearchEntry.translationId(entryKey) + TitleFeature( + Component.translatable(TitleFeature.translationId(baseId, titleIndex)).withStyle(ChatFormatting.BOLD), + mustStartPage, mustOccupySetPage, preferredPageIndex + ) + } +} + +private fun makeFigureFeature( + image: Texture, + giveCaption: Boolean, + mustStartPage: Boolean = false, + mustOccupySetPage: Boolean = false, + preferredPageIndex: Int = 1, + vararg styles: ChatFormatting? +): (ResourceKey, Int) -> FigureFeature { + return if (giveCaption) { entryKey, figureIndex -> + val baseId = ResearchEntry.translationId(entryKey) + val content = Component.translatable(FigureFeature.translationId(baseId, figureIndex)) + for (i in styles) { + content.apply { + if (i != null) { + this.withStyle(i) + } + } + } + FigureFeature(image, content, mustStartPage, mustOccupySetPage, preferredPageIndex) + } else { _, _ -> + FigureFeature(image, null, mustStartPage, mustOccupySetPage, preferredPageIndex) + } +} + +private fun makeRecipeFeature( + recipeRL: ResourceLocation, + coversOneWholePage: Boolean = true, + mustStartPage: Boolean = true, + mustOccupySetPage: Boolean = true, + preferredPageIndex: Int = 1 +): (ResourceKey, Int) -> RecipeFeature { + return { _, _ -> + RecipeFeature( + recipeRL, coversOneWholePage, mustStartPage, mustOccupySetPage, + preferredPageIndex + ) + } +} + private fun simpleTitle(pageIndex: Int, baseId: String) = Component.translatable(TextPage.titleTranslationId(baseId, pageIndex)).withStyle(ChatFormatting.BOLD) diff --git a/src/main/java/me/alegian/thavma/impl/init/data/providers/T7LanguageProvider.kt b/src/main/java/me/alegian/thavma/impl/init/data/providers/T7LanguageProvider.kt index dcb04a86..aa7e3576 100644 --- a/src/main/java/me/alegian/thavma/impl/init/data/providers/T7LanguageProvider.kt +++ b/src/main/java/me/alegian/thavma/impl/init/data/providers/T7LanguageProvider.kt @@ -11,23 +11,14 @@ import me.alegian.thavma.impl.client.gui.tooltip.AspectClientTooltipComponent import me.alegian.thavma.impl.common.block.HungryChestBlock import me.alegian.thavma.impl.common.block.ResearchTableBlock import me.alegian.thavma.impl.common.block.WorkbenchBlock -import me.alegian.thavma.impl.common.book.TextPage +import me.alegian.thavma.impl.common.book.* import me.alegian.thavma.impl.common.recipe.translationId import me.alegian.thavma.impl.common.research.ResearchCategory import me.alegian.thavma.impl.common.research.ResearchEntry import me.alegian.thavma.impl.common.wand.WandCoreMaterial import me.alegian.thavma.impl.common.wand.WandPlatingMaterial import me.alegian.thavma.impl.init.registries.T7Tags -import me.alegian.thavma.impl.init.registries.deferred.Aspects -import me.alegian.thavma.impl.init.registries.deferred.T7Blocks -import me.alegian.thavma.impl.init.registries.deferred.T7Items -import me.alegian.thavma.impl.init.registries.deferred.T7EntityTypes -import me.alegian.thavma.impl.init.registries.deferred.ResearchEntries -import me.alegian.thavma.impl.init.registries.deferred.ResearchCategories -import me.alegian.thavma.impl.init.registries.deferred.T7Attributes -import me.alegian.thavma.impl.init.registries.deferred.T7RecipeTypes -import me.alegian.thavma.impl.init.registries.deferred.WandCoreMaterials -import me.alegian.thavma.impl.init.registries.deferred.WandPlatingMaterials +import me.alegian.thavma.impl.init.registries.deferred.* import me.alegian.thavma.impl.integration.RecipeViewerAliases import me.alegian.thavma.impl.integration.RecipeViewerDescriptions import net.minecraft.Util @@ -68,7 +59,7 @@ class T7LanguageProvider(output: PackOutput, locale: String) : LanguageProvider( add(T7Items.ORICHALCUM_NUGGET.get(), "Orichalcum Nugget") add(T7Items.RESEARCH_SCROLL.get(), "Research Scroll") add(T7Items.ARCANE_LENS.get(), "Arcane Lens") - add(T7Items.BOOK.get(), "Elements of Thavma") + add(T7Items.BOOK.get(), "Elements") add(T7Items.BASIC_AMULET.get(), "Basic Amulet") add(T7Items.BASIC_BELT.get(), "Basic Belt") @@ -207,25 +198,50 @@ class T7LanguageProvider(output: PackOutput, locale: String) : LanguageProvider( addCategory(ResearchCategories.STORY, "???") addEntry(ResearchEntries.Story.STORY1, "A Courtesy Call") - addTextPage( - ResearchEntries.Story.STORY1, 0, - "A Courtesy Call 1", - "Lorem ipsum %s 1 sit amet,", - "this story a great meaning haveth." + addPageFeature(TITLE, ResearchEntries.Story.STORY1, 0, "A courtesy call starts the page") + addPageFeature( + PARAGRAPH, + ResearchEntries.Story.STORY1, 0, """ + This is a sample paragraph that does not start a page. + """ ) - - addTextPage( - ResearchEntries.Story.STORY1, 1, - "A Courtesy Call 2", - "Lorem dolor 2 sit amet,", - "this story a great meaning haveth." + addPageFeature( + PARAGRAPH, + ResearchEntries.Story.STORY1, 1, """ + Another paragraph just to make the content on the page stretch out a little longer. + Possible double trimIndent() in the LanguageProvider does not cause any trouble. + """.trimIndent() ) - - addTextPage( - ResearchEntries.Story.STORY1, 2, - "A Courtesy Call 3", - "Lorem lotrumatum dolor 3 sit amet,", - "this story a great meaning haveth." + addPageFeature( + FIGURE, + ResearchEntries.Story.STORY1, 0, """ +What follows is generic lorem ipsum so the text looks natural. +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque dapibus mattis lectus, quis aliquet ex. In hac habitasse platea dictumst. Praesent dignissim urna at feugiat pulvinar. Suspendisse laoreet lorem ut velit venenatis gravida. +Donec posuere diam est, ac malesuada libero fermentum sed. Phasellus ac cursus nibh, eget pharetra leo. Maecenas scelerisque velit massa, sit amet tincidunt nulla dictum non. Sed egestas congue bibendum. Aenean facilisis nunc vitae purus tincidunt, +sit amet dignissim libero gravida. Mauris vel tortor elit. Curabitur sit amet nisi sagittis, ullamcorper diam sed, condimentum est. Etiam blandit ac magna sit amet luctus. Duis nec mi tincidunt nunc. + """.trimIndent() + ) + addPageFeature(TITLE, ResearchEntries.Story.STORY1, 1, "This title might appear in the middle") + addPageFeature( + PARAGRAPH, + ResearchEntries.Story.STORY1, 2, """ + This paragraph should start a new page always. It has the mustStartPage property set to true. + """.trimIndent() + ) + addPageFeature(TITLE, ResearchEntries.Story.STORY1, 2, "(start of page)") + addPageFeature( + TITLE, + ResearchEntries.Story.STORY1, 3, """ + This is page number 1! + """.trimIndent() + ) + addPageFeature(PARAGRAPH, ResearchEntries.Story.STORY1, 3, "Just another random little paragraph :D") + addPageFeature( + PARAGRAPH, + ResearchEntries.Story.STORY1, 4, """ + This paragraph is inserted into the middle without it + being an image caption (pre-set page number) + """.trimIndent() ) addTextPage( @@ -236,7 +252,7 @@ class T7LanguageProvider(output: PackOutput, locale: String) : LanguageProvider( flew into my hands! I can sense great power within it. """, """ - The cover reads "Elements of Thavma", but a lot of its pages appear blank, sealed by some magic. + The cover reads "Elements", but a lot of its pages appear blank, sealed by some magic. """, """ To read them, I will first need to break that seal. It won't be easy... but @@ -359,4 +375,21 @@ class T7LanguageProvider(output: PackOutput, locale: String) : LanguageProvider( paragraphs[parIndex].trimIndent().replace("\n", " ") ) } + + private fun addPageFeature(identifier: Char, entryKey: ResourceKey, featureIndex: Int, text: String) { + val baseId = ResearchEntry.translationId(entryKey) + when (identifier) { + 'P' -> add(ParagraphFeature.translationId(baseId, featureIndex), text.trimIndent().replace("\n", " ")) + 'T' -> add(TitleFeature.translationId(baseId, featureIndex), text.trimIndent().replace("\n", " ")) + 'F' -> add(FigureFeature.translationId(baseId, featureIndex), text.trimIndent().replace("\n", " ")) + 'R' -> add(RecipeFeature.translationId(baseId, featureIndex), text.trimIndent().replace("\n", " ")) + } + } + + companion object { + val PARAGRAPH = 'P' + val TITLE = 'T' + val FIGURE = 'F' + val RECIPE = 'R' + } } diff --git a/src/main/java/me/alegian/thavma/impl/init/registries/T7Registries.kt b/src/main/java/me/alegian/thavma/impl/init/registries/T7Registries.kt index 74aac755..e9c61a7d 100644 --- a/src/main/java/me/alegian/thavma/impl/init/registries/T7Registries.kt +++ b/src/main/java/me/alegian/thavma/impl/init/registries/T7Registries.kt @@ -1,6 +1,7 @@ package me.alegian.thavma.impl.init.registries import me.alegian.thavma.impl.common.aspect.Aspect +import me.alegian.thavma.impl.common.book.PageFeatureType import me.alegian.thavma.impl.common.book.PageType import me.alegian.thavma.impl.common.wand.WandCoreMaterial import me.alegian.thavma.impl.common.wand.WandPlatingMaterial @@ -25,4 +26,8 @@ object T7Registries { val PAGE_TYPE = RegistryBuilder(ResourceKey.createRegistryKey>(rl("page_type"))) .maxId(Int.MAX_VALUE) .create() + + val PAGE_FEATURE_TYPE = RegistryBuilder(ResourceKey.createRegistryKey>(rl("page_feature_type"))) + .maxId(Int.MAX_VALUE) + .create() } diff --git a/src/main/java/me/alegian/thavma/impl/init/registries/deferred/PageFeatureTypes.kt b/src/main/java/me/alegian/thavma/impl/init/registries/deferred/PageFeatureTypes.kt new file mode 100644 index 00000000..c9e6397c --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/init/registries/deferred/PageFeatureTypes.kt @@ -0,0 +1,18 @@ +package me.alegian.thavma.impl.init.registries.deferred + +import me.alegian.thavma.impl.Thavma +import me.alegian.thavma.impl.common.book.* +import me.alegian.thavma.impl.init.registries.T7Registries +import me.alegian.thavma.impl.rl +import net.neoforged.neoforge.registries.DeferredRegister + +object PageFeatureTypes { + val REGISTRAR = DeferredRegister.create(T7Registries.PAGE_FEATURE_TYPE.key(), Thavma.MODID) + + val PARAGRAPH = + REGISTRAR.register("paragraph") { -> PageFeatureType(rl("paragraph"), ParagraphFeature.CODEC) } + val FORMATTED = REGISTRAR.register("formatted") { -> PageFeatureType(rl("formatted"), FormattedTextFeature.CODEC) } + val TITLE = REGISTRAR.register("title") { -> PageFeatureType(rl("title"), TitleFeature.CODEC) } + val FIGURE = REGISTRAR.register("figure") { -> PageFeatureType(rl("figure"), FigureFeature.CODEC) } + val RECIPE = REGISTRAR.register("recipe") { -> PageFeatureType(rl("recipe"), RecipeFeature.CODEC) } +} \ No newline at end of file diff --git a/src/main/java/me/alegian/thavma/impl/init/registries/deferred/PageTypes.kt b/src/main/java/me/alegian/thavma/impl/init/registries/deferred/PageTypes.kt index bec6449b..d38dd18e 100644 --- a/src/main/java/me/alegian/thavma/impl/init/registries/deferred/PageTypes.kt +++ b/src/main/java/me/alegian/thavma/impl/init/registries/deferred/PageTypes.kt @@ -2,6 +2,8 @@ package me.alegian.thavma.impl.init.registries.deferred import me.alegian.thavma.impl.Thavma import me.alegian.thavma.impl.common.book.CraftingPage +import me.alegian.thavma.impl.common.book.DynamicPage +import me.alegian.thavma.impl.common.book.PageFeature import me.alegian.thavma.impl.common.book.PageType import me.alegian.thavma.impl.common.book.TextPage import me.alegian.thavma.impl.init.registries.T7Registries @@ -10,7 +12,6 @@ import net.neoforged.neoforge.registries.DeferredRegister object PageTypes { val REGISTRAR = DeferredRegister.create(T7Registries.PAGE_TYPE.key(), Thavma.MODID) - val TEXT = REGISTRAR.register("text") { -> PageType(rl("text"), TextPage.CODEC) } val CRAFTING = REGISTRAR.register("crafting") { -> PageType(rl("crafting"), CraftingPage.CODEC) } } \ No newline at end of file diff --git a/src/main/resources/assets/thavma/textures/gui/images/haybales.png b/src/main/resources/assets/thavma/textures/gui/images/haybales.png new file mode 100644 index 00000000..a8e1cd2f Binary files /dev/null and b/src/main/resources/assets/thavma/textures/gui/images/haybales.png differ diff --git a/src/main/resources/assets/thavma/textures/gui/images/smileyface.png b/src/main/resources/assets/thavma/textures/gui/images/smileyface.png new file mode 100644 index 00000000..96ec3598 Binary files /dev/null and b/src/main/resources/assets/thavma/textures/gui/images/smileyface.png differ