From 144c6f7e76bb2464e219ca18a0a76591e3216bf8 Mon Sep 17 00:00:00 2001 From: KorrAn34 Date: Wed, 8 Jul 2026 16:50:34 +0200 Subject: [PATCH 1/8] First half of initial commit of original Dynamic Rendering --- .../thavma/impl/common/book/PageFeature.kt | 17 +++++++++++++ .../impl/common/book/PageFeatureType.kt | 12 +++++++++ .../impl/common/book/ParagraphFeature.kt | 25 +++++++++++++++++++ .../thavma/impl/common/book/TitleFeature.kt | 22 ++++++++++++++++ .../impl/init/registries/T7Registries.kt | 5 ++++ .../registries/deferred/PageFeatureTypes.kt | 18 +++++++++++++ 6 files changed, 99 insertions(+) create mode 100644 src/main/java/me/alegian/thavma/impl/common/book/PageFeature.kt create mode 100644 src/main/java/me/alegian/thavma/impl/common/book/PageFeatureType.kt create mode 100644 src/main/java/me/alegian/thavma/impl/common/book/ParagraphFeature.kt create mode 100644 src/main/java/me/alegian/thavma/impl/common/book/TitleFeature.kt create mode 100644 src/main/java/me/alegian/thavma/impl/init/registries/deferred/PageFeatureTypes.kt 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..2bc145a5 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/PageFeature.kt @@ -0,0 +1,17 @@ +package me.alegian.thavma.impl.common.book + +import me.alegian.thavma.impl.init.registries.T7Registries + +interface PageFeature { + + val startsPage: Boolean + val forceIndex: Int? + get() = null + + val type: PageFeatureType<*> + + companion object { + val CODEC = + T7Registries.PAGE_FEATURE_TYPE.byNameCodec().dispatch({ pageFeature -> pageFeature.type }, { type -> type.codec }) + } +} 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..83b3ca40 --- /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..247bc96a --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/ParagraphFeature.kt @@ -0,0 +1,25 @@ +package me.alegian.thavma.impl.common.book + +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 startsPage: Boolean = false, + override val forceIndex: Int? = null +) : PageFeature { + override val type: PageFeatureType<*> + get() = PageFeatureTypes.PARAGRAPH.get() + + companion object { + val CODEC = RecordCodecBuilder.mapCodec { builder -> + builder.group( + ComponentSerialization.CODEC.fieldOf("text").forGetter(ParagraphFeature::text), + ).apply(builder, ::ParagraphFeature) + } + + fun translationId(baseId: String, featureIndex: Int) = "$baseId.paragraphFeature$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..75f4535c --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/TitleFeature.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.PageFeatureTypes +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.ComponentSerialization + +class TitleFeature(val text: Component, override val startsPage: Boolean = true, override val forceIndex: Int? = null) : + PageFeature { + override val type: PageFeatureType<*> + get() = PageFeatureTypes.TITLE.get() + + companion object { + val CODEC = RecordCodecBuilder.mapCodec { builder -> + builder.group( + ComponentSerialization.CODEC.fieldOf("text").forGetter(TitleFeature::text) + ).apply(builder, ::TitleFeature) + } + + fun translationId(baseId: String, featureIndex: Int) = "$baseId.titleFeature$featureIndex" + } +} \ No newline at end of file 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..a717b08c 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"))) + .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..5e45ae80 --- /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.PageFeatureType +import me.alegian.thavma.impl.common.book.ParagraphFeature +import me.alegian.thavma.impl.common.book.TitleFeature +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 TITLE = REGISTRAR.register("title") { -> PageFeatureType(rl("paragraph"), TitleFeature.CODEC) } + +} \ No newline at end of file From f62c31339aa58db4ba29c9f409a343a89ab8197e Mon Sep 17 00:00:00 2001 From: KorrAn34 Date: Wed, 15 Jul 2026 19:57:10 +0200 Subject: [PATCH 2/8] All the way until second to last commit, EntryScreen & DynamicRenderingHelper await, no adjustable size and each feature on a separate page for simplicity --- .../java/me/alegian/thavma/impl/Thavma.kt | 1 + .../RegisterPageFeatureRenderersEvent.kt | 14 ++ .../impl/client/event/T7ClientModEvents.kt | 13 +- .../client/gui/book/DynamicRenderingHelper.kt | 232 ++++++++++++++++++ .../impl/client/gui/book/EntryScreen.kt | 47 +++- .../client/gui/book/FigureFeatureRenderer.kt | 25 ++ .../gui/book/FormattedCharSeqRenderer.kt | 37 +++ .../client/gui/book/PageFeatureRenderer.kt | 11 + .../gui/book/ParagraphFeatureRenderer.kt | 38 +++ .../client/gui/book/RecipeFeatureRenderer.kt | 52 ++++ .../client/gui/book/TitleFeatureRenderer.kt | 55 +++++ .../client/gui/layout/LayoutExtensions.kt | 35 ++- .../thavma/impl/client/texture/Texture.kt | 22 +- .../impl/client/util/GuiGraphicsExtensions.kt | 4 + .../thavma/impl/common/book/FigureFeature.kt | 37 +++ .../impl/common/book/FormCharSeqFeature.kt | 29 +++ .../impl/common/book/ParagraphFeature.kt | 5 +- .../thavma/impl/common/book/RecipeFeature.kt | 25 ++ .../thavma/impl/common/book/TitleFeature.kt | 7 +- .../impl/common/event/T7CommonModEvents.kt | 1 + .../impl/common/research/ResearchEntry.kt | 8 +- .../T7DatapackBuiltinEntriesProvider.kt | 135 ++++++++-- .../init/data/providers/T7LanguageProvider.kt | 83 ++++--- .../impl/init/registries/T7Registries.kt | 2 +- .../registries/deferred/PageFeatureTypes.kt | 11 +- .../thavma/textures/gui/images/haybales.png | Bin 0 -> 31177 bytes .../thavma/textures/gui/images/smileyface.png | Bin 0 -> 7471 bytes 27 files changed, 862 insertions(+), 67 deletions(-) create mode 100644 src/main/java/me/alegian/thavma/impl/client/event/RegisterPageFeatureRenderersEvent.kt create mode 100644 src/main/java/me/alegian/thavma/impl/client/gui/book/DynamicRenderingHelper.kt create mode 100644 src/main/java/me/alegian/thavma/impl/client/gui/book/FigureFeatureRenderer.kt create mode 100644 src/main/java/me/alegian/thavma/impl/client/gui/book/FormattedCharSeqRenderer.kt create mode 100644 src/main/java/me/alegian/thavma/impl/client/gui/book/PageFeatureRenderer.kt create mode 100644 src/main/java/me/alegian/thavma/impl/client/gui/book/ParagraphFeatureRenderer.kt create mode 100644 src/main/java/me/alegian/thavma/impl/client/gui/book/RecipeFeatureRenderer.kt create mode 100644 src/main/java/me/alegian/thavma/impl/client/gui/book/TitleFeatureRenderer.kt create mode 100644 src/main/java/me/alegian/thavma/impl/common/book/FigureFeature.kt create mode 100644 src/main/java/me/alegian/thavma/impl/common/book/FormCharSeqFeature.kt create mode 100644 src/main/java/me/alegian/thavma/impl/common/book/RecipeFeature.kt create mode 100644 src/main/resources/assets/thavma/textures/gui/images/haybales.png create mode 100644 src/main/resources/assets/thavma/textures/gui/images/smileyface.png 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/T7ClientModEvents.kt b/src/main/java/me/alegian/thavma/impl/client/event/T7ClientModEvents.kt index 2dddd63a..f2e44637 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.FORMCHARSEQ.get(), FormCharSeqRenderer) + 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/book/DynamicRenderingHelper.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/DynamicRenderingHelper.kt new file mode 100644 index 00000000..06d6cd46 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/DynamicRenderingHelper.kt @@ -0,0 +1,232 @@ +package me.alegian.thavma.impl.client.gui.book + +import me.alegian.thavma.impl.common.book.FigureFeature +import me.alegian.thavma.impl.common.book.FormCharSeqFeature +import me.alegian.thavma.impl.common.book.PageFeature +import me.alegian.thavma.impl.common.book.ParagraphFeature +import net.minecraft.client.Minecraft +import net.minecraft.network.chat.Style + +val LINE_HEIGHT = Minecraft.getInstance().font.lineHeight + 2 +const val PARAGRAPH_OFFSET = 2 / 3f +val PRG_OFFSET_OTHER = LINE_HEIGHT * 2 / 3 + + +/** + * Return a list of PageFeatures containing a list of lines - FormattedText + * or possibly a Figure with just the image and its processed caption - FormattedText, + * so that they all fit on their + * respective pages (represented by list indices) + */ +fun spliceParagraphOrFigure( + input: PageFeature, maxPageHeight: Int, + currentHeight: Int +): List { + val result = mutableListOf() + + if (input is ParagraphFeature) { + // how many lines fit in the current page + val lineHeight = input.LINE_HEIGHT + + // so that we get at least 2 lines at the end of the first page + 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 - linesRemainingAtStart * lineHeight) / maxPageHeight / lineHeight + val linesCroppingOutAtEnd: Int = + (input.renderedHeight - linesRemainingAtStart * lineHeight - numOfLinesCoveringFullPages * lineHeight) / lineHeight + + val maxLinesPerPage = maxPageHeight / lineHeight + val numOfFullPagesCovered = numOfLinesCoveringFullPages / maxLinesPerPage + val lines = input.font.splitter.splitLines(input.text, input.pageWidth, Style.EMPTY) + + when { + lines.size == 1 && currentHeight + lineHeight <= maxPageHeight -> result += FormCharSeqFeature(lines) + + lines.size == 1 -> result += listOf( + FormCharSeqFeature(listOf()), + FormCharSeqFeature(lines) + ) + + linesRemainingAtStart != 1 && linesCroppingOutAtEnd != 1 -> { + // separate into "underhang", middle and overhang + val start = lines.slice(0 until linesRemainingAtStart) + result += FormCharSeqFeature(start) + + for (i in 0 until numOfFullPagesCovered) { + val middle = + lines.slice(linesRemainingAtStart + i * maxLinesPerPage until linesRemainingAtStart + (i + 1) * maxLinesPerPage) + result += FormCharSeqFeature(middle) + } + + val end = + lines.slice(lines.size - 1 - linesCroppingOutAtEnd until lines.size - 1) + result += FormCharSeqFeature(end) + } + + else -> { + // add an empty paragraph to current page and continue on the next + result += FormCharSeqFeature(listOf()) + for (i in 0 until numOfFullPagesCovered) { + val page = lines.slice(i * maxLinesPerPage until (i + 1) * maxLinesPerPage) + result += FormCharSeqFeature(page) + } + val finish = + lines.slice(numOfFullPagesCovered * maxLinesPerPage until lines.size) + result += FormCharSeqFeature(finish) + } + } + return result + } + + if (input is FigureFeature) { + with(input) { + when { + caption == null && currentHeight + textureHeight <= maxPageHeight -> result += input + caption == null -> { + result += listOf() + result += this + } + + caption != null && currentHeight + textureHeight <= maxPageHeight -> { + result += FigureFeature(image, null) + result.addAll( + spliceParagraphOrFigure( + ParagraphFeature(caption), + maxPageHeight, + currentHeight + textureHeight + ) + ) + } + + caption != null -> { + result += FormCharSeqFeature(listOf()) + result += FigureFeature(image, null) + result.addAll(spliceParagraphOrFigure(ParagraphFeature(caption), maxPageHeight, textureHeight)) + } + } + } + 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): List> { + // maxHeight is height of background texture minus padding (32 top 42 bottom) + //val maxHeight = this@EntryScreen.height - 74 + val partition = features.partition { !it.mustOccupySetPage } + val pages = mutableListOf>() + val buffer = mutableListOf() + fun currentHeight() = buffer.sumOf { it.renderedHeight } + + // 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 > maxHeight -> throw IllegalArgumentException( + "The size of the element ${this::class.simpleName} is too large at $renderedHeight while allowed $maxHeight." + ) + + coversOneWholePage -> { + if (buffer.isNotEmpty()) { + pages += buffer + buffer.clear() + } + pages += listOf(this) + } + + mustStartPage -> { + if (buffer.isNotEmpty()) { + pages += buffer + buffer.clear() + } + if (this is ParagraphFeature || this is FigureFeature) { + val processed = spliceParagraphOrFigure(this, maxHeight, currentHeight()) + if (processed.size == 1) buffer += processed.first() + // only need to check this much since buffer is empty (starts page) + // applies to both Paragraph and Figure + else { + processed.slice(0 until processed.size - 1).forEach { pages += listOf(it) } + buffer += processed.last() + } + } else buffer += this + } + + (this is ParagraphFeature || this is FigureFeature) -> { + val processed = spliceParagraphOrFigure(this, maxHeight, currentHeight()) + if (processed.size < 2) buffer += processed.first() + else if (this is ParagraphFeature) { + buffer += processed.first() + pages += buffer + buffer.clear() + 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 FormCharSeqFeature, + // it's an empty one to signify end of page. Size 2 means + // there is only this emptiness + image. + processed.first() is FormCharSeqFeature && processed.size == 2 -> { + pages += buffer + buffer.clear() + 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 FormCharSeqFeature) { + pages += buffer + buffer.clear() + 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) { + pages += buffer + buffer.clear() + for (j in 2 + i until processed.size - 1) { + pages += listOf(processed[j]) + } + buffer += processed[processed.size - 1] + } + } + } + } + } + + currentHeight() + renderedHeight <= 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 while allowed $maxHeight.") + else -> { + pages += buffer + buffer.clear() + buffer += this + } + } + } + } + + // add anything left over in the buffer + if (buffer.isNotEmpty()) pages += buffer + + // finally add features with pre-determined positions (cannot be 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..99571577 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 @@ -3,18 +3,22 @@ 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(val 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 + override fun init() { super.init() @@ -76,6 +80,40 @@ class EntryScreen(private val entry: Holder) : Screen(Component.l } } + private fun PageTurnerRow() { + Row({ + width = grow() + }) { + 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 @@ -93,5 +131,12 @@ class EntryScreen(private val entry: Holder) : Screen(Component.l renderer.initPage(this, page) } + private fun initPageFeature(feature: T) { + if (feature != null) { + val renderer = PAGE_FEATURE_RENDERERS[feature.type] as PageFeatureRenderer + renderer.initPageFeature(this, feature, maxWidth, fontify) + } + } + override fun isPauseScreen() = false } \ No newline at end of file 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..890388fd --- /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 + ) { + 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/FormattedCharSeqRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/FormattedCharSeqRenderer.kt new file mode 100644 index 00000000..1051d045 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/FormattedCharSeqRenderer.kt @@ -0,0 +1,37 @@ +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.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.FormCharSeqFeature +import net.minecraft.client.gui.Font +import net.minecraft.client.gui.components.Renderable + +object FormCharSeqRenderer : PageFeatureRenderer { + + override fun initPageFeature( + screen: EntryScreen, + feature: FormCharSeqFeature, + maxWidth: Int, + font: Font + ) { + + Row({ + width = grow() + height = fixed(LINE_HEIGHT * (feature.text.size + PARAGRAPH_OFFSET)) + }) { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.usePose { + for (line in feature.text) { + guiGraphics.drawString(font, line) + translateXY(0, LINE_HEIGHT) + } + translateXY(0, PRG_OFFSET_OTHER) + } + } + } + } +} 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..33da2343 --- /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) +} \ 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..ac259efc --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/ParagraphFeatureRenderer.kt @@ -0,0 +1,38 @@ +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.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 + +object ParagraphFeatureRenderer : PageFeatureRenderer { + + override fun initPageFeature( + screen: EntryScreen, + feature: ParagraphFeature, + maxWidth: Int, + font: Font + ) { + + val lines = font.split(feature.text, maxWidth) + Row({ + width = grow() + height = fixed(LINE_HEIGHT * (lines.size + PARAGRAPH_OFFSET)) + }) { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.usePose { + for (line in lines) { + guiGraphics.drawString(font, line) + translateXY(0, LINE_HEIGHT) + } + translateXY(0, PRG_OFFSET_OTHER) + } + } + } + } +} \ 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..f33c06f2 --- /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) { + 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) + }) { + draw { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.drawCenteredString(font, TITLE, size.x / 2) + } + } + } + } +} \ No newline at end of file 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..38ae740e --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/TitleFeatureRenderer.kt @@ -0,0 +1,55 @@ +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 + +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 + ) { + Title(feature, maxWidth, font) + Separator() + } + + private fun Separator() { + Row({ + width = grow() + alignMain = Alignment.CENTER + }) { + TextureBox(SEPARATOR) {} + } + } + + private fun Title(title: TitleFeature, maxWidth: Int, font: Font) { + + val lines = font.split(title.text, maxWidth) + + Row({ + width = grow() + height = fixed(LINE_HEIGHT * (lines.size + PARAGRAPH_OFFSET)) + }) { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.usePose { + for ((index, line) in lines.withIndex()) { + guiGraphics.drawCenteredString( + font, line, size.x / 2 + ) + if (index != lines.size - 1) translateXY(0, PRG_OFFSET_OTHER) + } + } + } + } + } +} \ No newline at end of file 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 4855f0e0..56bc7204 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 @@ -41,13 +40,13 @@ private fun renderableTexture(texture: Texture) = Renderable { guiGraphics: GuiG } fun draw(getRenderable: T7LayoutElement.() -> Renderable) { - val screen = LayoutExtensions.currScreen ?: throw IllegalStateException("Thavma Exception: cannot add renderable without setting LayoutExtensions.currScreen first!") + val screen = LayoutExtensions.currScreen + ?: throw IllegalStateException("Thavma Exception: cannot add renderable without setting LayoutExtensions.currScreen first!") afterLayout { - val renderable = getRenderable() 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) } }) } @@ -62,6 +61,25 @@ fun TextureBox(texture: Texture, children: T7LayoutElement.() -> Unit) = 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 @@ -79,7 +97,14 @@ fun Slot(slot: Slot, texture: Texture? = null, slotSize: Int? = null) = afterLayout { slotSetup(slot) } } -fun Grid(rows: Int, columns: Int, elements: List, bgLayers: List = listOf(), gapSize: Int = 0, child: (T) -> Unit) = +fun Grid( + rows: Int, + columns: Int, + elements: List, + bgLayers: List = listOf(), + gapSize: Int = 0, + child: (T) -> Unit +) = Column({ gap = gapSize }) { for (layer in bgLayers) draw { renderableTexture(layer) } 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..82f30752 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,29 @@ 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) 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..08290a54 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 @@ -18,6 +18,10 @@ fun GuiGraphics.drawCenteredString(font: Font, text: Component, centerX: Float, drawString(font, text, (centerX - font.width(text) / 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) +} + fun GuiGraphics.drawString(font: Font, text: Component, color: Int = 0) { drawString(font, text, 0, 0, color, false) } 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..64757eca --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/FigureFeature.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.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 startsPage: Boolean = false, + override val forceIndex: Int? = null +) : PageFeature { + override val type: PageFeatureType<*> + get() = PageFeatureTypes.FIGURE.get() + + 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::startsPage), + Codec.INT.optionalFieldOf("force_index", null).forGetter(FigureFeature::forceIndex) + ).apply(builder) { img, cap, start, index -> + FigureFeature(img, cap.orElse(null), start, index) + } + } + + 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/FormCharSeqFeature.kt b/src/main/java/me/alegian/thavma/impl/common/book/FormCharSeqFeature.kt new file mode 100644 index 00000000..4feb7e56 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/FormCharSeqFeature.kt @@ -0,0 +1,29 @@ +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 FormCharSeqFeature( + val text: List, override val startsPage: Boolean = false, + override val forceIndex: Int? = null +) : PageFeature { + override val type: PageFeatureType<*> + get() = PageFeatureTypes.FORMCHARSEQ.get() + + 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 -> + FormCharSeqFeature(components.map { it.visualOrderText }) + } + } + } +} \ 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 index 247bc96a..cede2206 100644 --- a/src/main/java/me/alegian/thavma/impl/common/book/ParagraphFeature.kt +++ b/src/main/java/me/alegian/thavma/impl/common/book/ParagraphFeature.kt @@ -1,5 +1,6 @@ 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 @@ -17,9 +18,11 @@ class ParagraphFeature( val CODEC = RecordCodecBuilder.mapCodec { builder -> builder.group( ComponentSerialization.CODEC.fieldOf("text").forGetter(ParagraphFeature::text), + Codec.BOOL.optionalFieldOf("starts_page", false).forGetter(ParagraphFeature::startsPage), + Codec.INT.optionalFieldOf("force_index", null).forGetter(ParagraphFeature::forceIndex) ).apply(builder, ::ParagraphFeature) } - fun translationId(baseId: String, featureIndex: Int) = "$baseId.paragraphFeature$featureIndex" + 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..db604d34 --- /dev/null +++ b/src/main/java/me/alegian/thavma/impl/common/book/RecipeFeature.kt @@ -0,0 +1,25 @@ +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 startsPage: Boolean = true, override val forceIndex: Int? = 1 +) : PageFeature { + override val type: PageFeatureType<*> + get() = PageFeatureTypes.RECIPE.get() + + companion object { + val CODEC = RecordCodecBuilder.mapCodec { builder -> + builder.group( + ResourceLocation.CODEC.fieldOf("recipeRL").forGetter(RecipeFeature::recipeRL), + Codec.BOOL.optionalFieldOf("starts_page", true).forGetter(RecipeFeature::startsPage), + Codec.INT.optionalFieldOf("force_index", 1).forGetter(RecipeFeature::forceIndex) + ).apply(builder, ::RecipeFeature) + } + + fun translationId(baseId: String, featureIndex: Int) = "$baseId.recipe_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 index 75f4535c..678095f7 100644 --- a/src/main/java/me/alegian/thavma/impl/common/book/TitleFeature.kt +++ b/src/main/java/me/alegian/thavma/impl/common/book/TitleFeature.kt @@ -1,5 +1,6 @@ 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 @@ -13,10 +14,12 @@ class TitleFeature(val text: Component, override val startsPage: Boolean = true, companion object { val CODEC = RecordCodecBuilder.mapCodec { builder -> builder.group( - ComponentSerialization.CODEC.fieldOf("text").forGetter(TitleFeature::text) + ComponentSerialization.CODEC.fieldOf("text").forGetter(TitleFeature::text), + Codec.BOOL.optionalFieldOf("starts_page", true).forGetter(TitleFeature::startsPage), + Codec.INT.optionalFieldOf("force_index", null).forGetter(TitleFeature::forceIndex) ).apply(builder, ::TitleFeature) } - fun translationId(baseId: String, featureIndex: Int) = "$baseId.titleFeature$featureIndex" + 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..59b9ee63 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 @@ -3,7 +3,7 @@ package me.alegian.thavma.impl.common.research 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 @@ -24,7 +24,8 @@ class ResearchEntry( val position: Vector2i, val preferX: Boolean, val children: List>, - val pages: List, + //val pages: List, + val pageFeatures: List, val icon: ItemStack, val title: Component, val defaultResearchState: List, @@ -45,7 +46,8 @@ class ResearchEntry( 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), + //Page.CODEC.listOf().fieldOf("pages").forGetter(ResearchEntry::pages), + 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..023f3f24 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,16 +1,14 @@ 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.Page -import me.alegian.thavma.impl.common.book.TextPage +import me.alegian.thavma.impl.common.book.* 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 import me.alegian.thavma.impl.common.research.SocketState import me.alegian.thavma.impl.common.util.Indices -import me.alegian.thavma.impl.init.data.Recipes import me.alegian.thavma.impl.init.data.worldgen.Node import me.alegian.thavma.impl.init.data.worldgen.ore.InfusedOre import me.alegian.thavma.impl.init.data.worldgen.ore.InfusedStoneOre @@ -33,6 +31,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 @@ -127,8 +126,8 @@ class T7DatapackBuiltinEntriesProvider(output: PackOutput, registries: Completab T7Items.BOOK.get().defaultInstance ) .research(lockedAspect(2, 0, Aspects.AETHER), lockedAspect(2, 4, Aspects.AETHER)) - .addPage(simpleTextPage(3, true)) - .addPage(simpleTextPage(1, false)) +// .addPage(simpleTextPage(3, true)) +// .addPage(simpleTextPage(1, false)) .addChild(ResearchEntries.Thavma.TREES) .addChild(ResearchEntries.Thavma.ORES) .defaultKnown() @@ -141,9 +140,26 @@ class T7DatapackBuiltinEntriesProvider(output: PackOutput, registries: Completab Items.TURTLE_HELMET.defaultInstance ) .research() - .addPage(simpleTextPage(2, true)) - .addPage(simpleTextPage(2, true)) - .addPage(simpleTextPage(2, true)) + .addPageFeature(makeTitleFeature()) + .addPageFeature(makeParagraphFeature(false)) + .addPageFeature(makeParagraphFeature()) + .addPageFeature( + makeFigureFeature( + Texture("gui/images/haybales", 180, 101, 180, 101), + true, + false, + 1, + ChatFormatting.DARK_AQUA, + ChatFormatting.ITALIC + ) + ) + .addPageFeature(makeTitleFeature()) + .addPageFeature(makeParagraphFeature(true)) + .addPageFeature(makeTitleFeature(true)) + .addPageFeature(makeTitleFeature(true, 0)) + .addPageFeature(makeParagraphFeature()) + .addPageFeature(makeFigureFeature(Texture("gui/images/smileyface", 87, 77, 87, 77), false, false, 2)) + .addPageFeature(makeParagraphFeature(false, 2)) .defaultKnown() .build(ctx) @@ -175,7 +191,7 @@ class T7DatapackBuiltinEntriesProvider(output: PackOutput, registries: Completab ) .research(lockedAspect(2, 0, Aspects.LUX), lockedAspect(2, 4, Aspects.AETHER), broken(2, 2)) .addChild(ResearchEntries.Thavma.RESEARCH_TABLE) - .addPage(simpleTextPage(3, true)) +// .addPage(simpleTextPage(3, true)) .build(ctx) ResearchEntryBuilder( @@ -185,7 +201,7 @@ class T7DatapackBuiltinEntriesProvider(output: PackOutput, registries: Completab T7Blocks.RESEARCH_TABLE.get().asItem().defaultInstance ) .research(lockedAspect(2, 0, Aspects.AETHER), lockedAspect(2, 4, Aspects.HERBA)) - .addPage { _, _ -> CraftingPage(Recipes.CHEST) } +// .addPage { _, _ -> CraftingPage(Recipes.CHEST) } .addChild(ResearchEntries.Thavma.WANDS) .addChild(ResearchEntries.Thavma.TECHNOLOGY) .addChild(ResearchEntries.Thavma.ALCHEMY) @@ -261,7 +277,9 @@ private class ResearchEntryBuilder( private val icon: ItemStack ) { private val children = mutableListOf>() - private val pages = mutableListOf() + + // private val pages = mutableListOf() + private val pageFeatures = mutableListOf() private val socketStates = mutableListOf() private var defaultKnown = false @@ -270,8 +288,13 @@ private class ResearchEntryBuilder( return this } - fun addPage(makePage: (ResourceKey, Int) -> Page): ResearchEntryBuilder { - pages.add(makePage(key, pages.size)) +// fun addPage(makePage: (ResourceKey, Int) -> Page): ResearchEntryBuilder { +// pages.add(makePage(key, pages.size)) +// return this +// } + + inline fun addPageFeature(crossinline makeFeature: (ResourceKey, Int) -> T): ResearchEntryBuilder { + pageFeatures.add(makeFeature(key, pageFeatures.filterIsInstance().size)) return this } @@ -297,7 +320,8 @@ private class ResearchEntryBuilder( pos, preferX, childrenHolders, - pages, + //pages, + pageFeatures, icon, Component.translatable(ResearchEntry.translationId(key)).withStyle(Rarity.UNCOMMON.styleModifier), socketStates, @@ -325,6 +349,87 @@ private fun simpleTextPage(paragraphCount: Int, hasTitle: Boolean): (ResourceKey } } +private fun makeParagraphFeature( + startsPage: Boolean = false, + forceIndex: Int? = null +): (ResourceKey, Int) -> ParagraphFeature { + return { entryKey, paragraphIndex -> + val baseId = ResearchEntry.translationId(entryKey) + ParagraphFeature( + Component.translatable(ParagraphFeature.translationId(baseId, paragraphIndex)), + startsPage, forceIndex + ) + } +} + +private fun makeTitleFeature( + startsPage: Boolean = false, + forceIndex: Int? = null +): (ResourceKey, Int) -> TitleFeature { + return { entryKey, titleIndex -> + val baseId = ResearchEntry.translationId(entryKey) + TitleFeature( + Component.translatable(TitleFeature.translationId(baseId, titleIndex)), + startsPage, forceIndex + ) + } +} + + +private fun makeFigureFeature( + image: Texture, + giveCaption: Boolean, + startsPage: Boolean = false, + forceIndex: Int? = null, + 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, startsPage, forceIndex) + } else { _, _ -> + FigureFeature(image, null, startsPage, forceIndex) + } +} + +private fun makeRecipeFeature( + recipeRL: ResourceLocation, + startsPage: Boolean = true, + forceIndex: Int? = 1 +): (ResourceKey, Int) -> RecipeFeature { + return { _, _ -> + RecipeFeature( + recipeRL, startsPage, forceIndex + ) + } +} + +private fun makeStyledParagraphFeature( + startsPage: Boolean = false, + forceIndex: Int? = null, + 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, startsPage, forceIndex) + } +} + 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..a98c689b 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,37 @@ 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, "This title starts the page") + addPageFeature( + PARAGRAPH, + ResearchEntries.Story.STORY1, 0, """ + This is a short paragraph just to showcase that the feature exists. + """ ) - - 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, """ + This is a longer paragraph to highlight the importance of splicing longer pieces of text over multiple pages. If you do not splice the paragraph to go over multiple pages, the text will spill out of the bottom of the screen and we don't want that to happen, hence DynamicRenderingHelper.kt + """.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, """ A short caption for the figure just to show the custom style """.trimIndent() + ) + addPageFeature(TITLE, ResearchEntries.Story.STORY1, 1, "This might appear in the middle") + addPageFeature( + PARAGRAPH, + ResearchEntries.Story.STORY1, 2, """ + This paragraph should start a new page always. + """.trimIndent() + ) + addPageFeature(TITLE, ResearchEntries.Story.STORY1, 2, "(start of page)") + addPageFeature(TITLE, ResearchEntries.Story.STORY1, 3, "This is page number 1!") + addPageFeature(PARAGRAPH, ResearchEntries.Story.STORY1, 3, "Just another random little paragraph :D") + addPageFeature( + PARAGRAPH, ResearchEntries.Story.STORY1, 4, """ + This paragraph has a pre-set page index of 5 + """.trimIndent() ) addTextPage( @@ -236,7 +239,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 @@ -310,7 +313,7 @@ class T7LanguageProvider(output: PackOutput, locale: String) : LanguageProvider( ) add( RecipeViewerDescriptions.RESEARCH_SCROLL, - "Obtained by clicking any unknown entry in the \"Elements of Thavma\"" + "Obtained by clicking any unknown entry in the \"Elements\"" ) add(RecipeViewerAliases.BOOK, "Book") @@ -359,4 +362,22 @@ 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 a717b08c..b13ad482 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 @@ -27,7 +27,7 @@ object T7Registries { .maxId(Int.MAX_VALUE) .create() - val PAGE_FEATURE_TYPE = RegistryBuilder(ResourceKey.createRegistryKey>(rl("page_feature"))) + 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 index 5e45ae80..e901c54d 100644 --- 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 @@ -1,9 +1,7 @@ package me.alegian.thavma.impl.init.registries.deferred import me.alegian.thavma.impl.Thavma -import me.alegian.thavma.impl.common.book.PageFeatureType -import me.alegian.thavma.impl.common.book.ParagraphFeature -import me.alegian.thavma.impl.common.book.TitleFeature +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 @@ -13,6 +11,9 @@ object PageFeatureTypes { val PARAGRAPH = REGISTRAR.register("paragraph") { -> PageFeatureType(rl("paragraph"), ParagraphFeature.CODEC) } - val TITLE = REGISTRAR.register("title") { -> PageFeatureType(rl("paragraph"), TitleFeature.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) } + val FORMCHARSEQ = + REGISTRAR.register("formcharseq") { -> PageFeatureType(rl("formcharseq"), FormCharSeqFeature.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 0000000000000000000000000000000000000000..a8e1cd2fbc6885fe80ae60cf4da36986ca263b9c GIT binary patch literal 31177 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 NjBlC2D)b*l{~rwy)@lF% literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..96ec3598540a79f5a1c2ac59a7f6191ded59ce0b GIT binary patch literal 7471 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 literal 0 HcmV?d00001 From cba967d78f7085ce66ab31221943b73b5df1b963 Mon Sep 17 00:00:00 2001 From: KorrAn34 Date: Thu, 16 Jul 2026 13:53:37 +0200 Subject: [PATCH 3/8] OK codecs really hate default value [null] but things work now just fix alignment --- .../resources/assets/thavma/lang/en_us.json | 25 +- .../research/entry/alchemy/alchemy.json | 1 + .../thavma/research/entry/story/story1.json | 114 ++++++--- .../thavma/research/entry/thavma/alchemy.json | 1 + .../research/entry/thavma/arcane_lens.json | 22 +- .../research/entry/thavma/infusion.json | 1 + .../thavma/research/entry/thavma/ores.json | 1 + .../entry/thavma/research_proficiency.json | 1 + .../research/entry/thavma/research_table.json | 8 +- .../research/entry/thavma/technology.json | 1 + .../thavma/research/entry/thavma/thavma.json | 30 +-- .../thavma/research/entry/thavma/trees.json | 1 + .../thavma/research/entry/thavma/wands.json | 1 + .../client/gui/book/DynamicRenderingHelper.kt | 222 +----------------- .../impl/client/gui/book/EntryScreen.kt | 76 +++--- ...rSeqRenderer.kt => FormCharSeqRenderer.kt} | 15 +- .../gui/book/ParagraphFeatureRenderer.kt | 3 + .../client/gui/book/TitleFeatureRenderer.kt | 4 +- .../thavma/impl/common/book/FigureFeature.kt | 6 +- .../impl/common/book/ParagraphFeature.kt | 4 +- .../thavma/impl/common/book/RecipeFeature.kt | 2 +- .../thavma/impl/common/book/TitleFeature.kt | 4 +- .../impl/common/research/ResearchEntry.kt | 9 +- .../T7DatapackBuiltinEntriesProvider.kt | 18 +- .../init/data/providers/T7LanguageProvider.kt | 2 +- 25 files changed, 183 insertions(+), 389 deletions(-) rename src/main/java/me/alegian/thavma/impl/client/gui/book/{FormattedCharSeqRenderer.kt => FormCharSeqRenderer.kt} (72%) diff --git a/src/generated/resources/assets/thavma/lang/en_us.json b/src/generated/resources/assets/thavma/lang/en_us.json index bddd0d24..b57b80a0 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", @@ -157,7 +157,7 @@ "recipe_viewer.thavma.description.greatwood": "Greatwoods are very tall, ancient trees. They are somewhat rare, but they can spawn in all overworld biomes.", "recipe_viewer.thavma.description.infused_stones": "A piece of stone, infused with a primal element. Found in the overworld, at any height.", "recipe_viewer.thavma.description.pillar": "Formed by right clicking the Infusion Matrix, after completing the Infusion Multiblock.", - "recipe_viewer.thavma.description.research_scroll": "Obtained by clicking any unknown entry in the \"Elements of Thavma\"", + "recipe_viewer.thavma.description.research_scroll": "Obtained by clicking any unknown entry in the \"Elements\"", "recipe_viewer.thavma.description.research_table": "Can be formed by placing 2 tables next to each other, and right clicking one with a wand.", "recipe_viewer.thavma.description.rotten_brain": "Sometimes dropped by angry zombies.", "recipe_viewer.thavma.description.silverwood": "Silverwoods are magical trees, with uniquely blue leaves. They are very rare, but they can spawn in all overworld biomes.", @@ -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": "A short caption for the figure just to show the custom style ", + "research/entry.thavma.story.story1.paragraph_feature0": "This is a short paragraph just to showcase that the feature exists.", + "research/entry.thavma.story.story1.paragraph_feature1": "This is a longer paragraph to highlight the importance of splicing longer pieces of text over multiple pages. If you do not splice the paragraph to go over multiple pages, the text will spill out of the bottom of the screen and we don't want that to happen, hence DynamicRenderingHelper.kt", + "research/entry.thavma.story.story1.paragraph_feature2": "This paragraph should start a new page always.", + "research/entry.thavma.story.story1.paragraph_feature3": "Just another random little paragraph :D", + "research/entry.thavma.story.story1.paragraph_feature4": "This paragraph has a pre-set page index of 5", + "research/entry.thavma.story.story1.title_feature0": "This title starts the page", + "research/entry.thavma.story.story1.title_feature1": "This 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..3f95b085 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,6 +26,7 @@ "count": 1, "id": "thavma:crucible" }, + "pageFeatures": [], "pages": [], "position": [ 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..fbb24318 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,53 +7,93 @@ "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": { - "bold": true, - "translate": "research/entry.thavma.story.story1.page0.title" + "type": "thavma:title", + "text": { + "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": { - "bold": true, - "translate": "research/entry.thavma.story.story1.page1.title" + "type": "thavma:paragraph", + "text": { + "translate": "research/entry.thavma.story.story1.paragraph_feature0" } }, { - "type": "thavma:text", - "paragraphs": [ - { - "translate": "research/entry.thavma.story.story1.page2.paragraph0" - }, - { - "translate": "research/entry.thavma.story.story1.page2.paragraph1" - } - ], - "title": { - "bold": true, - "translate": "research/entry.thavma.story.story1.page2.title" + "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" + }, + "force_index": 1, + "image": { + "canvas_height": 101, + "canvas_width": 180, + "height": 101, + "location": "thavma:textures/gui/images/haybales.png", + "width": 180 + } + }, + { + "type": "thavma:title", + "text": { + "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", + "text": { + "translate": "research/entry.thavma.story.story1.title_feature2" + } + }, + { + "type": "thavma:title", + "force_index": 0, + "text": { + "translate": "research/entry.thavma.story.story1.title_feature3" + } + }, + { + "type": "thavma:paragraph", + "text": { + "translate": "research/entry.thavma.story.story1.paragraph_feature3" + } + }, + { + "type": "thavma:figure", + "force_index": 2, + "image": { + "canvas_height": 77, + "canvas_width": 87, + "height": 77, + "location": "thavma:textures/gui/images/smileyface.png", + "width": 87 + } + }, + { + "type": "thavma:paragraph", + "force_index": 2, + "text": { + "translate": "research/entry.thavma.story.story1.paragraph_feature4" } } ], + "pages": [], "position": [ 0, -3 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..7ccf78c2 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,6 +26,7 @@ "count": 1, "id": "thavma:crucible" }, + "pageFeatures": [], "pages": [], "position": [ -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..6c7af87d 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,8 @@ "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": [], + "pages": [], "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..31d4628b 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,6 +26,7 @@ "count": 1, "id": "thavma:infusion_matrix" }, + "pageFeatures": [], "pages": [], "position": [ 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..fa5ce5e6 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,6 +28,7 @@ "count": 1, "id": "thavma:aqua_shard" }, + "pageFeatures": [], "pages": [], "position": [ 2, 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..0fdbd369 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,6 +26,7 @@ "count": 1, "id": "thavma:research_table" }, + "pageFeatures": [], "pages": [], "position": [ -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..1e8ce870 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,8 @@ "count": 1, "id": "thavma:research_table" }, - "pages": [ - { - "type": "thavma:crafting", - "recipeRL": "minecraft:chest" - } - ], + "pageFeatures": [], + "pages": [], "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..ece3a4d0 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,6 +26,7 @@ "count": 1, "id": "thavma:goggles" }, + "pageFeatures": [], "pages": [], "position": [ 2, 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..9c48db7b 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,8 @@ "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": [], + "pages": [], "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..e6e12baf 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,6 +28,7 @@ "count": 1, "id": "thavma:greatwood_log" }, + "pageFeatures": [], "pages": [], "position": [ 0, 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..2705a125 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,6 +26,7 @@ "count": 1, "id": "thavma:thavmite_silverwood_wand" }, + "pageFeatures": [], "pages": [], "position": [ -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 index 06d6cd46..628921cf 100644 --- 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 @@ -1,11 +1,7 @@ package me.alegian.thavma.impl.client.gui.book -import me.alegian.thavma.impl.common.book.FigureFeature -import me.alegian.thavma.impl.common.book.FormCharSeqFeature import me.alegian.thavma.impl.common.book.PageFeature -import me.alegian.thavma.impl.common.book.ParagraphFeature import net.minecraft.client.Minecraft -import net.minecraft.network.chat.Style val LINE_HEIGHT = Minecraft.getInstance().font.lineHeight + 2 const val PARAGRAPH_OFFSET = 2 / 3f @@ -13,220 +9,8 @@ val PRG_OFFSET_OTHER = LINE_HEIGHT * 2 / 3 /** - * Return a list of PageFeatures containing a list of lines - FormattedText - * or possibly a Figure with just the image and its processed caption - FormattedText, - * so that they all fit on their - * respective pages (represented by list indices) + * Returns a list of lists of features, one feature per list -> one per page. */ -fun spliceParagraphOrFigure( - input: PageFeature, maxPageHeight: Int, - currentHeight: Int -): List { - val result = mutableListOf() - - if (input is ParagraphFeature) { - // how many lines fit in the current page - val lineHeight = input.LINE_HEIGHT - - // so that we get at least 2 lines at the end of the first page - 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 - linesRemainingAtStart * lineHeight) / maxPageHeight / lineHeight - val linesCroppingOutAtEnd: Int = - (input.renderedHeight - linesRemainingAtStart * lineHeight - numOfLinesCoveringFullPages * lineHeight) / lineHeight - - val maxLinesPerPage = maxPageHeight / lineHeight - val numOfFullPagesCovered = numOfLinesCoveringFullPages / maxLinesPerPage - val lines = input.font.splitter.splitLines(input.text, input.pageWidth, Style.EMPTY) - - when { - lines.size == 1 && currentHeight + lineHeight <= maxPageHeight -> result += FormCharSeqFeature(lines) - - lines.size == 1 -> result += listOf( - FormCharSeqFeature(listOf()), - FormCharSeqFeature(lines) - ) - - linesRemainingAtStart != 1 && linesCroppingOutAtEnd != 1 -> { - // separate into "underhang", middle and overhang - val start = lines.slice(0 until linesRemainingAtStart) - result += FormCharSeqFeature(start) - - for (i in 0 until numOfFullPagesCovered) { - val middle = - lines.slice(linesRemainingAtStart + i * maxLinesPerPage until linesRemainingAtStart + (i + 1) * maxLinesPerPage) - result += FormCharSeqFeature(middle) - } - - val end = - lines.slice(lines.size - 1 - linesCroppingOutAtEnd until lines.size - 1) - result += FormCharSeqFeature(end) - } - - else -> { - // add an empty paragraph to current page and continue on the next - result += FormCharSeqFeature(listOf()) - for (i in 0 until numOfFullPagesCovered) { - val page = lines.slice(i * maxLinesPerPage until (i + 1) * maxLinesPerPage) - result += FormCharSeqFeature(page) - } - val finish = - lines.slice(numOfFullPagesCovered * maxLinesPerPage until lines.size) - result += FormCharSeqFeature(finish) - } - } - return result - } - - if (input is FigureFeature) { - with(input) { - when { - caption == null && currentHeight + textureHeight <= maxPageHeight -> result += input - caption == null -> { - result += listOf() - result += this - } - - caption != null && currentHeight + textureHeight <= maxPageHeight -> { - result += FigureFeature(image, null) - result.addAll( - spliceParagraphOrFigure( - ParagraphFeature(caption), - maxPageHeight, - currentHeight + textureHeight - ) - ) - } - - caption != null -> { - result += FormCharSeqFeature(listOf()) - result += FigureFeature(image, null) - result.addAll(spliceParagraphOrFigure(ParagraphFeature(caption), maxPageHeight, textureHeight)) - } - } - } - 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): List> { - // maxHeight is height of background texture minus padding (32 top 42 bottom) - //val maxHeight = this@EntryScreen.height - 74 - val partition = features.partition { !it.mustOccupySetPage } - val pages = mutableListOf>() - val buffer = mutableListOf() - fun currentHeight() = buffer.sumOf { it.renderedHeight } - - // 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 > maxHeight -> throw IllegalArgumentException( - "The size of the element ${this::class.simpleName} is too large at $renderedHeight while allowed $maxHeight." - ) - - coversOneWholePage -> { - if (buffer.isNotEmpty()) { - pages += buffer - buffer.clear() - } - pages += listOf(this) - } - - mustStartPage -> { - if (buffer.isNotEmpty()) { - pages += buffer - buffer.clear() - } - if (this is ParagraphFeature || this is FigureFeature) { - val processed = spliceParagraphOrFigure(this, maxHeight, currentHeight()) - if (processed.size == 1) buffer += processed.first() - // only need to check this much since buffer is empty (starts page) - // applies to both Paragraph and Figure - else { - processed.slice(0 until processed.size - 1).forEach { pages += listOf(it) } - buffer += processed.last() - } - } else buffer += this - } - - (this is ParagraphFeature || this is FigureFeature) -> { - val processed = spliceParagraphOrFigure(this, maxHeight, currentHeight()) - if (processed.size < 2) buffer += processed.first() - else if (this is ParagraphFeature) { - buffer += processed.first() - pages += buffer - buffer.clear() - 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 FormCharSeqFeature, - // it's an empty one to signify end of page. Size 2 means - // there is only this emptiness + image. - processed.first() is FormCharSeqFeature && processed.size == 2 -> { - pages += buffer - buffer.clear() - 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 FormCharSeqFeature) { - pages += buffer - buffer.clear() - 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) { - pages += buffer - buffer.clear() - for (j in 2 + i until processed.size - 1) { - pages += listOf(processed[j]) - } - buffer += processed[processed.size - 1] - } - } - } - } - } - - currentHeight() + renderedHeight <= 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 while allowed $maxHeight.") - else -> { - pages += buffer - buffer.clear() - buffer += this - } - } - } - } - - // add anything left over in the buffer - if (buffer.isNotEmpty()) pages += buffer - - // finally add features with pre-determined positions (cannot be 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 +fun pagifyFeatures(features: List): List> { + return features.map { listOf(it) } } \ 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 99571577..933bfca8 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,7 +2,6 @@ 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 @@ -11,13 +10,17 @@ import net.minecraft.client.gui.screens.Screen import net.minecraft.core.Holder import net.minecraft.network.chat.Component -class EntryScreen(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() + + var maxWidth = 0 + var pages = listOf>() override fun init() { @@ -31,50 +34,53 @@ class EntryScreen(val entry: Holder) : Screen(Component.literal(" 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 }) { - 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) - }) + //afterLayout { + if (pages.isEmpty() && entry.pageFeatures != null) { + pages = pagifyFeatures(entry.pageFeatures) + //maxWidth = this@afterLayout.size.x.toInt() + maxWidth = 60 + } + //} + 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() } } } @@ -114,7 +120,7 @@ class EntryScreen(val entry: Holder) : Screen(Component.literal(" } } - fun turnPage(right: Boolean){ + fun turnPage(right: Boolean) { if (right) currentPage += 2 else currentPage -= 2 init() @@ -125,12 +131,6 @@ class EntryScreen(val entry: Holder) : Screen(Component.literal(" } // 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) - } - private fun initPageFeature(feature: T) { if (feature != null) { val renderer = PAGE_FEATURE_RENDERERS[feature.type] as PageFeatureRenderer diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/FormattedCharSeqRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/FormCharSeqRenderer.kt similarity index 72% rename from src/main/java/me/alegian/thavma/impl/client/gui/book/FormattedCharSeqRenderer.kt rename to src/main/java/me/alegian/thavma/impl/client/gui/book/FormCharSeqRenderer.kt index 1051d045..7b1be697 100644 --- a/src/main/java/me/alegian/thavma/impl/client/gui/book/FormattedCharSeqRenderer.kt +++ b/src/main/java/me/alegian/thavma/impl/client/gui/book/FormCharSeqRenderer.kt @@ -1,6 +1,7 @@ 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.draw import me.alegian.thavma.impl.client.gui.layout.fixed import me.alegian.thavma.impl.client.gui.layout.grow import me.alegian.thavma.impl.client.util.drawString @@ -23,13 +24,15 @@ object FormCharSeqRenderer : PageFeatureRenderer { width = grow() height = fixed(LINE_HEIGHT * (feature.text.size + PARAGRAPH_OFFSET)) }) { - Renderable { guiGraphics, _, _, _ -> - guiGraphics.usePose { - for (line in feature.text) { - guiGraphics.drawString(font, line) - translateXY(0, LINE_HEIGHT) + draw { + Renderable { guiGraphics, _, _, _ -> + guiGraphics.usePose { + for (line in feature.text) { + guiGraphics.drawString(font, line) + translateXY(0, LINE_HEIGHT) + } + translateXY(0, PRG_OFFSET_OTHER) } - translateXY(0, PRG_OFFSET_OTHER) } } } 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 index ac259efc..4cd155d2 100644 --- 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 @@ -1,6 +1,7 @@ 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.draw import me.alegian.thavma.impl.client.gui.layout.fixed import me.alegian.thavma.impl.client.gui.layout.grow import me.alegian.thavma.impl.client.util.drawString @@ -24,6 +25,7 @@ object ParagraphFeatureRenderer : PageFeatureRenderer { width = grow() height = fixed(LINE_HEIGHT * (lines.size + PARAGRAPH_OFFSET)) }) { + draw { Renderable { guiGraphics, _, _, _ -> guiGraphics.usePose { for (line in lines) { @@ -33,6 +35,7 @@ object ParagraphFeatureRenderer : PageFeatureRenderer { translateXY(0, PRG_OFFSET_OTHER) } } + } } } } \ No newline at end of file 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 index 38ae740e..4176fcc7 100644 --- 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 @@ -40,7 +40,8 @@ object TitleFeatureRenderer : PageFeatureRenderer { width = grow() height = fixed(LINE_HEIGHT * (lines.size + PARAGRAPH_OFFSET)) }) { - Renderable { guiGraphics, _, _, _ -> + draw { + Renderable { guiGraphics, _, _, _ -> guiGraphics.usePose { for ((index, line) in lines.withIndex()) { guiGraphics.drawCenteredString( @@ -49,6 +50,7 @@ object TitleFeatureRenderer : PageFeatureRenderer { if (index != lines.size - 1) translateXY(0, PRG_OFFSET_OTHER) } } + } } } } 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 index 64757eca..21c2224f 100644 --- a/src/main/java/me/alegian/thavma/impl/common/book/FigureFeature.kt +++ b/src/main/java/me/alegian/thavma/impl/common/book/FigureFeature.kt @@ -12,7 +12,7 @@ class FigureFeature( val image: Texture, val caption: Component?, override val startsPage: Boolean = false, - override val forceIndex: Int? = null + override val forceIndex: Int = -1 ) : PageFeature { override val type: PageFeatureType<*> get() = PageFeatureTypes.FIGURE.get() @@ -25,9 +25,9 @@ class FigureFeature( 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::startsPage), - Codec.INT.optionalFieldOf("force_index", null).forGetter(FigureFeature::forceIndex) + Codec.INT.optionalFieldOf("force_index", -1).forGetter(FigureFeature::forceIndex) ).apply(builder) { img, cap, start, index -> - FigureFeature(img, cap.orElse(null), start, index) + FigureFeature(img, cap.orElse(Component.literal("")), start, index) } } 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 index cede2206..7958e191 100644 --- a/src/main/java/me/alegian/thavma/impl/common/book/ParagraphFeature.kt +++ b/src/main/java/me/alegian/thavma/impl/common/book/ParagraphFeature.kt @@ -9,7 +9,7 @@ import net.minecraft.network.chat.ComponentSerialization class ParagraphFeature( val text: Component, override val startsPage: Boolean = false, - override val forceIndex: Int? = null + override val forceIndex: Int = -1 ) : PageFeature { override val type: PageFeatureType<*> get() = PageFeatureTypes.PARAGRAPH.get() @@ -19,7 +19,7 @@ class ParagraphFeature( builder.group( ComponentSerialization.CODEC.fieldOf("text").forGetter(ParagraphFeature::text), Codec.BOOL.optionalFieldOf("starts_page", false).forGetter(ParagraphFeature::startsPage), - Codec.INT.optionalFieldOf("force_index", null).forGetter(ParagraphFeature::forceIndex) + Codec.INT.optionalFieldOf("force_index", -1).forGetter(ParagraphFeature::forceIndex) ).apply(builder, ::ParagraphFeature) } 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 index db604d34..23fb9908 100644 --- a/src/main/java/me/alegian/thavma/impl/common/book/RecipeFeature.kt +++ b/src/main/java/me/alegian/thavma/impl/common/book/RecipeFeature.kt @@ -6,7 +6,7 @@ import me.alegian.thavma.impl.init.registries.deferred.PageFeatureTypes import net.minecraft.resources.ResourceLocation class RecipeFeature( - val recipeRL: ResourceLocation, override val startsPage: Boolean = true, override val forceIndex: Int? = 1 + val recipeRL: ResourceLocation, override val startsPage: Boolean = true, override val forceIndex: Int = 1 ) : PageFeature { override val type: PageFeatureType<*> get() = PageFeatureTypes.RECIPE.get() 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 index 678095f7..6bd19522 100644 --- a/src/main/java/me/alegian/thavma/impl/common/book/TitleFeature.kt +++ b/src/main/java/me/alegian/thavma/impl/common/book/TitleFeature.kt @@ -6,7 +6,7 @@ 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 startsPage: Boolean = true, override val forceIndex: Int? = null) : +class TitleFeature(val text: Component, override val startsPage: Boolean = true, override val forceIndex: Int = -1) : PageFeature { override val type: PageFeatureType<*> get() = PageFeatureTypes.TITLE.get() @@ -16,7 +16,7 @@ class TitleFeature(val text: Component, override val startsPage: Boolean = true, builder.group( ComponentSerialization.CODEC.fieldOf("text").forGetter(TitleFeature::text), Codec.BOOL.optionalFieldOf("starts_page", true).forGetter(TitleFeature::startsPage), - Codec.INT.optionalFieldOf("force_index", null).forGetter(TitleFeature::forceIndex) + Codec.INT.optionalFieldOf("force_index", -1).forGetter(TitleFeature::forceIndex) ).apply(builder, ::TitleFeature) } 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 59b9ee63..da44f121 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 @@ -3,6 +3,7 @@ package me.alegian.thavma.impl.common.research 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 @@ -24,8 +25,8 @@ class ResearchEntry( val position: Vector2i, val preferX: Boolean, val children: List>, - //val pages: List, - val pageFeatures: List, + val pages: List?, + val pageFeatures: List?, val icon: ItemStack, val title: Component, val defaultResearchState: List, @@ -46,8 +47,8 @@ class ResearchEntry( 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), - PageFeature.CODEC.listOf().fieldOf("pageFeatures").forGetter(ResearchEntry::pageFeatures), + Page.CODEC.listOf().optionalFieldOf("pages", null).forGetter(ResearchEntry::pages), + PageFeature.CODEC.listOf().optionalFieldOf("pageFeatures", null).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 023f3f24..47b4e71f 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 @@ -278,7 +278,7 @@ private class ResearchEntryBuilder( ) { private val children = mutableListOf>() - // private val pages = mutableListOf() + private val pages = mutableListOf() private val pageFeatures = mutableListOf() private val socketStates = mutableListOf() private var defaultKnown = false @@ -320,7 +320,7 @@ private class ResearchEntryBuilder( pos, preferX, childrenHolders, - //pages, + pages, pageFeatures, icon, Component.translatable(ResearchEntry.translationId(key)).withStyle(Rarity.UNCOMMON.styleModifier), @@ -351,7 +351,7 @@ private fun simpleTextPage(paragraphCount: Int, hasTitle: Boolean): (ResourceKey private fun makeParagraphFeature( startsPage: Boolean = false, - forceIndex: Int? = null + forceIndex: Int = -1 ): (ResourceKey, Int) -> ParagraphFeature { return { entryKey, paragraphIndex -> val baseId = ResearchEntry.translationId(entryKey) @@ -363,13 +363,13 @@ private fun makeParagraphFeature( } private fun makeTitleFeature( - startsPage: Boolean = false, - forceIndex: Int? = null + startsPage: Boolean = true, + forceIndex: Int = -1 ): (ResourceKey, Int) -> TitleFeature { return { entryKey, titleIndex -> val baseId = ResearchEntry.translationId(entryKey) TitleFeature( - Component.translatable(TitleFeature.translationId(baseId, titleIndex)), + Component.translatable(TitleFeature.translationId(baseId, titleIndex)).withStyle(ChatFormatting.BOLD), startsPage, forceIndex ) } @@ -380,7 +380,7 @@ private fun makeFigureFeature( image: Texture, giveCaption: Boolean, startsPage: Boolean = false, - forceIndex: Int? = null, + forceIndex: Int = -1, vararg styles: ChatFormatting? ): (ResourceKey, Int) -> FigureFeature { return if (giveCaption) { entryKey, figureIndex -> @@ -402,7 +402,7 @@ private fun makeFigureFeature( private fun makeRecipeFeature( recipeRL: ResourceLocation, startsPage: Boolean = true, - forceIndex: Int? = 1 + forceIndex: Int = 1 ): (ResourceKey, Int) -> RecipeFeature { return { _, _ -> RecipeFeature( @@ -413,7 +413,7 @@ private fun makeRecipeFeature( private fun makeStyledParagraphFeature( startsPage: Boolean = false, - forceIndex: Int? = null, + forceIndex: Int = -1, vararg styles: ChatFormatting? ): (ResourceKey, Int) -> ParagraphFeature { return { entryKey, paragraphIndex -> 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 a98c689b..70b39240 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 @@ -227,7 +227,7 @@ class T7LanguageProvider(output: PackOutput, locale: String) : LanguageProvider( addPageFeature(PARAGRAPH, ResearchEntries.Story.STORY1, 3, "Just another random little paragraph :D") addPageFeature( PARAGRAPH, ResearchEntries.Story.STORY1, 4, """ - This paragraph has a pre-set page index of 5 + This paragraph has a pre-set page index of 2 """.trimIndent() ) From db469e3d1eb30850445d931c89faf1f560d10a38 Mon Sep 17 00:00:00 2001 From: KorrAn34 Date: Thu, 16 Jul 2026 14:36:29 +0200 Subject: [PATCH 4/8] My genius is... it's almost frightening --- .../resources/assets/thavma/lang/en_us.json | 4 +-- .../thavma/research/entry/story/story1.json | 4 +++ .../impl/client/gui/book/EntryScreen.kt | 31 ++++++++++++------- .../client/gui/book/TitleFeatureRenderer.kt | 17 +++++----- .../init/data/providers/T7LanguageProvider.kt | 4 ++- 5 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/generated/resources/assets/thavma/lang/en_us.json b/src/generated/resources/assets/thavma/lang/en_us.json index b57b80a0..be0b84e5 100644 --- a/src/generated/resources/assets/thavma/lang/en_us.json +++ b/src/generated/resources/assets/thavma/lang/en_us.json @@ -171,10 +171,10 @@ "research/entry.thavma.story.story1": "A Courtesy Call", "research/entry.thavma.story.story1.figure_feature0": "A short caption for the figure just to show the custom style ", "research/entry.thavma.story.story1.paragraph_feature0": "This is a short paragraph just to showcase that the feature exists.", - "research/entry.thavma.story.story1.paragraph_feature1": "This is a longer paragraph to highlight the importance of splicing longer pieces of text over multiple pages. If you do not splice the paragraph to go over multiple pages, the text will spill out of the bottom of the screen and we don't want that to happen, hence DynamicRenderingHelper.kt", + "research/entry.thavma.story.story1.paragraph_feature1": "This is a longer paragraph to highlight the importance of splicing longer pieces of text over multiple pages. If you do not splice the paragraph to go over multiple pages, the text will spill out of the bottom of the screen and we don't want that to happen, hence DynamicRenderingHelper.kt. I will continue with some lorem ipsum. 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_feature2": "This paragraph should start a new page always.", "research/entry.thavma.story.story1.paragraph_feature3": "Just another random little paragraph :D", - "research/entry.thavma.story.story1.paragraph_feature4": "This paragraph has a pre-set page index of 5", + "research/entry.thavma.story.story1.paragraph_feature4": "This paragraph has a pre-set page index of 2", "research/entry.thavma.story.story1.title_feature0": "This title starts the page", "research/entry.thavma.story.story1.title_feature1": "This might appear in the middle", "research/entry.thavma.story.story1.title_feature2": "(start of page)", 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 fbb24318..16c0eaf4 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 @@ -11,6 +11,7 @@ { "type": "thavma:title", "text": { + "bold": true, "translate": "research/entry.thavma.story.story1.title_feature0" } }, @@ -45,6 +46,7 @@ { "type": "thavma:title", "text": { + "bold": true, "translate": "research/entry.thavma.story.story1.title_feature1" } }, @@ -58,6 +60,7 @@ { "type": "thavma:title", "text": { + "bold": true, "translate": "research/entry.thavma.story.story1.title_feature2" } }, @@ -65,6 +68,7 @@ "type": "thavma:title", "force_index": 0, "text": { + "bold": true, "translate": "research/entry.thavma.story.story1.title_feature3" } }, 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 933bfca8..2efa1c74 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 @@ -20,7 +20,9 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo private val entry = _entry.value() var maxWidth = 0 + var maxHeight = 0 var pages = listOf>() + private var isFirstPass = true override fun init() { @@ -36,7 +38,7 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo TextureBox(BG) { Column({ size = grow() - gap = 8 + //gap = 8 }) { Row({ size = grow() @@ -52,16 +54,21 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo size = grow() gap = 4 }) { - //afterLayout { - if (pages.isEmpty() && entry.pageFeatures != null) { - pages = pagifyFeatures(entry.pageFeatures) - //maxWidth = this@afterLayout.size.x.toInt() - maxWidth = 60 - } - //} - val features = pages.getOrNull(currentPage) - if (features != null) { - for (feature in features) initPageFeature(feature) + if (isFirstPass) { + afterLayout { + maxWidth = this@afterLayout.size.x.toInt() + maxHeight = this@afterLayout.size.y.toInt() + isFirstPass = false + init() + } + } else { + if (pages.isEmpty() && entry.pageFeatures != null) { + pages = pagifyFeatures(entry.pageFeatures) + } + val features = pages.getOrNull(currentPage) + if (features != null) { + for (feature in features) initPageFeature(feature) + } } } } @@ -89,6 +96,8 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo private fun PageTurnerRow() { Row({ width = grow() + paddingX = 30 + paddingY = 15 }) { if (currentPage != 0) { Box({ 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 index 4176fcc7..ea0b27cf 100644 --- 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 @@ -38,19 +38,20 @@ object TitleFeatureRenderer : PageFeatureRenderer { Row({ width = grow() - height = fixed(LINE_HEIGHT * (lines.size + PARAGRAPH_OFFSET)) + height = fixed(LINE_HEIGHT * lines.size) }) { draw { Renderable { guiGraphics, _, _, _ -> - guiGraphics.usePose { - for ((index, line) in lines.withIndex()) { - guiGraphics.drawCenteredString( - font, line, size.x / 2 - ) - if (index != lines.size - 1) translateXY(0, PRG_OFFSET_OTHER) + guiGraphics.usePose { + for ((index, line) in lines.withIndex()) { + guiGraphics.drawCenteredString( + font, line, size.x / 2 + ) + translateXY(0, LINE_HEIGHT) + if (index != lines.size - 1) translateXY(0, PRG_OFFSET_OTHER) + } } } - } } } } 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 70b39240..629c2920 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 @@ -208,7 +208,9 @@ class T7LanguageProvider(output: PackOutput, locale: String) : LanguageProvider( addPageFeature( PARAGRAPH, ResearchEntries.Story.STORY1, 1, """ - This is a longer paragraph to highlight the importance of splicing longer pieces of text over multiple pages. If you do not splice the paragraph to go over multiple pages, the text will spill out of the bottom of the screen and we don't want that to happen, hence DynamicRenderingHelper.kt + This is a longer paragraph to highlight the importance of splicing longer pieces of text over multiple pages. If you do not splice the paragraph to go over multiple pages, the text will spill out of the bottom of the screen and we don't want that to happen, hence DynamicRenderingHelper.kt. I will continue with some lorem ipsum. 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( From b3cf141b9f4502578c9daf778934d7b74c2c78dd Mon Sep 17 00:00:00 2001 From: KorrAn34 Date: Thu, 16 Jul 2026 17:44:00 +0200 Subject: [PATCH 5/8] Addressed preliminary comments on PR --- .../research/entry/alchemy/alchemy.json | 1 - .../thavma/research/entry/story/story1.json | 1 - .../thavma/research/entry/thavma/alchemy.json | 1 - .../research/entry/thavma/arcane_lens.json | 1 - .../research/entry/thavma/infusion.json | 1 - .../thavma/research/entry/thavma/ores.json | 1 - .../entry/thavma/research_proficiency.json | 1 - .../research/entry/thavma/research_table.json | 1 - .../research/entry/thavma/technology.json | 1 - .../thavma/research/entry/thavma/thavma.json | 1 - .../thavma/research/entry/thavma/trees.json | 1 - .../thavma/research/entry/thavma/wands.json | 1 - .../impl/client/event/T7ClientModEvents.kt | 2 +- .../impl/client/gui/book/EntryScreen.kt | 1 - .../client/gui/book/FormCharSeqRenderer.kt | 40 ------------------- .../impl/common/book/FormCharSeqFeature.kt | 29 -------------- .../impl/common/research/ResearchEntry.kt | 7 +--- .../T7DatapackBuiltinEntriesProvider.kt | 1 - .../registries/deferred/PageFeatureTypes.kt | 3 +- 19 files changed, 4 insertions(+), 91 deletions(-) delete mode 100644 src/main/java/me/alegian/thavma/impl/client/gui/book/FormCharSeqRenderer.kt delete mode 100644 src/main/java/me/alegian/thavma/impl/common/book/FormCharSeqFeature.kt 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 3f95b085..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 @@ -27,7 +27,6 @@ "id": "thavma:crucible" }, "pageFeatures": [], - "pages": [], "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 16c0eaf4..b86ff040 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 @@ -97,7 +97,6 @@ } } ], - "pages": [], "position": [ 0, -3 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 7ccf78c2..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 @@ -27,7 +27,6 @@ "id": "thavma:crucible" }, "pageFeatures": [], - "pages": [], "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 6c7af87d..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 @@ -37,7 +37,6 @@ "id": "thavma:arcane_lens" }, "pageFeatures": [], - "pages": [], "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 31d4628b..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 @@ -27,7 +27,6 @@ "id": "thavma:infusion_matrix" }, "pageFeatures": [], - "pages": [], "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 fa5ce5e6..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 @@ -29,7 +29,6 @@ "id": "thavma:aqua_shard" }, "pageFeatures": [], - "pages": [], "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 0fdbd369..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 @@ -27,7 +27,6 @@ "id": "thavma:research_table" }, "pageFeatures": [], - "pages": [], "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 1e8ce870..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 @@ -33,7 +33,6 @@ "id": "thavma:research_table" }, "pageFeatures": [], - "pages": [], "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 ece3a4d0..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 @@ -27,7 +27,6 @@ "id": "thavma:goggles" }, "pageFeatures": [], - "pages": [], "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 9c48db7b..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 @@ -30,7 +30,6 @@ "id": "thavma:book" }, "pageFeatures": [], - "pages": [], "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 e6e12baf..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 @@ -29,7 +29,6 @@ "id": "thavma:greatwood_log" }, "pageFeatures": [], - "pages": [], "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 2705a125..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 @@ -27,7 +27,6 @@ "id": "thavma:thavmite_silverwood_wand" }, "pageFeatures": [], - "pages": [], "position": [ -2, 4 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 f2e44637..bdf03f6b 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 @@ -230,7 +230,7 @@ private fun registerPageFeatureRenderers(event: RegisterPageFeatureRenderersEven event.register(PageFeatureTypes.PARAGRAPH.get(), ParagraphFeatureRenderer) event.register(PageFeatureTypes.TITLE.get(), TitleFeatureRenderer) event.register(PageFeatureTypes.FIGURE.get(), FigureFeatureRenderer) - event.register(PageFeatureTypes.FORMCHARSEQ.get(), FormCharSeqRenderer) + //event.register(PageFeatureTypes.FORMCHARSEQ.get(), FormCharSeqRenderer) event.register(PageFeatureTypes.RECIPE.get(), RecipeFeatureRenderer) } 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 2efa1c74..d495bcb3 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 @@ -38,7 +38,6 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo TextureBox(BG) { Column({ size = grow() - //gap = 8 }) { Row({ size = grow() diff --git a/src/main/java/me/alegian/thavma/impl/client/gui/book/FormCharSeqRenderer.kt b/src/main/java/me/alegian/thavma/impl/client/gui/book/FormCharSeqRenderer.kt deleted file mode 100644 index 7b1be697..00000000 --- a/src/main/java/me/alegian/thavma/impl/client/gui/book/FormCharSeqRenderer.kt +++ /dev/null @@ -1,40 +0,0 @@ -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.draw -import me.alegian.thavma.impl.client.gui.layout.fixed -import me.alegian.thavma.impl.client.gui.layout.grow -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.FormCharSeqFeature -import net.minecraft.client.gui.Font -import net.minecraft.client.gui.components.Renderable - -object FormCharSeqRenderer : PageFeatureRenderer { - - override fun initPageFeature( - screen: EntryScreen, - feature: FormCharSeqFeature, - maxWidth: Int, - font: Font - ) { - - Row({ - width = grow() - height = fixed(LINE_HEIGHT * (feature.text.size + PARAGRAPH_OFFSET)) - }) { - draw { - Renderable { guiGraphics, _, _, _ -> - guiGraphics.usePose { - for (line in feature.text) { - guiGraphics.drawString(font, line) - translateXY(0, LINE_HEIGHT) - } - translateXY(0, PRG_OFFSET_OTHER) - } - } - } - } - } -} diff --git a/src/main/java/me/alegian/thavma/impl/common/book/FormCharSeqFeature.kt b/src/main/java/me/alegian/thavma/impl/common/book/FormCharSeqFeature.kt deleted file mode 100644 index 4feb7e56..00000000 --- a/src/main/java/me/alegian/thavma/impl/common/book/FormCharSeqFeature.kt +++ /dev/null @@ -1,29 +0,0 @@ -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 FormCharSeqFeature( - val text: List, override val startsPage: Boolean = false, - override val forceIndex: Int? = null -) : PageFeature { - override val type: PageFeatureType<*> - get() = PageFeatureTypes.FORMCHARSEQ.get() - - 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 -> - FormCharSeqFeature(components.map { it.visualOrderText }) - } - } - } -} \ No newline at end of file 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 da44f121..612cff5e 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 @@ -3,7 +3,6 @@ package me.alegian.thavma.impl.common.research 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 @@ -25,8 +24,7 @@ class ResearchEntry( val position: Vector2i, val preferX: Boolean, val children: List>, - val pages: List?, - val pageFeatures: List?, + val pageFeatures: List, val icon: ItemStack, val title: Component, val defaultResearchState: List, @@ -47,8 +45,7 @@ class ResearchEntry( Codec.BOOL.fieldOf("preferX").forGetter(ResearchEntry::preferX), RegistryFileCodec.create(T7DatapackRegistries.RESEARCH_ENTRY, CODEC).listOf().fieldOf("children") .forGetter(ResearchEntry::children), - Page.CODEC.listOf().optionalFieldOf("pages", null).forGetter(ResearchEntry::pages), - PageFeature.CODEC.listOf().optionalFieldOf("pageFeatures", null).forGetter(ResearchEntry::pageFeatures), + 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 47b4e71f..eafb9fa6 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 @@ -320,7 +320,6 @@ private class ResearchEntryBuilder( pos, preferX, childrenHolders, - pages, pageFeatures, icon, Component.translatable(ResearchEntry.translationId(key)).withStyle(Rarity.UNCOMMON.styleModifier), 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 index e901c54d..0586259f 100644 --- 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 @@ -14,6 +14,5 @@ object PageFeatureTypes { 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) } - val FORMCHARSEQ = - REGISTRAR.register("formcharseq") { -> PageFeatureType(rl("formcharseq"), FormCharSeqFeature.CODEC) } + //val FORMCHARSEQ = REGISTRAR.register("formcharseq") { -> PageFeatureType(rl("formcharseq"), FormCharSeqFeature.CODEC) } } \ No newline at end of file From ed8348ec7841aa7a398fbe27dae9ccff22197cde Mon Sep 17 00:00:00 2001 From: KorrAn34 Date: Sun, 19 Jul 2026 19:56:39 +0200 Subject: [PATCH 6/8] PR comments #1 --- .../thavma/impl/client/event/T7ClientModEvents.kt | 1 - .../impl/client/gui/book/DynamicRenderingHelper.kt | 9 --------- .../alegian/thavma/impl/client/gui/book/EntryScreen.kt | 7 +++---- .../thavma/impl/client/gui/book/FigureFeatureRenderer.kt | 2 +- .../thavma/impl/client/gui/layout/LayoutExtensions.kt | 3 +-- .../data/providers/T7DatapackBuiltinEntriesProvider.kt | 9 --------- .../impl/init/registries/deferred/PageFeatureTypes.kt | 1 - 7 files changed, 5 insertions(+), 27 deletions(-) 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 bdf03f6b..491ad516 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 @@ -230,7 +230,6 @@ private fun registerPageFeatureRenderers(event: RegisterPageFeatureRenderersEven event.register(PageFeatureTypes.PARAGRAPH.get(), ParagraphFeatureRenderer) event.register(PageFeatureTypes.TITLE.get(), TitleFeatureRenderer) event.register(PageFeatureTypes.FIGURE.get(), FigureFeatureRenderer) - //event.register(PageFeatureTypes.FORMCHARSEQ.get(), FormCharSeqRenderer) event.register(PageFeatureTypes.RECIPE.get(), RecipeFeatureRenderer) } 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 index 628921cf..2f1840d6 100644 --- 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 @@ -1,16 +1,7 @@ package me.alegian.thavma.impl.client.gui.book -import me.alegian.thavma.impl.common.book.PageFeature import net.minecraft.client.Minecraft val LINE_HEIGHT = Minecraft.getInstance().font.lineHeight + 2 const val PARAGRAPH_OFFSET = 2 / 3f val PRG_OFFSET_OTHER = LINE_HEIGHT * 2 / 3 - - -/** - * Returns a list of lists of features, one feature per list -> one per page. - */ -fun pagifyFeatures(features: List): List> { - return features.map { listOf(it) } -} \ 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 d495bcb3..1f51d23c 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 @@ -4,7 +4,6 @@ import me.alegian.thavma.impl.client.gui.layout.* import me.alegian.thavma.impl.client.texture.Texture 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 @@ -16,7 +15,7 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo } private var currentPage = 0 - private val fontify = Minecraft.getInstance().font + private val fontify = super.font private val entry = _entry.value() var maxWidth = 0 @@ -61,8 +60,8 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo init() } } else { - if (pages.isEmpty() && entry.pageFeatures != null) { - pages = pagifyFeatures(entry.pageFeatures) + if (pages.isEmpty()) { + pages = entry.pageFeatures.map { listOf(it) } } val features = pages.getOrNull(currentPage) if (features != null) { 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 index 890388fd..481e53c2 100644 --- 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 @@ -19,7 +19,7 @@ object FigureFeatureRenderer : PageFeatureRenderer { width = grow() height = fixed(feature.textureHeight) }) { - CenteredTextureBox(feature.image, maxWidth) {} + CenteredTextureBox(feature.image, maxWidth) } } } \ No newline at end of file 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 56bc7204..958eb1c7 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 @@ -61,7 +61,7 @@ fun TextureBox(texture: Texture, children: T7LayoutElement.() -> Unit) = children() } -fun CenteredTextureBox(texture: Texture, maxWidth: Int, children: T7LayoutElement.() -> Unit) { +fun CenteredTextureBox(texture: Texture, maxWidth: Int) { val screen = LayoutExtensions.currScreen ?: throw IllegalStateException("Thavma Exception: cannot add renderable without setting LayoutExtensions.currScreen first!") Row({ @@ -76,7 +76,6 @@ fun CenteredTextureBox(texture: Texture, maxWidth: Int, children: T7LayoutElemen } }) } - children() } } 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 eafb9fa6..4393d743 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 @@ -126,8 +126,6 @@ class T7DatapackBuiltinEntriesProvider(output: PackOutput, registries: Completab T7Items.BOOK.get().defaultInstance ) .research(lockedAspect(2, 0, Aspects.AETHER), lockedAspect(2, 4, Aspects.AETHER)) -// .addPage(simpleTextPage(3, true)) -// .addPage(simpleTextPage(1, false)) .addChild(ResearchEntries.Thavma.TREES) .addChild(ResearchEntries.Thavma.ORES) .defaultKnown() @@ -191,7 +189,6 @@ class T7DatapackBuiltinEntriesProvider(output: PackOutput, registries: Completab ) .research(lockedAspect(2, 0, Aspects.LUX), lockedAspect(2, 4, Aspects.AETHER), broken(2, 2)) .addChild(ResearchEntries.Thavma.RESEARCH_TABLE) -// .addPage(simpleTextPage(3, true)) .build(ctx) ResearchEntryBuilder( @@ -201,7 +198,6 @@ class T7DatapackBuiltinEntriesProvider(output: PackOutput, registries: Completab T7Blocks.RESEARCH_TABLE.get().asItem().defaultInstance ) .research(lockedAspect(2, 0, Aspects.AETHER), lockedAspect(2, 4, Aspects.HERBA)) -// .addPage { _, _ -> CraftingPage(Recipes.CHEST) } .addChild(ResearchEntries.Thavma.WANDS) .addChild(ResearchEntries.Thavma.TECHNOLOGY) .addChild(ResearchEntries.Thavma.ALCHEMY) @@ -288,11 +284,6 @@ private class ResearchEntryBuilder( return this } -// fun addPage(makePage: (ResourceKey, Int) -> Page): ResearchEntryBuilder { -// pages.add(makePage(key, pages.size)) -// return this -// } - inline fun addPageFeature(crossinline makeFeature: (ResourceKey, Int) -> T): ResearchEntryBuilder { pageFeatures.add(makeFeature(key, pageFeatures.filterIsInstance().size)) return this 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 index 0586259f..01ddd273 100644 --- 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 @@ -14,5 +14,4 @@ object PageFeatureTypes { 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) } - //val FORMCHARSEQ = REGISTRAR.register("formcharseq") { -> PageFeatureType(rl("formcharseq"), FormCharSeqFeature.CODEC) } } \ No newline at end of file From fea02eb6cce697742b2b67a9a8e7f6c4ac7db19f Mon Sep 17 00:00:00 2001 From: KorrAn34 Date: Thu, 23 Jul 2026 11:08:31 +0200 Subject: [PATCH 7/8] Removed font variable in EntryScreen, was not needed --- .../java/me/alegian/thavma/impl/client/gui/book/EntryScreen.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 1f51d23c..413bf454 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 @@ -15,7 +15,6 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo } private var currentPage = 0 - private val fontify = super.font private val entry = _entry.value() var maxWidth = 0 @@ -141,7 +140,7 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo private fun initPageFeature(feature: T) { if (feature != null) { val renderer = PAGE_FEATURE_RENDERERS[feature.type] as PageFeatureRenderer - renderer.initPageFeature(this, feature, maxWidth, fontify) + renderer.initPageFeature(this, feature, maxWidth, this.font) } } From 40ee6f0a4ed90dcfe4afa77b772e6870537db1ec Mon Sep 17 00:00:00 2001 From: alegian Date: Mon, 27 Jul 2026 18:20:41 +0300 Subject: [PATCH 8/8] remove maxes --- .../impl/client/gui/book/EntryScreen.kt | 29 +++++-------------- .../client/gui/book/FigureFeatureRenderer.kt | 5 ++-- .../client/gui/book/PageFeatureRenderer.kt | 4 +-- .../gui/book/ParagraphFeatureRenderer.kt | 27 +++++++++-------- .../client/gui/book/RecipeFeatureRenderer.kt | 4 +-- .../client/gui/book/TitleFeatureRenderer.kt | 15 ++++++---- .../client/gui/layout/LayoutExtensions.kt | 4 +-- 7 files changed, 40 insertions(+), 48 deletions(-) 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 413bf454..1403ae65 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 @@ -17,11 +17,7 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo private var currentPage = 0 private val entry = _entry.value() - var maxWidth = 0 - var maxHeight = 0 var pages = listOf>() - private var isFirstPass = true - override fun init() { super.init() @@ -51,21 +47,12 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo size = grow() gap = 4 }) { - if (isFirstPass) { - afterLayout { - maxWidth = this@afterLayout.size.x.toInt() - maxHeight = this@afterLayout.size.y.toInt() - isFirstPass = false - init() - } - } else { - if (pages.isEmpty()) { - pages = entry.pageFeatures.map { listOf(it) } - } - val features = pages.getOrNull(currentPage) - if (features != null) { - for (feature in features) initPageFeature(feature) - } + if (pages.isEmpty()) { + pages = entry.pageFeatures.map { listOf(it) } + } + val features = pages.getOrNull(currentPage) + if (features != null) { + for (feature in features) initPageFeature(feature) } } } @@ -140,9 +127,9 @@ class EntryScreen(_entry: Holder) : Screen(Component.literal("Boo private fun initPageFeature(feature: T) { if (feature != null) { val renderer = PAGE_FEATURE_RENDERERS[feature.type] as PageFeatureRenderer - renderer.initPageFeature(this, feature, maxWidth, this.font) + renderer.initPageFeature(this, feature, this.font) } } override fun isPauseScreen() = false -} \ No newline at end of file +} 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 index 481e53c2..d5d5c170 100644 --- 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 @@ -12,14 +12,13 @@ object FigureFeatureRenderer : PageFeatureRenderer { override fun initPageFeature( screen: EntryScreen, feature: FigureFeature, - maxWidth: Int, font: Font ) { Row({ width = grow() height = fixed(feature.textureHeight) }) { - CenteredTextureBox(feature.image, maxWidth) + CenteredTextureBox(feature.image) } } -} \ No newline at end of file +} 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 index 33da2343..fe1366db 100644 --- 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 @@ -7,5 +7,5 @@ import net.minecraft.client.gui.Font val PAGE_FEATURE_RENDERERS = mutableMapOf, PageFeatureRenderer<*>>() interface PageFeatureRenderer { - fun initPageFeature(screen: EntryScreen, feature: T, maxWidth: Int, font: Font) -} \ No newline at end of file + fun initPageFeature(screen: EntryScreen, feature: T, font: Font) +} 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 index 4cd155d2..c59a7914 100644 --- 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 @@ -1,8 +1,8 @@ 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.derived import me.alegian.thavma.impl.client.gui.layout.draw -import me.alegian.thavma.impl.client.gui.layout.fixed import me.alegian.thavma.impl.client.gui.layout.grow import me.alegian.thavma.impl.client.util.drawString import me.alegian.thavma.impl.client.util.translateXY @@ -10,32 +10,35 @@ 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.FormattedCharSequence object ParagraphFeatureRenderer : PageFeatureRenderer { override fun initPageFeature( screen: EntryScreen, feature: ParagraphFeature, - maxWidth: Int, font: Font ) { - val lines = font.split(feature.text, maxWidth) + var lines: List = listOf() Row({ width = grow() - height = fixed(LINE_HEIGHT * (lines.size + PARAGRAPH_OFFSET)) + height = derived { w -> + lines = font.split(feature.text, w.toInt()) + LINE_HEIGHT * (lines.size + PARAGRAPH_OFFSET) + } }) { draw { - Renderable { guiGraphics, _, _, _ -> - guiGraphics.usePose { - for (line in lines) { - guiGraphics.drawString(font, line) - translateXY(0, LINE_HEIGHT) + Renderable { guiGraphics, _, _, _ -> + guiGraphics.usePose { + for (line in lines) { + guiGraphics.drawString(font, line) + translateXY(0, LINE_HEIGHT) + } + translateXY(0, PRG_OFFSET_OTHER) } - translateXY(0, PRG_OFFSET_OTHER) } } - } } } -} \ 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 index f33c06f2..5f85ffbc 100644 --- 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 @@ -19,7 +19,7 @@ object RecipeFeatureRenderer : PageFeatureRenderer { private val TITLE = Component.translatable(RecipeType.CRAFTING.translationId) private const val GAP = 12 - override fun initPageFeature(screen: EntryScreen, feature: RecipeFeature, maxWidth: Int, font: Font) { + override fun initPageFeature(screen: EntryScreen, feature: RecipeFeature, font: Font) { val recipe = Minecraft.getInstance().level?.recipeManager?.byKey(feature.recipeRL)?.getOrNull()?.value if (recipe !is CraftingRecipe) return // TODO: support other recipe types @@ -49,4 +49,4 @@ object RecipeFeatureRenderer : PageFeatureRenderer { } } } -} \ No newline at end of file +} 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 index ea0b27cf..123c0b77 100644 --- 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 @@ -8,6 +8,7 @@ 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.FormattedCharSequence object TitleFeatureRenderer : PageFeatureRenderer { @@ -16,10 +17,9 @@ object TitleFeatureRenderer : PageFeatureRenderer { override fun initPageFeature( screen: EntryScreen, feature: TitleFeature, - maxWidth: Int, font: Font ) { - Title(feature, maxWidth, font) + Title(feature, font) Separator() } @@ -32,13 +32,16 @@ object TitleFeatureRenderer : PageFeatureRenderer { } } - private fun Title(title: TitleFeature, maxWidth: Int, font: Font) { + private fun Title(title: TitleFeature, font: Font) { - val lines = font.split(title.text, maxWidth) + var lines: List = listOf() Row({ width = grow() - height = fixed(LINE_HEIGHT * lines.size) + height = derived { w -> + lines = font.split(title.text, w.toInt()) + (LINE_HEIGHT * lines.size).toFloat() + } }) { draw { Renderable { guiGraphics, _, _, _ -> @@ -55,4 +58,4 @@ object TitleFeatureRenderer : PageFeatureRenderer { } } } -} \ No newline at end of file +} 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 958eb1c7..c433a72d 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 @@ -61,7 +61,7 @@ fun TextureBox(texture: Texture, children: T7LayoutElement.() -> Unit) = children() } -fun CenteredTextureBox(texture: Texture, maxWidth: Int) { +fun CenteredTextureBox(texture: Texture) { val screen = LayoutExtensions.currScreen ?: throw IllegalStateException("Thavma Exception: cannot add renderable without setting LayoutExtensions.currScreen first!") Row({ @@ -71,7 +71,7 @@ fun CenteredTextureBox(texture: Texture, maxWidth: Int) { afterLayout { screen.renderables.add(Renderable { guiGraphics, mouseX, mouseY, partialTick -> guiGraphics.usePose { - translateXY(position.x + (maxWidth - texture.width) / 2, position.y) + translateXY(position.x + (size.x - texture.width) / 2, position.y) renderableTexture(texture).render(guiGraphics, mouseX, mouseY, partialTick) } })