From 5a9903637baed93d043f6dc66cb8d7a7241c39d5 Mon Sep 17 00:00:00 2001 From: P-nutsK <107553542+P-nutsK@users.noreply.github.com> Date: Thu, 21 May 2026 18:35:54 +0900 Subject: [PATCH 01/12] =?UTF-8?q?wip=20=E9=80=80=E9=81=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mixin/ScreenAccessor.java | 14 + .../p_nsk/replicated_integration/ModMain.kt | 6 + .../client/MatterIndexClientEvents.kt | 146 +++++ .../client/MatterIndexScreen.kt | 558 ++++++++++++++++++ .../config/ForgeUiConfig.kt | 25 + .../replicated_integration.mixins.json | 1 + .../mixin/neo/ScreenAccessor.java | 14 + .../p_nsk/replicated_integration/ModMain.kt | 12 + .../client/MatterIndexClientEvents.kt | 138 +++++ .../client/MatterIndexScreen.kt | 554 +++++++++++++++++ .../config/NeoUiConfig.kt | 25 + .../replicated_integration.mixins.json | 3 + .../api/client/MatterAmountFormatter.kt | 69 +++ .../api/client/MatterIndexModels.kt | 145 +++++ .../test/kotlin/MatterAmountFormatterTest.kt | 31 + 15 files changed, 1741 insertions(+) create mode 100644 1.20.1-forge/src/main/java/com/p_nsk/replicated_integration/mixin/ScreenAccessor.java create mode 100644 1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt create mode 100644 1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt create mode 100644 1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/config/ForgeUiConfig.kt create mode 100644 1.21.1-neo/src/main/java/com/p_nsk/replicated_integration/mixin/neo/ScreenAccessor.java create mode 100644 1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt create mode 100644 1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt create mode 100644 1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/config/NeoUiConfig.kt create mode 100644 common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterAmountFormatter.kt create mode 100644 common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexModels.kt create mode 100644 common/src/test/kotlin/MatterAmountFormatterTest.kt diff --git a/1.20.1-forge/src/main/java/com/p_nsk/replicated_integration/mixin/ScreenAccessor.java b/1.20.1-forge/src/main/java/com/p_nsk/replicated_integration/mixin/ScreenAccessor.java new file mode 100644 index 0000000..c149e77 --- /dev/null +++ b/1.20.1-forge/src/main/java/com/p_nsk/replicated_integration/mixin/ScreenAccessor.java @@ -0,0 +1,14 @@ +package com.p_nsk.replicated_integration.mixin; + +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.screens.Screen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(Screen.class) +public interface ScreenAccessor { + @Invoker("addRenderableWidget") + T replicatedIntegration$addRenderableWidget(T widget); +} diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt index 9cee8e6..55632e6 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt @@ -5,6 +5,7 @@ package com.p_nsk.replicated_integration import com.p_nsk.replicated_integration.command.MatterNodeDebugCommand import com.p_nsk.replicated_integration.command.MatterCommand import com.p_nsk.replicated_integration.config.ForgeCompatibilityConfig +import com.p_nsk.replicated_integration.config.ForgeUiConfig import com.p_nsk.replicated_integration.core.ForgeBuiltinReplicationAddons import com.p_nsk.replicated_integration.data.ReplicatedIntegrationDataGen import com.p_nsk.replicated_integration.data.ReplicationReloadHooks @@ -43,5 +44,10 @@ object ModMain { ForgeCompatibilityConfig.spec, "replicated_integration-compatibility.toml", ) + ModLoadingContext.get().registerConfig( + ModConfig.Type.CLIENT, + ForgeUiConfig.spec, + "replicated_integration-ui.toml", + ) } } diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt new file mode 100644 index 0000000..0bd777f --- /dev/null +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt @@ -0,0 +1,146 @@ +package com.p_nsk.replicated_integration.client + +import com.p_nsk.replicated_integration.Constants +import com.p_nsk.replicated_integration.mixin.ScreenAccessor +import net.minecraft.ChatFormatting +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.AbstractButton +import net.minecraft.client.gui.narration.NarrationElementOutput +import net.minecraft.client.gui.screens.Screen +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import net.minecraftforge.api.distmarker.Dist +import net.minecraftforge.client.event.ScreenEvent +import net.minecraftforge.event.TickEvent +import net.minecraftforge.event.entity.player.ItemTooltipEvent +import net.minecraftforge.eventbus.api.SubscribeEvent +import net.minecraftforge.fml.common.Mod +import org.lwjgl.glfw.GLFW +import kotlin.math.max +import kotlin.math.min + +@Mod.EventBusSubscriber(modid = Constants.MOD_ID, value = [Dist.CLIENT], bus = Mod.EventBusSubscriber.Bus.FORGE) +object MatterIndexClientEvents { + private const val HOLD_TICKS = 20 + + private var hoveredStack: ItemStack = ItemStack.EMPTY + private var trackingStack: ItemStack = ItemStack.EMPTY + private var holdTicks: Int = 0 + + @JvmStatic + @SubscribeEvent + fun onScreenInit(event: ScreenEvent.Init.Post) { + val screen = event.screen + if (screen !is AbstractContainerScreen<*>) return + (screen as ScreenAccessor).`replicatedIntegration$addRenderableWidget`(MatterIndexSidebarButton(screen)) + } + + @JvmStatic + @SubscribeEvent + fun onItemTooltip(event: ItemTooltipEvent) { + val minecraft = Minecraft.getInstance() + val screen = minecraft.screen ?: return + if (screen is MatterIndexScreen || event.entity == null) return + updateHovered(event.itemStack) + if (trackingStack !== event.itemStack && !trackingStack.`is`(event.itemStack.item)) return + val progress = holdTicks.toFloat() / HOLD_TICKS.toFloat() + val line = + if (progress <= 0f) { + Component.literal("Hold W to open Replication Index").withStyle(ChatFormatting.DARK_GRAY) + } else { + Component.literal(progressBar(progress)).withStyle(ChatFormatting.GRAY) + } + if (event.toolTip.size < 2) { + event.toolTip.add(line) + } else { + event.toolTip.add(1, line) + } + } + + @JvmStatic + @SubscribeEvent + fun onClientTick(event: TickEvent.ClientTickEvent) { + if (event.phase != TickEvent.Phase.END) return + val minecraft = Minecraft.getInstance() + val screen = minecraft.screen + if (screen == null || screen is MatterIndexScreen || trackingStack.isEmpty) { + resetHold() + return + } + + val window = minecraft.window.window + val holding = !hoveredStack.isEmpty && GLFW.glfwGetKey(window, GLFW.GLFW_KEY_W) == GLFW.GLFW_PRESS + holdTicks = + if (holding) { + min(HOLD_TICKS, holdTicks + 1) + } else { + max(0, holdTicks - 1) + } + + if (holdTicks >= HOLD_TICKS) { + val itemId = MatterIndexScreen.itemId(trackingStack) + resetHold() + minecraft.setScreen(MatterIndexScreen(screen, itemId)) + return + } + + hoveredStack = ItemStack.EMPTY + } + + private fun updateHovered(stack: ItemStack) { + hoveredStack = ItemStack.EMPTY + if (stack.isEmpty) return + if (!MatterIndexScreen.hasMatter(MatterIndexScreen.itemId(stack))) return + if (trackingStack.isEmpty || !trackingStack.`is`(stack.item)) { + holdTicks = 0 + } + trackingStack = stack + hoveredStack = stack + } + + private fun resetHold() { + hoveredStack = ItemStack.EMPTY + trackingStack = ItemStack.EMPTY + holdTicks = 0 + } + + private fun progressBar(progress: Float): String { + val total = 18 + val current = (progress.coerceIn(0f, 1f) * total).toInt() + return "|".repeat(current) + ChatFormatting.DARK_GRAY + "|".repeat(total - current) + } + + private class MatterIndexSidebarButton( + private val owner: Screen, + ) : AbstractButton(2, 2, 22, 22, Component.literal("Replication Index")) { + override fun onPress() { + Minecraft.getInstance().setScreen(MatterIndexScreen(owner)) + } + + override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { + guiGraphics.fill(x, y, x + width, y + height, 0xCC071010.toInt()) + guiGraphics.fill(x, y, x + width, y + 1, 0xFF41514D.toInt()) + guiGraphics.fill(x, y + height - 1, x + width, y + height, 0xFF41514D.toInt()) + guiGraphics.fill(x, y, x + 1, y + height, 0xFF41514D.toInt()) + guiGraphics.fill(x + width - 1, y, x + width, y + height, 0xFF41514D.toInt()) + if (isHoveredOrFocused) { + guiGraphics.fill(x + 1, y + 1, x + width - 1, y + height - 1, 0x5536D84A) + guiGraphics.renderTooltip(Minecraft.getInstance().font, message, mouseX, mouseY) + } + drawRiMark(guiGraphics, x + 4, y + 4) + } + + override fun updateWidgetNarration(output: NarrationElementOutput) { + defaultButtonNarrationText(output) + } + + private fun drawRiMark(guiGraphics: GuiGraphics, x: Int, y: Int) { + guiGraphics.fill(x + 5, y, x + 9, y + 4, 0xFF42E84F.toInt()) + guiGraphics.fill(x, y + 5, x + 4, y + 9, 0xFF42E84F.toInt()) + guiGraphics.fill(x + 10, y + 5, x + 14, y + 9, 0xFF42E84F.toInt()) + guiGraphics.fill(x + 5, y + 10, x + 9, y + 14, 0xFF42E84F.toInt()) + } + } +} diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt new file mode 100644 index 0000000..665556c --- /dev/null +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -0,0 +1,558 @@ +package com.p_nsk.replicated_integration.client + +import com.buuz135.replication.Replication +import com.buuz135.replication.calculation.MatterCompound +import com.buuz135.replication.calculation.client.ClientReplicationCalculation +import com.p_nsk.replicated_integration.api.client.MatterAmountFormatter +import com.p_nsk.replicated_integration.api.client.MatterIndexFiltering +import com.p_nsk.replicated_integration.api.client.MatterIndexRecord +import com.p_nsk.replicated_integration.api.client.MatterIndexSort +import com.p_nsk.replicated_integration.api.client.MatterIndexSortDirection +import com.p_nsk.replicated_integration.config.ForgeUiConfig +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.Button +import net.minecraft.client.gui.components.EditBox +import net.minecraft.client.gui.screens.Screen +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.ItemStack +import kotlin.math.max +import kotlin.math.min + +class MatterIndexScreen( + private val parent: Screen?, + private val focusedItemId: String? = null, +) : Screen(Component.literal("Replication Index")) { + private val entries: List = loadEntries() + private val matterTypes: List = + entries + .flatMap { it.record.matterValues.keys } + .distinct() + .filterNot { it == "empty" } + .sorted() + + private var selectedItemId: String? = focusedItemId + private var sort = MatterIndexSort() + private var indexScroll = 0 + private var equivalentScroll = 0 + private var matterOnly = true + private var replicableOnly = false + private var decomposableOnly = false + private var hoveredStack: ItemStack = ItemStack.EMPTY + private var hoveredAmountTooltip: List = emptyList() + + private lateinit var searchBox: EditBox + private lateinit var matterOnlyButton: Button + private lateinit var replicableButton: Button + private lateinit var decomposableButton: Button + + override fun init() { + val layout = layout() + searchBox = EditBox(font, layout.index.x + 16, layout.index.y + 35, min(260, layout.index.w / 3), 18, Component.literal("search")) + searchBox.setMaxLength(80) + searchBox.setTextColor(0xE8F3ED) + searchBox.value = "" + addWidget(searchBox) + + matterOnlyButton = addRenderableWidget( + Button.builder(toggleLabel("M", matterOnly)) { + matterOnly = !matterOnly + matterOnlyButton.message = toggleLabel("M", matterOnly) + indexScroll = 0 + }.bounds(searchBox.x + searchBox.width + 8, searchBox.y, 34, 18).build(), + ) + replicableButton = addRenderableWidget( + Button.builder(toggleLabel("R", replicableOnly)) { + replicableOnly = !replicableOnly + replicableButton.message = toggleLabel("R", replicableOnly) + indexScroll = 0 + }.bounds(matterOnlyButton.x + matterOnlyButton.width + 4, searchBox.y, 34, 18).build(), + ) + decomposableButton = addRenderableWidget( + Button.builder(toggleLabel("D", decomposableOnly)) { + decomposableOnly = !decomposableOnly + decomposableButton.message = toggleLabel("D", decomposableOnly) + indexScroll = 0 + }.bounds(replicableButton.x + replicableButton.width + 4, searchBox.y, 34, 18).build(), + ) + } + + override fun tick() { + searchBox.tick() + } + + override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { + hoveredStack = ItemStack.EMPTY + hoveredAmountTooltip = emptyList() + val layout = layout() + guiGraphics.fill(0, 0, width, height, 0xAA050A0A.toInt()) + renderPanel(guiGraphics, layout.index) + renderPanel(guiGraphics, layout.focus) + renderTitle(guiGraphics, layout) + searchBox.render(guiGraphics, mouseX, mouseY, partialTick) + renderControls(guiGraphics, mouseX, mouseY, partialTick) + + val visible = visibleEntries() + if (selectedItemId == null || entries.none { it.record.itemId == selectedItemId }) { + selectedItemId = visible.firstOrNull()?.record?.itemId ?: entries.firstOrNull()?.record?.itemId + } + + renderIndex(guiGraphics, layout, visible, mouseX, mouseY) + renderFocus(guiGraphics, layout, mouseX, mouseY) + + if (!hoveredStack.isEmpty) { + guiGraphics.renderTooltip(font, hoveredStack, mouseX, mouseY) + } else if (hoveredAmountTooltip.isNotEmpty()) { + guiGraphics.renderTooltip(font, hoveredAmountTooltip.map { it.visualOrderText }, mouseX, mouseY) + } + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (super.mouseClicked(mouseX, mouseY, button)) return true + if (button != 0) return false + + val layout = layout() + val visible = visibleEntries() + if (clickHeader(layout, mouseX.toInt(), mouseY.toInt())) return true + + val rowArea = indexRows(layout) + if (mouseX.toInt() in rowArea.x until rowArea.x + rowArea.w && mouseY.toInt() in rowArea.y until rowArea.y + rowArea.h) { + val index = indexScroll + (mouseY.toInt() - rowArea.y) / ROW_H + visible.getOrNull(index)?.let { + selectedItemId = it.record.itemId + equivalentScroll = 0 + return true + } + } + + equivalentAt(layout, mouseX.toInt(), mouseY.toInt())?.let { + selectedItemId = it.record.itemId + equivalentScroll = 0 + return true + } + + return false + } + + override fun mouseScrolled(mouseX: Double, mouseY: Double, delta: Double): Boolean { + val layout = layout() + if (contains(indexRows(layout), mouseX.toInt(), mouseY.toInt())) { + val visibleRows = max(1, indexRows(layout).h / ROW_H) + val maxScroll = max(0, visibleEntries().size - visibleRows) + indexScroll = (indexScroll - (delta * 3).toInt()).coerceIn(0, maxScroll) + return true + } + if (contains(equivalentRect(layout), mouseX.toInt(), mouseY.toInt())) { + val columns = equivalentColumns(layout) + val rows = max(1, equivalentRect(layout).h / SLOT_STEP) + val maxScroll = max(0, (equivalentEntries().size + columns - 1) / columns - rows) + equivalentScroll = (equivalentScroll - delta.toInt()).coerceIn(0, maxScroll) + return true + } + return super.mouseScrolled(mouseX, mouseY, delta) + } + + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (searchBox.keyPressed(keyCode, scanCode, modifiers)) { + indexScroll = 0 + return true + } + return super.keyPressed(keyCode, scanCode, modifiers) + } + + override fun charTyped(codePoint: Char, modifiers: Int): Boolean { + if (searchBox.charTyped(codePoint, modifiers)) { + indexScroll = 0 + return true + } + return super.charTyped(codePoint, modifiers) + } + + override fun onClose() { + minecraft?.setScreen(parent) + } + + override fun isPauseScreen(): Boolean = false + + private fun renderTitle(guiGraphics: GuiGraphics, layout: Layout) { + guiGraphics.drawString(font, "Replication Integration", layout.index.x + 16, layout.index.y + 14, 0xDDEFE7, true) + drawRiMark(guiGraphics, layout.index.x + 6, layout.index.y + 11) + } + + private fun renderControls(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { + matterOnlyButton.render(guiGraphics, mouseX, mouseY, partialTick) + replicableButton.render(guiGraphics, mouseX, mouseY, partialTick) + decomposableButton.render(guiGraphics, mouseX, mouseY, partialTick) + } + + private fun renderIndex(guiGraphics: GuiGraphics, layout: Layout, visible: List, mouseX: Int, mouseY: Int) { + val rows = indexRows(layout) + val columns = columns(layout) + renderHeader(guiGraphics, columns, rows.y - HEADER_H) + + val visibleRows = max(1, rows.h / ROW_H) + indexScroll = indexScroll.coerceIn(0, max(0, visible.size - visibleRows)) + val page = visible.drop(indexScroll).take(visibleRows) + + for ((rowIndex, entry) in page.withIndex()) { + val y = rows.y + rowIndex * ROW_H + val selected = entry.record.itemId == selectedItemId + if (selected) { + guiGraphics.fill(rows.x, y, rows.x + rows.w, y + ROW_H, 0x7736D84A) + guiGraphics.fill(rows.x, y, rows.x + 4, y + ROW_H, 0xFF44E35A.toInt()) + } else if (rowIndex % 2 == 0) { + guiGraphics.fill(rows.x, y, rows.x + rows.w, y + ROW_H, 0x33000000) + } + drawHorizontalLine(guiGraphics, rows.x, rows.x + rows.w, y + ROW_H, 0x335E716D) + guiGraphics.renderItem(entry.stack, columns.item.x + 8, y + 5) + if (mouseX in columns.item.x + 8 until columns.item.x + 24 && mouseY in y + 5 until y + 21) { + hoveredStack = entry.stack + } + drawTrimmed(guiGraphics, entry.record.displayName, columns.item.x + 32, y + 9, columns.item.w - 38, 0xE6ECE8) + drawRight(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - 8, y + 9, 0xE6ECE8) + if (contains(Rect(columns.total.x, y, columns.total.w, ROW_H), mouseX, mouseY)) { + setAmountTooltip("Total", entry.record.total) + } + for ((matterId, rect) in columns.matter) { + val amount = entry.record.matterValues[matterId] ?: 0.0 + drawCentered(guiGraphics, formatAmount(amount), rect.x + rect.w / 2, y + 9, 0xE6ECE8) + if (contains(Rect(rect.x, y, rect.w, ROW_H), mouseX, mouseY)) { + setAmountTooltip(matterId, amount) + } + } + } + + drawScrollbar(guiGraphics, rows.x + rows.w - 5, rows.y, rows.h, visible.size, visibleRows, indexScroll) + guiGraphics.drawString(font, "${visible.size} items", layout.index.x + 16, layout.index.y + layout.index.h - 18, 0xAAB8B2, false) + } + + private fun renderHeader(guiGraphics: GuiGraphics, columns: Columns, y: Int) { + val h = HEADER_H + guiGraphics.fill(columns.item.x, y, columns.endX, y + h, 0x44000000) + drawCell(guiGraphics, columns.item, y, h) + drawCell(guiGraphics, columns.total, y, h) + guiGraphics.drawString(font, headerText("Item", MatterIndexSort.ITEM_COLUMN), columns.item.x + 10, y + 10, 0xBFD0CA, false) + drawCentered(guiGraphics, headerText("Total", MatterIndexSort.TOTAL_COLUMN), columns.total.x + columns.total.w / 2, y + 10, 0xBFD0CA) + for ((matterId, rect) in columns.matter) { + drawCell(guiGraphics, rect, y, h) + renderMatterIcon(guiGraphics, matterId, rect.x + rect.w / 2 - 8, y + 7) + if (sort.column == MatterIndexSort.matterColumn(matterId)) { + guiGraphics.drawString(font, sortArrow(), rect.x + rect.w - 10, y + 10, 0x7CF47B, false) + } + } + } + + private fun renderFocus(guiGraphics: GuiGraphics, layout: Layout, mouseX: Int, mouseY: Int) { + val selected = selectedEntry() + guiGraphics.drawString(font, "Selected Item", layout.focus.x + 16, layout.focus.y + 14, 0xDDEFE7, true) + if (selected == null) { + guiGraphics.drawString(font, "No matter data", layout.focus.x + 16, layout.focus.y + 42, 0xAAB8B2, false) + return + } + + val slotX = layout.focus.x + 18 + val slotY = layout.focus.y + 42 + drawSlot(guiGraphics, slotX, slotY, 54, true) + guiGraphics.renderItem(selected.stack, slotX + 19, slotY + 19) + if (mouseX in slotX until slotX + 54 && mouseY in slotY until slotY + 54) { + hoveredStack = selected.stack + } + drawTrimmed(guiGraphics, selected.record.displayName, slotX + 70, slotY + 4, layout.focus.w - 100, 0xE6ECE8) + drawTrimmed(guiGraphics, selected.record.itemId, slotX + 70, slotY + 18, layout.focus.w - 100, 0x94A29C) + + val matterStartX = slotX + 70 + val matterStartY = slotY + 36 + for ((index, matterId) in matterTypes.withIndex()) { + val x = matterStartX + (index % 8) * 48 + val y = matterStartY + (index / 8) * 24 + if (x + 42 > layout.focus.x + layout.focus.w - 8) continue + drawSlot(guiGraphics, x, y, 20, false) + renderMatterIcon(guiGraphics, matterId, x + 2, y + 2) + val amount = selected.record.matterValues[matterId] ?: 0.0 + drawCentered(guiGraphics, formatAmount(amount), x + 10, y + 22, 0xE6ECE8) + if (mouseX in x until x + 42 && mouseY in y until y + 34) { + setAmountTooltip(matterId, amount) + } + } + + val badgeY = slotY + 94 + drawBadge(guiGraphics, slotX, badgeY, "Replicable") + drawBadge(guiGraphics, slotX + 94, badgeY, "Decomposable") + drawDivider(guiGraphics, layout.focus.x + 12, badgeY + 32, layout.focus.w - 24) + + val eq = equivalentRect(layout) + guiGraphics.drawString(font, "Matter Equivalent", eq.x, eq.y - 15, 0xDDEFE7, false) + renderEquivalentGrid(guiGraphics, layout, mouseX, mouseY) + } + + private fun renderEquivalentGrid(guiGraphics: GuiGraphics, layout: Layout, mouseX: Int, mouseY: Int) { + val rect = equivalentRect(layout) + guiGraphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, 0x66000000) + val columns = equivalentColumns(layout) + val rows = max(1, rect.h / SLOT_STEP) + val equivalent = equivalentEntries() + equivalentScroll = equivalentScroll.coerceIn(0, max(0, (equivalent.size + columns - 1) / columns - rows)) + val first = equivalentScroll * columns + for (index in 0 until min(equivalent.size - first, columns * rows)) { + val entry = equivalent[first + index] + val x = rect.x + 8 + (index % columns) * SLOT_STEP + val y = rect.y + 8 + (index / columns) * SLOT_STEP + val selected = entry.record.itemId == selectedItemId + drawSlot(guiGraphics, x, y, SLOT_SIZE, selected) + guiGraphics.renderItem(entry.stack, x + 5, y + 5) + if (mouseX in x until x + SLOT_SIZE && mouseY in y until y + SLOT_SIZE) { + hoveredStack = entry.stack + } + } + drawScrollbar(guiGraphics, rect.x + rect.w - 5, rect.y, rect.h, (equivalent.size + columns - 1) / columns, rows, equivalentScroll) + } + + private fun clickHeader(layout: Layout, mouseX: Int, mouseY: Int): Boolean { + val rows = indexRows(layout) + val y = rows.y - HEADER_H + if (mouseY !in y until y + HEADER_H) return false + val columns = columns(layout) + val column = + when { + contains(columns.item, mouseX, mouseY) -> MatterIndexSort.ITEM_COLUMN + contains(columns.total, mouseX, mouseY) -> MatterIndexSort.TOTAL_COLUMN + else -> columns.matter.firstNotNullOfOrNull { (matterId, rect) -> + if (contains(rect, mouseX, mouseY)) MatterIndexSort.matterColumn(matterId) else null + } + } ?: return false + sort = + if (sort.column == column) { + sort.copy(direction = if (sort.direction == MatterIndexSortDirection.ASC) MatterIndexSortDirection.DESC else MatterIndexSortDirection.ASC) + } else { + MatterIndexSort(column, MatterIndexSortDirection.DESC) + } + indexScroll = 0 + return true + } + + private fun equivalentAt(layout: Layout, mouseX: Int, mouseY: Int): MatterIndexEntry? { + val rect = equivalentRect(layout) + if (!contains(rect, mouseX, mouseY)) return null + val columns = equivalentColumns(layout) + val localX = mouseX - rect.x - 8 + val localY = mouseY - rect.y - 8 + if (localX < 0 || localY < 0) return null + val column = localX / SLOT_STEP + val row = localY / SLOT_STEP + if (column !in 0 until columns) return null + if (localX % SLOT_STEP >= SLOT_SIZE || localY % SLOT_STEP >= SLOT_SIZE) return null + return equivalentEntries().getOrNull((equivalentScroll + row) * columns + column) + } + + private fun visibleEntries(): List { + val query = MatterIndexFiltering.parseQuery(searchBox.value, matterOnly, replicableOnly, decomposableOnly) + val ordered = MatterIndexFiltering.filterAndSort(entries.map { it.record }, query, sort) + val byId = entries.associateBy { it.record.itemId } + return ordered.mapNotNull { byId[it.itemId] } + } + + private fun selectedEntry(): MatterIndexEntry? = + entries.firstOrNull { it.record.itemId == selectedItemId } + + private fun equivalentEntries(): List { + val records = MatterIndexFiltering.equivalentRecords(entries.map { it.record }, selectedEntry()?.record) + val byId = entries.associateBy { it.record.itemId } + return records.mapNotNull { byId[it.itemId] } + } + + private fun columns(layout: Layout): Columns { + val rows = indexRows(layout) + val itemW = max(180, (rows.w * 0.30).toInt()) + val totalW = 76 + val matterW = max(42, (rows.w - itemW - totalW) / max(1, matterTypes.size)) + val matter = matterTypes.mapIndexed { index, id -> + id to Rect(rows.x + itemW + totalW + index * matterW, rows.y - HEADER_H, matterW, HEADER_H) + }.filter { it.second.x + it.second.w <= rows.x + rows.w } + return Columns( + item = Rect(rows.x, rows.y - HEADER_H, itemW, HEADER_H), + total = Rect(rows.x + itemW, rows.y - HEADER_H, totalW, HEADER_H), + matter = matter, + endX = rows.x + rows.w, + ) + } + + private fun layout(): Layout { + val margin = 24 + val gap = 6 + val panelH = height - margin * 2 + val totalW = width - margin * 2 - gap + val indexW = (totalW * 0.60).toInt() + val focusW = totalW - indexW + return Layout( + index = Rect(margin, margin, indexW, panelH), + focus = Rect(margin + indexW + gap, margin, focusW, panelH), + ) + } + + private fun indexRows(layout: Layout): Rect = + Rect(layout.index.x + 14, layout.index.y + 88, layout.index.w - 28, layout.index.h - 124) + + private fun equivalentRect(layout: Layout): Rect = + Rect(layout.focus.x + 16, layout.focus.y + 204, layout.focus.w - 32, layout.focus.h - 224) + + private fun equivalentColumns(layout: Layout): Int = + max(2, (equivalentRect(layout).w - 12) / SLOT_STEP) + + private fun renderPanel(guiGraphics: GuiGraphics, rect: Rect) { + guiGraphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, 0xCC071010.toInt()) + drawBorder(guiGraphics, rect, 0xFF41514D.toInt()) + drawBorder(guiGraphics, rect.inflate(-2), 0x6625C34A) + } + + private fun drawCell(guiGraphics: GuiGraphics, rect: Rect, y: Int, h: Int) { + drawVerticalLine(guiGraphics, rect.x, y, y + h, 0x445E716D) + drawVerticalLine(guiGraphics, rect.x + rect.w, y, y + h, 0x445E716D) + } + + private fun drawSlot(guiGraphics: GuiGraphics, x: Int, y: Int, size: Int, selected: Boolean) { + val edge = if (selected) 0xFF58E65B.toInt() else 0xFF59625F.toInt() + guiGraphics.fill(x, y, x + size, y + size, edge) + guiGraphics.fill(x + 1, y + 1, x + size - 1, y + size - 1, 0xFF131918.toInt()) + guiGraphics.fill(x + 2, y + 2, x + size - 2, y + size - 2, 0xFF202826.toInt()) + guiGraphics.fill(x + 3, y + 3, x + size - 3, y + size - 3, 0xFF0D1211.toInt()) + } + + private fun drawBadge(guiGraphics: GuiGraphics, x: Int, y: Int, text: String) { + val w = 86 + guiGraphics.fill(x, y, x + w, y + 22, 0x5517E517) + drawBorder(guiGraphics, Rect(x, y, w, 22), 0xAA36D84A.toInt()) + drawCentered(guiGraphics, text, x + w / 2, y + 7, 0x70F06E) + } + + private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int) { + guiGraphics.blit(ResourceLocation.fromNamespaceAndPath(Replication.MOD_ID, "textures/gui/mattertypes/${matterId.substringAfter(':')}.png"), x, y, 0f, 0f, 16, 16, 16, 16) + } + + private fun drawRiMark(guiGraphics: GuiGraphics, x: Int, y: Int) { + guiGraphics.fill(x + 5, y, x + 9, y + 4, 0xFF42E84F.toInt()) + guiGraphics.fill(x, y + 5, x + 4, y + 9, 0xFF42E84F.toInt()) + guiGraphics.fill(x + 10, y + 5, x + 14, y + 9, 0xFF42E84F.toInt()) + guiGraphics.fill(x + 5, y + 10, x + 9, y + 14, 0xFF42E84F.toInt()) + } + + private fun drawScrollbar(guiGraphics: GuiGraphics, x: Int, y: Int, h: Int, total: Int, visible: Int, scroll: Int) { + if (total <= visible) return + guiGraphics.fill(x, y, x + 4, y + h, 0xAA050606.toInt()) + val thumbH = max(16, h * visible / total) + val thumbY = y + ((h - thumbH) * scroll / max(1, total - visible)) + guiGraphics.fill(x, thumbY, x + 4, thumbY + thumbH, 0xFF9EAAA5.toInt()) + } + + private fun drawDivider(guiGraphics: GuiGraphics, x: Int, y: Int, w: Int) = + guiGraphics.fill(x, y, x + w, y + 1, 0x6641514D) + + private fun drawBorder(guiGraphics: GuiGraphics, rect: Rect, color: Int) { + drawHorizontalLine(guiGraphics, rect.x, rect.x + rect.w, rect.y, color) + drawHorizontalLine(guiGraphics, rect.x, rect.x + rect.w, rect.y + rect.h, color) + drawVerticalLine(guiGraphics, rect.x, rect.y, rect.y + rect.h, color) + drawVerticalLine(guiGraphics, rect.x + rect.w, rect.y, rect.y + rect.h, color) + } + + private fun drawHorizontalLine(guiGraphics: GuiGraphics, x1: Int, x2: Int, y: Int, color: Int) = + guiGraphics.fill(x1, y, x2, y + 1, color) + + private fun drawVerticalLine(guiGraphics: GuiGraphics, x: Int, y1: Int, y2: Int, color: Int) = + guiGraphics.fill(x, y1, x + 1, y2, color) + + private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) { + val trimmed = font.plainSubstrByWidth(text, maxWidth - font.width("...")) + guiGraphics.drawString(font, if (font.width(text) > maxWidth) "$trimmed..." else text, x, y, color, false) + } + + private fun drawRight(guiGraphics: GuiGraphics, text: String, rightX: Int, y: Int, color: Int) = + guiGraphics.drawString(font, text, rightX - font.width(text), y, color, false) + + private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) = + guiGraphics.drawString(font, text, centerX - font.width(text) / 2, y, color, false) + + private fun headerText(text: String, column: String): String = + if (sort.column == column) "$text ${sortArrow()}" else text + + private fun sortArrow(): String = + if (sort.direction == MatterIndexSortDirection.ASC) "^" else "v" + + private fun toggleLabel(text: String, active: Boolean): Component = + Component.literal(if (active) "[$text]" else text) + + private fun formatAmount(amount: Double): String = + MatterAmountFormatter.compact(amount, ForgeUiConfig.matterAmountNotation()) + + private fun setAmountTooltip(label: String, amount: Double) { + hoveredAmountTooltip = + listOf( + Component.literal(label), + Component.literal(MatterAmountFormatter.exact(amount)), + ) + } + + private fun contains(rect: Rect, x: Int, y: Int): Boolean = + x in rect.x until rect.x + rect.w && y in rect.y until rect.y + rect.h + + companion object { + private const val HEADER_H = 30 + private const val ROW_H = 26 + private const val SLOT_SIZE = 28 + private const val SLOT_STEP = 34 + + fun hasMatter(itemId: String): Boolean = + ClientReplicationCalculation.DEFAULT_MATTER_COMPOUND.containsKey(itemId) + + fun itemId(stack: ItemStack): String = + BuiltInRegistries.ITEM.getKey(stack.item).toString() + + private fun loadEntries(): List = + ClientReplicationCalculation.DEFAULT_MATTER_COMPOUND.entries.mapNotNull { (id, compound) -> + val item = BuiltInRegistries.ITEM.get(ResourceLocation.parse(id)) + val stack = item.defaultInstance + if (stack.isEmpty) return@mapNotNull null + val tags = stack.tags.map { "#${it.location()}" }.toList().joinToString(" ") + MatterIndexEntry( + record = MatterIndexRecord( + itemId = id, + displayName = stack.hoverName.string, + matterValues = compound.toMatterMap(), + extraSearchText = tags, + ), + stack = stack, + ) + }.sortedBy { it.record.displayName.lowercase() } + + private fun MatterCompound.toMatterMap(): Map = + values.values.associate { value -> + value.matter.name to value.amount + } + } + + private data class MatterIndexEntry( + val record: MatterIndexRecord, + val stack: ItemStack, + ) + + private data class Layout( + val index: Rect, + val focus: Rect, + ) + + private data class Columns( + val item: Rect, + val total: Rect, + val matter: List>, + val endX: Int, + ) + + private data class Rect( + val x: Int, + val y: Int, + val w: Int, + val h: Int, + ) { + fun inflate(amount: Int): Rect = + Rect(x - amount, y - amount, w + amount * 2, h + amount * 2) + } +} diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/config/ForgeUiConfig.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/config/ForgeUiConfig.kt new file mode 100644 index 0000000..5ad6d6e --- /dev/null +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/config/ForgeUiConfig.kt @@ -0,0 +1,25 @@ +package com.p_nsk.replicated_integration.config + +import com.p_nsk.replicated_integration.api.client.MatterAmountNotation +import net.minecraftforge.common.ForgeConfigSpec + +object ForgeUiConfig { + private const val KEY_MATTER_AMOUNT_NOTATION = "matterAmountNotation" + + val spec: ForgeConfigSpec + private val matterAmountNotationValue: ForgeConfigSpec.EnumValue + + init { + val builder = ForgeConfigSpec.Builder() + builder.push("matterIndex") + matterAmountNotationValue = + builder + .comment("Notation used for compact matter amounts. SI uses K/M/G/T; SHORT_SCALE uses K/M/B/T.") + .translation("replicated_integration.config.ui.matter_amount_notation") + .defineEnum(KEY_MATTER_AMOUNT_NOTATION, MatterAmountNotation.SHORT_SCALE) + builder.pop() + spec = builder.build() + } + + fun matterAmountNotation(): MatterAmountNotation = matterAmountNotationValue.get() +} diff --git a/1.20.1-forge/src/main/resources/replicated_integration.mixins.json b/1.20.1-forge/src/main/resources/replicated_integration.mixins.json index 60cf888..bcd606c 100644 --- a/1.20.1-forge/src/main/resources/replicated_integration.mixins.json +++ b/1.20.1-forge/src/main/resources/replicated_integration.mixins.json @@ -12,6 +12,7 @@ "ReplicationCalculationMixin" ], "client": [ + "ScreenAccessor" ], "server": [], "injectors": { diff --git a/1.21.1-neo/src/main/java/com/p_nsk/replicated_integration/mixin/neo/ScreenAccessor.java b/1.21.1-neo/src/main/java/com/p_nsk/replicated_integration/mixin/neo/ScreenAccessor.java new file mode 100644 index 0000000..793ad57 --- /dev/null +++ b/1.21.1-neo/src/main/java/com/p_nsk/replicated_integration/mixin/neo/ScreenAccessor.java @@ -0,0 +1,14 @@ +package com.p_nsk.replicated_integration.mixin.neo; + +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.screens.Screen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(Screen.class) +public interface ScreenAccessor { + @Invoker("addRenderableWidget") + T replicatedIntegration$addRenderableWidget(T widget); +} diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt index fed55f2..84b4892 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt @@ -2,12 +2,16 @@ package com.p_nsk.replicated_integration import com.p_nsk.replicated_integration.command.MatterCommand import com.p_nsk.replicated_integration.command.MatterNodeDebugCommand +import com.p_nsk.replicated_integration.client.MatterIndexClientEvents import com.p_nsk.replicated_integration.config.NeoCompatibilityConfig +import com.p_nsk.replicated_integration.config.NeoUiConfig import com.p_nsk.replicated_integration.core.NeoBuiltinReplicationAddons import com.p_nsk.replicated_integration.data.ReplicationReloadHooks import com.p_nsk.replicated_integration.data.NeoReplicatedIntegrationDataGen import com.p_nsk.replicated_integration.data.ReplicationServerLifecycleHooks import com.p_nsk.replicated_integration.network.NeoReplicationCalculationSyncChannel +import net.neoforged.api.distmarker.Dist +import net.neoforged.fml.loading.FMLEnvironment import net.neoforged.fml.ModLoadingContext import net.neoforged.neoforge.common.NeoForge import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent @@ -24,6 +28,9 @@ object ModMain { NeoForge.EVENT_BUS.register(MatterNodeDebugCommand) NeoForge.EVENT_BUS.register(ReplicationReloadHooks) NeoForge.EVENT_BUS.register(ReplicationServerLifecycleHooks) + if (FMLEnvironment.dist == Dist.CLIENT) { + NeoForge.EVENT_BUS.register(MatterIndexClientEvents) + } } private fun registerConfigs() { @@ -32,6 +39,11 @@ object ModMain { NeoCompatibilityConfig.spec, "replicated_integration-compatibility.toml", ) + ModLoadingContext.get().activeContainer.registerConfig( + ModConfig.Type.CLIENT, + NeoUiConfig.spec, + "replicated_integration-ui.toml", + ) } private fun registerNetwork() { diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt new file mode 100644 index 0000000..64f5141 --- /dev/null +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt @@ -0,0 +1,138 @@ +package com.p_nsk.replicated_integration.client + +import com.p_nsk.replicated_integration.mixin.neo.ScreenAccessor +import net.minecraft.ChatFormatting +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.AbstractButton +import net.minecraft.client.gui.narration.NarrationElementOutput +import net.minecraft.client.gui.screens.Screen +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import net.neoforged.bus.api.SubscribeEvent +import net.neoforged.neoforge.client.event.ClientTickEvent +import net.neoforged.neoforge.client.event.ScreenEvent +import net.neoforged.neoforge.event.entity.player.ItemTooltipEvent +import org.lwjgl.glfw.GLFW +import kotlin.math.max +import kotlin.math.min + +object MatterIndexClientEvents { + private const val HOLD_TICKS = 20 + + private var hoveredStack: ItemStack = ItemStack.EMPTY + private var trackingStack: ItemStack = ItemStack.EMPTY + private var holdTicks: Int = 0 + + @SubscribeEvent + fun onScreenInit(event: ScreenEvent.Init.Post) { + val screen = event.screen + if (screen !is AbstractContainerScreen<*>) return + (screen as ScreenAccessor).`replicatedIntegration$addRenderableWidget`(MatterIndexSidebarButton(screen)) + } + + @SubscribeEvent + fun onItemTooltip(event: ItemTooltipEvent) { + val minecraft = Minecraft.getInstance() + val screen = minecraft.screen ?: return + if (screen is MatterIndexScreen || event.entity == null) return + updateHovered(event.itemStack) + if (trackingStack !== event.itemStack && !trackingStack.`is`(event.itemStack.item)) return + val progress = holdTicks.toFloat() / HOLD_TICKS.toFloat() + val line = + if (progress <= 0f) { + Component.literal("Hold W to open Replication Index").withStyle(ChatFormatting.DARK_GRAY) + } else { + Component.literal(progressBar(progress)).withStyle(ChatFormatting.GRAY) + } + if (event.toolTip.size < 2) { + event.toolTip.add(line) + } else { + event.toolTip.add(1, line) + } + } + + @SubscribeEvent + fun onClientTick(event: ClientTickEvent.Post) { + val minecraft = Minecraft.getInstance() + val screen = minecraft.screen + if (screen == null || screen is MatterIndexScreen || trackingStack.isEmpty) { + resetHold() + return + } + + val window = minecraft.window.window + val holding = !hoveredStack.isEmpty && GLFW.glfwGetKey(window, GLFW.GLFW_KEY_W) == GLFW.GLFW_PRESS + holdTicks = + if (holding) { + min(HOLD_TICKS, holdTicks + 1) + } else { + max(0, holdTicks - 1) + } + + if (holdTicks >= HOLD_TICKS) { + val itemId = MatterIndexScreen.itemId(trackingStack) + resetHold() + minecraft.setScreen(MatterIndexScreen(screen, itemId)) + return + } + + hoveredStack = ItemStack.EMPTY + } + + private fun updateHovered(stack: ItemStack) { + hoveredStack = ItemStack.EMPTY + if (stack.isEmpty) return + if (!MatterIndexScreen.hasMatter(MatterIndexScreen.itemId(stack))) return + if (trackingStack.isEmpty || !trackingStack.`is`(stack.item)) { + holdTicks = 0 + } + trackingStack = stack + hoveredStack = stack + } + + private fun resetHold() { + hoveredStack = ItemStack.EMPTY + trackingStack = ItemStack.EMPTY + holdTicks = 0 + } + + private fun progressBar(progress: Float): String { + val total = 18 + val current = (progress.coerceIn(0f, 1f) * total).toInt() + return "|".repeat(current) + ChatFormatting.DARK_GRAY + "|".repeat(total - current) + } + + private class MatterIndexSidebarButton( + private val owner: Screen, + ) : AbstractButton(2, 2, 22, 22, Component.literal("Replication Index")) { + override fun onPress() { + Minecraft.getInstance().setScreen(MatterIndexScreen(owner)) + } + + override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { + guiGraphics.fill(x, y, x + width, y + height, 0xCC071010.toInt()) + guiGraphics.fill(x, y, x + width, y + 1, 0xFF41514D.toInt()) + guiGraphics.fill(x, y + height - 1, x + width, y + height, 0xFF41514D.toInt()) + guiGraphics.fill(x, y, x + 1, y + height, 0xFF41514D.toInt()) + guiGraphics.fill(x + width - 1, y, x + width, y + height, 0xFF41514D.toInt()) + if (isHoveredOrFocused) { + guiGraphics.fill(x + 1, y + 1, x + width - 1, y + height - 1, 0x5536D84A) + guiGraphics.renderTooltip(Minecraft.getInstance().font, message, mouseX, mouseY) + } + drawRiMark(guiGraphics, x + 4, y + 4) + } + + override fun updateWidgetNarration(output: NarrationElementOutput) { + defaultButtonNarrationText(output) + } + + private fun drawRiMark(guiGraphics: GuiGraphics, x: Int, y: Int) { + guiGraphics.fill(x + 5, y, x + 9, y + 4, 0xFF42E84F.toInt()) + guiGraphics.fill(x, y + 5, x + 4, y + 9, 0xFF42E84F.toInt()) + guiGraphics.fill(x + 10, y + 5, x + 14, y + 9, 0xFF42E84F.toInt()) + guiGraphics.fill(x + 5, y + 10, x + 9, y + 14, 0xFF42E84F.toInt()) + } + } +} diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt new file mode 100644 index 0000000..5ad2dc6 --- /dev/null +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -0,0 +1,554 @@ +package com.p_nsk.replicated_integration.client + +import com.buuz135.replication.Replication +import com.buuz135.replication.calculation.MatterCompound +import com.buuz135.replication.calculation.client.ClientReplicationCalculation +import com.p_nsk.replicated_integration.api.client.MatterAmountFormatter +import com.p_nsk.replicated_integration.api.client.MatterIndexFiltering +import com.p_nsk.replicated_integration.api.client.MatterIndexRecord +import com.p_nsk.replicated_integration.api.client.MatterIndexSort +import com.p_nsk.replicated_integration.api.client.MatterIndexSortDirection +import com.p_nsk.replicated_integration.config.NeoUiConfig +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.Button +import net.minecraft.client.gui.components.EditBox +import net.minecraft.client.gui.screens.Screen +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.ItemStack +import kotlin.math.max +import kotlin.math.min + +class MatterIndexScreen( + private val parent: Screen?, + private val focusedItemId: String? = null, +) : Screen(Component.literal("Replication Index")) { + private val entries: List = loadEntries() + private val matterTypes: List = + entries + .flatMap { it.record.matterValues.keys } + .distinct() + .filterNot { it == "empty" } + .sorted() + + private var selectedItemId: String? = focusedItemId + private var sort = MatterIndexSort() + private var indexScroll = 0 + private var equivalentScroll = 0 + private var matterOnly = true + private var replicableOnly = false + private var decomposableOnly = false + private var hoveredStack: ItemStack = ItemStack.EMPTY + private var hoveredAmountTooltip: List = emptyList() + + private lateinit var searchBox: EditBox + private lateinit var matterOnlyButton: Button + private lateinit var replicableButton: Button + private lateinit var decomposableButton: Button + + override fun init() { + val layout = layout() + searchBox = EditBox(font, layout.index.x + 16, layout.index.y + 35, min(260, layout.index.w / 3), 18, Component.literal("search")) + searchBox.setMaxLength(80) + searchBox.setTextColor(0xE8F3ED) + searchBox.setValue("") + addWidget(searchBox) + + matterOnlyButton = addRenderableWidget( + Button.builder(toggleLabel("M", matterOnly)) { + matterOnly = !matterOnly + matterOnlyButton.message = toggleLabel("M", matterOnly) + indexScroll = 0 + }.bounds(searchBox.x + searchBox.width + 8, searchBox.y, 34, 18).build(), + ) + replicableButton = addRenderableWidget( + Button.builder(toggleLabel("R", replicableOnly)) { + replicableOnly = !replicableOnly + replicableButton.message = toggleLabel("R", replicableOnly) + indexScroll = 0 + }.bounds(matterOnlyButton.x + matterOnlyButton.width + 4, searchBox.y, 34, 18).build(), + ) + decomposableButton = addRenderableWidget( + Button.builder(toggleLabel("D", decomposableOnly)) { + decomposableOnly = !decomposableOnly + decomposableButton.message = toggleLabel("D", decomposableOnly) + indexScroll = 0 + }.bounds(replicableButton.x + replicableButton.width + 4, searchBox.y, 34, 18).build(), + ) + } + + override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { + hoveredStack = ItemStack.EMPTY + hoveredAmountTooltip = emptyList() + val layout = layout() + guiGraphics.fill(0, 0, width, height, 0xAA050A0A.toInt()) + renderPanel(guiGraphics, layout.index) + renderPanel(guiGraphics, layout.focus) + renderTitle(guiGraphics, layout) + searchBox.render(guiGraphics, mouseX, mouseY, partialTick) + renderControls(guiGraphics, mouseX, mouseY, partialTick) + + val visible = visibleEntries() + if (selectedItemId == null || entries.none { it.record.itemId == selectedItemId }) { + selectedItemId = visible.firstOrNull()?.record?.itemId ?: entries.firstOrNull()?.record?.itemId + } + + renderIndex(guiGraphics, layout, visible, mouseX, mouseY) + renderFocus(guiGraphics, layout, mouseX, mouseY) + + if (!hoveredStack.isEmpty) { + guiGraphics.renderTooltip(font, hoveredStack, mouseX, mouseY) + } else if (hoveredAmountTooltip.isNotEmpty()) { + guiGraphics.renderTooltip(font, hoveredAmountTooltip.map { it.visualOrderText }, mouseX, mouseY) + } + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (super.mouseClicked(mouseX, mouseY, button)) return true + if (button != 0) return false + + val layout = layout() + val visible = visibleEntries() + if (clickHeader(layout, mouseX.toInt(), mouseY.toInt())) return true + + val rowArea = indexRows(layout) + if (mouseX.toInt() in rowArea.x until rowArea.x + rowArea.w && mouseY.toInt() in rowArea.y until rowArea.y + rowArea.h) { + val index = indexScroll + (mouseY.toInt() - rowArea.y) / ROW_H + visible.getOrNull(index)?.let { + selectedItemId = it.record.itemId + equivalentScroll = 0 + return true + } + } + + equivalentAt(layout, mouseX.toInt(), mouseY.toInt())?.let { + selectedItemId = it.record.itemId + equivalentScroll = 0 + return true + } + + return false + } + + override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean { + val layout = layout() + if (contains(indexRows(layout), mouseX.toInt(), mouseY.toInt())) { + val visibleRows = max(1, indexRows(layout).h / ROW_H) + val maxScroll = max(0, visibleEntries().size - visibleRows) + indexScroll = (indexScroll - (scrollY * 3).toInt()).coerceIn(0, maxScroll) + return true + } + if (contains(equivalentRect(layout), mouseX.toInt(), mouseY.toInt())) { + val columns = equivalentColumns(layout) + val rows = max(1, equivalentRect(layout).h / SLOT_STEP) + val maxScroll = max(0, (equivalentEntries().size + columns - 1) / columns - rows) + equivalentScroll = (equivalentScroll - scrollY.toInt()).coerceIn(0, maxScroll) + return true + } + return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY) + } + + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (searchBox.keyPressed(keyCode, scanCode, modifiers)) { + indexScroll = 0 + return true + } + return super.keyPressed(keyCode, scanCode, modifiers) + } + + override fun charTyped(codePoint: Char, modifiers: Int): Boolean { + if (searchBox.charTyped(codePoint, modifiers)) { + indexScroll = 0 + return true + } + return super.charTyped(codePoint, modifiers) + } + + override fun onClose() { + minecraft?.setScreen(parent) + } + + override fun isPauseScreen(): Boolean = false + + private fun renderTitle(guiGraphics: GuiGraphics, layout: Layout) { + guiGraphics.drawString(font, "Replication Integration", layout.index.x + 16, layout.index.y + 14, 0xDDEFE7, true) + drawRiMark(guiGraphics, layout.index.x + 6, layout.index.y + 11) + } + + private fun renderControls(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { + matterOnlyButton.render(guiGraphics, mouseX, mouseY, partialTick) + replicableButton.render(guiGraphics, mouseX, mouseY, partialTick) + decomposableButton.render(guiGraphics, mouseX, mouseY, partialTick) + } + + private fun renderIndex(guiGraphics: GuiGraphics, layout: Layout, visible: List, mouseX: Int, mouseY: Int) { + val rows = indexRows(layout) + val columns = columns(layout) + renderHeader(guiGraphics, columns, rows.y - HEADER_H) + + val visibleRows = max(1, rows.h / ROW_H) + indexScroll = indexScroll.coerceIn(0, max(0, visible.size - visibleRows)) + val page = visible.drop(indexScroll).take(visibleRows) + + for ((rowIndex, entry) in page.withIndex()) { + val y = rows.y + rowIndex * ROW_H + val selected = entry.record.itemId == selectedItemId + if (selected) { + guiGraphics.fill(rows.x, y, rows.x + rows.w, y + ROW_H, 0x7736D84A) + guiGraphics.fill(rows.x, y, rows.x + 4, y + ROW_H, 0xFF44E35A.toInt()) + } else if (rowIndex % 2 == 0) { + guiGraphics.fill(rows.x, y, rows.x + rows.w, y + ROW_H, 0x33000000) + } + drawHorizontalLine(guiGraphics, rows.x, rows.x + rows.w, y + ROW_H, 0x335E716D) + guiGraphics.renderItem(entry.stack, columns.item.x + 8, y + 5) + if (mouseX in columns.item.x + 8 until columns.item.x + 24 && mouseY in y + 5 until y + 21) { + hoveredStack = entry.stack + } + drawTrimmed(guiGraphics, entry.record.displayName, columns.item.x + 32, y + 9, columns.item.w - 38, 0xE6ECE8) + drawRight(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - 8, y + 9, 0xE6ECE8) + if (contains(Rect(columns.total.x, y, columns.total.w, ROW_H), mouseX, mouseY)) { + setAmountTooltip("Total", entry.record.total) + } + for ((matterId, rect) in columns.matter) { + val amount = entry.record.matterValues[matterId] ?: 0.0 + drawCentered(guiGraphics, formatAmount(amount), rect.x + rect.w / 2, y + 9, 0xE6ECE8) + if (contains(Rect(rect.x, y, rect.w, ROW_H), mouseX, mouseY)) { + setAmountTooltip(matterId, amount) + } + } + } + + drawScrollbar(guiGraphics, rows.x + rows.w - 5, rows.y, rows.h, visible.size, visibleRows, indexScroll) + guiGraphics.drawString(font, "${visible.size} items", layout.index.x + 16, layout.index.y + layout.index.h - 18, 0xAAB8B2, false) + } + + private fun renderHeader(guiGraphics: GuiGraphics, columns: Columns, y: Int) { + val h = HEADER_H + guiGraphics.fill(columns.item.x, y, columns.endX, y + h, 0x44000000) + drawCell(guiGraphics, columns.item, y, h) + drawCell(guiGraphics, columns.total, y, h) + guiGraphics.drawString(font, headerText("Item", MatterIndexSort.ITEM_COLUMN), columns.item.x + 10, y + 10, 0xBFD0CA, false) + drawCentered(guiGraphics, headerText("Total", MatterIndexSort.TOTAL_COLUMN), columns.total.x + columns.total.w / 2, y + 10, 0xBFD0CA) + for ((matterId, rect) in columns.matter) { + drawCell(guiGraphics, rect, y, h) + renderMatterIcon(guiGraphics, matterId, rect.x + rect.w / 2 - 8, y + 7) + if (sort.column == MatterIndexSort.matterColumn(matterId)) { + guiGraphics.drawString(font, sortArrow(), rect.x + rect.w - 10, y + 10, 0x7CF47B, false) + } + } + } + + private fun renderFocus(guiGraphics: GuiGraphics, layout: Layout, mouseX: Int, mouseY: Int) { + val selected = selectedEntry() + guiGraphics.drawString(font, "Selected Item", layout.focus.x + 16, layout.focus.y + 14, 0xDDEFE7, true) + if (selected == null) { + guiGraphics.drawString(font, "No matter data", layout.focus.x + 16, layout.focus.y + 42, 0xAAB8B2, false) + return + } + + val slotX = layout.focus.x + 18 + val slotY = layout.focus.y + 42 + drawSlot(guiGraphics, slotX, slotY, 54, true) + guiGraphics.renderItem(selected.stack, slotX + 19, slotY + 19) + if (mouseX in slotX until slotX + 54 && mouseY in slotY until slotY + 54) { + hoveredStack = selected.stack + } + drawTrimmed(guiGraphics, selected.record.displayName, slotX + 70, slotY + 4, layout.focus.w - 100, 0xE6ECE8) + drawTrimmed(guiGraphics, selected.record.itemId, slotX + 70, slotY + 18, layout.focus.w - 100, 0x94A29C) + + val matterStartX = slotX + 70 + val matterStartY = slotY + 36 + for ((index, matterId) in matterTypes.withIndex()) { + val x = matterStartX + (index % 8) * 48 + val y = matterStartY + (index / 8) * 24 + if (x + 42 > layout.focus.x + layout.focus.w - 8) continue + drawSlot(guiGraphics, x, y, 20, false) + renderMatterIcon(guiGraphics, matterId, x + 2, y + 2) + val amount = selected.record.matterValues[matterId] ?: 0.0 + drawCentered(guiGraphics, formatAmount(amount), x + 10, y + 22, 0xE6ECE8) + if (mouseX in x until x + 42 && mouseY in y until y + 34) { + setAmountTooltip(matterId, amount) + } + } + + val badgeY = slotY + 94 + drawBadge(guiGraphics, slotX, badgeY, "Replicable") + drawBadge(guiGraphics, slotX + 94, badgeY, "Decomposable") + drawDivider(guiGraphics, layout.focus.x + 12, badgeY + 32, layout.focus.w - 24) + + val eq = equivalentRect(layout) + guiGraphics.drawString(font, "Matter Equivalent", eq.x, eq.y - 15, 0xDDEFE7, false) + renderEquivalentGrid(guiGraphics, layout, mouseX, mouseY) + } + + private fun renderEquivalentGrid(guiGraphics: GuiGraphics, layout: Layout, mouseX: Int, mouseY: Int) { + val rect = equivalentRect(layout) + guiGraphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, 0x66000000) + val columns = equivalentColumns(layout) + val rows = max(1, rect.h / SLOT_STEP) + val equivalent = equivalentEntries() + equivalentScroll = equivalentScroll.coerceIn(0, max(0, (equivalent.size + columns - 1) / columns - rows)) + val first = equivalentScroll * columns + for (index in 0 until min(equivalent.size - first, columns * rows)) { + val entry = equivalent[first + index] + val x = rect.x + 8 + (index % columns) * SLOT_STEP + val y = rect.y + 8 + (index / columns) * SLOT_STEP + val selected = entry.record.itemId == selectedItemId + drawSlot(guiGraphics, x, y, SLOT_SIZE, selected) + guiGraphics.renderItem(entry.stack, x + 5, y + 5) + if (mouseX in x until x + SLOT_SIZE && mouseY in y until y + SLOT_SIZE) { + hoveredStack = entry.stack + } + } + drawScrollbar(guiGraphics, rect.x + rect.w - 5, rect.y, rect.h, (equivalent.size + columns - 1) / columns, rows, equivalentScroll) + } + + private fun clickHeader(layout: Layout, mouseX: Int, mouseY: Int): Boolean { + val rows = indexRows(layout) + val y = rows.y - HEADER_H + if (mouseY !in y until y + HEADER_H) return false + val columns = columns(layout) + val column = + when { + contains(columns.item, mouseX, mouseY) -> MatterIndexSort.ITEM_COLUMN + contains(columns.total, mouseX, mouseY) -> MatterIndexSort.TOTAL_COLUMN + else -> columns.matter.firstNotNullOfOrNull { (matterId, rect) -> + if (contains(rect, mouseX, mouseY)) MatterIndexSort.matterColumn(matterId) else null + } + } ?: return false + sort = + if (sort.column == column) { + sort.copy(direction = if (sort.direction == MatterIndexSortDirection.ASC) MatterIndexSortDirection.DESC else MatterIndexSortDirection.ASC) + } else { + MatterIndexSort(column, MatterIndexSortDirection.DESC) + } + indexScroll = 0 + return true + } + + private fun equivalentAt(layout: Layout, mouseX: Int, mouseY: Int): MatterIndexEntry? { + val rect = equivalentRect(layout) + if (!contains(rect, mouseX, mouseY)) return null + val columns = equivalentColumns(layout) + val localX = mouseX - rect.x - 8 + val localY = mouseY - rect.y - 8 + if (localX < 0 || localY < 0) return null + val column = localX / SLOT_STEP + val row = localY / SLOT_STEP + if (column !in 0 until columns) return null + if (localX % SLOT_STEP >= SLOT_SIZE || localY % SLOT_STEP >= SLOT_SIZE) return null + return equivalentEntries().getOrNull((equivalentScroll + row) * columns + column) + } + + private fun visibleEntries(): List { + val query = MatterIndexFiltering.parseQuery(searchBox.value, matterOnly, replicableOnly, decomposableOnly) + val ordered = MatterIndexFiltering.filterAndSort(entries.map { it.record }, query, sort) + val byId = entries.associateBy { it.record.itemId } + return ordered.mapNotNull { byId[it.itemId] } + } + + private fun selectedEntry(): MatterIndexEntry? = + entries.firstOrNull { it.record.itemId == selectedItemId } + + private fun equivalentEntries(): List { + val records = MatterIndexFiltering.equivalentRecords(entries.map { it.record }, selectedEntry()?.record) + val byId = entries.associateBy { it.record.itemId } + return records.mapNotNull { byId[it.itemId] } + } + + private fun columns(layout: Layout): Columns { + val rows = indexRows(layout) + val itemW = max(180, (rows.w * 0.30).toInt()) + val totalW = 76 + val matterW = max(42, (rows.w - itemW - totalW) / max(1, matterTypes.size)) + val matter = matterTypes.mapIndexed { index, id -> + id to Rect(rows.x + itemW + totalW + index * matterW, rows.y - HEADER_H, matterW, HEADER_H) + }.filter { it.second.x + it.second.w <= rows.x + rows.w } + return Columns( + item = Rect(rows.x, rows.y - HEADER_H, itemW, HEADER_H), + total = Rect(rows.x + itemW, rows.y - HEADER_H, totalW, HEADER_H), + matter = matter, + endX = rows.x + rows.w, + ) + } + + private fun layout(): Layout { + val margin = 24 + val gap = 6 + val panelH = height - margin * 2 + val totalW = width - margin * 2 - gap + val indexW = (totalW * 0.60).toInt() + val focusW = totalW - indexW + return Layout( + index = Rect(margin, margin, indexW, panelH), + focus = Rect(margin + indexW + gap, margin, focusW, panelH), + ) + } + + private fun indexRows(layout: Layout): Rect = + Rect(layout.index.x + 14, layout.index.y + 88, layout.index.w - 28, layout.index.h - 124) + + private fun equivalentRect(layout: Layout): Rect = + Rect(layout.focus.x + 16, layout.focus.y + 204, layout.focus.w - 32, layout.focus.h - 224) + + private fun equivalentColumns(layout: Layout): Int = + max(2, (equivalentRect(layout).w - 12) / SLOT_STEP) + + private fun renderPanel(guiGraphics: GuiGraphics, rect: Rect) { + guiGraphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, 0xCC071010.toInt()) + drawBorder(guiGraphics, rect, 0xFF41514D.toInt()) + drawBorder(guiGraphics, rect.inflate(-2), 0x6625C34A) + } + + private fun drawCell(guiGraphics: GuiGraphics, rect: Rect, y: Int, h: Int) { + drawVerticalLine(guiGraphics, rect.x, y, y + h, 0x445E716D) + drawVerticalLine(guiGraphics, rect.x + rect.w, y, y + h, 0x445E716D) + } + + private fun drawSlot(guiGraphics: GuiGraphics, x: Int, y: Int, size: Int, selected: Boolean) { + val edge = if (selected) 0xFF58E65B.toInt() else 0xFF59625F.toInt() + guiGraphics.fill(x, y, x + size, y + size, edge) + guiGraphics.fill(x + 1, y + 1, x + size - 1, y + size - 1, 0xFF131918.toInt()) + guiGraphics.fill(x + 2, y + 2, x + size - 2, y + size - 2, 0xFF202826.toInt()) + guiGraphics.fill(x + 3, y + 3, x + size - 3, y + size - 3, 0xFF0D1211.toInt()) + } + + private fun drawBadge(guiGraphics: GuiGraphics, x: Int, y: Int, text: String) { + val w = 86 + guiGraphics.fill(x, y, x + w, y + 22, 0x5517E517) + drawBorder(guiGraphics, Rect(x, y, w, 22), 0xAA36D84A.toInt()) + drawCentered(guiGraphics, text, x + w / 2, y + 7, 0x70F06E) + } + + private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int) { + guiGraphics.blit(ResourceLocation.fromNamespaceAndPath(Replication.MOD_ID, "textures/gui/mattertypes/${matterId.substringAfter(':')}.png"), x, y, 0f, 0f, 16, 16, 16, 16) + } + + private fun drawRiMark(guiGraphics: GuiGraphics, x: Int, y: Int) { + guiGraphics.fill(x + 5, y, x + 9, y + 4, 0xFF42E84F.toInt()) + guiGraphics.fill(x, y + 5, x + 4, y + 9, 0xFF42E84F.toInt()) + guiGraphics.fill(x + 10, y + 5, x + 14, y + 9, 0xFF42E84F.toInt()) + guiGraphics.fill(x + 5, y + 10, x + 9, y + 14, 0xFF42E84F.toInt()) + } + + private fun drawScrollbar(guiGraphics: GuiGraphics, x: Int, y: Int, h: Int, total: Int, visible: Int, scroll: Int) { + if (total <= visible) return + guiGraphics.fill(x, y, x + 4, y + h, 0xAA050606.toInt()) + val thumbH = max(16, h * visible / total) + val thumbY = y + ((h - thumbH) * scroll / max(1, total - visible)) + guiGraphics.fill(x, thumbY, x + 4, thumbY + thumbH, 0xFF9EAAA5.toInt()) + } + + private fun drawDivider(guiGraphics: GuiGraphics, x: Int, y: Int, w: Int) = + guiGraphics.fill(x, y, x + w, y + 1, 0x6641514D) + + private fun drawBorder(guiGraphics: GuiGraphics, rect: Rect, color: Int) { + drawHorizontalLine(guiGraphics, rect.x, rect.x + rect.w, rect.y, color) + drawHorizontalLine(guiGraphics, rect.x, rect.x + rect.w, rect.y + rect.h, color) + drawVerticalLine(guiGraphics, rect.x, rect.y, rect.y + rect.h, color) + drawVerticalLine(guiGraphics, rect.x + rect.w, rect.y, rect.y + rect.h, color) + } + + private fun drawHorizontalLine(guiGraphics: GuiGraphics, x1: Int, x2: Int, y: Int, color: Int) = + guiGraphics.fill(x1, y, x2, y + 1, color) + + private fun drawVerticalLine(guiGraphics: GuiGraphics, x: Int, y1: Int, y2: Int, color: Int) = + guiGraphics.fill(x, y1, x + 1, y2, color) + + private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) { + val trimmed = font.plainSubstrByWidth(text, maxWidth - font.width("...")) + guiGraphics.drawString(font, if (font.width(text) > maxWidth) "$trimmed..." else text, x, y, color, false) + } + + private fun drawRight(guiGraphics: GuiGraphics, text: String, rightX: Int, y: Int, color: Int) = + guiGraphics.drawString(font, text, rightX - font.width(text), y, color, false) + + private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) = + guiGraphics.drawString(font, text, centerX - font.width(text) / 2, y, color, false) + + private fun headerText(text: String, column: String): String = + if (sort.column == column) "$text ${sortArrow()}" else text + + private fun sortArrow(): String = + if (sort.direction == MatterIndexSortDirection.ASC) "^" else "v" + + private fun toggleLabel(text: String, active: Boolean): Component = + Component.literal(if (active) "[$text]" else text) + + private fun formatAmount(amount: Double): String = + MatterAmountFormatter.compact(amount, NeoUiConfig.matterAmountNotation()) + + private fun setAmountTooltip(label: String, amount: Double) { + hoveredAmountTooltip = + listOf( + Component.literal(label), + Component.literal(MatterAmountFormatter.exact(amount)), + ) + } + + private fun contains(rect: Rect, x: Int, y: Int): Boolean = + x in rect.x until rect.x + rect.w && y in rect.y until rect.y + rect.h + + companion object { + private const val HEADER_H = 30 + private const val ROW_H = 26 + private const val SLOT_SIZE = 28 + private const val SLOT_STEP = 34 + + fun hasMatter(itemId: String): Boolean = + ClientReplicationCalculation.DEFAULT_MATTER_COMPOUND.containsKey(itemId) + + fun itemId(stack: ItemStack): String = + BuiltInRegistries.ITEM.getKey(stack.item).toString() + + private fun loadEntries(): List = + ClientReplicationCalculation.DEFAULT_MATTER_COMPOUND.entries.mapNotNull { (id, compound) -> + val item = BuiltInRegistries.ITEM.get(ResourceLocation.parse(id)) + val stack = item.defaultInstance + if (stack.isEmpty) return@mapNotNull null + val tags = stack.tags.map { "#${it.location()}" }.toList().joinToString(" ") + MatterIndexEntry( + record = MatterIndexRecord( + itemId = id, + displayName = stack.hoverName.string, + matterValues = compound.toMatterMap(), + extraSearchText = tags, + ), + stack = stack, + ) + }.sortedBy { it.record.displayName.lowercase() } + + private fun MatterCompound.toMatterMap(): Map = + values.values.associate { value -> + value.matter.name to value.amount + } + } + + private data class MatterIndexEntry( + val record: MatterIndexRecord, + val stack: ItemStack, + ) + + private data class Layout( + val index: Rect, + val focus: Rect, + ) + + private data class Columns( + val item: Rect, + val total: Rect, + val matter: List>, + val endX: Int, + ) + + private data class Rect( + val x: Int, + val y: Int, + val w: Int, + val h: Int, + ) { + fun inflate(amount: Int): Rect = + Rect(x - amount, y - amount, w + amount * 2, h + amount * 2) + } +} diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/config/NeoUiConfig.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/config/NeoUiConfig.kt new file mode 100644 index 0000000..8533e41 --- /dev/null +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/config/NeoUiConfig.kt @@ -0,0 +1,25 @@ +package com.p_nsk.replicated_integration.config + +import com.p_nsk.replicated_integration.api.client.MatterAmountNotation +import net.neoforged.neoforge.common.ModConfigSpec + +object NeoUiConfig { + private const val KEY_MATTER_AMOUNT_NOTATION = "matterAmountNotation" + + val spec: ModConfigSpec + private val matterAmountNotationValue: ModConfigSpec.EnumValue + + init { + val builder = ModConfigSpec.Builder() + builder.push("matterIndex") + matterAmountNotationValue = + builder + .comment("Notation used for compact matter amounts. SI uses K/M/G/T; SHORT_SCALE uses K/M/B/T.") + .translation("replicated_integration.config.ui.matter_amount_notation") + .defineEnum(KEY_MATTER_AMOUNT_NOTATION, MatterAmountNotation.SHORT_SCALE) + builder.pop() + spec = builder.build() + } + + fun matterAmountNotation(): MatterAmountNotation = matterAmountNotationValue.get() +} diff --git a/1.21.1-neo/src/main/resources/replicated_integration.mixins.json b/1.21.1-neo/src/main/resources/replicated_integration.mixins.json index afce7a8..9c3dc95 100644 --- a/1.21.1-neo/src/main/resources/replicated_integration.mixins.json +++ b/1.21.1-neo/src/main/resources/replicated_integration.mixins.json @@ -8,6 +8,9 @@ "MatterValueRecipeMixin", "ReplicationCalculationMixin" ], + "client": [ + "ScreenAccessor" + ], "injectors": { "defaultRequire": 1 }, diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterAmountFormatter.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterAmountFormatter.kt new file mode 100644 index 0000000..365e59a --- /dev/null +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterAmountFormatter.kt @@ -0,0 +1,69 @@ +package com.p_nsk.replicated_integration.api.client + +import java.text.DecimalFormat +import java.util.Locale +import kotlin.math.abs +import kotlin.math.floor +import kotlin.math.log10 +import kotlin.math.pow + +enum class MatterAmountNotation { + SI, + SHORT_SCALE, +} + +object MatterAmountFormatter { + private val exactFormat = DecimalFormat("#,##0.##") + private val siSuffixes = listOf("", "K", "M", "G", "T", "P", "E") + private val shortScaleSuffixes = listOf("", "K", "M", "B", "T", "Qa", "Qi") + + fun compact( + amount: Double, + notation: MatterAmountNotation, + ): String { + if (!amount.isFinite()) return amount.toString() + if (amount == 0.0) return "0" + val suffixes = if (notation == MatterAmountNotation.SI) siSuffixes else shortScaleSuffixes + val absAmount = abs(amount) + var group = floor(log10(absAmount) / 3.0).toInt() + if (group > suffixes.lastIndex) return scientific(amount) + group = group.coerceAtLeast(0) + if (group == 0) return exact(amount) + + var scaled = amount / 1000.0.pow(group) + if (abs(scaled) >= 999.5 && group < suffixes.lastIndex) { + group += 1 + scaled = amount / 1000.0.pow(group) + } + return "${formatThreeSignificantDigits(scaled)}${suffixes[group]}" + } + + fun exact(amount: Double): String = + if (amount.isFinite()) exactFormat.format(amount) else amount.toString() + + fun parseNotation(value: String): MatterAmountNotation = + MatterAmountNotation.entries.firstOrNull { it.name.equals(value, ignoreCase = true) } + ?: MatterAmountNotation.SHORT_SCALE + + private fun formatThreeSignificantDigits(value: Double): String { + val absValue = abs(value) + val decimals = + when { + absValue >= 100.0 -> 0 + absValue >= 10.0 -> 1 + else -> 2 + } + val formatted = "%.${decimals}f".format(Locale.ROOT, value) + return if ('.' in formatted) { + formatted.trimEnd('0').trimEnd('.') + } else { + formatted + } + } + + private fun scientific(value: Double): String { + val exponent = floor(log10(abs(value))).toInt() + val mantissa = value / 10.0.pow(exponent) + return "${formatThreeSignificantDigits(mantissa)}e$exponent" + } +} diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexModels.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexModels.kt new file mode 100644 index 0000000..539530c --- /dev/null +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexModels.kt @@ -0,0 +1,145 @@ +package com.p_nsk.replicated_integration.api.client + +data class MatterIndexRecord( + val itemId: String, + val displayName: String, + val matterValues: Map, + val extraSearchText: String = "", +) { + val namespace: String = itemId.substringBefore(':', "") + val total: Double = matterValues.values.sum() + val signature: String = + matterValues + .filterValues { it > 0.0 } + .entries + .sortedBy { it.key } + .joinToString("|") { (matter, amount) -> "$matter=${amount.formatSignatureAmount()}" } + + fun hasMatter(matterId: String): Boolean = + (matterValues[matterId] ?: 0.0) > 0.0 +} + +data class MatterIndexQuery( + val searchText: String = "", + val matterOnly: Boolean = true, + val replicableOnly: Boolean = false, + val decomposableOnly: Boolean = false, + val namespaceFilter: String? = null, + val matterFilter: String? = null, +) + +enum class MatterIndexSortDirection { + ASC, + DESC, +} + +data class MatterIndexSort( + val column: String = TOTAL_COLUMN, + val direction: MatterIndexSortDirection = MatterIndexSortDirection.DESC, +) { + companion object { + const val ITEM_COLUMN = "item" + const val TOTAL_COLUMN = "total" + const val MATTER_PREFIX = "matter:" + + fun matterColumn(matterId: String): String = "$MATTER_PREFIX$matterId" + } +} + +object MatterIndexFiltering { + fun parseQuery( + raw: String, + matterOnly: Boolean, + replicableOnly: Boolean, + decomposableOnly: Boolean, + ): MatterIndexQuery { + var namespaceFilter: String? = null + var matterFilter: String? = null + val textTerms = mutableListOf() + + for (token in raw.split(Regex("\\s+")).filter { it.isNotBlank() }) { + when { + token.startsWith("namespace:", ignoreCase = true) -> + namespaceFilter = token.substringAfter(':').ifBlank { null } + token.startsWith("mod:", ignoreCase = true) -> + namespaceFilter = token.substringAfter(':').ifBlank { null } + token.startsWith("matter:", ignoreCase = true) -> + matterFilter = token.substringAfter(':').ifBlank { null } + else -> textTerms += token + } + } + + return MatterIndexQuery( + searchText = textTerms.joinToString(" "), + matterOnly = matterOnly, + replicableOnly = replicableOnly, + decomposableOnly = decomposableOnly, + namespaceFilter = namespaceFilter, + matterFilter = matterFilter, + ) + } + + fun filterAndSort( + records: List, + query: MatterIndexQuery, + sort: MatterIndexSort, + ): List { + val normalizedSearch = query.searchText.trim().lowercase() + val namespace = query.namespaceFilter?.lowercase() + val matter = query.matterFilter?.lowercase() + + val filtered = + records.filter { record -> + if (query.matterOnly && record.total <= 0.0) return@filter false + if (query.replicableOnly && record.total <= 0.0) return@filter false + if (query.decomposableOnly && record.total <= 0.0) return@filter false + if (namespace != null && record.namespace.lowercase() != namespace) return@filter false + if (matter != null && record.matterValues.none { (id, amount) -> id.lowercase().contains(matter) && amount > 0.0 }) { + return@filter false + } + if (normalizedSearch.isBlank()) return@filter true + record.displayName.lowercase().contains(normalizedSearch) || + record.itemId.lowercase().contains(normalizedSearch) || + record.namespace.lowercase().contains(normalizedSearch) || + record.extraSearchText.lowercase().contains(normalizedSearch) + } + + val comparator = + when { + sort.column == MatterIndexSort.ITEM_COLUMN -> + compareBy { it.displayName.lowercase() }.thenBy { it.itemId } + sort.column == MatterIndexSort.TOTAL_COLUMN -> + compareBy { it.total }.thenBy { it.displayName.lowercase() } + sort.column.startsWith(MatterIndexSort.MATTER_PREFIX) -> { + val matterId = sort.column.removePrefix(MatterIndexSort.MATTER_PREFIX) + compareBy { it.matterValues[matterId] ?: 0.0 }.thenBy { it.displayName.lowercase() } + } + else -> + compareBy { it.total }.thenBy { it.displayName.lowercase() } + } + + return if (sort.direction == MatterIndexSortDirection.ASC) { + filtered.sortedWith(comparator) + } else { + filtered.sortedWith(comparator.reversed()) + } + } + + fun equivalentRecords( + records: List, + selected: MatterIndexRecord?, + ): List { + val signature = selected?.signature + if (signature.isNullOrBlank()) return emptyList() + return records + .filter { it.signature == signature } + .sortedBy { it.displayName.lowercase() } + } +} + +private fun Double.formatSignatureAmount(): String = + if (this % 1.0 == 0.0) { + toLong().toString() + } else { + toString() + } diff --git a/common/src/test/kotlin/MatterAmountFormatterTest.kt b/common/src/test/kotlin/MatterAmountFormatterTest.kt new file mode 100644 index 0000000..c3ab066 --- /dev/null +++ b/common/src/test/kotlin/MatterAmountFormatterTest.kt @@ -0,0 +1,31 @@ +package com.p_nsk.replicated_integration.api + +import com.p_nsk.replicated_integration.api.client.MatterAmountFormatter +import com.p_nsk.replicated_integration.api.client.MatterAmountNotation +import kotlin.test.Test +import kotlin.test.assertEquals + +class MatterAmountFormatterTest { + @Test + fun compactUsesShortScaleSuffixes() { + assertEquals("300K", MatterAmountFormatter.compact(300_000.0, MatterAmountNotation.SHORT_SCALE)) + assertEquals("20M", MatterAmountFormatter.compact(20_000_000.0, MatterAmountNotation.SHORT_SCALE)) + assertEquals("10B", MatterAmountFormatter.compact(10_000_000_000.0, MatterAmountNotation.SHORT_SCALE)) + } + + @Test + fun compactUsesSiSuffixes() { + assertEquals("10G", MatterAmountFormatter.compact(10_000_000_000.0, MatterAmountNotation.SI)) + } + + @Test + fun exactKeepsGroupedValue() { + assertEquals("1,234,567.89", MatterAmountFormatter.exact(1_234_567.89)) + } + + @Test + fun compactFallsBackToScientificWhenSuffixesRunOut() { + assertEquals("1e21", MatterAmountFormatter.compact(1.0e21, MatterAmountNotation.SHORT_SCALE)) + assertEquals("1e21", MatterAmountFormatter.compact(1.0e21, MatterAmountNotation.SI)) + } +} From 7f2ee3b63786fa5712419f233ece7c2543b692a9 Mon Sep 17 00:00:00 2001 From: P-nutsK <107553542+P-nutsK@users.noreply.github.com> Date: Thu, 21 May 2026 19:15:33 +0900 Subject: [PATCH 02/12] codex add scaling --- .../client/MatterIndexScreen.kt | 107 ++++++++++++++---- .../client/MatterIndexScreen.kt | 102 ++++++++++++++--- .../api/client/MatterIndexDisplayScale.kt | 20 ++++ .../kotlin/MatterIndexDisplayScaleTest.kt | 25 ++++ 4 files changed, 213 insertions(+), 41 deletions(-) create mode 100644 common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexDisplayScale.kt create mode 100644 common/src/test/kotlin/MatterIndexDisplayScaleTest.kt diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt index 665556c..45623fc 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -3,11 +3,7 @@ package com.p_nsk.replicated_integration.client import com.buuz135.replication.Replication import com.buuz135.replication.calculation.MatterCompound import com.buuz135.replication.calculation.client.ClientReplicationCalculation -import com.p_nsk.replicated_integration.api.client.MatterAmountFormatter -import com.p_nsk.replicated_integration.api.client.MatterIndexFiltering -import com.p_nsk.replicated_integration.api.client.MatterIndexRecord -import com.p_nsk.replicated_integration.api.client.MatterIndexSort -import com.p_nsk.replicated_integration.api.client.MatterIndexSortDirection +import com.p_nsk.replicated_integration.api.client.* import com.p_nsk.replicated_integration.config.ForgeUiConfig import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.components.Button @@ -17,6 +13,7 @@ import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack +import kotlin.math.ceil import kotlin.math.max import kotlin.math.min @@ -39,6 +36,7 @@ class MatterIndexScreen( private var matterOnly = true private var replicableOnly = false private var decomposableOnly = false + private var displayScalePercent = MatterIndexDisplayScale.DEFAULT_PERCENT private var hoveredStack: ItemStack = ItemStack.EMPTY private var hoveredAmountTooltip: List = emptyList() @@ -46,6 +44,8 @@ class MatterIndexScreen( private lateinit var matterOnlyButton: Button private lateinit var replicableButton: Button private lateinit var decomposableButton: Button + private lateinit var decreaseDisplayScaleButton: Button + private lateinit var increaseDisplayScaleButton: Button override fun init() { val layout = layout() @@ -76,6 +76,19 @@ class MatterIndexScreen( indexScroll = 0 }.bounds(replicableButton.x + replicableButton.width + 4, searchBox.y, 34, 18).build(), ) + val scaleControlX = max(layout.index.x + 16, layout.index.x + layout.index.w - 94) + val scaleControlY = layout.index.y + 10 + decreaseDisplayScaleButton = addRenderableWidget( + Button.builder(Component.literal("-")) { + adjustDisplayScale(-1) + }.bounds(scaleControlX, scaleControlY, 20, 18).build(), + ) + increaseDisplayScaleButton = addRenderableWidget( + Button.builder(Component.literal("+")) { + adjustDisplayScale(1) + }.bounds(scaleControlX + 70, scaleControlY, 20, 18).build(), + ) + refreshDisplayScaleButtons() } override fun tick() { @@ -184,6 +197,10 @@ class MatterIndexScreen( matterOnlyButton.render(guiGraphics, mouseX, mouseY, partialTick) replicableButton.render(guiGraphics, mouseX, mouseY, partialTick) decomposableButton.render(guiGraphics, mouseX, mouseY, partialTick) + decreaseDisplayScaleButton.render(guiGraphics, mouseX, mouseY, partialTick) + increaseDisplayScaleButton.render(guiGraphics, mouseX, mouseY, partialTick) + val label = MatterIndexDisplayScale.label(displayScalePercent) + guiGraphics.drawString(font, label, decreaseDisplayScaleButton.x + 35 - font.width(label) / 2, decreaseDisplayScaleButton.y + 5, 0xBFD0CA, false) } private fun renderIndex(guiGraphics: GuiGraphics, layout: Layout, visible: List, mouseX: Int, mouseY: Int) { @@ -224,7 +241,7 @@ class MatterIndexScreen( } drawScrollbar(guiGraphics, rows.x + rows.w - 5, rows.y, rows.h, visible.size, visibleRows, indexScroll) - guiGraphics.drawString(font, "${visible.size} items", layout.index.x + 16, layout.index.y + layout.index.h - 18, 0xAAB8B2, false) + drawScaled(guiGraphics, "${visible.size} items", layout.index.x + 16, layout.index.y + layout.index.h - 18, 0xAAB8B2, false) } private fun renderHeader(guiGraphics: GuiGraphics, columns: Columns, y: Int) { @@ -232,22 +249,22 @@ class MatterIndexScreen( guiGraphics.fill(columns.item.x, y, columns.endX, y + h, 0x44000000) drawCell(guiGraphics, columns.item, y, h) drawCell(guiGraphics, columns.total, y, h) - guiGraphics.drawString(font, headerText("Item", MatterIndexSort.ITEM_COLUMN), columns.item.x + 10, y + 10, 0xBFD0CA, false) + drawScaled(guiGraphics, headerText("Item", MatterIndexSort.ITEM_COLUMN), columns.item.x + 10, y + 10, 0xBFD0CA, false) drawCentered(guiGraphics, headerText("Total", MatterIndexSort.TOTAL_COLUMN), columns.total.x + columns.total.w / 2, y + 10, 0xBFD0CA) for ((matterId, rect) in columns.matter) { drawCell(guiGraphics, rect, y, h) renderMatterIcon(guiGraphics, matterId, rect.x + rect.w / 2 - 8, y + 7) if (sort.column == MatterIndexSort.matterColumn(matterId)) { - guiGraphics.drawString(font, sortArrow(), rect.x + rect.w - 10, y + 10, 0x7CF47B, false) + drawScaled(guiGraphics, sortArrow(), rect.x + rect.w - scaledWidth(sortArrow()) - 3, y + 10, 0x7CF47B, false) } } } private fun renderFocus(guiGraphics: GuiGraphics, layout: Layout, mouseX: Int, mouseY: Int) { val selected = selectedEntry() - guiGraphics.drawString(font, "Selected Item", layout.focus.x + 16, layout.focus.y + 14, 0xDDEFE7, true) + drawScaled(guiGraphics, "Selected Item", layout.focus.x + 16, layout.focus.y + 14, 0xDDEFE7, true) if (selected == null) { - guiGraphics.drawString(font, "No matter data", layout.focus.x + 16, layout.focus.y + 42, 0xAAB8B2, false) + drawScaled(guiGraphics, "No matter data", layout.focus.x + 16, layout.focus.y + 42, 0xAAB8B2, false) return } @@ -278,11 +295,11 @@ class MatterIndexScreen( val badgeY = slotY + 94 drawBadge(guiGraphics, slotX, badgeY, "Replicable") - drawBadge(guiGraphics, slotX + 94, badgeY, "Decomposable") + drawBadge(guiGraphics, slotX + scaledPixels(94), badgeY, "Decomposable") drawDivider(guiGraphics, layout.focus.x + 12, badgeY + 32, layout.focus.w - 24) val eq = equivalentRect(layout) - guiGraphics.drawString(font, "Matter Equivalent", eq.x, eq.y - 15, 0xDDEFE7, false) + drawScaled(guiGraphics, "Matter Equivalent", eq.x, eq.y - 15, 0xDDEFE7, false) renderEquivalentGrid(guiGraphics, layout, mouseX, mouseY) } @@ -363,9 +380,9 @@ class MatterIndexScreen( private fun columns(layout: Layout): Columns { val rows = indexRows(layout) - val itemW = max(180, (rows.w * 0.30).toInt()) - val totalW = 76 - val matterW = max(42, (rows.w - itemW - totalW) / max(1, matterTypes.size)) + val itemW = max(scaledPixels(180), (rows.w * 0.30).toInt()) + val totalW = scaledPixels(76) + val matterW = max(scaledPixels(42), (rows.w - itemW - totalW) / max(1, matterTypes.size)) val matter = matterTypes.mapIndexed { index, id -> id to Rect(rows.x + itemW + totalW + index * matterW, rows.y - HEADER_H, matterW, HEADER_H) }.filter { it.second.x + it.second.w <= rows.x + rows.w } @@ -419,10 +436,11 @@ class MatterIndexScreen( } private fun drawBadge(guiGraphics: GuiGraphics, x: Int, y: Int, text: String) { - val w = 86 - guiGraphics.fill(x, y, x + w, y + 22, 0x5517E517) - drawBorder(guiGraphics, Rect(x, y, w, 22), 0xAA36D84A.toInt()) - drawCentered(guiGraphics, text, x + w / 2, y + 7, 0x70F06E) + val w = scaledPixels(86) + val h = scaledPixels(22) + guiGraphics.fill(x, y, x + w, y + h, 0x5517E517) + drawBorder(guiGraphics, Rect(x, y, w, h), 0xAA36D84A.toInt()) + drawCentered(guiGraphics, text, x + w / 2, y + max(1, (h - scaledPixels(font.lineHeight)) / 2), 0x70F06E) } private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int) { @@ -461,15 +479,58 @@ class MatterIndexScreen( guiGraphics.fill(x, y1, x + 1, y2, color) private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) { - val trimmed = font.plainSubstrByWidth(text, maxWidth - font.width("...")) - guiGraphics.drawString(font, if (font.width(text) > maxWidth) "$trimmed..." else text, x, y, color, false) + if (maxWidth <= 0) return + val logicalMaxWidth = max(1, (maxWidth / displayScale()).toInt()) + val ellipsis = "..." + val rendered = + if (font.width(text) <= logicalMaxWidth) { + text + } else if (logicalMaxWidth <= font.width(ellipsis)) { + font.plainSubstrByWidth(text, logicalMaxWidth) + } else { + "${font.plainSubstrByWidth(text, logicalMaxWidth - font.width(ellipsis))}$ellipsis" + } + drawScaled(guiGraphics, rendered, x, y, color, false) } private fun drawRight(guiGraphics: GuiGraphics, text: String, rightX: Int, y: Int, color: Int) = - guiGraphics.drawString(font, text, rightX - font.width(text), y, color, false) + drawScaled(guiGraphics, text, rightX - scaledWidth(text), y, color, false) private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) = - guiGraphics.drawString(font, text, centerX - font.width(text) / 2, y, color, false) + drawScaled(guiGraphics, text, centerX - scaledWidth(text) / 2, y, color, false) + + private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean) { + val scale = displayScale() + if (scale == 1f) { + guiGraphics.drawString(font, text, x, y, color, shadow) + return + } + guiGraphics.pose().pushPose() + guiGraphics.pose().translate(x.toFloat(), y.toFloat(), 0f) + guiGraphics.pose().scale(scale, scale, 1f) + guiGraphics.drawString(font, text, 0, 0, color, shadow) + guiGraphics.pose().popPose() + } + + private fun scaledWidth(text: String): Int = + ceil(font.width(text) * displayScale()).toInt() + + private fun scaledPixels(value: Int): Int = + MatterIndexDisplayScale.scalePixels(value, displayScalePercent) + + private fun displayScale(): Float = + MatterIndexDisplayScale.toScale(displayScalePercent) + + private fun adjustDisplayScale(steps: Int) { + displayScalePercent = MatterIndexDisplayScale.adjust(displayScalePercent, steps) + refreshDisplayScaleButtons() + } + + private fun refreshDisplayScaleButtons() { + if (!::decreaseDisplayScaleButton.isInitialized || !::increaseDisplayScaleButton.isInitialized) return + decreaseDisplayScaleButton.active = displayScalePercent > MatterIndexDisplayScale.MIN_PERCENT + increaseDisplayScaleButton.active = displayScalePercent < MatterIndexDisplayScale.MAX_PERCENT + } private fun headerText(text: String, column: String): String = if (sort.column == column) "$text ${sortArrow()}" else text diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt index 5ad2dc6..fcee9d2 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -8,6 +8,7 @@ import com.p_nsk.replicated_integration.api.client.MatterIndexFiltering import com.p_nsk.replicated_integration.api.client.MatterIndexRecord import com.p_nsk.replicated_integration.api.client.MatterIndexSort import com.p_nsk.replicated_integration.api.client.MatterIndexSortDirection +import com.p_nsk.replicated_integration.api.client.MatterIndexDisplayScale import com.p_nsk.replicated_integration.config.NeoUiConfig import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.components.Button @@ -17,6 +18,7 @@ import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack +import kotlin.math.ceil import kotlin.math.max import kotlin.math.min @@ -39,6 +41,7 @@ class MatterIndexScreen( private var matterOnly = true private var replicableOnly = false private var decomposableOnly = false + private var displayScalePercent = MatterIndexDisplayScale.DEFAULT_PERCENT private var hoveredStack: ItemStack = ItemStack.EMPTY private var hoveredAmountTooltip: List = emptyList() @@ -46,6 +49,8 @@ class MatterIndexScreen( private lateinit var matterOnlyButton: Button private lateinit var replicableButton: Button private lateinit var decomposableButton: Button + private lateinit var decreaseDisplayScaleButton: Button + private lateinit var increaseDisplayScaleButton: Button override fun init() { val layout = layout() @@ -76,6 +81,19 @@ class MatterIndexScreen( indexScroll = 0 }.bounds(replicableButton.x + replicableButton.width + 4, searchBox.y, 34, 18).build(), ) + val scaleControlX = max(layout.index.x + 16, layout.index.x + layout.index.w - 94) + val scaleControlY = layout.index.y + 10 + decreaseDisplayScaleButton = addRenderableWidget( + Button.builder(Component.literal("-")) { + adjustDisplayScale(-1) + }.bounds(scaleControlX, scaleControlY, 20, 18).build(), + ) + increaseDisplayScaleButton = addRenderableWidget( + Button.builder(Component.literal("+")) { + adjustDisplayScale(1) + }.bounds(scaleControlX + 70, scaleControlY, 20, 18).build(), + ) + refreshDisplayScaleButtons() } override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { @@ -180,6 +198,10 @@ class MatterIndexScreen( matterOnlyButton.render(guiGraphics, mouseX, mouseY, partialTick) replicableButton.render(guiGraphics, mouseX, mouseY, partialTick) decomposableButton.render(guiGraphics, mouseX, mouseY, partialTick) + decreaseDisplayScaleButton.render(guiGraphics, mouseX, mouseY, partialTick) + increaseDisplayScaleButton.render(guiGraphics, mouseX, mouseY, partialTick) + val label = MatterIndexDisplayScale.label(displayScalePercent) + guiGraphics.drawString(font, label, decreaseDisplayScaleButton.x + 35 - font.width(label) / 2, decreaseDisplayScaleButton.y + 5, 0xBFD0CA, false) } private fun renderIndex(guiGraphics: GuiGraphics, layout: Layout, visible: List, mouseX: Int, mouseY: Int) { @@ -220,7 +242,7 @@ class MatterIndexScreen( } drawScrollbar(guiGraphics, rows.x + rows.w - 5, rows.y, rows.h, visible.size, visibleRows, indexScroll) - guiGraphics.drawString(font, "${visible.size} items", layout.index.x + 16, layout.index.y + layout.index.h - 18, 0xAAB8B2, false) + drawScaled(guiGraphics, "${visible.size} items", layout.index.x + 16, layout.index.y + layout.index.h - 18, 0xAAB8B2, false) } private fun renderHeader(guiGraphics: GuiGraphics, columns: Columns, y: Int) { @@ -228,22 +250,22 @@ class MatterIndexScreen( guiGraphics.fill(columns.item.x, y, columns.endX, y + h, 0x44000000) drawCell(guiGraphics, columns.item, y, h) drawCell(guiGraphics, columns.total, y, h) - guiGraphics.drawString(font, headerText("Item", MatterIndexSort.ITEM_COLUMN), columns.item.x + 10, y + 10, 0xBFD0CA, false) + drawScaled(guiGraphics, headerText("Item", MatterIndexSort.ITEM_COLUMN), columns.item.x + 10, y + 10, 0xBFD0CA, false) drawCentered(guiGraphics, headerText("Total", MatterIndexSort.TOTAL_COLUMN), columns.total.x + columns.total.w / 2, y + 10, 0xBFD0CA) for ((matterId, rect) in columns.matter) { drawCell(guiGraphics, rect, y, h) renderMatterIcon(guiGraphics, matterId, rect.x + rect.w / 2 - 8, y + 7) if (sort.column == MatterIndexSort.matterColumn(matterId)) { - guiGraphics.drawString(font, sortArrow(), rect.x + rect.w - 10, y + 10, 0x7CF47B, false) + drawScaled(guiGraphics, sortArrow(), rect.x + rect.w - scaledWidth(sortArrow()) - 3, y + 10, 0x7CF47B, false) } } } private fun renderFocus(guiGraphics: GuiGraphics, layout: Layout, mouseX: Int, mouseY: Int) { val selected = selectedEntry() - guiGraphics.drawString(font, "Selected Item", layout.focus.x + 16, layout.focus.y + 14, 0xDDEFE7, true) + drawScaled(guiGraphics, "Selected Item", layout.focus.x + 16, layout.focus.y + 14, 0xDDEFE7, true) if (selected == null) { - guiGraphics.drawString(font, "No matter data", layout.focus.x + 16, layout.focus.y + 42, 0xAAB8B2, false) + drawScaled(guiGraphics, "No matter data", layout.focus.x + 16, layout.focus.y + 42, 0xAAB8B2, false) return } @@ -274,11 +296,11 @@ class MatterIndexScreen( val badgeY = slotY + 94 drawBadge(guiGraphics, slotX, badgeY, "Replicable") - drawBadge(guiGraphics, slotX + 94, badgeY, "Decomposable") + drawBadge(guiGraphics, slotX + scaledPixels(94), badgeY, "Decomposable") drawDivider(guiGraphics, layout.focus.x + 12, badgeY + 32, layout.focus.w - 24) val eq = equivalentRect(layout) - guiGraphics.drawString(font, "Matter Equivalent", eq.x, eq.y - 15, 0xDDEFE7, false) + drawScaled(guiGraphics, "Matter Equivalent", eq.x, eq.y - 15, 0xDDEFE7, false) renderEquivalentGrid(guiGraphics, layout, mouseX, mouseY) } @@ -359,9 +381,9 @@ class MatterIndexScreen( private fun columns(layout: Layout): Columns { val rows = indexRows(layout) - val itemW = max(180, (rows.w * 0.30).toInt()) - val totalW = 76 - val matterW = max(42, (rows.w - itemW - totalW) / max(1, matterTypes.size)) + val itemW = max(scaledPixels(180), (rows.w * 0.30).toInt()) + val totalW = scaledPixels(76) + val matterW = max(scaledPixels(42), (rows.w - itemW - totalW) / max(1, matterTypes.size)) val matter = matterTypes.mapIndexed { index, id -> id to Rect(rows.x + itemW + totalW + index * matterW, rows.y - HEADER_H, matterW, HEADER_H) }.filter { it.second.x + it.second.w <= rows.x + rows.w } @@ -415,10 +437,11 @@ class MatterIndexScreen( } private fun drawBadge(guiGraphics: GuiGraphics, x: Int, y: Int, text: String) { - val w = 86 - guiGraphics.fill(x, y, x + w, y + 22, 0x5517E517) - drawBorder(guiGraphics, Rect(x, y, w, 22), 0xAA36D84A.toInt()) - drawCentered(guiGraphics, text, x + w / 2, y + 7, 0x70F06E) + val w = scaledPixels(86) + val h = scaledPixels(22) + guiGraphics.fill(x, y, x + w, y + h, 0x5517E517) + drawBorder(guiGraphics, Rect(x, y, w, h), 0xAA36D84A.toInt()) + drawCentered(guiGraphics, text, x + w / 2, y + max(1, (h - scaledPixels(font.lineHeight)) / 2), 0x70F06E) } private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int) { @@ -457,15 +480,58 @@ class MatterIndexScreen( guiGraphics.fill(x, y1, x + 1, y2, color) private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) { - val trimmed = font.plainSubstrByWidth(text, maxWidth - font.width("...")) - guiGraphics.drawString(font, if (font.width(text) > maxWidth) "$trimmed..." else text, x, y, color, false) + if (maxWidth <= 0) return + val logicalMaxWidth = max(1, (maxWidth / displayScale()).toInt()) + val ellipsis = "..." + val rendered = + if (font.width(text) <= logicalMaxWidth) { + text + } else if (logicalMaxWidth <= font.width(ellipsis)) { + font.plainSubstrByWidth(text, logicalMaxWidth) + } else { + "${font.plainSubstrByWidth(text, logicalMaxWidth - font.width(ellipsis))}$ellipsis" + } + drawScaled(guiGraphics, rendered, x, y, color, false) } private fun drawRight(guiGraphics: GuiGraphics, text: String, rightX: Int, y: Int, color: Int) = - guiGraphics.drawString(font, text, rightX - font.width(text), y, color, false) + drawScaled(guiGraphics, text, rightX - scaledWidth(text), y, color, false) private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) = - guiGraphics.drawString(font, text, centerX - font.width(text) / 2, y, color, false) + drawScaled(guiGraphics, text, centerX - scaledWidth(text) / 2, y, color, false) + + private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean) { + val scale = displayScale() + if (scale == 1f) { + guiGraphics.drawString(font, text, x, y, color, shadow) + return + } + guiGraphics.pose().pushPose() + guiGraphics.pose().translate(x.toFloat(), y.toFloat(), 0f) + guiGraphics.pose().scale(scale, scale, 1f) + guiGraphics.drawString(font, text, 0, 0, color, shadow) + guiGraphics.pose().popPose() + } + + private fun scaledWidth(text: String): Int = + ceil(font.width(text) * displayScale()).toInt() + + private fun scaledPixels(value: Int): Int = + MatterIndexDisplayScale.scalePixels(value, displayScalePercent) + + private fun displayScale(): Float = + MatterIndexDisplayScale.toScale(displayScalePercent) + + private fun adjustDisplayScale(steps: Int) { + displayScalePercent = MatterIndexDisplayScale.adjust(displayScalePercent, steps) + refreshDisplayScaleButtons() + } + + private fun refreshDisplayScaleButtons() { + if (!::decreaseDisplayScaleButton.isInitialized || !::increaseDisplayScaleButton.isInitialized) return + decreaseDisplayScaleButton.active = displayScalePercent > MatterIndexDisplayScale.MIN_PERCENT + increaseDisplayScaleButton.active = displayScalePercent < MatterIndexDisplayScale.MAX_PERCENT + } private fun headerText(text: String, column: String): String = if (sort.column == column) "$text ${sortArrow()}" else text diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexDisplayScale.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexDisplayScale.kt new file mode 100644 index 0000000..1b53eea --- /dev/null +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexDisplayScale.kt @@ -0,0 +1,20 @@ +package com.p_nsk.replicated_integration.api.client + +object MatterIndexDisplayScale { + const val MIN_PERCENT = 70 + const val DEFAULT_PERCENT = 100 + const val MAX_PERCENT = 130 + const val STEP_PERCENT = 10 + + fun adjust(currentPercent: Int, steps: Int): Int = + (currentPercent + steps * STEP_PERCENT).coerceIn(MIN_PERCENT, MAX_PERCENT) + + fun toScale(percent: Int): Float = + percent.coerceIn(MIN_PERCENT, MAX_PERCENT) / 100f + + fun scalePixels(value: Int, percent: Int): Int = + (value * toScale(percent)).toInt().coerceAtLeast(1) + + fun label(percent: Int): String = + "${percent.coerceIn(MIN_PERCENT, MAX_PERCENT)}%" +} diff --git a/common/src/test/kotlin/MatterIndexDisplayScaleTest.kt b/common/src/test/kotlin/MatterIndexDisplayScaleTest.kt new file mode 100644 index 0000000..532c3c1 --- /dev/null +++ b/common/src/test/kotlin/MatterIndexDisplayScaleTest.kt @@ -0,0 +1,25 @@ +package com.p_nsk.replicated_integration.api + +import com.p_nsk.replicated_integration.api.client.MatterIndexDisplayScale +import kotlin.test.Test +import kotlin.test.assertEquals + +class MatterIndexDisplayScaleTest { + @Test + fun adjustsInTenPercentSteps() { + assertEquals(90, MatterIndexDisplayScale.adjust(100, -1)) + assertEquals(110, MatterIndexDisplayScale.adjust(100, 1)) + } + + @Test + fun clampsToSupportedRange() { + assertEquals(70, MatterIndexDisplayScale.adjust(70, -1)) + assertEquals(130, MatterIndexDisplayScale.adjust(130, 1)) + } + + @Test + fun scalesPixelConstantsLocally() { + assertEquals(37, MatterIndexDisplayScale.scalePixels(42, 90)) + assertEquals(46, MatterIndexDisplayScale.scalePixels(42, 110)) + } +} From 49cab42dc99f70cb83ba0ab437c73cff29ec819c Mon Sep 17 00:00:00 2001 From: P-nutsK <107553542+P-nutsK@users.noreply.github.com> Date: Thu, 21 May 2026 20:28:51 +0900 Subject: [PATCH 03/12] =?UTF-8?q?wip=20=E8=AA=BF=E6=95=B4=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/MatterIndexScreen.kt | 128 ++++++++++++----- .../client/MatterIndexScreen.kt | 136 ++++++++++++------ 2 files changed, 186 insertions(+), 78 deletions(-) diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt index 45623fc..14291b1 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -44,8 +44,6 @@ class MatterIndexScreen( private lateinit var matterOnlyButton: Button private lateinit var replicableButton: Button private lateinit var decomposableButton: Button - private lateinit var decreaseDisplayScaleButton: Button - private lateinit var increaseDisplayScaleButton: Button override fun init() { val layout = layout() @@ -76,19 +74,6 @@ class MatterIndexScreen( indexScroll = 0 }.bounds(replicableButton.x + replicableButton.width + 4, searchBox.y, 34, 18).build(), ) - val scaleControlX = max(layout.index.x + 16, layout.index.x + layout.index.w - 94) - val scaleControlY = layout.index.y + 10 - decreaseDisplayScaleButton = addRenderableWidget( - Button.builder(Component.literal("-")) { - adjustDisplayScale(-1) - }.bounds(scaleControlX, scaleControlY, 20, 18).build(), - ) - increaseDisplayScaleButton = addRenderableWidget( - Button.builder(Component.literal("+")) { - adjustDisplayScale(1) - }.bounds(scaleControlX + 70, scaleControlY, 20, 18).build(), - ) - refreshDisplayScaleButtons() } override fun tick() { @@ -122,6 +107,7 @@ class MatterIndexScreen( } override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (button == 0 && clickDisplayScaleControl(layout(), mouseX.toInt(), mouseY.toInt())) return true if (super.mouseClicked(mouseX, mouseY, button)) return true if (button != 0) return false @@ -197,10 +183,7 @@ class MatterIndexScreen( matterOnlyButton.render(guiGraphics, mouseX, mouseY, partialTick) replicableButton.render(guiGraphics, mouseX, mouseY, partialTick) decomposableButton.render(guiGraphics, mouseX, mouseY, partialTick) - decreaseDisplayScaleButton.render(guiGraphics, mouseX, mouseY, partialTick) - increaseDisplayScaleButton.render(guiGraphics, mouseX, mouseY, partialTick) - val label = MatterIndexDisplayScale.label(displayScalePercent) - guiGraphics.drawString(font, label, decreaseDisplayScaleButton.x + 35 - font.width(label) / 2, decreaseDisplayScaleButton.y + 5, 0xBFD0CA, false) + renderDisplayScaleControl(guiGraphics, layout(), mouseX, mouseY) } private fun renderIndex(guiGraphics: GuiGraphics, layout: Layout, visible: List, mouseX: Int, mouseY: Int) { @@ -226,14 +209,14 @@ class MatterIndexScreen( if (mouseX in columns.item.x + 8 until columns.item.x + 24 && mouseY in y + 5 until y + 21) { hoveredStack = entry.stack } - drawTrimmed(guiGraphics, entry.record.displayName, columns.item.x + 32, y + 9, columns.item.w - 38, 0xE6ECE8) - drawRight(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - 8, y + 9, 0xE6ECE8) + drawItemName(guiGraphics, entry.record.displayName, columns.item.x + 30, y + 9, columns.item.w - 34, 0xE6ECE8) + drawRight(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - TOTAL_VALUE_RIGHT_PADDING, y + 9, 0xE6ECE8) if (contains(Rect(columns.total.x, y, columns.total.w, ROW_H), mouseX, mouseY)) { setAmountTooltip("Total", entry.record.total) } for ((matterId, rect) in columns.matter) { val amount = entry.record.matterValues[matterId] ?: 0.0 - drawCentered(guiGraphics, formatAmount(amount), rect.x + rect.w / 2, y + 9, 0xE6ECE8) + drawTableMatterAmount(guiGraphics, formatAmount(amount), rect.x + rect.w / 2, y + 9, 0xE6ECE8) if (contains(Rect(rect.x, y, rect.w, ROW_H), mouseX, mouseY)) { setAmountTooltip(matterId, amount) } @@ -380,9 +363,9 @@ class MatterIndexScreen( private fun columns(layout: Layout): Columns { val rows = indexRows(layout) - val itemW = max(scaledPixels(180), (rows.w * 0.30).toInt()) - val totalW = scaledPixels(76) - val matterW = max(scaledPixels(42), (rows.w - itemW - totalW) / max(1, matterTypes.size)) + val itemW = max(scaledPixels(126), (rows.w * 0.22).toInt()) + val totalW = scaledPixels(52) + val matterW = max(tableMatterColumnMinWidth(), (rows.w - itemW - totalW) / max(1, matterTypes.size)) val matter = matterTypes.mapIndexed { index, id -> id to Rect(rows.x + itemW + totalW + index * matterW, rows.y - HEADER_H, matterW, HEADER_H) }.filter { it.second.x + it.second.w <= rows.x + rows.w } @@ -478,9 +461,20 @@ class MatterIndexScreen( private fun drawVerticalLine(guiGraphics: GuiGraphics, x: Int, y1: Int, y2: Int, color: Int) = guiGraphics.fill(x, y1, x + 1, y2, color) - private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) { + private fun drawItemName(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) = + drawTrimmed(guiGraphics, text, x, y, maxWidth, color, itemNameScale()) + + private fun drawTableMatterAmount(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) { + val scale = tableMatterAmountScale() + drawScaled(guiGraphics, text, centerX - scaledWidth(text, scale) / 2, y, color, false, scale) + } + + private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) = + drawTrimmed(guiGraphics, text, x, y, maxWidth, color, displayScale()) + + private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int, scale: Float) { if (maxWidth <= 0) return - val logicalMaxWidth = max(1, (maxWidth / displayScale()).toInt()) + val logicalMaxWidth = max(1, (maxWidth / scale).toInt()) val ellipsis = "..." val rendered = if (font.width(text) <= logicalMaxWidth) { @@ -490,7 +484,7 @@ class MatterIndexScreen( } else { "${font.plainSubstrByWidth(text, logicalMaxWidth - font.width(ellipsis))}$ellipsis" } - drawScaled(guiGraphics, rendered, x, y, color, false) + drawScaled(guiGraphics, rendered, x, y, color, false, scale) } private fun drawRight(guiGraphics: GuiGraphics, text: String, rightX: Int, y: Int, color: Int) = @@ -499,8 +493,10 @@ class MatterIndexScreen( private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) = drawScaled(guiGraphics, text, centerX - scaledWidth(text) / 2, y, color, false) - private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean) { - val scale = displayScale() + private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean) = + drawScaled(guiGraphics, text, x, y, color, shadow, displayScale()) + + private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean, scale: Float) { if (scale == 1f) { guiGraphics.drawString(font, text, x, y, color, shadow) return @@ -513,7 +509,10 @@ class MatterIndexScreen( } private fun scaledWidth(text: String): Int = - ceil(font.width(text) * displayScale()).toInt() + scaledWidth(text, displayScale()) + + private fun scaledWidth(text: String, scale: Float): Int = + ceil(font.width(text) * scale).toInt() private fun scaledPixels(value: Int): Int = MatterIndexDisplayScale.scalePixels(value, displayScalePercent) @@ -521,15 +520,61 @@ class MatterIndexScreen( private fun displayScale(): Float = MatterIndexDisplayScale.toScale(displayScalePercent) + private fun itemNameScale(): Float = + displayScale() * ITEM_NAME_SCALE + + private fun tableMatterAmountScale(): Float = + displayScale() * AMOUNT_SCALE + + private fun tableMatterColumnMinWidth(): Int = + scaledWidth(MATTER_AMOUNT_WIDTH_SAMPLE, tableMatterAmountScale()) + MATTER_AMOUNT_HORIZONTAL_PADDING + private fun adjustDisplayScale(steps: Int) { displayScalePercent = MatterIndexDisplayScale.adjust(displayScalePercent, steps) - refreshDisplayScaleButtons() } - private fun refreshDisplayScaleButtons() { - if (!::decreaseDisplayScaleButton.isInitialized || !::increaseDisplayScaleButton.isInitialized) return - decreaseDisplayScaleButton.active = displayScalePercent > MatterIndexDisplayScale.MIN_PERCENT - increaseDisplayScaleButton.active = displayScalePercent < MatterIndexDisplayScale.MAX_PERCENT + private fun renderDisplayScaleControl(guiGraphics: GuiGraphics, layout: Layout, mouseX: Int, mouseY: Int) { + val control = displayScaleControl(layout) + drawScaleButton(guiGraphics, control.minus, "-", displayScalePercent > MatterIndexDisplayScale.MIN_PERCENT, contains(control.minus, mouseX, mouseY)) + drawScaleButton(guiGraphics, control.plus, "+", displayScalePercent < MatterIndexDisplayScale.MAX_PERCENT, contains(control.plus, mouseX, mouseY)) + val label = MatterIndexDisplayScale.label(displayScalePercent) + guiGraphics.drawString(font, label, control.label.x + (control.label.w - font.width(label)) / 2, control.label.y + 4, 0xBFD0CA, false) + } + + private fun drawScaleButton(guiGraphics: GuiGraphics, rect: Rect, text: String, enabled: Boolean, hovered: Boolean) { + val fill = + when { + !enabled -> 0x33151918 + hovered -> 0x6636D84A + else -> 0x44151918 + } + val color = if (enabled) 0xDDEFE7 else 0x66706F6A + guiGraphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, fill) + drawBorder(guiGraphics, rect, if (enabled) 0x8841514D.toInt() else 0x4441514D) + guiGraphics.drawString(font, text, rect.x + (rect.w - font.width(text)) / 2, rect.y + 4, color, false) + } + + private fun clickDisplayScaleControl(layout: Layout, mouseX: Int, mouseY: Int): Boolean { + val control = displayScaleControl(layout) + if (contains(control.minus, mouseX, mouseY)) { + adjustDisplayScale(-1) + return true + } + if (contains(control.plus, mouseX, mouseY)) { + adjustDisplayScale(1) + return true + } + return false + } + + private fun displayScaleControl(layout: Layout): DisplayScaleControl { + val x = max(layout.index.x + 16, layout.index.x + layout.index.w - 82) + val y = layout.index.y + 11 + return DisplayScaleControl( + minus = Rect(x, y, 16, 16), + label = Rect(x + 18, y, 44, 16), + plus = Rect(x + 64, y, 16, 16), + ) } private fun headerText(text: String, column: String): String = @@ -558,6 +603,11 @@ class MatterIndexScreen( companion object { private const val HEADER_H = 30 private const val ROW_H = 26 + private const val ITEM_NAME_SCALE = 0.90f + private const val AMOUNT_SCALE = 0.80f + private const val MATTER_AMOUNT_WIDTH_SAMPLE = "88.8Qa" + private const val MATTER_AMOUNT_HORIZONTAL_PADDING = 8 + private const val TOTAL_VALUE_RIGHT_PADDING = 8 private const val SLOT_SIZE = 28 private const val SLOT_STEP = 34 @@ -607,6 +657,12 @@ class MatterIndexScreen( val endX: Int, ) + private data class DisplayScaleControl( + val minus: Rect, + val label: Rect, + val plus: Rect, + ) + private data class Rect( val x: Int, val y: Int, diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt index fcee9d2..ea450b5 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -3,12 +3,7 @@ package com.p_nsk.replicated_integration.client import com.buuz135.replication.Replication import com.buuz135.replication.calculation.MatterCompound import com.buuz135.replication.calculation.client.ClientReplicationCalculation -import com.p_nsk.replicated_integration.api.client.MatterAmountFormatter -import com.p_nsk.replicated_integration.api.client.MatterIndexFiltering -import com.p_nsk.replicated_integration.api.client.MatterIndexRecord -import com.p_nsk.replicated_integration.api.client.MatterIndexSort -import com.p_nsk.replicated_integration.api.client.MatterIndexSortDirection -import com.p_nsk.replicated_integration.api.client.MatterIndexDisplayScale +import com.p_nsk.replicated_integration.api.client.* import com.p_nsk.replicated_integration.config.NeoUiConfig import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.components.Button @@ -49,8 +44,6 @@ class MatterIndexScreen( private lateinit var matterOnlyButton: Button private lateinit var replicableButton: Button private lateinit var decomposableButton: Button - private lateinit var decreaseDisplayScaleButton: Button - private lateinit var increaseDisplayScaleButton: Button override fun init() { val layout = layout() @@ -81,19 +74,6 @@ class MatterIndexScreen( indexScroll = 0 }.bounds(replicableButton.x + replicableButton.width + 4, searchBox.y, 34, 18).build(), ) - val scaleControlX = max(layout.index.x + 16, layout.index.x + layout.index.w - 94) - val scaleControlY = layout.index.y + 10 - decreaseDisplayScaleButton = addRenderableWidget( - Button.builder(Component.literal("-")) { - adjustDisplayScale(-1) - }.bounds(scaleControlX, scaleControlY, 20, 18).build(), - ) - increaseDisplayScaleButton = addRenderableWidget( - Button.builder(Component.literal("+")) { - adjustDisplayScale(1) - }.bounds(scaleControlX + 70, scaleControlY, 20, 18).build(), - ) - refreshDisplayScaleButtons() } override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { @@ -123,6 +103,7 @@ class MatterIndexScreen( } override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (button == 0 && clickDisplayScaleControl(layout(), mouseX.toInt(), mouseY.toInt())) return true if (super.mouseClicked(mouseX, mouseY, button)) return true if (button != 0) return false @@ -198,10 +179,7 @@ class MatterIndexScreen( matterOnlyButton.render(guiGraphics, mouseX, mouseY, partialTick) replicableButton.render(guiGraphics, mouseX, mouseY, partialTick) decomposableButton.render(guiGraphics, mouseX, mouseY, partialTick) - decreaseDisplayScaleButton.render(guiGraphics, mouseX, mouseY, partialTick) - increaseDisplayScaleButton.render(guiGraphics, mouseX, mouseY, partialTick) - val label = MatterIndexDisplayScale.label(displayScalePercent) - guiGraphics.drawString(font, label, decreaseDisplayScaleButton.x + 35 - font.width(label) / 2, decreaseDisplayScaleButton.y + 5, 0xBFD0CA, false) + renderDisplayScaleControl(guiGraphics, layout(), mouseX, mouseY) } private fun renderIndex(guiGraphics: GuiGraphics, layout: Layout, visible: List, mouseX: Int, mouseY: Int) { @@ -227,14 +205,14 @@ class MatterIndexScreen( if (mouseX in columns.item.x + 8 until columns.item.x + 24 && mouseY in y + 5 until y + 21) { hoveredStack = entry.stack } - drawTrimmed(guiGraphics, entry.record.displayName, columns.item.x + 32, y + 9, columns.item.w - 38, 0xE6ECE8) - drawRight(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - 8, y + 9, 0xE6ECE8) + drawItemName(guiGraphics, entry.record.displayName, columns.item.x + 30, y + 9, columns.item.w - 34, 0xE6ECE8) + drawRight(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - TOTAL_VALUE_RIGHT_PADDING, y + 9, 0xE6ECE8) if (contains(Rect(columns.total.x, y, columns.total.w, ROW_H), mouseX, mouseY)) { setAmountTooltip("Total", entry.record.total) } for ((matterId, rect) in columns.matter) { val amount = entry.record.matterValues[matterId] ?: 0.0 - drawCentered(guiGraphics, formatAmount(amount), rect.x + rect.w / 2, y + 9, 0xE6ECE8) + drawTableMatterAmount(guiGraphics, formatAmount(amount), rect.x + rect.w / 2, y + 9, 0xE6ECE8) if (contains(Rect(rect.x, y, rect.w, ROW_H), mouseX, mouseY)) { setAmountTooltip(matterId, amount) } @@ -276,6 +254,7 @@ class MatterIndexScreen( if (mouseX in slotX until slotX + 54 && mouseY in slotY until slotY + 54) { hoveredStack = selected.stack } + drawTrimmed(guiGraphics, selected.record.displayName, slotX + 70, slotY + 4, layout.focus.w - 100, 0xE6ECE8) drawTrimmed(guiGraphics, selected.record.itemId, slotX + 70, slotY + 18, layout.focus.w - 100, 0x94A29C) @@ -381,9 +360,9 @@ class MatterIndexScreen( private fun columns(layout: Layout): Columns { val rows = indexRows(layout) - val itemW = max(scaledPixels(180), (rows.w * 0.30).toInt()) - val totalW = scaledPixels(76) - val matterW = max(scaledPixels(42), (rows.w - itemW - totalW) / max(1, matterTypes.size)) + val itemW = max(scaledPixels(126), (rows.w * 0.22).toInt()) + val totalW = scaledPixels(52) + val matterW = max(tableMatterColumnMinWidth(), (rows.w - itemW - totalW) / max(1, matterTypes.size)) val matter = matterTypes.mapIndexed { index, id -> id to Rect(rows.x + itemW + totalW + index * matterW, rows.y - HEADER_H, matterW, HEADER_H) }.filter { it.second.x + it.second.w <= rows.x + rows.w } @@ -479,9 +458,20 @@ class MatterIndexScreen( private fun drawVerticalLine(guiGraphics: GuiGraphics, x: Int, y1: Int, y2: Int, color: Int) = guiGraphics.fill(x, y1, x + 1, y2, color) - private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) { + private fun drawItemName(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) = + drawTrimmed(guiGraphics, text, x, y, maxWidth, color, itemNameScale()) + + private fun drawTableMatterAmount(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) { + val scale = tableMatterAmountScale() + drawScaled(guiGraphics, text, centerX - scaledWidth(text, scale) / 2, y, color, false, scale) + } + + private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) = + drawTrimmed(guiGraphics, text, x, y, maxWidth, color, displayScale()) + + private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int, scale: Float) { if (maxWidth <= 0) return - val logicalMaxWidth = max(1, (maxWidth / displayScale()).toInt()) + val logicalMaxWidth = max(1, (maxWidth / scale).toInt()) val ellipsis = "..." val rendered = if (font.width(text) <= logicalMaxWidth) { @@ -491,7 +481,7 @@ class MatterIndexScreen( } else { "${font.plainSubstrByWidth(text, logicalMaxWidth - font.width(ellipsis))}$ellipsis" } - drawScaled(guiGraphics, rendered, x, y, color, false) + drawScaled(guiGraphics, rendered, x, y, color, false, scale) } private fun drawRight(guiGraphics: GuiGraphics, text: String, rightX: Int, y: Int, color: Int) = @@ -500,8 +490,10 @@ class MatterIndexScreen( private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) = drawScaled(guiGraphics, text, centerX - scaledWidth(text) / 2, y, color, false) - private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean) { - val scale = displayScale() + private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean) = + drawScaled(guiGraphics, text, x, y, color, shadow, displayScale()) + + private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean, scale: Float) { if (scale == 1f) { guiGraphics.drawString(font, text, x, y, color, shadow) return @@ -514,7 +506,10 @@ class MatterIndexScreen( } private fun scaledWidth(text: String): Int = - ceil(font.width(text) * displayScale()).toInt() + scaledWidth(text, displayScale()) + + private fun scaledWidth(text: String, scale: Float): Int = + ceil(font.width(text) * scale).toInt() private fun scaledPixels(value: Int): Int = MatterIndexDisplayScale.scalePixels(value, displayScalePercent) @@ -522,15 +517,61 @@ class MatterIndexScreen( private fun displayScale(): Float = MatterIndexDisplayScale.toScale(displayScalePercent) + private fun itemNameScale(): Float = + displayScale() * ITEM_NAME_SCALE + + private fun tableMatterAmountScale(): Float = + displayScale() * AMOUNT_SCALE + + private fun tableMatterColumnMinWidth(): Int = + scaledWidth(MATTER_AMOUNT_WIDTH_SAMPLE, tableMatterAmountScale()) + MATTER_AMOUNT_HORIZONTAL_PADDING + private fun adjustDisplayScale(steps: Int) { displayScalePercent = MatterIndexDisplayScale.adjust(displayScalePercent, steps) - refreshDisplayScaleButtons() } - private fun refreshDisplayScaleButtons() { - if (!::decreaseDisplayScaleButton.isInitialized || !::increaseDisplayScaleButton.isInitialized) return - decreaseDisplayScaleButton.active = displayScalePercent > MatterIndexDisplayScale.MIN_PERCENT - increaseDisplayScaleButton.active = displayScalePercent < MatterIndexDisplayScale.MAX_PERCENT + private fun renderDisplayScaleControl(guiGraphics: GuiGraphics, layout: Layout, mouseX: Int, mouseY: Int) { + val control = displayScaleControl(layout) + drawScaleButton(guiGraphics, control.minus, "-", displayScalePercent > MatterIndexDisplayScale.MIN_PERCENT, contains(control.minus, mouseX, mouseY)) + drawScaleButton(guiGraphics, control.plus, "+", displayScalePercent < MatterIndexDisplayScale.MAX_PERCENT, contains(control.plus, mouseX, mouseY)) + val label = MatterIndexDisplayScale.label(displayScalePercent) + guiGraphics.drawString(font, label, control.label.x + (control.label.w - font.width(label)) / 2, control.label.y + 4, 0xBFD0CA, false) + } + + private fun drawScaleButton(guiGraphics: GuiGraphics, rect: Rect, text: String, enabled: Boolean, hovered: Boolean) { + val fill = + when { + !enabled -> 0x33151918 + hovered -> 0x6636D84A + else -> 0x44151918 + } + val color = if (enabled) 0xDDEFE7 else 0x66706F6A + guiGraphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, fill) + drawBorder(guiGraphics, rect, if (enabled) 0x8841514D.toInt() else 0x4441514D) + guiGraphics.drawString(font, text, rect.x + (rect.w - font.width(text)) / 2, rect.y + 4, color, false) + } + + private fun clickDisplayScaleControl(layout: Layout, mouseX: Int, mouseY: Int): Boolean { + val control = displayScaleControl(layout) + if (contains(control.minus, mouseX, mouseY)) { + adjustDisplayScale(-1) + return true + } + if (contains(control.plus, mouseX, mouseY)) { + adjustDisplayScale(1) + return true + } + return false + } + + private fun displayScaleControl(layout: Layout): DisplayScaleControl { + val x = max(layout.index.x + 16, layout.index.x + layout.index.w - 82) + val y = layout.index.y + 11 + return DisplayScaleControl( + minus = Rect(x, y, 16, 16), + label = Rect(x + 18, y, 44, 16), + plus = Rect(x + 64, y, 16, 16), + ) } private fun headerText(text: String, column: String): String = @@ -559,6 +600,11 @@ class MatterIndexScreen( companion object { private const val HEADER_H = 30 private const val ROW_H = 26 + private const val ITEM_NAME_SCALE = 0.90f + private const val AMOUNT_SCALE = 0.80f + private const val MATTER_AMOUNT_WIDTH_SAMPLE = "88.8Qa" + private const val MATTER_AMOUNT_HORIZONTAL_PADDING = 8 + private const val TOTAL_VALUE_RIGHT_PADDING = 8 private const val SLOT_SIZE = 28 private const val SLOT_STEP = 34 @@ -608,6 +654,12 @@ class MatterIndexScreen( val endX: Int, ) + private data class DisplayScaleControl( + val minus: Rect, + val label: Rect, + val plus: Rect, + ) + private data class Rect( val x: Int, val y: Int, From aa82542269eadc786123ef9a8aad9055c671ab69 Mon Sep 17 00:00:00 2001 From: P-nutsK <107553542+P-nutsK@users.noreply.github.com> Date: Thu, 21 May 2026 21:47:45 +0900 Subject: [PATCH 04/12] =?UTF-8?q?wip=20=E8=AA=BF=E6=95=B4=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/MatterIndexClientEvents.kt | 62 +++- .../client/MatterIndexScreen.kt | 271 ++++++++++++++++-- .../ReplicatedIntegrationLanguageProvider.kt | 4 + .../p_nsk/replicated_integration/ModMain.kt | 4 + .../client/MatterIndexClientEvents.kt | 57 +++- .../client/MatterIndexScreen.kt | 271 ++++++++++++++++-- ...eoReplicatedIntegrationLanguageProvider.kt | 4 + 7 files changed, 613 insertions(+), 60 deletions(-) diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt index 0bd777f..344d7c9 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt @@ -1,8 +1,10 @@ package com.p_nsk.replicated_integration.client +import com.mojang.blaze3d.platform.InputConstants import com.p_nsk.replicated_integration.Constants import com.p_nsk.replicated_integration.mixin.ScreenAccessor import net.minecraft.ChatFormatting +import net.minecraft.client.KeyMapping import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.components.AbstractButton @@ -12,18 +14,19 @@ import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraftforge.api.distmarker.Dist +import net.minecraftforge.client.event.RegisterKeyMappingsEvent import net.minecraftforge.client.event.ScreenEvent +import net.minecraftforge.client.settings.KeyConflictContext import net.minecraftforge.event.TickEvent import net.minecraftforge.event.entity.player.ItemTooltipEvent import net.minecraftforge.eventbus.api.SubscribeEvent import net.minecraftforge.fml.common.Mod -import org.lwjgl.glfw.GLFW import kotlin.math.max import kotlin.math.min @Mod.EventBusSubscriber(modid = Constants.MOD_ID, value = [Dist.CLIENT], bus = Mod.EventBusSubscriber.Bus.FORGE) object MatterIndexClientEvents { - private const val HOLD_TICKS = 20 + private const val HOLD_TICKS = 10 private var hoveredStack: ItemStack = ItemStack.EMPTY private var trackingStack: ItemStack = ItemStack.EMPTY @@ -48,9 +51,9 @@ object MatterIndexClientEvents { val progress = holdTicks.toFloat() / HOLD_TICKS.toFloat() val line = if (progress <= 0f) { - Component.literal("Hold W to open Replication Index").withStyle(ChatFormatting.DARK_GRAY) + holdHint() } else { - Component.literal(progressBar(progress)).withStyle(ChatFormatting.GRAY) + progressBar(progress, holdHint()) } if (event.toolTip.size < 2) { event.toolTip.add(line) @@ -70,8 +73,7 @@ object MatterIndexClientEvents { return } - val window = minecraft.window.window - val holding = !hoveredStack.isEmpty && GLFW.glfwGetKey(window, GLFW.GLFW_KEY_W) == GLFW.GLFW_PRESS + val holding = !hoveredStack.isEmpty && MatterIndexKeyMappings.isOpenIndexDown(minecraft.window.window) holdTicks = if (holding) { min(HOLD_TICKS, holdTicks + 1) @@ -106,12 +108,26 @@ object MatterIndexClientEvents { holdTicks = 0 } - private fun progressBar(progress: Float): String { - val total = 18 - val current = (progress.coerceIn(0f, 1f) * total).toInt() - return "|".repeat(current) + ChatFormatting.DARK_GRAY + "|".repeat(total - current) + private fun progressBar(progress: Float, hint: Component): Component { + val total = max(26, Minecraft.getInstance().font.width(hint) / Minecraft.getInstance().font.width("|")) + val easedProgress = easeIn(progress.coerceIn(0f, 1f)) + val current = (easedProgress * total).toInt() + return Component.literal("|".repeat(current)) + .withStyle(ChatFormatting.GRAY) + .append(Component.literal("|".repeat(total - current)).withStyle(ChatFormatting.DARK_GRAY)) } + private fun easeIn(progress: Float): Float = + progress * progress + + private fun holdHint(): Component = + Component.literal("Hold ") + .withStyle(ChatFormatting.DARK_GRAY) + .append(Component.literal("[").withStyle(ChatFormatting.GRAY)) + .append(MatterIndexKeyMappings.openIndex.translatedKeyMessage.copy().withStyle(ChatFormatting.GRAY)) + .append(Component.literal("]").withStyle(ChatFormatting.GRAY)) + .append(Component.literal(" to open Replication Index").withStyle(ChatFormatting.DARK_GRAY)) + private class MatterIndexSidebarButton( private val owner: Screen, ) : AbstractButton(2, 2, 22, 22, Component.literal("Replication Index")) { @@ -144,3 +160,29 @@ object MatterIndexClientEvents { } } } + +object MatterIndexKeyMappings { + val openIndex: KeyMapping = + KeyMapping( + "key.replicated_integration.open_replication_index", + KeyConflictContext.GUI, + InputConstants.Type.KEYSYM, + InputConstants.KEY_W, + "key.categories.replicated_integration", + ) + + fun isOpenIndexDown(window: Long): Boolean { + if (openIndex.isUnbound) return false + val key = openIndex.key + return InputConstants.isKeyDown(window, key.value) + } +} + +@Mod.EventBusSubscriber(modid = Constants.MOD_ID, value = [Dist.CLIENT], bus = Mod.EventBusSubscriber.Bus.MOD) +object MatterIndexClientModEvents { + @JvmStatic + @SubscribeEvent + fun onRegisterKeyMappings(event: RegisterKeyMappingsEvent) { + event.register(MatterIndexKeyMappings.openIndex) + } +} diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt index 14291b1..84f7005 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -13,6 +13,7 @@ import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack +import org.lwjgl.glfw.GLFW import kotlin.math.ceil import kotlin.math.max import kotlin.math.min @@ -39,6 +40,7 @@ class MatterIndexScreen( private var displayScalePercent = MatterIndexDisplayScale.DEFAULT_PERCENT private var hoveredStack: ItemStack = ItemStack.EMPTY private var hoveredAmountTooltip: List = emptyList() + private var draggingScrollbar: DraggingScrollbar? = null private lateinit var searchBox: EditBox private lateinit var matterOnlyButton: Button @@ -113,6 +115,7 @@ class MatterIndexScreen( val layout = layout() val visible = visibleEntries() + if (startScrollbarDrag(layout, visible, mouseX.toInt(), mouseY.toInt())) return true if (clickHeader(layout, mouseX.toInt(), mouseY.toInt())) return true val rowArea = indexRows(layout) @@ -134,6 +137,19 @@ class MatterIndexScreen( return false } + override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, dragX: Double, dragY: Double): Boolean { + if (button == 0 && dragScrollbar(mouseY.toInt())) return true + return super.mouseDragged(mouseX, mouseY, button, dragX, dragY) + } + + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (button == 0 && draggingScrollbar != null) { + draggingScrollbar = null + return true + } + return super.mouseReleased(mouseX, mouseY, button) + } + override fun mouseScrolled(mouseX: Double, mouseY: Double, delta: Double): Boolean { val layout = layout() if (contains(indexRows(layout), mouseX.toInt(), mouseY.toInt())) { @@ -153,6 +169,12 @@ class MatterIndexScreen( } override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (keyCode == GLFW.GLFW_KEY_UP) { + return moveSelection(-1) + } + if (keyCode == GLFW.GLFW_KEY_DOWN) { + return moveSelection(1) + } if (searchBox.keyPressed(keyCode, scanCode, modifiers)) { indexScroll = 0 return true @@ -210,7 +232,7 @@ class MatterIndexScreen( hoveredStack = entry.stack } drawItemName(guiGraphics, entry.record.displayName, columns.item.x + 30, y + 9, columns.item.w - 34, 0xE6ECE8) - drawRight(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - TOTAL_VALUE_RIGHT_PADDING, y + 9, 0xE6ECE8) + drawTableTotalAmount(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - TOTAL_VALUE_RIGHT_PADDING, y + 9, 0xE6ECE8) if (contains(Rect(columns.total.x, y, columns.total.w, ROW_H), mouseX, mouseY)) { setAmountTooltip("Total", entry.record.total) } @@ -261,22 +283,20 @@ class MatterIndexScreen( drawTrimmed(guiGraphics, selected.record.displayName, slotX + 70, slotY + 4, layout.focus.w - 100, 0xE6ECE8) drawTrimmed(guiGraphics, selected.record.itemId, slotX + 70, slotY + 18, layout.focus.w - 100, 0x94A29C) - val matterStartX = slotX + 70 - val matterStartY = slotY + 36 + val focusMatter = focusMatterLayout(layout, slotX, slotY) for ((index, matterId) in matterTypes.withIndex()) { - val x = matterStartX + (index % 8) * 48 - val y = matterStartY + (index / 8) * 24 - if (x + 42 > layout.focus.x + layout.focus.w - 8) continue - drawSlot(guiGraphics, x, y, 20, false) - renderMatterIcon(guiGraphics, matterId, x + 2, y + 2) + val column = index % focusMatter.columns + val row = index / focusMatter.columns + val x = focusMatter.x + column * focusMatter.cellW + val y = focusMatter.y + row * FOCUS_MATTER_ROW_H val amount = selected.record.matterValues[matterId] ?: 0.0 - drawCentered(guiGraphics, formatAmount(amount), x + 10, y + 22, 0xE6ECE8) - if (mouseX in x until x + 42 && mouseY in y until y + 34) { + drawFocusMatterAmount(guiGraphics, matterId, formatAmount(amount), x, y, focusMatter.cellW, focusMatterAmountColor(amount)) + if (mouseX in x until x + focusMatter.cellW && mouseY in y until y + FOCUS_MATTER_ROW_H) { setAmountTooltip(matterId, amount) } } - val badgeY = slotY + 94 + val badgeY = max(slotY + 82, focusMatter.y + focusMatter.rows * FOCUS_MATTER_ROW_H + 6) drawBadge(guiGraphics, slotX, badgeY, "Replicable") drawBadge(guiGraphics, slotX + scaledPixels(94), badgeY, "Decomposable") drawDivider(guiGraphics, layout.focus.x + 12, badgeY + 32, layout.focus.w - 24) @@ -345,6 +365,110 @@ class MatterIndexScreen( return equivalentEntries().getOrNull((equivalentScroll + row) * columns + column) } + private fun startScrollbarDrag(layout: Layout, visible: List, mouseX: Int, mouseY: Int): Boolean { + val bar = scrollbarAt(layout, visible, mouseX, mouseY) ?: return false + val thumb = scrollbarThumb(bar) ?: return false + val thumbOffset = + if (contains(thumb.inflate(SCROLLBAR_HIT_PAD), mouseX, mouseY)) { + mouseY - thumb.y + } else { + thumb.h / 2 + } + draggingScrollbar = DraggingScrollbar(bar.target, thumbOffset) + setScrollbarFromThumbTop(bar, mouseY - thumbOffset) + return true + } + + private fun dragScrollbar(mouseY: Int): Boolean { + val dragging = draggingScrollbar ?: return false + val bar = scrollbarForTarget(layout(), visibleEntries(), dragging.target) ?: return false + setScrollbarFromThumbTop(bar, mouseY - dragging.thumbOffset) + return true + } + + private fun scrollbarAt(layout: Layout, visible: List, mouseX: Int, mouseY: Int): Scrollbar? = + listOfNotNull( + scrollbarForTarget(layout, visible, ScrollbarTarget.INDEX), + scrollbarForTarget(layout, visible, ScrollbarTarget.EQUIVALENT), + ).firstOrNull { contains(scrollbarHitRect(it), mouseX, mouseY) && it.total > it.visible } + + private fun scrollbarForTarget(layout: Layout, visible: List, target: ScrollbarTarget): Scrollbar? = + when (target) { + ScrollbarTarget.INDEX -> { + val rows = indexRows(layout) + Scrollbar( + target = target, + x = rows.x + rows.w - 5, + y = rows.y, + h = rows.h, + total = visible.size, + visible = max(1, rows.h / ROW_H), + scroll = indexScroll, + ) + } + ScrollbarTarget.EQUIVALENT -> { + val rect = equivalentRect(layout) + val columns = equivalentColumns(layout) + Scrollbar( + target = target, + x = rect.x + rect.w - 5, + y = rect.y, + h = rect.h, + total = (equivalentEntries().size + columns - 1) / columns, + visible = max(1, rect.h / SLOT_STEP), + scroll = equivalentScroll, + ) + } + } + + private fun scrollbarHitRect(bar: Scrollbar): Rect = + Rect(bar.x - SCROLLBAR_HIT_PAD, bar.y, SCROLLBAR_W + SCROLLBAR_HIT_PAD * 2, bar.h) + + private fun scrollbarThumb(bar: Scrollbar): Rect? { + if (bar.total <= bar.visible) return null + val thumbH = max(16, bar.h * bar.visible / bar.total) + val thumbY = bar.y + ((bar.h - thumbH) * bar.scroll / max(1, bar.total - bar.visible)) + return Rect(bar.x, thumbY, SCROLLBAR_W, thumbH) + } + + private fun setScrollbarFromThumbTop(bar: Scrollbar, thumbTop: Int) { + val thumb = scrollbarThumb(bar) ?: return + val maxScroll = max(0, bar.total - bar.visible) + val track = max(1, bar.h - thumb.h) + val local = (thumbTop - bar.y).coerceIn(0, track) + val scroll = ((local * maxScroll.toFloat()) / track.toFloat()).toInt().coerceIn(0, maxScroll) + when (bar.target) { + ScrollbarTarget.INDEX -> indexScroll = scroll + ScrollbarTarget.EQUIVALENT -> equivalentScroll = scroll + } + } + + private fun moveSelection(delta: Int): Boolean { + val visible = visibleEntries() + if (visible.isEmpty()) return false + val current = visible.indexOfFirst { it.record.itemId == selectedItemId } + val target = + if (current < 0) { + if (delta < 0) visible.lastIndex else 0 + } else { + (current + delta).coerceIn(0, visible.lastIndex) + } + selectedItemId = visible[target].record.itemId + equivalentScroll = 0 + keepSelectionVisible(target, visible.size) + return true + } + + private fun keepSelectionVisible(index: Int, total: Int) { + val visibleRows = max(1, indexRows(layout()).h / ROW_H) + indexScroll = + when { + index < indexScroll -> index + index >= indexScroll + visibleRows -> index - visibleRows + 1 + else -> indexScroll + }.coerceIn(0, max(0, total - visibleRows)) + } + private fun visibleEntries(): List { val query = MatterIndexFiltering.parseQuery(searchBox.value, matterOnly, replicableOnly, decomposableOnly) val ordered = MatterIndexFiltering.filterAndSort(entries.map { it.record }, query, sort) @@ -363,7 +487,7 @@ class MatterIndexScreen( private fun columns(layout: Layout): Columns { val rows = indexRows(layout) - val itemW = max(scaledPixels(126), (rows.w * 0.22).toInt()) + val itemW = max(scaledPixels(126), (rows.w * ITEM_COLUMN_WIDTH_RATIO).toInt()) val totalW = scaledPixels(52) val matterW = max(tableMatterColumnMinWidth(), (rows.w - itemW - totalW) / max(1, matterTypes.size)) val matter = matterTypes.mapIndexed { index, id -> @@ -382,7 +506,9 @@ class MatterIndexScreen( val gap = 6 val panelH = height - margin * 2 val totalW = width - margin * 2 - gap - val indexW = (totalW * 0.60).toInt() + val defaultIndexW = (totalW * 0.60).toInt() + val maxIndexW = max(0, totalW - FOCUS_MIN_W) + val indexW = min(maxIndexW, max(defaultIndexW, requiredIndexPanelWidth())) val focusW = totalW - indexW return Layout( index = Rect(margin, margin, indexW, panelH), @@ -390,15 +516,45 @@ class MatterIndexScreen( ) } + private fun requiredIndexPanelWidth(): Int { + val matterCount = max(1, matterTypes.size) + val denominator = 1.0 - ITEM_COLUMN_WIDTH_RATIO + val fixedWidth = scaledPixels(52) + matterCount * tableMatterColumnMinWidth() + val rowsW = max(scaledPixels(126) + fixedWidth, ceil(fixedWidth / denominator).toInt()) + return rowsW + INDEX_HORIZONTAL_PADDING + } + private fun indexRows(layout: Layout): Rect = Rect(layout.index.x + 14, layout.index.y + 88, layout.index.w - 28, layout.index.h - 124) - private fun equivalentRect(layout: Layout): Rect = - Rect(layout.focus.x + 16, layout.focus.y + 204, layout.focus.w - 32, layout.focus.h - 224) + private fun equivalentRect(layout: Layout): Rect { + val y = focusEquivalentY(layout) + return Rect(layout.focus.x + 16, y, layout.focus.w - 32, max(0, layout.focus.y + layout.focus.h - y - 20)) + } private fun equivalentColumns(layout: Layout): Int = max(2, (equivalentRect(layout).w - 12) / SLOT_STEP) + private fun focusEquivalentY(layout: Layout): Int { + val focusMatter = focusMatterLayout(layout, layout.focus.x + 18, layout.focus.y + 42) + val badgeY = max(layout.focus.y + 124, focusMatter.y + focusMatter.rows * FOCUS_MATTER_ROW_H + 6) + return badgeY + 60 + } + + private fun focusMatterLayout(layout: Layout, slotX: Int, slotY: Int): FocusMatterLayout { + val x = slotX + 70 + val availableW = max(FOCUS_MATTER_MIN_CELL_W, layout.focus.x + layout.focus.w - 8 - x) + val columns = min(FOCUS_MATTER_COLUMNS, matterTypes.size.coerceAtLeast(1)) + val rows = (matterTypes.size + columns - 1) / columns + return FocusMatterLayout( + x = x, + y = slotY + 36, + cellW = availableW / columns, + columns = columns, + rows = rows, + ) + } + private fun renderPanel(guiGraphics: GuiGraphics, rect: Rect) { guiGraphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, 0xCC071010.toInt()) drawBorder(guiGraphics, rect, 0xFF41514D.toInt()) @@ -430,6 +586,15 @@ class MatterIndexScreen( guiGraphics.blit(ResourceLocation.fromNamespaceAndPath(Replication.MOD_ID, "textures/gui/mattertypes/${matterId.substringAfter(':')}.png"), x, y, 0f, 0f, 16, 16, 16, 16) } + private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int, size: Int) { + val scale = size / 16f + guiGraphics.pose().pushPose() + guiGraphics.pose().translate(x.toFloat(), y.toFloat(), 0f) + guiGraphics.pose().scale(scale, scale, 1f) + renderMatterIcon(guiGraphics, matterId, 0, 0) + guiGraphics.pose().popPose() + } + private fun drawRiMark(guiGraphics: GuiGraphics, x: Int, y: Int) { guiGraphics.fill(x + 5, y, x + 9, y + 4, 0xFF42E84F.toInt()) guiGraphics.fill(x, y + 5, x + 4, y + 9, 0xFF42E84F.toInt()) @@ -438,11 +603,10 @@ class MatterIndexScreen( } private fun drawScrollbar(guiGraphics: GuiGraphics, x: Int, y: Int, h: Int, total: Int, visible: Int, scroll: Int) { - if (total <= visible) return - guiGraphics.fill(x, y, x + 4, y + h, 0xAA050606.toInt()) - val thumbH = max(16, h * visible / total) - val thumbY = y + ((h - thumbH) * scroll / max(1, total - visible)) - guiGraphics.fill(x, thumbY, x + 4, thumbY + thumbH, 0xFF9EAAA5.toInt()) + val bar = Scrollbar(ScrollbarTarget.INDEX, x, y, h, total, visible, scroll) + val thumb = scrollbarThumb(bar) ?: return + guiGraphics.fill(x, y, x + SCROLLBAR_W, y + h, 0xAA050606.toInt()) + guiGraphics.fill(thumb.x, thumb.y, thumb.x + thumb.w, thumb.y + thumb.h, 0xFF9EAAA5.toInt()) } private fun drawDivider(guiGraphics: GuiGraphics, x: Int, y: Int, w: Int) = @@ -469,6 +633,28 @@ class MatterIndexScreen( drawScaled(guiGraphics, text, centerX - scaledWidth(text, scale) / 2, y, color, false, scale) } + private fun drawTableTotalAmount(guiGraphics: GuiGraphics, text: String, rightX: Int, y: Int, color: Int) { + val scale = tableMatterAmountScale() + drawScaled(guiGraphics, text, rightX - scaledWidth(text, scale), y, color, false, scale) + } + + private fun drawFocusMatterAmount(guiGraphics: GuiGraphics, matterId: String, text: String, x: Int, y: Int, cellW: Int, color: Int) { + val textMaxW = cellW - FOCUS_MATTER_ICON_SIZE - FOCUS_MATTER_VALUE_GAP + if (textMaxW <= 0) return + val scale = min(focusMatterAmountScale(), textMaxW.toFloat() / max(1, font.width(text))).coerceAtLeast(0.35f) + val textW = scaledWidth(text, scale) + val textH = ceil(font.lineHeight * scale).toInt() + val pairX = x + val iconY = y + (FOCUS_MATTER_ROW_H - FOCUS_MATTER_ICON_SIZE) / 2 + val textX = pairX + FOCUS_MATTER_ICON_SIZE + FOCUS_MATTER_VALUE_GAP + val textY = y + (FOCUS_MATTER_ROW_H - textH) / 2 + renderMatterIcon(guiGraphics, matterId, pairX, iconY, FOCUS_MATTER_ICON_SIZE) + drawScaled(guiGraphics, text, textX, textY, color, false, scale) + } + + private fun focusMatterAmountColor(amount: Double): Int = + if (amount == 0.0) 0x8EA09A else 0xE6ECE8 + private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) = drawTrimmed(guiGraphics, text, x, y, maxWidth, color, displayScale()) @@ -493,6 +679,9 @@ class MatterIndexScreen( private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) = drawScaled(guiGraphics, text, centerX - scaledWidth(text) / 2, y, color, false) + private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int, scale: Float) = + drawScaled(guiGraphics, text, centerX - scaledWidth(text, scale) / 2, y, color, false, scale) + private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean) = drawScaled(guiGraphics, text, x, y, color, shadow, displayScale()) @@ -526,6 +715,9 @@ class MatterIndexScreen( private fun tableMatterAmountScale(): Float = displayScale() * AMOUNT_SCALE + private fun focusMatterAmountScale(): Float = + displayScale() * FOCUS_MATTER_AMOUNT_SCALE + private fun tableMatterColumnMinWidth(): Int = scaledWidth(MATTER_AMOUNT_WIDTH_SAMPLE, tableMatterAmountScale()) + MATTER_AMOUNT_HORIZONTAL_PADDING @@ -603,11 +795,22 @@ class MatterIndexScreen( companion object { private const val HEADER_H = 30 private const val ROW_H = 26 + private const val FOCUS_MIN_W = 220 + private const val INDEX_HORIZONTAL_PADDING = 28 + private const val ITEM_COLUMN_WIDTH_RATIO = 0.22 private const val ITEM_NAME_SCALE = 0.90f private const val AMOUNT_SCALE = 0.80f private const val MATTER_AMOUNT_WIDTH_SAMPLE = "88.8Qa" private const val MATTER_AMOUNT_HORIZONTAL_PADDING = 8 private const val TOTAL_VALUE_RIGHT_PADDING = 8 + private const val FOCUS_MATTER_COLUMNS = 4 + private const val FOCUS_MATTER_MIN_CELL_W = 26 + private const val FOCUS_MATTER_ROW_H = 18 + private const val FOCUS_MATTER_ICON_SIZE = 8 + private const val FOCUS_MATTER_VALUE_GAP = 3 + private const val FOCUS_MATTER_AMOUNT_SCALE = 0.50f + private const val SCROLLBAR_W = 4 + private const val SCROLLBAR_HIT_PAD = 3 private const val SLOT_SIZE = 28 private const val SLOT_STEP = 34 @@ -663,6 +866,34 @@ class MatterIndexScreen( val plus: Rect, ) + private data class FocusMatterLayout( + val x: Int, + val y: Int, + val cellW: Int, + val columns: Int, + val rows: Int, + ) + + private enum class ScrollbarTarget { + INDEX, + EQUIVALENT, + } + + private data class DraggingScrollbar( + val target: ScrollbarTarget, + val thumbOffset: Int, + ) + + private data class Scrollbar( + val target: ScrollbarTarget, + val x: Int, + val y: Int, + val h: Int, + val total: Int, + val visible: Int, + val scroll: Int, + ) + private data class Rect( val x: Int, val y: Int, diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ReplicatedIntegrationLanguageProvider.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ReplicatedIntegrationLanguageProvider.kt index 9c9828e..1cd650d 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ReplicatedIntegrationLanguageProvider.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ReplicatedIntegrationLanguageProvider.kt @@ -18,6 +18,8 @@ class ReplicatedIntegrationLanguageProvider( private fun addEnglishTranslations() { add("replicated_integration.config.enable_nuclear_recipe", "Enable Mekanism nuclear recipe support") add("replicated_integration.config.notice.compatibility_summary_shown", "Compatibility summary notice shown") + add("key.categories.replicated_integration", "Replicated Integration") + add("key.replicated_integration.open_replication_index", "Open Replication Index") add("replicated_integration.chat.notice.title", "Replicated Integration") add( "replicated_integration.chat.notice.supported_integrations", @@ -36,6 +38,8 @@ class ReplicatedIntegrationLanguageProvider( private fun addJapaneseTranslations() { add("replicated_integration.config.enable_nuclear_recipe", "Mekanism の核分裂レシピ対応を有効化") add("replicated_integration.config.notice.compatibility_summary_shown", "互換性サマリー通知を表示済み") + add("key.categories.replicated_integration", "Replicated Integration") + add("key.replicated_integration.open_replication_index", "Replication Index を開く") add("replicated_integration.chat.notice.title", "Replicated Integration") add( "replicated_integration.chat.notice.supported_integrations", diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt index 84b4892..01f77b1 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/ModMain.kt @@ -3,6 +3,7 @@ package com.p_nsk.replicated_integration import com.p_nsk.replicated_integration.command.MatterCommand import com.p_nsk.replicated_integration.command.MatterNodeDebugCommand import com.p_nsk.replicated_integration.client.MatterIndexClientEvents +import com.p_nsk.replicated_integration.client.MatterIndexKeyMappings import com.p_nsk.replicated_integration.config.NeoCompatibilityConfig import com.p_nsk.replicated_integration.config.NeoUiConfig import com.p_nsk.replicated_integration.core.NeoBuiltinReplicationAddons @@ -50,6 +51,9 @@ object ModMain { val eventBus = requireNotNull(ModLoadingContext.get().activeContainer.eventBus) eventBus.addListener(::onRegisterPayloadHandlers) eventBus.addListener(NeoReplicatedIntegrationDataGen::gatherData) + if (FMLEnvironment.dist == Dist.CLIENT) { + eventBus.addListener(MatterIndexKeyMappings::register) + } } private fun onRegisterPayloadHandlers(event: RegisterPayloadHandlersEvent) { diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt index 64f5141..c6feceb 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexClientEvents.kt @@ -1,7 +1,9 @@ package com.p_nsk.replicated_integration.client +import com.mojang.blaze3d.platform.InputConstants import com.p_nsk.replicated_integration.mixin.neo.ScreenAccessor import net.minecraft.ChatFormatting +import net.minecraft.client.KeyMapping import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.components.AbstractButton @@ -12,14 +14,15 @@ import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.neoforged.bus.api.SubscribeEvent import net.neoforged.neoforge.client.event.ClientTickEvent +import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent import net.neoforged.neoforge.client.event.ScreenEvent +import net.neoforged.neoforge.client.settings.KeyConflictContext import net.neoforged.neoforge.event.entity.player.ItemTooltipEvent -import org.lwjgl.glfw.GLFW import kotlin.math.max import kotlin.math.min object MatterIndexClientEvents { - private const val HOLD_TICKS = 20 + private const val HOLD_TICKS = 10 private var hoveredStack: ItemStack = ItemStack.EMPTY private var trackingStack: ItemStack = ItemStack.EMPTY @@ -42,9 +45,9 @@ object MatterIndexClientEvents { val progress = holdTicks.toFloat() / HOLD_TICKS.toFloat() val line = if (progress <= 0f) { - Component.literal("Hold W to open Replication Index").withStyle(ChatFormatting.DARK_GRAY) + holdHint() } else { - Component.literal(progressBar(progress)).withStyle(ChatFormatting.GRAY) + progressBar(progress, holdHint()) } if (event.toolTip.size < 2) { event.toolTip.add(line) @@ -62,8 +65,7 @@ object MatterIndexClientEvents { return } - val window = minecraft.window.window - val holding = !hoveredStack.isEmpty && GLFW.glfwGetKey(window, GLFW.GLFW_KEY_W) == GLFW.GLFW_PRESS + val holding = !hoveredStack.isEmpty && MatterIndexKeyMappings.isOpenIndexDown(minecraft.window.window) holdTicks = if (holding) { min(HOLD_TICKS, holdTicks + 1) @@ -98,12 +100,26 @@ object MatterIndexClientEvents { holdTicks = 0 } - private fun progressBar(progress: Float): String { - val total = 18 - val current = (progress.coerceIn(0f, 1f) * total).toInt() - return "|".repeat(current) + ChatFormatting.DARK_GRAY + "|".repeat(total - current) + private fun progressBar(progress: Float, hint: Component): Component { + val total = max(26, Minecraft.getInstance().font.width(hint) / Minecraft.getInstance().font.width("|")) + val easedProgress = easeIn(progress.coerceIn(0f, 1f)) + val current = (easedProgress * total).toInt() + return Component.literal("|".repeat(current)) + .withStyle(ChatFormatting.GRAY) + .append(Component.literal("|".repeat(total - current)).withStyle(ChatFormatting.DARK_GRAY)) } + private fun easeIn(progress: Float): Float = + progress * progress + + private fun holdHint(): Component = + Component.literal("Hold ") + .withStyle(ChatFormatting.DARK_GRAY) + .append(Component.literal("[").withStyle(ChatFormatting.GRAY)) + .append(MatterIndexKeyMappings.openIndex.translatedKeyMessage.copy().withStyle(ChatFormatting.GRAY)) + .append(Component.literal("]").withStyle(ChatFormatting.GRAY)) + .append(Component.literal(" to open Replication Index").withStyle(ChatFormatting.DARK_GRAY)) + private class MatterIndexSidebarButton( private val owner: Screen, ) : AbstractButton(2, 2, 22, 22, Component.literal("Replication Index")) { @@ -136,3 +152,24 @@ object MatterIndexClientEvents { } } } + +object MatterIndexKeyMappings { + val openIndex: KeyMapping = + KeyMapping( + "key.replicated_integration.open_replication_index", + KeyConflictContext.GUI, + InputConstants.Type.KEYSYM, + InputConstants.KEY_W, + "key.categories.replicated_integration", + ) + + fun isOpenIndexDown(window: Long): Boolean { + if (openIndex.isUnbound) return false + val key = openIndex.key + return InputConstants.isKeyDown(window, key.value) + } + + fun register(event: RegisterKeyMappingsEvent) { + event.register(openIndex) + } +} diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt index ea450b5..5d460c3 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -13,6 +13,7 @@ import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack +import org.lwjgl.glfw.GLFW import kotlin.math.ceil import kotlin.math.max import kotlin.math.min @@ -39,6 +40,7 @@ class MatterIndexScreen( private var displayScalePercent = MatterIndexDisplayScale.DEFAULT_PERCENT private var hoveredStack: ItemStack = ItemStack.EMPTY private var hoveredAmountTooltip: List = emptyList() + private var draggingScrollbar: DraggingScrollbar? = null private lateinit var searchBox: EditBox private lateinit var matterOnlyButton: Button @@ -109,6 +111,7 @@ class MatterIndexScreen( val layout = layout() val visible = visibleEntries() + if (startScrollbarDrag(layout, visible, mouseX.toInt(), mouseY.toInt())) return true if (clickHeader(layout, mouseX.toInt(), mouseY.toInt())) return true val rowArea = indexRows(layout) @@ -130,6 +133,19 @@ class MatterIndexScreen( return false } + override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, dragX: Double, dragY: Double): Boolean { + if (button == 0 && dragScrollbar(mouseY.toInt())) return true + return super.mouseDragged(mouseX, mouseY, button, dragX, dragY) + } + + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (button == 0 && draggingScrollbar != null) { + draggingScrollbar = null + return true + } + return super.mouseReleased(mouseX, mouseY, button) + } + override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean { val layout = layout() if (contains(indexRows(layout), mouseX.toInt(), mouseY.toInt())) { @@ -149,6 +165,12 @@ class MatterIndexScreen( } override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (keyCode == GLFW.GLFW_KEY_UP) { + return moveSelection(-1) + } + if (keyCode == GLFW.GLFW_KEY_DOWN) { + return moveSelection(1) + } if (searchBox.keyPressed(keyCode, scanCode, modifiers)) { indexScroll = 0 return true @@ -206,7 +228,7 @@ class MatterIndexScreen( hoveredStack = entry.stack } drawItemName(guiGraphics, entry.record.displayName, columns.item.x + 30, y + 9, columns.item.w - 34, 0xE6ECE8) - drawRight(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - TOTAL_VALUE_RIGHT_PADDING, y + 9, 0xE6ECE8) + drawTableTotalAmount(guiGraphics, formatAmount(entry.record.total), columns.total.x + columns.total.w - TOTAL_VALUE_RIGHT_PADDING, y + 9, 0xE6ECE8) if (contains(Rect(columns.total.x, y, columns.total.w, ROW_H), mouseX, mouseY)) { setAmountTooltip("Total", entry.record.total) } @@ -258,22 +280,20 @@ class MatterIndexScreen( drawTrimmed(guiGraphics, selected.record.displayName, slotX + 70, slotY + 4, layout.focus.w - 100, 0xE6ECE8) drawTrimmed(guiGraphics, selected.record.itemId, slotX + 70, slotY + 18, layout.focus.w - 100, 0x94A29C) - val matterStartX = slotX + 70 - val matterStartY = slotY + 36 + val focusMatter = focusMatterLayout(layout, slotX, slotY) for ((index, matterId) in matterTypes.withIndex()) { - val x = matterStartX + (index % 8) * 48 - val y = matterStartY + (index / 8) * 24 - if (x + 42 > layout.focus.x + layout.focus.w - 8) continue - drawSlot(guiGraphics, x, y, 20, false) - renderMatterIcon(guiGraphics, matterId, x + 2, y + 2) + val column = index % focusMatter.columns + val row = index / focusMatter.columns + val x = focusMatter.x + column * focusMatter.cellW + val y = focusMatter.y + row * FOCUS_MATTER_ROW_H val amount = selected.record.matterValues[matterId] ?: 0.0 - drawCentered(guiGraphics, formatAmount(amount), x + 10, y + 22, 0xE6ECE8) - if (mouseX in x until x + 42 && mouseY in y until y + 34) { + drawFocusMatterAmount(guiGraphics, matterId, formatAmount(amount), x, y, focusMatter.cellW, focusMatterAmountColor(amount)) + if (mouseX in x until x + focusMatter.cellW && mouseY in y until y + FOCUS_MATTER_ROW_H) { setAmountTooltip(matterId, amount) } } - val badgeY = slotY + 94 + val badgeY = max(slotY + 82, focusMatter.y + focusMatter.rows * FOCUS_MATTER_ROW_H + 6) drawBadge(guiGraphics, slotX, badgeY, "Replicable") drawBadge(guiGraphics, slotX + scaledPixels(94), badgeY, "Decomposable") drawDivider(guiGraphics, layout.focus.x + 12, badgeY + 32, layout.focus.w - 24) @@ -342,6 +362,110 @@ class MatterIndexScreen( return equivalentEntries().getOrNull((equivalentScroll + row) * columns + column) } + private fun startScrollbarDrag(layout: Layout, visible: List, mouseX: Int, mouseY: Int): Boolean { + val bar = scrollbarAt(layout, visible, mouseX, mouseY) ?: return false + val thumb = scrollbarThumb(bar) ?: return false + val thumbOffset = + if (contains(thumb.inflate(SCROLLBAR_HIT_PAD), mouseX, mouseY)) { + mouseY - thumb.y + } else { + thumb.h / 2 + } + draggingScrollbar = DraggingScrollbar(bar.target, thumbOffset) + setScrollbarFromThumbTop(bar, mouseY - thumbOffset) + return true + } + + private fun dragScrollbar(mouseY: Int): Boolean { + val dragging = draggingScrollbar ?: return false + val bar = scrollbarForTarget(layout(), visibleEntries(), dragging.target) ?: return false + setScrollbarFromThumbTop(bar, mouseY - dragging.thumbOffset) + return true + } + + private fun scrollbarAt(layout: Layout, visible: List, mouseX: Int, mouseY: Int): Scrollbar? = + listOfNotNull( + scrollbarForTarget(layout, visible, ScrollbarTarget.INDEX), + scrollbarForTarget(layout, visible, ScrollbarTarget.EQUIVALENT), + ).firstOrNull { contains(scrollbarHitRect(it), mouseX, mouseY) && it.total > it.visible } + + private fun scrollbarForTarget(layout: Layout, visible: List, target: ScrollbarTarget): Scrollbar? = + when (target) { + ScrollbarTarget.INDEX -> { + val rows = indexRows(layout) + Scrollbar( + target = target, + x = rows.x + rows.w - 5, + y = rows.y, + h = rows.h, + total = visible.size, + visible = max(1, rows.h / ROW_H), + scroll = indexScroll, + ) + } + ScrollbarTarget.EQUIVALENT -> { + val rect = equivalentRect(layout) + val columns = equivalentColumns(layout) + Scrollbar( + target = target, + x = rect.x + rect.w - 5, + y = rect.y, + h = rect.h, + total = (equivalentEntries().size + columns - 1) / columns, + visible = max(1, rect.h / SLOT_STEP), + scroll = equivalentScroll, + ) + } + } + + private fun scrollbarHitRect(bar: Scrollbar): Rect = + Rect(bar.x - SCROLLBAR_HIT_PAD, bar.y, SCROLLBAR_W + SCROLLBAR_HIT_PAD * 2, bar.h) + + private fun scrollbarThumb(bar: Scrollbar): Rect? { + if (bar.total <= bar.visible) return null + val thumbH = max(16, bar.h * bar.visible / bar.total) + val thumbY = bar.y + ((bar.h - thumbH) * bar.scroll / max(1, bar.total - bar.visible)) + return Rect(bar.x, thumbY, SCROLLBAR_W, thumbH) + } + + private fun setScrollbarFromThumbTop(bar: Scrollbar, thumbTop: Int) { + val thumb = scrollbarThumb(bar) ?: return + val maxScroll = max(0, bar.total - bar.visible) + val track = max(1, bar.h - thumb.h) + val local = (thumbTop - bar.y).coerceIn(0, track) + val scroll = ((local * maxScroll.toFloat()) / track.toFloat()).toInt().coerceIn(0, maxScroll) + when (bar.target) { + ScrollbarTarget.INDEX -> indexScroll = scroll + ScrollbarTarget.EQUIVALENT -> equivalentScroll = scroll + } + } + + private fun moveSelection(delta: Int): Boolean { + val visible = visibleEntries() + if (visible.isEmpty()) return false + val current = visible.indexOfFirst { it.record.itemId == selectedItemId } + val target = + if (current < 0) { + if (delta < 0) visible.lastIndex else 0 + } else { + (current + delta).coerceIn(0, visible.lastIndex) + } + selectedItemId = visible[target].record.itemId + equivalentScroll = 0 + keepSelectionVisible(target, visible.size) + return true + } + + private fun keepSelectionVisible(index: Int, total: Int) { + val visibleRows = max(1, indexRows(layout()).h / ROW_H) + indexScroll = + when { + index < indexScroll -> index + index >= indexScroll + visibleRows -> index - visibleRows + 1 + else -> indexScroll + }.coerceIn(0, max(0, total - visibleRows)) + } + private fun visibleEntries(): List { val query = MatterIndexFiltering.parseQuery(searchBox.value, matterOnly, replicableOnly, decomposableOnly) val ordered = MatterIndexFiltering.filterAndSort(entries.map { it.record }, query, sort) @@ -360,7 +484,7 @@ class MatterIndexScreen( private fun columns(layout: Layout): Columns { val rows = indexRows(layout) - val itemW = max(scaledPixels(126), (rows.w * 0.22).toInt()) + val itemW = max(scaledPixels(126), (rows.w * ITEM_COLUMN_WIDTH_RATIO).toInt()) val totalW = scaledPixels(52) val matterW = max(tableMatterColumnMinWidth(), (rows.w - itemW - totalW) / max(1, matterTypes.size)) val matter = matterTypes.mapIndexed { index, id -> @@ -379,7 +503,9 @@ class MatterIndexScreen( val gap = 6 val panelH = height - margin * 2 val totalW = width - margin * 2 - gap - val indexW = (totalW * 0.60).toInt() + val defaultIndexW = (totalW * 0.60).toInt() + val maxIndexW = max(0, totalW - FOCUS_MIN_W) + val indexW = min(maxIndexW, max(defaultIndexW, requiredIndexPanelWidth())) val focusW = totalW - indexW return Layout( index = Rect(margin, margin, indexW, panelH), @@ -387,15 +513,45 @@ class MatterIndexScreen( ) } + private fun requiredIndexPanelWidth(): Int { + val matterCount = max(1, matterTypes.size) + val denominator = 1.0 - ITEM_COLUMN_WIDTH_RATIO + val fixedWidth = scaledPixels(52) + matterCount * tableMatterColumnMinWidth() + val rowsW = max(scaledPixels(126) + fixedWidth, ceil(fixedWidth / denominator).toInt()) + return rowsW + INDEX_HORIZONTAL_PADDING + } + private fun indexRows(layout: Layout): Rect = Rect(layout.index.x + 14, layout.index.y + 88, layout.index.w - 28, layout.index.h - 124) - private fun equivalentRect(layout: Layout): Rect = - Rect(layout.focus.x + 16, layout.focus.y + 204, layout.focus.w - 32, layout.focus.h - 224) + private fun equivalentRect(layout: Layout): Rect { + val y = focusEquivalentY(layout) + return Rect(layout.focus.x + 16, y, layout.focus.w - 32, max(0, layout.focus.y + layout.focus.h - y - 20)) + } private fun equivalentColumns(layout: Layout): Int = max(2, (equivalentRect(layout).w - 12) / SLOT_STEP) + private fun focusEquivalentY(layout: Layout): Int { + val focusMatter = focusMatterLayout(layout, layout.focus.x + 18, layout.focus.y + 42) + val badgeY = max(layout.focus.y + 124, focusMatter.y + focusMatter.rows * FOCUS_MATTER_ROW_H + 6) + return badgeY + 60 + } + + private fun focusMatterLayout(layout: Layout, slotX: Int, slotY: Int): FocusMatterLayout { + val x = slotX + 70 + val availableW = max(FOCUS_MATTER_MIN_CELL_W, layout.focus.x + layout.focus.w - 8 - x) + val columns = min(FOCUS_MATTER_COLUMNS, matterTypes.size.coerceAtLeast(1)) + val rows = (matterTypes.size + columns - 1) / columns + return FocusMatterLayout( + x = x, + y = slotY + 36, + cellW = availableW / columns, + columns = columns, + rows = rows, + ) + } + private fun renderPanel(guiGraphics: GuiGraphics, rect: Rect) { guiGraphics.fill(rect.x, rect.y, rect.x + rect.w, rect.y + rect.h, 0xCC071010.toInt()) drawBorder(guiGraphics, rect, 0xFF41514D.toInt()) @@ -427,6 +583,15 @@ class MatterIndexScreen( guiGraphics.blit(ResourceLocation.fromNamespaceAndPath(Replication.MOD_ID, "textures/gui/mattertypes/${matterId.substringAfter(':')}.png"), x, y, 0f, 0f, 16, 16, 16, 16) } + private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int, size: Int) { + val scale = size / 16f + guiGraphics.pose().pushPose() + guiGraphics.pose().translate(x.toFloat(), y.toFloat(), 0f) + guiGraphics.pose().scale(scale, scale, 1f) + renderMatterIcon(guiGraphics, matterId, 0, 0) + guiGraphics.pose().popPose() + } + private fun drawRiMark(guiGraphics: GuiGraphics, x: Int, y: Int) { guiGraphics.fill(x + 5, y, x + 9, y + 4, 0xFF42E84F.toInt()) guiGraphics.fill(x, y + 5, x + 4, y + 9, 0xFF42E84F.toInt()) @@ -435,11 +600,10 @@ class MatterIndexScreen( } private fun drawScrollbar(guiGraphics: GuiGraphics, x: Int, y: Int, h: Int, total: Int, visible: Int, scroll: Int) { - if (total <= visible) return - guiGraphics.fill(x, y, x + 4, y + h, 0xAA050606.toInt()) - val thumbH = max(16, h * visible / total) - val thumbY = y + ((h - thumbH) * scroll / max(1, total - visible)) - guiGraphics.fill(x, thumbY, x + 4, thumbY + thumbH, 0xFF9EAAA5.toInt()) + val bar = Scrollbar(ScrollbarTarget.INDEX, x, y, h, total, visible, scroll) + val thumb = scrollbarThumb(bar) ?: return + guiGraphics.fill(x, y, x + SCROLLBAR_W, y + h, 0xAA050606.toInt()) + guiGraphics.fill(thumb.x, thumb.y, thumb.x + thumb.w, thumb.y + thumb.h, 0xFF9EAAA5.toInt()) } private fun drawDivider(guiGraphics: GuiGraphics, x: Int, y: Int, w: Int) = @@ -466,6 +630,28 @@ class MatterIndexScreen( drawScaled(guiGraphics, text, centerX - scaledWidth(text, scale) / 2, y, color, false, scale) } + private fun drawTableTotalAmount(guiGraphics: GuiGraphics, text: String, rightX: Int, y: Int, color: Int) { + val scale = tableMatterAmountScale() + drawScaled(guiGraphics, text, rightX - scaledWidth(text, scale), y, color, false, scale) + } + + private fun drawFocusMatterAmount(guiGraphics: GuiGraphics, matterId: String, text: String, x: Int, y: Int, cellW: Int, color: Int) { + val textMaxW = cellW - FOCUS_MATTER_ICON_SIZE - FOCUS_MATTER_VALUE_GAP + if (textMaxW <= 0) return + val scale = min(focusMatterAmountScale(), textMaxW.toFloat() / max(1, font.width(text))).coerceAtLeast(0.35f) + val textW = scaledWidth(text, scale) + val textH = ceil(font.lineHeight * scale).toInt() + val pairX = x + val iconY = y + (FOCUS_MATTER_ROW_H - FOCUS_MATTER_ICON_SIZE) / 2 + val textX = pairX + FOCUS_MATTER_ICON_SIZE + FOCUS_MATTER_VALUE_GAP + val textY = y + (FOCUS_MATTER_ROW_H - textH) / 2 + renderMatterIcon(guiGraphics, matterId, pairX, iconY, FOCUS_MATTER_ICON_SIZE) + drawScaled(guiGraphics, text, textX, textY, color, false, scale) + } + + private fun focusMatterAmountColor(amount: Double): Int = + if (amount == 0.0) 0x8EA09A else 0xE6ECE8 + private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) = drawTrimmed(guiGraphics, text, x, y, maxWidth, color, displayScale()) @@ -490,6 +676,9 @@ class MatterIndexScreen( private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int) = drawScaled(guiGraphics, text, centerX - scaledWidth(text) / 2, y, color, false) + private fun drawCentered(guiGraphics: GuiGraphics, text: String, centerX: Int, y: Int, color: Int, scale: Float) = + drawScaled(guiGraphics, text, centerX - scaledWidth(text, scale) / 2, y, color, false, scale) + private fun drawScaled(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, color: Int, shadow: Boolean) = drawScaled(guiGraphics, text, x, y, color, shadow, displayScale()) @@ -523,6 +712,9 @@ class MatterIndexScreen( private fun tableMatterAmountScale(): Float = displayScale() * AMOUNT_SCALE + private fun focusMatterAmountScale(): Float = + displayScale() * FOCUS_MATTER_AMOUNT_SCALE + private fun tableMatterColumnMinWidth(): Int = scaledWidth(MATTER_AMOUNT_WIDTH_SAMPLE, tableMatterAmountScale()) + MATTER_AMOUNT_HORIZONTAL_PADDING @@ -600,11 +792,22 @@ class MatterIndexScreen( companion object { private const val HEADER_H = 30 private const val ROW_H = 26 + private const val FOCUS_MIN_W = 220 + private const val INDEX_HORIZONTAL_PADDING = 28 + private const val ITEM_COLUMN_WIDTH_RATIO = 0.22 private const val ITEM_NAME_SCALE = 0.90f private const val AMOUNT_SCALE = 0.80f private const val MATTER_AMOUNT_WIDTH_SAMPLE = "88.8Qa" private const val MATTER_AMOUNT_HORIZONTAL_PADDING = 8 private const val TOTAL_VALUE_RIGHT_PADDING = 8 + private const val FOCUS_MATTER_COLUMNS = 4 + private const val FOCUS_MATTER_MIN_CELL_W = 26 + private const val FOCUS_MATTER_ROW_H = 18 + private const val FOCUS_MATTER_ICON_SIZE = 8 + private const val FOCUS_MATTER_VALUE_GAP = 3 + private const val FOCUS_MATTER_AMOUNT_SCALE = 0.50f + private const val SCROLLBAR_W = 4 + private const val SCROLLBAR_HIT_PAD = 3 private const val SLOT_SIZE = 28 private const val SLOT_STEP = 34 @@ -660,6 +863,34 @@ class MatterIndexScreen( val plus: Rect, ) + private data class FocusMatterLayout( + val x: Int, + val y: Int, + val cellW: Int, + val columns: Int, + val rows: Int, + ) + + private enum class ScrollbarTarget { + INDEX, + EQUIVALENT, + } + + private data class DraggingScrollbar( + val target: ScrollbarTarget, + val thumbOffset: Int, + ) + + private data class Scrollbar( + val target: ScrollbarTarget, + val x: Int, + val y: Int, + val h: Int, + val total: Int, + val visible: Int, + val scroll: Int, + ) + private data class Rect( val x: Int, val y: Int, diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoReplicatedIntegrationLanguageProvider.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoReplicatedIntegrationLanguageProvider.kt index bb2925f..099fade 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoReplicatedIntegrationLanguageProvider.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoReplicatedIntegrationLanguageProvider.kt @@ -18,6 +18,8 @@ class NeoReplicatedIntegrationLanguageProvider( private fun addEnglishTranslations() { add("replicated_integration.config.enable_nuclear_recipe", "Enable Mekanism nuclear recipe support") add("replicated_integration.config.notice.compatibility_summary_shown", "Compatibility summary notice shown") + add("key.categories.replicated_integration", "Replicated Integration") + add("key.replicated_integration.open_replication_index", "Open Replication Index") add("replicated_integration.chat.notice.title", "Replicated Integration") add( "replicated_integration.chat.notice.supported_integrations", @@ -36,6 +38,8 @@ class NeoReplicatedIntegrationLanguageProvider( private fun addJapaneseTranslations() { add("replicated_integration.config.enable_nuclear_recipe", "Mekanism の核分裂レシピ対応を有効化") add("replicated_integration.config.notice.compatibility_summary_shown", "互換性サマリー通知を表示済み") + add("key.categories.replicated_integration", "Replicated Integration") + add("key.replicated_integration.open_replication_index", "Replication Index を開く") add("replicated_integration.chat.notice.title", "Replicated Integration") add( "replicated_integration.chat.notice.supported_integrations", From 4794be88e340264672dc5c25c2bbcf81bb7d37b2 Mon Sep 17 00:00:00 2001 From: P-nutsK <107553542+P-nutsK@users.noreply.github.com> Date: Fri, 22 May 2026 04:21:47 +0900 Subject: [PATCH 05/12] add kjs --- 1.20.1-forge/build.gradle.kts | 3 + .../client/MatterIndexScreen.kt | 71 ++++++++++++------- 1.21.1-neo/build.gradle.kts | 4 ++ .../vanilla/ReplicationVanillaAddon.kt | 7 ++ .../client/MatterIndexScreen.kt | 71 ++++++++++++------- .../core/NeoRecipeConversionSupport.kt | 18 +++++ build.gradle.kts | 25 +++++++ .../api/client/MatterIndexModels.kt | 18 ++--- gradle/libs.versions.toml | 13 ++++ 9 files changed, 168 insertions(+), 62 deletions(-) diff --git a/1.20.1-forge/build.gradle.kts b/1.20.1-forge/build.gradle.kts index 998ec69..033609c 100644 --- a/1.20.1-forge/build.gradle.kts +++ b/1.20.1-forge/build.gradle.kts @@ -20,6 +20,9 @@ dependencies { modImplementation(libs.titanium.mc1201) modImplementation(libs.ae2.mc1201) modImplementation(libs.aae.mc1201) + modImplementation(libs.kubejs.mc1201) + modImplementation(libs.architectury.mc1201) + modImplementation(libs.rhino.mc1201) // aaeについてくる modCompileOnly(libs.ae2addonlib.mc1201) // aaeに不可欠 diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt index 84f7005..f1b7450 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -3,6 +3,7 @@ package com.p_nsk.replicated_integration.client import com.buuz135.replication.Replication import com.buuz135.replication.calculation.MatterCompound import com.buuz135.replication.calculation.client.ClientReplicationCalculation +import com.buuz135.replication.util.ReplicationTags import com.p_nsk.replicated_integration.api.client.* import com.p_nsk.replicated_integration.config.ForgeUiConfig import net.minecraft.client.gui.GuiGraphics @@ -35,8 +36,8 @@ class MatterIndexScreen( private var indexScroll = 0 private var equivalentScroll = 0 private var matterOnly = true - private var replicableOnly = false - private var decomposableOnly = false + private var replicatableOnly = false + private var disintegratableOnly = false private var displayScalePercent = MatterIndexDisplayScale.DEFAULT_PERCENT private var hoveredStack: ItemStack = ItemStack.EMPTY private var hoveredAmountTooltip: List = emptyList() @@ -44,8 +45,8 @@ class MatterIndexScreen( private lateinit var searchBox: EditBox private lateinit var matterOnlyButton: Button - private lateinit var replicableButton: Button - private lateinit var decomposableButton: Button + private lateinit var replicatableButton: Button + private lateinit var disintegratableButton: Button override fun init() { val layout = layout() @@ -62,19 +63,19 @@ class MatterIndexScreen( indexScroll = 0 }.bounds(searchBox.x + searchBox.width + 8, searchBox.y, 34, 18).build(), ) - replicableButton = addRenderableWidget( - Button.builder(toggleLabel("R", replicableOnly)) { - replicableOnly = !replicableOnly - replicableButton.message = toggleLabel("R", replicableOnly) + replicatableButton = addRenderableWidget( + Button.builder(toggleLabel("R", replicatableOnly)) { + replicatableOnly = !replicatableOnly + replicatableButton.message = toggleLabel("R", replicatableOnly) indexScroll = 0 }.bounds(matterOnlyButton.x + matterOnlyButton.width + 4, searchBox.y, 34, 18).build(), ) - decomposableButton = addRenderableWidget( - Button.builder(toggleLabel("D", decomposableOnly)) { - decomposableOnly = !decomposableOnly - decomposableButton.message = toggleLabel("D", decomposableOnly) + disintegratableButton = addRenderableWidget( + Button.builder(toggleLabel("D", disintegratableOnly)) { + disintegratableOnly = !disintegratableOnly + disintegratableButton.message = toggleLabel("D", disintegratableOnly) indexScroll = 0 - }.bounds(replicableButton.x + replicableButton.width + 4, searchBox.y, 34, 18).build(), + }.bounds(replicatableButton.x + replicatableButton.width + 4, searchBox.y, 34, 18).build(), ) } @@ -197,14 +198,14 @@ class MatterIndexScreen( override fun isPauseScreen(): Boolean = false private fun renderTitle(guiGraphics: GuiGraphics, layout: Layout) { - guiGraphics.drawString(font, "Replication Integration", layout.index.x + 16, layout.index.y + 14, 0xDDEFE7, true) + guiGraphics.drawString(font, "Replicated Integration", layout.index.x + 28, layout.index.y + 14, 0xDDEFE7, true) drawRiMark(guiGraphics, layout.index.x + 6, layout.index.y + 11) } private fun renderControls(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { matterOnlyButton.render(guiGraphics, mouseX, mouseY, partialTick) - replicableButton.render(guiGraphics, mouseX, mouseY, partialTick) - decomposableButton.render(guiGraphics, mouseX, mouseY, partialTick) + replicatableButton.render(guiGraphics, mouseX, mouseY, partialTick) + disintegratableButton.render(guiGraphics, mouseX, mouseY, partialTick) renderDisplayScaleControl(guiGraphics, layout(), mouseX, mouseY) } @@ -297,8 +298,8 @@ class MatterIndexScreen( } val badgeY = max(slotY + 82, focusMatter.y + focusMatter.rows * FOCUS_MATTER_ROW_H + 6) - drawBadge(guiGraphics, slotX, badgeY, "Replicable") - drawBadge(guiGraphics, slotX + scaledPixels(94), badgeY, "Decomposable") + drawBadge(guiGraphics, slotX, badgeY, "Replicatable", selected.record.replicatable) + drawBadge(guiGraphics, slotX + scaledPixels(104), badgeY, "Disintegratable", selected.record.disintegratable) drawDivider(guiGraphics, layout.focus.x + 12, badgeY + 32, layout.focus.w - 24) val eq = equivalentRect(layout) @@ -470,7 +471,7 @@ class MatterIndexScreen( } private fun visibleEntries(): List { - val query = MatterIndexFiltering.parseQuery(searchBox.value, matterOnly, replicableOnly, decomposableOnly) + val query = MatterIndexFiltering.parseQuery(searchBox.value, matterOnly, replicatableOnly, disintegratableOnly) val ordered = MatterIndexFiltering.filterAndSort(entries.map { it.record }, query, sort) val byId = entries.associateBy { it.record.itemId } return ordered.mapNotNull { byId[it.itemId] } @@ -574,12 +575,16 @@ class MatterIndexScreen( guiGraphics.fill(x + 3, y + 3, x + size - 3, y + size - 3, 0xFF0D1211.toInt()) } - private fun drawBadge(guiGraphics: GuiGraphics, x: Int, y: Int, text: String) { - val w = scaledPixels(86) + private fun drawBadge(guiGraphics: GuiGraphics, x: Int, y: Int, text: String, active: Boolean) { + val w = scaledPixels(100) val h = scaledPixels(22) - guiGraphics.fill(x, y, x + w, y + h, 0x5517E517) - drawBorder(guiGraphics, Rect(x, y, w, h), 0xAA36D84A.toInt()) - drawCentered(guiGraphics, text, x + w / 2, y + max(1, (h - scaledPixels(font.lineHeight)) / 2), 0x70F06E) + val fill = if (active) 0x5517E517 else 0x55E03232 + val border = if (active) 0xAA36D84A.toInt() else 0xAAC74949.toInt() + val color = if (active) 0x70F06E else 0xF07171 + val label = if (active) text else "Not $text" + guiGraphics.fill(x, y, x + w, y + h, fill) + drawBorder(guiGraphics, Rect(x, y, w, h), border) + drawCentered(guiGraphics, label, x + w / 2, y + max(1, (h - scaledPixels(font.lineHeight)) / 2), color) } private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int) { @@ -595,6 +600,16 @@ class MatterIndexScreen( guiGraphics.pose().popPose() } + private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int, size: Int, muted: Boolean) { + if (!muted) { + renderMatterIcon(guiGraphics, matterId, x, y, size) + return + } + guiGraphics.setColor(0.56f, 0.63f, 0.60f, 0.70f) + renderMatterIcon(guiGraphics, matterId, x, y, size) + guiGraphics.setColor(1f, 1f, 1f, 1f) + } + private fun drawRiMark(guiGraphics: GuiGraphics, x: Int, y: Int) { guiGraphics.fill(x + 5, y, x + 9, y + 4, 0xFF42E84F.toInt()) guiGraphics.fill(x, y + 5, x + 4, y + 9, 0xFF42E84F.toInt()) @@ -642,18 +657,17 @@ class MatterIndexScreen( val textMaxW = cellW - FOCUS_MATTER_ICON_SIZE - FOCUS_MATTER_VALUE_GAP if (textMaxW <= 0) return val scale = min(focusMatterAmountScale(), textMaxW.toFloat() / max(1, font.width(text))).coerceAtLeast(0.35f) - val textW = scaledWidth(text, scale) val textH = ceil(font.lineHeight * scale).toInt() val pairX = x val iconY = y + (FOCUS_MATTER_ROW_H - FOCUS_MATTER_ICON_SIZE) / 2 val textX = pairX + FOCUS_MATTER_ICON_SIZE + FOCUS_MATTER_VALUE_GAP val textY = y + (FOCUS_MATTER_ROW_H - textH) / 2 - renderMatterIcon(guiGraphics, matterId, pairX, iconY, FOCUS_MATTER_ICON_SIZE) + renderMatterIcon(guiGraphics, matterId, pairX, iconY, FOCUS_MATTER_ICON_SIZE, color == FOCUS_MATTER_ZERO_COLOR) drawScaled(guiGraphics, text, textX, textY, color, false, scale) } private fun focusMatterAmountColor(amount: Double): Int = - if (amount == 0.0) 0x8EA09A else 0xE6ECE8 + if (amount == 0.0) FOCUS_MATTER_ZERO_COLOR else 0xE6ECE8 private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) = drawTrimmed(guiGraphics, text, x, y, maxWidth, color, displayScale()) @@ -809,6 +823,7 @@ class MatterIndexScreen( private const val FOCUS_MATTER_ICON_SIZE = 8 private const val FOCUS_MATTER_VALUE_GAP = 3 private const val FOCUS_MATTER_AMOUNT_SCALE = 0.50f + private const val FOCUS_MATTER_ZERO_COLOR = 0x8EA09A private const val SCROLLBAR_W = 4 private const val SCROLLBAR_HIT_PAD = 3 private const val SLOT_SIZE = 28 @@ -831,6 +846,8 @@ class MatterIndexScreen( itemId = id, displayName = stack.hoverName.string, matterValues = compound.toMatterMap(), + replicatable = !stack.`is`(ReplicationTags.CANT_BE_SCANNED), + disintegratable = !stack.`is`(ReplicationTags.CANT_BE_DISINTEGRATED), extraSearchText = tags, ), stack = stack, diff --git a/1.21.1-neo/build.gradle.kts b/1.21.1-neo/build.gradle.kts index 90bcb44..69cc305 100644 --- a/1.21.1-neo/build.gradle.kts +++ b/1.21.1-neo/build.gradle.kts @@ -11,12 +11,16 @@ dependencies { implementation(libs.kff.mc1211) implementation(libs.titanium.mc1211) { + // JEIがついてくるけど古過ぎて役に立たないので除外 exclude(group = "mezz.jei") } implementation(libs.replication.mc1211) implementation(libs.ae2.mc1211) implementation(libs.aae.mc1211) implementation(libs.draconic.evolution.mc1211) + implementation(libs.kubejs.mc1211) + implementation(libs.architectury.mc1211) + implementation(libs.rhino.mc1211) // depend (optional) compileOnly(mekclassifier("api")) diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt index 6fe4e9b..e4d5a7e 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt @@ -3,6 +3,7 @@ package com.p_nsk.replicated_integration.adapter.vanilla import com.buuz135.replication.ReplicationRegistry import com.buuz135.replication.calculation.MatterValue import com.buuz135.replication.recipe.MatterValueRecipe +import com.buuz135.replication.util.ReplicationTags import com.p_nsk.replicated_integration.api.addon.ReplicationAddonEnvironment import com.p_nsk.replicated_integration.api.addon.ReplicationAddonLoadSafetyContract import com.p_nsk.replicated_integration.api.graph.IConversionSink @@ -247,6 +248,12 @@ object ReplicationVanillaAddon : NeoReplicationAddon { return@mapNotNull null } val item = itemById(consume.node.id) ?: return@mapNotNull null + // Passive Replication compatibility: this opts an input item out of + // the vanilla crafting-remainder subtraction only. It does not change + // the value of the item itself. + if (ItemStack(item).`is`(ReplicationTags.DONT_CHECK_FOR_CRAFTING_RESULT)) { + return@mapNotNull null + } val remainder = item.craftingRemainingItem ?: return@mapNotNull null val remainderNode = BuiltinNodeResolver.itemNode(ItemStack(remainder)) ?: return@mapNotNull null NodeAmount(remainderNode, consume.amount) diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt index 5d460c3..f4ad391 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt @@ -3,6 +3,7 @@ package com.p_nsk.replicated_integration.client import com.buuz135.replication.Replication import com.buuz135.replication.calculation.MatterCompound import com.buuz135.replication.calculation.client.ClientReplicationCalculation +import com.buuz135.replication.util.ReplicationTags import com.p_nsk.replicated_integration.api.client.* import com.p_nsk.replicated_integration.config.NeoUiConfig import net.minecraft.client.gui.GuiGraphics @@ -35,8 +36,8 @@ class MatterIndexScreen( private var indexScroll = 0 private var equivalentScroll = 0 private var matterOnly = true - private var replicableOnly = false - private var decomposableOnly = false + private var replicatableOnly = false + private var disintegratableOnly = false private var displayScalePercent = MatterIndexDisplayScale.DEFAULT_PERCENT private var hoveredStack: ItemStack = ItemStack.EMPTY private var hoveredAmountTooltip: List = emptyList() @@ -44,8 +45,8 @@ class MatterIndexScreen( private lateinit var searchBox: EditBox private lateinit var matterOnlyButton: Button - private lateinit var replicableButton: Button - private lateinit var decomposableButton: Button + private lateinit var replicatableButton: Button + private lateinit var disintegratableButton: Button override fun init() { val layout = layout() @@ -62,19 +63,19 @@ class MatterIndexScreen( indexScroll = 0 }.bounds(searchBox.x + searchBox.width + 8, searchBox.y, 34, 18).build(), ) - replicableButton = addRenderableWidget( - Button.builder(toggleLabel("R", replicableOnly)) { - replicableOnly = !replicableOnly - replicableButton.message = toggleLabel("R", replicableOnly) + replicatableButton = addRenderableWidget( + Button.builder(toggleLabel("R", replicatableOnly)) { + replicatableOnly = !replicatableOnly + replicatableButton.message = toggleLabel("R", replicatableOnly) indexScroll = 0 }.bounds(matterOnlyButton.x + matterOnlyButton.width + 4, searchBox.y, 34, 18).build(), ) - decomposableButton = addRenderableWidget( - Button.builder(toggleLabel("D", decomposableOnly)) { - decomposableOnly = !decomposableOnly - decomposableButton.message = toggleLabel("D", decomposableOnly) + disintegratableButton = addRenderableWidget( + Button.builder(toggleLabel("D", disintegratableOnly)) { + disintegratableOnly = !disintegratableOnly + disintegratableButton.message = toggleLabel("D", disintegratableOnly) indexScroll = 0 - }.bounds(replicableButton.x + replicableButton.width + 4, searchBox.y, 34, 18).build(), + }.bounds(replicatableButton.x + replicatableButton.width + 4, searchBox.y, 34, 18).build(), ) } @@ -193,14 +194,14 @@ class MatterIndexScreen( override fun isPauseScreen(): Boolean = false private fun renderTitle(guiGraphics: GuiGraphics, layout: Layout) { - guiGraphics.drawString(font, "Replication Integration", layout.index.x + 16, layout.index.y + 14, 0xDDEFE7, true) + guiGraphics.drawString(font, "Replicated Integration", layout.index.x + 28, layout.index.y + 14, 0xDDEFE7, true) drawRiMark(guiGraphics, layout.index.x + 6, layout.index.y + 11) } private fun renderControls(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { matterOnlyButton.render(guiGraphics, mouseX, mouseY, partialTick) - replicableButton.render(guiGraphics, mouseX, mouseY, partialTick) - decomposableButton.render(guiGraphics, mouseX, mouseY, partialTick) + replicatableButton.render(guiGraphics, mouseX, mouseY, partialTick) + disintegratableButton.render(guiGraphics, mouseX, mouseY, partialTick) renderDisplayScaleControl(guiGraphics, layout(), mouseX, mouseY) } @@ -294,8 +295,8 @@ class MatterIndexScreen( } val badgeY = max(slotY + 82, focusMatter.y + focusMatter.rows * FOCUS_MATTER_ROW_H + 6) - drawBadge(guiGraphics, slotX, badgeY, "Replicable") - drawBadge(guiGraphics, slotX + scaledPixels(94), badgeY, "Decomposable") + drawBadge(guiGraphics, slotX, badgeY, "Replicatable", selected.record.replicatable) + drawBadge(guiGraphics, slotX + scaledPixels(104), badgeY, "Disintegratable", selected.record.disintegratable) drawDivider(guiGraphics, layout.focus.x + 12, badgeY + 32, layout.focus.w - 24) val eq = equivalentRect(layout) @@ -467,7 +468,7 @@ class MatterIndexScreen( } private fun visibleEntries(): List { - val query = MatterIndexFiltering.parseQuery(searchBox.value, matterOnly, replicableOnly, decomposableOnly) + val query = MatterIndexFiltering.parseQuery(searchBox.value, matterOnly, replicatableOnly, disintegratableOnly) val ordered = MatterIndexFiltering.filterAndSort(entries.map { it.record }, query, sort) val byId = entries.associateBy { it.record.itemId } return ordered.mapNotNull { byId[it.itemId] } @@ -571,12 +572,16 @@ class MatterIndexScreen( guiGraphics.fill(x + 3, y + 3, x + size - 3, y + size - 3, 0xFF0D1211.toInt()) } - private fun drawBadge(guiGraphics: GuiGraphics, x: Int, y: Int, text: String) { - val w = scaledPixels(86) + private fun drawBadge(guiGraphics: GuiGraphics, x: Int, y: Int, text: String, active: Boolean) { + val w = scaledPixels(100) val h = scaledPixels(22) - guiGraphics.fill(x, y, x + w, y + h, 0x5517E517) - drawBorder(guiGraphics, Rect(x, y, w, h), 0xAA36D84A.toInt()) - drawCentered(guiGraphics, text, x + w / 2, y + max(1, (h - scaledPixels(font.lineHeight)) / 2), 0x70F06E) + val fill = if (active) 0x5517E517 else 0x55E03232 + val border = if (active) 0xAA36D84A.toInt() else 0xAAC74949.toInt() + val color = if (active) 0x70F06E else 0xF07171 + val label = if (active) text else "Not $text" + guiGraphics.fill(x, y, x + w, y + h, fill) + drawBorder(guiGraphics, Rect(x, y, w, h), border) + drawCentered(guiGraphics, label, x + w / 2, y + max(1, (h - scaledPixels(font.lineHeight)) / 2), color) } private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int) { @@ -592,6 +597,16 @@ class MatterIndexScreen( guiGraphics.pose().popPose() } + private fun renderMatterIcon(guiGraphics: GuiGraphics, matterId: String, x: Int, y: Int, size: Int, muted: Boolean) { + if (!muted) { + renderMatterIcon(guiGraphics, matterId, x, y, size) + return + } + guiGraphics.setColor(0.56f, 0.63f, 0.60f, 0.70f) + renderMatterIcon(guiGraphics, matterId, x, y, size) + guiGraphics.setColor(1f, 1f, 1f, 1f) + } + private fun drawRiMark(guiGraphics: GuiGraphics, x: Int, y: Int) { guiGraphics.fill(x + 5, y, x + 9, y + 4, 0xFF42E84F.toInt()) guiGraphics.fill(x, y + 5, x + 4, y + 9, 0xFF42E84F.toInt()) @@ -639,18 +654,17 @@ class MatterIndexScreen( val textMaxW = cellW - FOCUS_MATTER_ICON_SIZE - FOCUS_MATTER_VALUE_GAP if (textMaxW <= 0) return val scale = min(focusMatterAmountScale(), textMaxW.toFloat() / max(1, font.width(text))).coerceAtLeast(0.35f) - val textW = scaledWidth(text, scale) val textH = ceil(font.lineHeight * scale).toInt() val pairX = x val iconY = y + (FOCUS_MATTER_ROW_H - FOCUS_MATTER_ICON_SIZE) / 2 val textX = pairX + FOCUS_MATTER_ICON_SIZE + FOCUS_MATTER_VALUE_GAP val textY = y + (FOCUS_MATTER_ROW_H - textH) / 2 - renderMatterIcon(guiGraphics, matterId, pairX, iconY, FOCUS_MATTER_ICON_SIZE) + renderMatterIcon(guiGraphics, matterId, pairX, iconY, FOCUS_MATTER_ICON_SIZE, color == FOCUS_MATTER_ZERO_COLOR) drawScaled(guiGraphics, text, textX, textY, color, false, scale) } private fun focusMatterAmountColor(amount: Double): Int = - if (amount == 0.0) 0x8EA09A else 0xE6ECE8 + if (amount == 0.0) FOCUS_MATTER_ZERO_COLOR else 0xE6ECE8 private fun drawTrimmed(guiGraphics: GuiGraphics, text: String, x: Int, y: Int, maxWidth: Int, color: Int) = drawTrimmed(guiGraphics, text, x, y, maxWidth, color, displayScale()) @@ -806,6 +820,7 @@ class MatterIndexScreen( private const val FOCUS_MATTER_ICON_SIZE = 8 private const val FOCUS_MATTER_VALUE_GAP = 3 private const val FOCUS_MATTER_AMOUNT_SCALE = 0.50f + private const val FOCUS_MATTER_ZERO_COLOR = 0x8EA09A private const val SCROLLBAR_W = 4 private const val SCROLLBAR_HIT_PAD = 3 private const val SLOT_SIZE = 28 @@ -828,6 +843,8 @@ class MatterIndexScreen( itemId = id, displayName = stack.hoverName.string, matterValues = compound.toMatterMap(), + replicatable = !stack.`is`(ReplicationTags.CANT_BE_SCANNED), + disintegratable = !stack.`is`(ReplicationTags.CANT_BE_DISINTEGRATED), extraSearchText = tags, ), stack = stack, diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoRecipeConversionSupport.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoRecipeConversionSupport.kt index 93c917c..c86e74c 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoRecipeConversionSupport.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoRecipeConversionSupport.kt @@ -1,14 +1,17 @@ package com.p_nsk.replicated_integration.core +import com.buuz135.replication.util.ReplicationTags import com.p_nsk.replicated_integration.api.graph.IConversionSink import com.p_nsk.replicated_integration.api.model.InputNodes import com.p_nsk.replicated_integration.api.model.LiteResourceLocation import com.p_nsk.replicated_integration.api.model.NodeAmount import com.p_nsk.replicated_integration.api.model.MatterConversion +import com.p_nsk.replicated_integration.api.node.MatterNodes import com.p_nsk.replicated_integration.api.node.NodeKey import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items import net.minecraft.world.item.crafting.Ingredient object NeoRecipeConversionSupport { @@ -26,6 +29,12 @@ object NeoRecipeConversionSupport { if (consumes.isEmpty()) { return } + // Passive Replication compatibility: this tag only blocks recipe-derived + // calculations. Explicit matter values and repint deny/set overrides stay + // in the higher-level default/selector pipeline. + if (produces.node.isSkipCalculationTaggedItem()) { + return + } collector.add( MatterConversion( id = id.toLite(), @@ -134,6 +143,15 @@ object NeoRecipeConversionSupport { ingredientInputNodesCache.clear() } + private fun NodeKey.isSkipCalculationTaggedItem(): Boolean { + if (type != MatterNodes.ITEM) { + return false + } + val itemId = ResourceLocation.fromNamespaceAndPath(id.namespace, id.path) + val item = BuiltInRegistries.ITEM.get(itemId).takeUnless { it == Items.AIR } ?: return false + return ItemStack(item).`is`(ReplicationTags.SKIP_CALCULATION) + } + fun ResourceLocation.toLite(): LiteResourceLocation = LiteResourceLocation.of(namespace, path) } diff --git a/build.gradle.kts b/build.gradle.kts index 517ed8a..f5aeb71 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -77,6 +77,14 @@ subprojects { url = uri("https://thedarkcolour.github.io/KotlinForForge/") } + maven { + name = "Architectury" + url = uri("https://maven.architectury.dev/") + content { + includeGroup("dev.architectury") + } + } + maven { name = "covers1624" url = uri("https://maven.covers1624.net/") @@ -86,6 +94,14 @@ subprojects { } } + maven { + name = "JitPack" + url = uri("https://jitpack.io") + content { + includeGroup("com.github.rtyley") + } + } + maven { name = "Curse Maven" url = uri("https://cursemaven.com") @@ -93,6 +109,15 @@ subprojects { includeGroup("curse.maven") } } + + maven { + name = "Latvian" + url = uri("https://maven.latvian.dev/releases") + content { + includeGroup ("dev.latvian.mods") + includeGroup ("dev.latvian.apps") + } + } } tasks.withType().configureEach { diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexModels.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexModels.kt index 539530c..a7491c1 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexModels.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterIndexModels.kt @@ -4,6 +4,8 @@ data class MatterIndexRecord( val itemId: String, val displayName: String, val matterValues: Map, + val replicatable: Boolean = true, + val disintegratable: Boolean = true, val extraSearchText: String = "", ) { val namespace: String = itemId.substringBefore(':', "") @@ -22,8 +24,8 @@ data class MatterIndexRecord( data class MatterIndexQuery( val searchText: String = "", val matterOnly: Boolean = true, - val replicableOnly: Boolean = false, - val decomposableOnly: Boolean = false, + val replicatableOnly: Boolean = false, + val disintegratableOnly: Boolean = false, val namespaceFilter: String? = null, val matterFilter: String? = null, ) @@ -50,8 +52,8 @@ object MatterIndexFiltering { fun parseQuery( raw: String, matterOnly: Boolean, - replicableOnly: Boolean, - decomposableOnly: Boolean, + replicatableOnly: Boolean, + disintegratableOnly: Boolean, ): MatterIndexQuery { var namespaceFilter: String? = null var matterFilter: String? = null @@ -72,8 +74,8 @@ object MatterIndexFiltering { return MatterIndexQuery( searchText = textTerms.joinToString(" "), matterOnly = matterOnly, - replicableOnly = replicableOnly, - decomposableOnly = decomposableOnly, + replicatableOnly = replicatableOnly, + disintegratableOnly = disintegratableOnly, namespaceFilter = namespaceFilter, matterFilter = matterFilter, ) @@ -91,8 +93,8 @@ object MatterIndexFiltering { val filtered = records.filter { record -> if (query.matterOnly && record.total <= 0.0) return@filter false - if (query.replicableOnly && record.total <= 0.0) return@filter false - if (query.decomposableOnly && record.total <= 0.0) return@filter false + if (query.replicatableOnly && !record.replicatable) return@filter false + if (query.disintegratableOnly && !record.disintegratable) return@filter false if (namespace != null && record.namespace.lowercase() != namespace) return@filter false if (matter != null && record.matterValues.none { (id, amount) -> id.lowercase().contains(matter) && amount > 0.0 }) { return@filter false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c0ceb63..4cea0b2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,6 +51,12 @@ curios-mc1211 = "6528893" configured-mc1201 = "5180900" configured-mc1211 = "7276577" +kubejs-mc1201 = "2001.6.4-build.120" +kubejs-mc1211 = "2101.7.2-build.285" +architectury-mc1201 = "9.2.14" +architectury-mc1211 = "13.0.8" +rhino-mc1201 = "2001.2.3-build.10" +rhino-mc1211 = "2101.2.7-build.82" [libraries] slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" } @@ -101,5 +107,12 @@ curios-mc1211 = { module = "curse.maven:curios-309927", version.ref = "curios-mc configured-mc1201 = { module = "curse.maven:configured-457570", version.ref = "configured-mc1201" } configured-mc1211 = { module = "curse.maven:configured-457570", version.ref = "configured-mc1211" } +kubejs-mc1201 = { module = "dev.latvian.mods:kubejs-forge", version.ref = "kubejs-mc1201" } +kubejs-mc1211 = { module = "dev.latvian.mods:kubejs-neoforge", version.ref = "kubejs-mc1211" } +architectury-mc1201 = { module = "dev.architectury:architectury-forge", version.ref = "architectury-mc1201" } +architectury-mc1211 = { module = "dev.architectury:architectury-neoforge", version.ref = "architectury-mc1211" } +rhino-mc1201 = { module = "dev.latvian.mods:rhino-forge", version.ref = "rhino-mc1201" } +rhino-mc1211 = { module = "dev.latvian.mods:rhino", version.ref = "rhino-mc1211" } + [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } From b64c9909488486ddac10a720930ee2b40f7b5c29 Mon Sep 17 00:00:00 2001 From: P-nutsK <107553542+P-nutsK@users.noreply.github.com> Date: Fri, 22 May 2026 05:24:25 +0900 Subject: [PATCH 06/12] =?UTF-8?q?wip:=20kjs=E5=AF=BE=E5=BF=9C=20=E5=8B=95?= =?UTF-8?q?=E4=BD=9C=E7=A2=BA=E8=AA=8D=E3=81=AF1.21.1=E3=81=AE=E3=81=BF.?= =?UTF-8?q?=20getRaw=E3=81=AA=E3=81=A9=E3=81=8C=E3=83=A2=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8B=E3=81=93?= =?UTF-8?q?=E3=81=A8=E3=81=AB=E6=B3=A8=E6=84=8F=E3=80=82=20ProbeJS?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E3=82=82=E3=81=82=E3=81=BE=E3=82=8A=E3=82=88?= =?UTF-8?q?=E3=82=8D=E3=81=97=E3=81=8F=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1.20.1-forge/build.gradle.kts | 1 + .../adapter/vanilla/BuiltinNodeResolver.kt | 15 +++ .../vanilla/ReplicationVanillaAddon.kt | 4 + .../command/MatterCommandTree.kt | 6 + .../replicated_integration/core/Aliases.kt | 1 + .../core/ForgeMatterNodeBuilder.kt | 9 +- .../ForgeReplicationCalculationService.kt | 42 ++++++- .../data/ForgeMatterConfigOverrides.kt | 25 +++- .../data/ForgeMatterRuntimeOverrides.kt | 8 ++ .../kubejs/ForgeKubeJSMatterRules.kt | 13 ++ .../kubejs/ForgeRepIntKubeJSPlugin.kt | 41 +++++++ .../src/main/resources/kubejs.plugins.txt | 1 + 1.21.1-neo/build.gradle.kts | 1 + .../adapter/mekanism/MekanismNodeResolver.kt | 11 ++ .../mekanism/ReplicationMekanismAddon.kt | 2 + .../adapter/vanilla/BuiltinNodeResolver.kt | 15 +++ .../vanilla/ReplicationVanillaAddon.kt | 4 + .../command/MatterCommandTree.kt | 6 + .../replicated_integration/core/Aliases.kt | 1 + .../core/NeoMatterNodeBuilder.kt | 8 ++ .../core/NeoReplicationCalculationService.kt | 43 ++++++- .../data/NeoMatterConfigOverrides.kt | 25 +++- .../data/NeoMatterRuntimeOverrides.kt | 8 ++ .../kubejs/NeoKubeJSMatterRules.kt | 13 ++ .../kubejs/NeoRepIntKubeJSPlugin.kt | 42 +++++++ .../src/main/resources/kubejs.plugins.txt | 1 + .../api/kubejs/RepInt.kt | 116 ++++++++++++++++++ .../api/model/ExplicitMatterValue.kt | 3 +- .../api/node/MatterNodeDef.kt | 1 + .../api/node/MatterNodeRegistry.kt | 6 +- .../api/selector/MatterRule.kt | 104 ++++++++++++++++ .../api/selector/MatterSelectorFormatter.kt | 3 + .../api/selector/MatterSelectorKind.kt | 1 + .../selector/MatterSelectorMaterializer.kt | 63 ++++------ .../selector/MatterSelectorStorageCodec.kt | 58 +++++++-- .../api/selector/MutableMatterSelectors.kt | 55 ++++----- .../test/kotlin/SimpleConversionSolverTest.kt | 35 ++++++ gradle/libs.versions.toml | 8 +- 38 files changed, 699 insertions(+), 100 deletions(-) create mode 100644 1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeKubeJSMatterRules.kt create mode 100644 1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntKubeJSPlugin.kt create mode 100644 1.20.1-forge/src/main/resources/kubejs.plugins.txt create mode 100644 1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoKubeJSMatterRules.kt create mode 100644 1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt create mode 100644 1.21.1-neo/src/main/resources/kubejs.plugins.txt create mode 100644 common/src/main/kotlin/com/p_nsk/replicated_integration/api/kubejs/RepInt.kt create mode 100644 common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterRule.kt diff --git a/1.20.1-forge/build.gradle.kts b/1.20.1-forge/build.gradle.kts index 033609c..51b574d 100644 --- a/1.20.1-forge/build.gradle.kts +++ b/1.20.1-forge/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { modRuntimeOnly(libs.emi.mc1201) modRuntimeOnly(libs.jade.mc1201) modRuntimeOnly(libs.configured.mc1201) + modRuntimeOnly(libs.probejsLegacy.mc1201) // ae2 dependency modRuntimeOnly(libs.guideme.mc1201) diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt index 9c64a8b..6b50d8c 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt @@ -54,6 +54,21 @@ object BuiltinNodeResolver { } .orElse(emptyList()) + fun allItemNodes(): List = + BuiltInRegistries.ITEM + .map { item -> MatterNodes.item(BuiltInRegistries.ITEM.getKey(item).toLite()) } + + fun allFluidNodes(): List = + BuiltInRegistries.FLUID + .filter { fluid -> fluid != net.minecraft.world.level.material.Fluids.EMPTY } + .map { fluid -> MatterNodes.fluid(BuiltInRegistries.FLUID.getKey(fluid).toLite()) } + + fun itemNodesInMod(modId: String): List = + allItemNodes().filter { it.id.namespace == modId } + + fun fluidNodesInMod(modId: String): List = + allFluidNodes().filter { it.id.namespace == modId } + fun itemAmount(stack: ItemStack): NodeAmount? = itemNode(stack)?.let { NodeAmount(it, stack.count.coerceAtLeast(1).toLong()) } diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt index 5fe6642..d1a494c 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt @@ -41,6 +41,8 @@ object ReplicationVanillaAddon : ForgeReplicationAddon { override fun registerMatterNodes(registry: ForgeMatterNodeRegistry) = with(registry) { node(MatterNodes.ITEM, "Item") { + enumerate(BuiltinNodeResolver::allItemNodes) + value( literal = "item", suggestions = VanillaMatterCommandInputs.itemSuggestions(), @@ -55,6 +57,8 @@ object ReplicationVanillaAddon : ForgeReplicationAddon { } node(MatterNodes.FLUID, "Fluid") { + enumerate(BuiltinNodeResolver::allFluidNodes) + value( literal = "fluid", suggestions = VanillaMatterCommandInputs.fluidSuggestions(), diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/command/MatterCommandTree.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/command/MatterCommandTree.kt index 0a11ffc..bb01de6 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/command/MatterCommandTree.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/command/MatterCommandTree.kt @@ -116,6 +116,12 @@ object MatterCommandTree { context, MatterCommandParsing.targetSelectorKey(context, target), ) + + MatterSelectorKind.MOD -> + MatterCommandActions.getSelector( + context, + MatterCommandParsing.targetSelectorKey(context, target), + ) } private fun targetSetSelector(target: ForgeMatterCommand): ArgumentBuilder = diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt index 77d7bd1..7807835 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt @@ -22,6 +22,7 @@ fun ForgeMatterNodeRegistry.node( id = id, displayName = displayName, formatter = formatter, + enumerateNodes = builder.buildEnumerator(), commands = builder.buildCommands(), ) ) diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeMatterNodeBuilder.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeMatterNodeBuilder.kt index f27126d..4a1ec1a 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeMatterNodeBuilder.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeMatterNodeBuilder.kt @@ -3,6 +3,7 @@ package com.p_nsk.replicated_integration.core import com.mojang.brigadier.suggestion.SuggestionProvider import com.p_nsk.replicated_integration.api.model.LiteResourceLocation import com.p_nsk.replicated_integration.api.node.MatterNodeCommands +import com.p_nsk.replicated_integration.api.node.NodeKey import com.p_nsk.replicated_integration.api.selector.MatterSelectorKind import net.minecraft.commands.CommandSourceStack import net.minecraft.resources.ResourceLocation @@ -13,6 +14,7 @@ class ForgeMatterNodeBuilder( ) { private var valueCommand: ForgeMatterCommand? = null private var tagCommand: ForgeMatterCommand? = null + private var enumerateNodes: () -> Iterable = { emptyList() } fun value( literal: String, @@ -50,10 +52,15 @@ class ForgeMatterNodeBuilder( ) } + fun enumerate(nodes: () -> Iterable) { + enumerateNodes = nodes + } + fun buildCommands(): MatterNodeCommands = MatterNodeCommands( value = valueCommand, tag = tagCommand, ) -} + fun buildEnumerator(): () -> Iterable = enumerateNodes +} diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt index 6d83df7..5623a3b 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt @@ -17,9 +17,11 @@ import com.p_nsk.replicated_integration.api.node.MutableMatterDefaults import com.p_nsk.replicated_integration.api.node.NodeKey import com.p_nsk.replicated_integration.api.selector.MatterSelectorMaterializer import com.p_nsk.replicated_integration.api.selector.MutableMatterSelectors +import com.p_nsk.replicated_integration.api.selector.formatSelector import com.p_nsk.replicated_integration.data.ForgeMatterConfigOverrides import com.p_nsk.replicated_integration.data.ForgeMatterRuntimeOverrides import com.p_nsk.replicated_integration.debug.MatterNodeDebugCache +import com.p_nsk.replicated_integration.kubejs.ForgeKubeJSMatterRules import com.p_nsk.replicated_integration.network.ReplicationCalculationSyncChannel import com.p_nsk.replicated_integration.network.ReplicationCalculationSyncPacket import net.minecraft.nbt.CompoundTag @@ -40,6 +42,7 @@ object ForgeReplicationCalculationService { @Volatile private var latestSyncHash: Int = 0 + private val warnedKubeJSConfigOverlaps = mutableSetOf() private val addons: ReplicationAddonRegistry by lazy { ReplicationAddonRegistry(ForgeReplicationAddonCollector.addons()) } @@ -61,11 +64,16 @@ object ForgeReplicationCalculationService { addon.collectDefaults(context, defaults) addon.collectSelectors(context, selectors) } - selectors.putAll(ForgeMatterConfigOverrides.snapshot()) - selectors.putAll(ForgeMatterRuntimeOverrides.snapshot(server)) + val configRules = ForgeMatterConfigOverrides.snapshotRules() + val kubeJSRules = ForgeKubeJSMatterRules.snapshot() + warnKubeJSConfigOverlap(configRules, kubeJSRules) + selectors.putRules(configRules) + selectors.putRules(kubeJSRules) + selectors.putRules(ForgeMatterRuntimeOverrides.snapshotRules(server)) val selectorSnapshot = selectors.snapshot() - defaults.putAll(MatterSelectorMaterializer.materialize(selectorSnapshot, ::expandSelectorTag)) + val selectorRules = selectors.snapshotRules() + defaults.putAll(MatterSelectorMaterializer.materialize(selectorRules, ::expandSelectorTag, ::expandSelectorMod, ::allMatterNodes)) val explicitSnapshot = defaults.snapshot() for (addon in activeAddons) { addon.collectConversions(context, builder) @@ -195,6 +203,34 @@ object ForgeReplicationCalculationService { } } + private fun expandSelectorMod(type: LiteResourceLocation, modId: String): Iterable = + when (type) { + MatterNodes.ITEM -> BuiltinNodeResolver.itemNodesInMod(modId) + MatterNodes.FLUID -> BuiltinNodeResolver.fluidNodesInMod(modId) + else -> emptyList() + } + + private fun allMatterNodes(): Iterable = + matterNodeRegistry().all().flatMap { it.enumerateNodes() } + + private fun warnKubeJSConfigOverlap( + configRules: List, + kubeJSRules: List, + ) { + val configKeys = configRules.mapNotNullTo(linkedSetOf()) { it.selector.staticKey } + val kubeKeys = kubeJSRules.mapNotNullTo(linkedSetOf()) { it.selector.staticKey } + for (key in configKeys.intersect(kubeKeys)) { + val warningKey = key.toString() + if (!warnedKubeJSConfigOverlaps.add(warningKey)) { + continue + } + Constants.LOGGER.warn( + "Matter selector {} is defined by both config and KubeJS; KubeJS has higher source priority", + matterNodeRegistry().formatSelector(key), + ) + } + } + private fun LiteMatterCompound.toMatterCompound(): MatterCompound? { val registry = ReplicationRegistry.MATTER_TYPES_REGISTRY.get() val compound = MatterCompound() diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ForgeMatterConfigOverrides.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ForgeMatterConfigOverrides.kt index 29b221f..550ff8e 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ForgeMatterConfigOverrides.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ForgeMatterConfigOverrides.kt @@ -2,18 +2,26 @@ package com.p_nsk.replicated_integration.data import com.p_nsk.replicated_integration.api.model.ExplicitMatterSource import com.p_nsk.replicated_integration.api.model.ExplicitMatterValue +import com.p_nsk.replicated_integration.api.selector.MatterRule import com.p_nsk.replicated_integration.api.selector.MatterSelectorKey import com.p_nsk.replicated_integration.api.selector.MatterSelectorStorageCodec +import com.p_nsk.replicated_integration.api.selector.asRuleSelector import net.minecraft.server.MinecraftServer import net.minecraftforge.fml.loading.FMLPaths import java.nio.file.Path object ForgeMatterConfigOverrides { private var loadedFrom: Path? = null - private var values: Map = emptyMap() + private var values: List = emptyList() @Synchronized fun snapshot(): Map { + loadIfNeeded() + return values.toStaticMap() + } + + @Synchronized + fun snapshotRules(): List { loadIfNeeded() return values } @@ -25,7 +33,7 @@ object ForgeMatterConfigOverrides { if (runtime.isEmpty()) { return 0 } - values = values.toMutableMap().also { it.putAll(runtime.mapValues { (_, value) -> value.asConfigValue() }) } + values += runtime.map { (selector, value) -> MatterRule(selector.asRuleSelector(), value.asConfigValue()) } save() ForgeMatterRuntimeOverrides.clear(server) return runtime.size @@ -37,12 +45,12 @@ object ForgeMatterConfigOverrides { return } loadedFrom = path - values = MatterSelectorStorageCodec.load(path, ExplicitMatterSource.CONFIG) + values = MatterSelectorStorageCodec.loadRules(path, ExplicitMatterSource.CONFIG) } private fun save() { val path = filePath() - MatterSelectorStorageCodec.save(path, values) + MatterSelectorStorageCodec.saveRules(path, values) } private fun filePath(): Path = @@ -53,4 +61,13 @@ object ForgeMatterConfigOverrides { is ExplicitMatterValue.Deny -> ExplicitMatterValue.Deny(ExplicitMatterSource.CONFIG) is ExplicitMatterValue.Set -> ExplicitMatterValue.Set(compound, ExplicitMatterSource.CONFIG) } + + private fun List.toStaticMap(): Map { + val map = linkedMapOf() + for (rule in this.sortedWith(compareBy { it.source.priority })) { + val key = rule.selector.staticKey ?: continue + map[key] = rule.value + } + return map + } } diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ForgeMatterRuntimeOverrides.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ForgeMatterRuntimeOverrides.kt index aafeeb3..a822c96 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ForgeMatterRuntimeOverrides.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ForgeMatterRuntimeOverrides.kt @@ -3,8 +3,10 @@ package com.p_nsk.replicated_integration.data import com.p_nsk.replicated_integration.api.model.ExplicitMatterSource import com.p_nsk.replicated_integration.api.model.ExplicitMatterValue import com.p_nsk.replicated_integration.api.model.LiteMatterCompound +import com.p_nsk.replicated_integration.api.selector.MatterRule import com.p_nsk.replicated_integration.api.selector.MatterSelectorKey import com.p_nsk.replicated_integration.api.selector.MatterSelectorStorageCodec +import com.p_nsk.replicated_integration.api.selector.asRuleSelector import net.minecraft.server.MinecraftServer import net.minecraft.world.level.storage.LevelResource import java.nio.file.Path @@ -19,6 +21,12 @@ object ForgeMatterRuntimeOverrides { return values } + @Synchronized + fun snapshotRules(server: MinecraftServer): List { + loadIfNeeded(server) + return values.map { (selector, value) -> MatterRule(selector.asRuleSelector(), value) } + } + @Synchronized fun set( server: MinecraftServer, diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeKubeJSMatterRules.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeKubeJSMatterRules.kt new file mode 100644 index 0000000..5580ad9 --- /dev/null +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeKubeJSMatterRules.kt @@ -0,0 +1,13 @@ +package com.p_nsk.replicated_integration.kubejs + +import com.p_nsk.replicated_integration.api.selector.MatterRule +import net.minecraftforge.fml.ModList + +object ForgeKubeJSMatterRules { + fun snapshot(): List = + if (ModList.get().isLoaded("kubejs")) { + ForgeRepIntKubeJSEvents.collectRules() + } else { + emptyList() + } +} diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntKubeJSPlugin.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntKubeJSPlugin.kt new file mode 100644 index 0000000..4bb57b0 --- /dev/null +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntKubeJSPlugin.kt @@ -0,0 +1,41 @@ +package com.p_nsk.replicated_integration.kubejs + +import com.p_nsk.replicated_integration.api.kubejs.RepInt +import com.p_nsk.replicated_integration.api.kubejs.RepIntMatterEvent +import com.p_nsk.replicated_integration.api.selector.MatterRule +import dev.latvian.mods.kubejs.KubeJSPlugin +import dev.latvian.mods.kubejs.event.EventGroup +import dev.latvian.mods.kubejs.event.EventJS +import dev.latvian.mods.kubejs.script.BindingsEvent + +class ForgeRepIntKubeJSPlugin : KubeJSPlugin() { + override fun registerEvents() { + ForgeRepIntKubeJSEvents.GROUP.register() + } + + override fun registerBindings(event: BindingsEvent) { + event.add("RepInt", RepInt) + } +} + +object ForgeRepIntKubeJSEvents { + val GROUP: EventGroup = EventGroup.of("RepIntEvents") + val MATTER = GROUP.server("matter") { ForgeRepIntMatterEvent::class.java } + + fun collectRules(): List { + val event = ForgeRepIntMatterEvent() + MATTER.post(event) + return event.rules + } +} + +class ForgeRepIntMatterEvent : EventJS() { + private val delegate = RepIntMatterEvent() + + val rules: List + get() = delegate.rules + + fun deny(selector: Any) = delegate.deny(selector) + + fun set(selector: Any, matter: Map) = delegate.set(selector, matter) +} diff --git a/1.20.1-forge/src/main/resources/kubejs.plugins.txt b/1.20.1-forge/src/main/resources/kubejs.plugins.txt new file mode 100644 index 0000000..ec3fe48 --- /dev/null +++ b/1.20.1-forge/src/main/resources/kubejs.plugins.txt @@ -0,0 +1 @@ +com.p_nsk.replicated_integration.kubejs.ForgeRepIntKubeJSPlugin diff --git a/1.21.1-neo/build.gradle.kts b/1.21.1-neo/build.gradle.kts index 69cc305..b044eef 100644 --- a/1.21.1-neo/build.gradle.kts +++ b/1.21.1-neo/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { runtimeOnly(libs.jade.mc1211) runtimeOnly(libs.guideme.mc1211) runtimeOnly(libs.configured.mc1211) + runtimeOnly(libs.probejs.mc1211) } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt index ec36b17..e6921e6 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt @@ -33,6 +33,17 @@ object MekanismNodeResolver { } .orElse(emptyList()) + @Suppress("DEPRECATION") + fun allChemicalNodes(): List = + MekanismAPI.CHEMICAL_REGISTRY + .mapNotNull { chemical -> + val chemicalId = MekanismAPI.CHEMICAL_REGISTRY.getKey(chemical) + if (chemicalId == MekanismAPI.EMPTY_CHEMICAL_KEY) null else MatterNodes.chemical(chemicalId.toLite()) + } + + fun chemicalNodesInMod(modId: String): List = + allChemicalNodes().filter { it.id.namespace == modId } + private fun ResourceLocation.toLite(): LiteResourceLocation = LiteResourceLocation.of(namespace, path) } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt index b62ff5e..c000c19 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt @@ -31,6 +31,8 @@ object ReplicationMekanismAddon : NeoReplicationAddon { override fun registerMatterNodes(registry: NeoMatterNodeRegistry) = with(registry) { node(MatterNodes.CHEMICAL, "Chemical") { + enumerate(MekanismNodeResolver::allChemicalNodes) + value( literal = "chemical", suggestions = MekanismMatterCommandInputs.chemicalSuggestions(), diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt index 01f388b..f64f26a 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt @@ -53,6 +53,21 @@ object BuiltinNodeResolver { } .orElse(emptyList()) + fun allItemNodes(): List = + BuiltInRegistries.ITEM + .map { item -> MatterNodes.item(BuiltInRegistries.ITEM.getKey(item).toLite()) } + + fun allFluidNodes(): List = + BuiltInRegistries.FLUID + .filter { fluid -> fluid != net.minecraft.world.level.material.Fluids.EMPTY } + .map { fluid -> MatterNodes.fluid(BuiltInRegistries.FLUID.getKey(fluid).toLite()) } + + fun itemNodesInMod(modId: String): List = + allItemNodes().filter { it.id.namespace == modId } + + fun fluidNodesInMod(modId: String): List = + allFluidNodes().filter { it.id.namespace == modId } + fun itemAmount(stack: ItemStack): NodeAmount? = itemNode(stack)?.let { NodeAmount(it, stack.count.coerceAtLeast(1).toLong()) } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt index e4d5a7e..441a6e5 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt @@ -39,6 +39,8 @@ object ReplicationVanillaAddon : NeoReplicationAddon { override fun registerMatterNodes(registry: NeoMatterNodeRegistry) = with(registry) { node(MatterNodes.ITEM, "Item") { + enumerate(BuiltinNodeResolver::allItemNodes) + value( literal = "item", suggestions = VanillaMatterCommandInputs.itemSuggestions(), @@ -53,6 +55,8 @@ object ReplicationVanillaAddon : NeoReplicationAddon { } node(MatterNodes.FLUID, "Fluid") { + enumerate(BuiltinNodeResolver::allFluidNodes) + value( literal = "fluid", suggestions = VanillaMatterCommandInputs.fluidSuggestions(), diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/command/MatterCommandTree.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/command/MatterCommandTree.kt index 9cb3768..7ea18bf 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/command/MatterCommandTree.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/command/MatterCommandTree.kt @@ -115,6 +115,12 @@ object MatterCommandTree { context, MatterCommandParsing.targetSelectorKey(context, target), ) + + MatterSelectorKind.MOD -> + MatterCommandActions.getSelector( + context, + MatterCommandParsing.targetSelectorKey(context, target), + ) } private fun targetSetSelector(target: NeoMatterCommand): ArgumentBuilder = diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt index ad61216..85a1f6c 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt @@ -22,6 +22,7 @@ fun NeoMatterNodeRegistry.node( id = id, displayName = displayName, formatter = formatter, + enumerateNodes = builder.buildEnumerator(), commands = builder.buildCommands(), ) ) diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoMatterNodeBuilder.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoMatterNodeBuilder.kt index f9a1500..ed08849 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoMatterNodeBuilder.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoMatterNodeBuilder.kt @@ -3,6 +3,7 @@ package com.p_nsk.replicated_integration.core import com.mojang.brigadier.suggestion.SuggestionProvider import com.p_nsk.replicated_integration.api.model.LiteResourceLocation import com.p_nsk.replicated_integration.api.node.MatterNodeCommands +import com.p_nsk.replicated_integration.api.node.NodeKey import com.p_nsk.replicated_integration.api.selector.MatterSelectorKind import net.minecraft.commands.CommandSourceStack import net.minecraft.resources.ResourceLocation @@ -13,6 +14,7 @@ class NeoMatterNodeBuilder( ) { private var valueCommand: NeoMatterCommand? = null private var tagCommand: NeoMatterCommand? = null + private var enumerateNodes: () -> Iterable = { emptyList() } fun value( literal: String, @@ -50,9 +52,15 @@ class NeoMatterNodeBuilder( ) } + fun enumerate(nodes: () -> Iterable) { + enumerateNodes = nodes + } + fun buildCommands(): MatterNodeCommands = MatterNodeCommands( value = valueCommand, tag = tagCommand, ) + + fun buildEnumerator(): () -> Iterable = enumerateNodes } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoReplicationCalculationService.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoReplicationCalculationService.kt index 4937639..55234e1 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoReplicationCalculationService.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoReplicationCalculationService.kt @@ -17,11 +17,13 @@ import com.p_nsk.replicated_integration.api.node.MatterNodes import com.p_nsk.replicated_integration.api.node.MutableMatterDefaults import com.p_nsk.replicated_integration.api.selector.MutableMatterSelectors import com.p_nsk.replicated_integration.api.selector.MatterSelectorMaterializer +import com.p_nsk.replicated_integration.api.selector.formatSelector import com.p_nsk.replicated_integration.api.addon.ReplicationAddonRegistry import com.p_nsk.replicated_integration.api.graph.SimpleConversionSolver import com.p_nsk.replicated_integration.debug.MatterNodeDebugCache import com.p_nsk.replicated_integration.data.NeoMatterConfigOverrides import com.p_nsk.replicated_integration.data.NeoMatterRuntimeOverrides +import com.p_nsk.replicated_integration.kubejs.NeoKubeJSMatterRules import com.p_nsk.replicated_integration.network.NeoReplicationCalculationSyncPayload import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.nbt.CompoundTag @@ -46,6 +48,7 @@ object NeoReplicationCalculationService { @Volatile private var latestSyncId: Long = 0L + private val warnedKubeJSConfigOverlaps = mutableSetOf() private val addons: ReplicationAddonRegistry by lazy { ReplicationAddonRegistry(NeoReplicationAddonCollector.addons()) @@ -68,11 +71,16 @@ object NeoReplicationCalculationService { addon.collectDefaults(context, defaults) addon.collectSelectors(context, selectors) } - selectors.putAll(NeoMatterConfigOverrides.snapshot()) - selectors.putAll(NeoMatterRuntimeOverrides.snapshot(server)) + val configRules = NeoMatterConfigOverrides.snapshotRules() + val kubeJSRules = NeoKubeJSMatterRules.snapshot() + warnKubeJSConfigOverlap(configRules, kubeJSRules) + selectors.putRules(configRules) + selectors.putRules(kubeJSRules) + selectors.putRules(NeoMatterRuntimeOverrides.snapshotRules(server)) val selectorSnapshot = selectors.snapshot() - defaults.putAll(MatterSelectorMaterializer.materialize(selectorSnapshot, ::expandSelectorTag)) + val selectorRules = selectors.snapshotRules() + defaults.putAll(MatterSelectorMaterializer.materialize(selectorRules, ::expandSelectorTag, ::expandSelectorMod, ::allMatterNodes)) val explicitSnapshot = defaults.snapshot() for (addon in activeAddons) { addon.collectConversions(context, builder) @@ -183,6 +191,35 @@ object NeoReplicationCalculationService { } } + private fun expandSelectorMod(type: LiteResourceLocation, modId: String): Iterable = + when (type) { + MatterNodes.ITEM -> BuiltinNodeResolver.itemNodesInMod(modId) + MatterNodes.FLUID -> BuiltinNodeResolver.fluidNodesInMod(modId) + MatterNodes.CHEMICAL -> MekanismNodeResolver.chemicalNodesInMod(modId) + else -> emptyList() + } + + private fun allMatterNodes(): Iterable = + matterNodeRegistry().all().flatMap { it.enumerateNodes() } + + private fun warnKubeJSConfigOverlap( + configRules: List, + kubeJSRules: List, + ) { + val configKeys = configRules.mapNotNullTo(linkedSetOf()) { it.selector.staticKey } + val kubeKeys = kubeJSRules.mapNotNullTo(linkedSetOf()) { it.selector.staticKey } + for (key in configKeys.intersect(kubeKeys)) { + val warningKey = key.toString() + if (!warnedKubeJSConfigOverlaps.add(warningKey)) { + continue + } + Constants.LOGGER.warn( + "Matter selector {} is defined by both config and KubeJS; KubeJS has higher source priority", + matterNodeRegistry().formatSelector(key), + ) + } + } + fun syncLatestToPlayer(player: ServerPlayer) { if (latestSyncTag.isEmpty) { return diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoMatterConfigOverrides.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoMatterConfigOverrides.kt index ab31c81..d5727fc 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoMatterConfigOverrides.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoMatterConfigOverrides.kt @@ -2,18 +2,26 @@ package com.p_nsk.replicated_integration.data import com.p_nsk.replicated_integration.api.model.ExplicitMatterSource import com.p_nsk.replicated_integration.api.model.ExplicitMatterValue +import com.p_nsk.replicated_integration.api.selector.MatterRule import com.p_nsk.replicated_integration.api.selector.MatterSelectorKey import com.p_nsk.replicated_integration.api.selector.MatterSelectorStorageCodec +import com.p_nsk.replicated_integration.api.selector.asRuleSelector import net.minecraft.server.MinecraftServer import net.neoforged.fml.loading.FMLPaths import java.nio.file.Path object NeoMatterConfigOverrides { private var loadedFrom: Path? = null - private var values: Map = emptyMap() + private var values: List = emptyList() @Synchronized fun snapshot(): Map { + loadIfNeeded() + return values.toStaticMap() + } + + @Synchronized + fun snapshotRules(): List { loadIfNeeded() return values } @@ -25,7 +33,7 @@ object NeoMatterConfigOverrides { if (runtime.isEmpty()) { return 0 } - values = values.toMutableMap().also { it.putAll(runtime.mapValues { (_, value) -> value.asConfigValue() }) } + values += runtime.map { (selector, value) -> MatterRule(selector.asRuleSelector(), value.asConfigValue()) } save() NeoMatterRuntimeOverrides.clear(server) return runtime.size @@ -37,12 +45,12 @@ object NeoMatterConfigOverrides { return } loadedFrom = path - values = MatterSelectorStorageCodec.load(path, ExplicitMatterSource.CONFIG) + values = MatterSelectorStorageCodec.loadRules(path, ExplicitMatterSource.CONFIG) } private fun save() { val path = filePath() - MatterSelectorStorageCodec.save(path, values) + MatterSelectorStorageCodec.saveRules(path, values) } private fun filePath(): Path = @@ -53,4 +61,13 @@ object NeoMatterConfigOverrides { is ExplicitMatterValue.Deny -> ExplicitMatterValue.Deny(ExplicitMatterSource.CONFIG) is ExplicitMatterValue.Set -> ExplicitMatterValue.Set(compound, ExplicitMatterSource.CONFIG) } + + private fun List.toStaticMap(): Map { + val map = linkedMapOf() + for (rule in this.sortedWith(compareBy { it.source.priority })) { + val key = rule.selector.staticKey ?: continue + map[key] = rule.value + } + return map + } } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoMatterRuntimeOverrides.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoMatterRuntimeOverrides.kt index 317d1eb..099bb60 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoMatterRuntimeOverrides.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoMatterRuntimeOverrides.kt @@ -3,8 +3,10 @@ package com.p_nsk.replicated_integration.data import com.p_nsk.replicated_integration.api.model.ExplicitMatterSource import com.p_nsk.replicated_integration.api.model.ExplicitMatterValue import com.p_nsk.replicated_integration.api.model.LiteMatterCompound +import com.p_nsk.replicated_integration.api.selector.MatterRule import com.p_nsk.replicated_integration.api.selector.MatterSelectorKey import com.p_nsk.replicated_integration.api.selector.MatterSelectorStorageCodec +import com.p_nsk.replicated_integration.api.selector.asRuleSelector import net.minecraft.server.MinecraftServer import net.minecraft.world.level.storage.LevelResource import java.nio.file.Path @@ -19,6 +21,12 @@ object NeoMatterRuntimeOverrides { return values } + @Synchronized + fun snapshotRules(server: MinecraftServer): List { + loadIfNeeded(server) + return values.map { (selector, value) -> MatterRule(selector.asRuleSelector(), value) } + } + @Synchronized fun set( server: MinecraftServer, diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoKubeJSMatterRules.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoKubeJSMatterRules.kt new file mode 100644 index 0000000..4750826 --- /dev/null +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoKubeJSMatterRules.kt @@ -0,0 +1,13 @@ +package com.p_nsk.replicated_integration.kubejs + +import com.p_nsk.replicated_integration.api.selector.MatterRule +import net.neoforged.fml.ModList + +object NeoKubeJSMatterRules { + fun snapshot(): List = + if (ModList.get().isLoaded("kubejs")) { + NeoRepIntKubeJSEvents.collectRules() + } else { + emptyList() + } +} diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt new file mode 100644 index 0000000..b8e81cf --- /dev/null +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt @@ -0,0 +1,42 @@ +package com.p_nsk.replicated_integration.kubejs + +import com.p_nsk.replicated_integration.api.kubejs.RepInt +import com.p_nsk.replicated_integration.api.kubejs.RepIntMatterEvent +import com.p_nsk.replicated_integration.api.selector.MatterRule +import dev.latvian.mods.kubejs.event.EventGroup +import dev.latvian.mods.kubejs.event.EventGroupRegistry +import dev.latvian.mods.kubejs.event.KubeEvent +import dev.latvian.mods.kubejs.plugin.KubeJSPlugin +import dev.latvian.mods.kubejs.script.BindingRegistry + +class NeoRepIntKubeJSPlugin : KubeJSPlugin { + override fun registerEvents(registry: EventGroupRegistry) { + registry.register(NeoRepIntKubeJSEvents.GROUP) + } + + override fun registerBindings(registry: BindingRegistry) { + registry.add("RepInt", RepInt::class.java) + } +} + +object NeoRepIntKubeJSEvents { + val GROUP: EventGroup = EventGroup.of("RepIntEvents") + val MATTER = GROUP.server("matter") { NeoRepIntMatterEvent::class.java } + + fun collectRules(): List { + val event = NeoRepIntMatterEvent() + MATTER.post(event) + return event.rules + } +} + +class NeoRepIntMatterEvent : KubeEvent { + private val delegate = RepIntMatterEvent() + + val rules: List + get() = delegate.rules + + fun deny(selector: Any) = delegate.deny(selector) + + fun set(selector: Any, matter: Map) = delegate.set(selector, matter) +} diff --git a/1.21.1-neo/src/main/resources/kubejs.plugins.txt b/1.21.1-neo/src/main/resources/kubejs.plugins.txt new file mode 100644 index 0000000..6d05038 --- /dev/null +++ b/1.21.1-neo/src/main/resources/kubejs.plugins.txt @@ -0,0 +1 @@ +com.p_nsk.replicated_integration.kubejs.NeoRepIntKubeJSPlugin diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/kubejs/RepInt.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/kubejs/RepInt.kt new file mode 100644 index 0000000..ecadf10 --- /dev/null +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/kubejs/RepInt.kt @@ -0,0 +1,116 @@ +package com.p_nsk.replicated_integration.api.kubejs + +import com.p_nsk.replicated_integration.api.model.ExplicitMatterSource +import com.p_nsk.replicated_integration.api.model.ExplicitMatterValue +import com.p_nsk.replicated_integration.api.model.LiteMatterCompound +import com.p_nsk.replicated_integration.api.model.LiteResourceLocation +import com.p_nsk.replicated_integration.api.node.MatterNodes +import com.p_nsk.replicated_integration.api.selector.AllMatterRuleSelector +import com.p_nsk.replicated_integration.api.selector.AnyMatterRuleSelector +import com.p_nsk.replicated_integration.api.selector.MatterRule +import com.p_nsk.replicated_integration.api.selector.MatterRuleSelector +import com.p_nsk.replicated_integration.api.selector.MatterSelectorKey +import com.p_nsk.replicated_integration.api.selector.MatterSelectorKind +import com.p_nsk.replicated_integration.api.selector.NodeView +import com.p_nsk.replicated_integration.api.selector.NotMatterRuleSelector +import com.p_nsk.replicated_integration.api.selector.PredicateMatterRuleSelector +import com.p_nsk.replicated_integration.api.selector.asRuleSelector +import java.util.function.Predicate + +object RepInt { + @JvmStatic fun node(type: String, id: String): MatterRuleSelector = + static(MatterSelectorKind.NODE, type, id) + + @JvmStatic fun nodeTag(type: String, tag: String): MatterRuleSelector = + static(MatterSelectorKind.TAG, type, tag) + + @JvmStatic fun nodeMod(type: String, modId: String): MatterRuleSelector = + MatterSelectorKey(MatterSelectorKind.MOD, type.toLite(), LiteResourceLocation.of("mod", modId)).asRuleSelector() + + @JvmStatic fun item(id: String): MatterRuleSelector = + node(MatterNodes.ITEM.toString(), id) + + @JvmStatic fun itemTag(tag: String): MatterRuleSelector = + nodeTag(MatterNodes.ITEM.toString(), tag) + + @JvmStatic fun itemMod(modId: String): MatterRuleSelector = + nodeMod(MatterNodes.ITEM.toString(), modId) + + @JvmStatic fun fluid(id: String): MatterRuleSelector = + node(MatterNodes.FLUID.toString(), id) + + @JvmStatic fun fluidTag(tag: String): MatterRuleSelector = + nodeTag(MatterNodes.FLUID.toString(), tag) + + @JvmStatic fun fluidMod(modId: String): MatterRuleSelector = + nodeMod(MatterNodes.FLUID.toString(), modId) + + @JvmStatic fun anyOf(vararg selectors: Any): MatterRuleSelector = + AnyMatterRuleSelector(selectors.map(::selector)) + + @JvmStatic fun allOf(vararg selectors: Any): MatterRuleSelector = + AllMatterRuleSelector(selectors.map(::selector)) + + @JvmStatic fun not(selector: Any): MatterRuleSelector = + NotMatterRuleSelector(selector(selector)) + + @JvmStatic fun whereNode(name: String, predicate: Predicate): MatterRuleSelector = + PredicateMatterRuleSelector(name) { predicate.test(it) } + + @JvmStatic fun whereItem(name: String, predicate: Predicate): MatterRuleSelector = + whereNode(name, Predicate { it.key.type == MatterNodes.ITEM && predicate.test(it) }) + + internal fun selector(value: Any): MatterRuleSelector = + when (value) { + is MatterRuleSelector -> value + is MatterSelectorKey -> value.asRuleSelector() + is String -> item(value) + else -> throw IllegalArgumentException("Unsupported RepInt selector: $value") + } + + private fun static(kind: MatterSelectorKind, type: String, id: String): MatterRuleSelector = + MatterSelectorKey(kind, type.toLite(), id.toLite()).asRuleSelector() + + private fun String.toLite(): LiteResourceLocation { + val parts = split(':', limit = 2) + require(parts.size == 2) { "Invalid resource location: $this" } + return LiteResourceLocation.of(parts[0], parts[1]) + } +} + +open class RepIntMatterEvent { + private val mutableRules = mutableListOf() + + val rules: List + get() = mutableRules.toList() + + fun deny(selector: Any) { + mutableRules += MatterRule(RepInt.selector(selector), ExplicitMatterValue.Deny(ExplicitMatterSource.KUBEJS)) + } + + fun set(selector: Any, matter: Map) { + mutableRules += MatterRule( + RepInt.selector(selector), + ExplicitMatterValue.Set(parseMatter(matter), ExplicitMatterSource.KUBEJS), + ) + } + + private fun parseMatter(value: Map): LiteMatterCompound { + val values = linkedMapOf() + for ((key, amount) in value) { + val id = key.toMatterId() + if (amount > 0.0) { + values[id] = amount + } + } + return LiteMatterCompound(values) + } + + private fun String.toMatterId(): LiteResourceLocation = + if (contains(':')) { + val parts = split(':', limit = 2) + LiteResourceLocation.of(parts[0], parts[1]) + } else { + LiteResourceLocation.of("replication", this) + } +} diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/model/ExplicitMatterValue.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/model/ExplicitMatterValue.kt index e2054e6..c8f887d 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/model/ExplicitMatterValue.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/model/ExplicitMatterValue.kt @@ -7,7 +7,8 @@ enum class ExplicitMatterSource( NODE_VALUE(0, "replicated_integration node value"), DATAPACK(1, "replication matter_values"), CONFIG(2, "config override"), - RUNTIME(3, "runtime override"), + KUBEJS(3, "KubeJS override"), + RUNTIME(4, "runtime override"), } sealed interface ExplicitMatterValue { diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeDef.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeDef.kt index 4687114..f25a295 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeDef.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeDef.kt @@ -7,6 +7,7 @@ data class MatterNodeDef( val id: LiteResourceLocation, val displayName: String, val formatter: (LiteResourceLocation) -> String = LiteResourceLocation::toString, + val enumerateNodes: () -> Iterable = { emptyList() }, val commands: MatterNodeCommands = MatterNodeCommands(), ) diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeRegistry.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeRegistry.kt index 81e6f89..308a409 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeRegistry.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeRegistry.kt @@ -6,6 +6,7 @@ import com.p_nsk.replicated_integration.api.model.LiteResourceLocation class MatterNodeRegistry { private val nodes = linkedMapOf>() private val commandsByLiteral = linkedMapOf() + private val emptyEnumerator: () -> Iterable = { emptyList() } fun register(node: MatterNodeDef) { val previous = nodes[node.id] @@ -39,5 +40,8 @@ class MatterNodeRegistry { commandsByLiteral.values.toList() private fun MatterNodeDef.withoutCommands(): MatterNodeDef = - copy(commands = MatterNodeCommands()) + copy( + commands = MatterNodeCommands(), + enumerateNodes = emptyEnumerator, + ) } diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterRule.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterRule.kt new file mode 100644 index 0000000..2ac3619 --- /dev/null +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterRule.kt @@ -0,0 +1,104 @@ +package com.p_nsk.replicated_integration.api.selector + +import com.p_nsk.replicated_integration.api.model.ExplicitMatterValue +import com.p_nsk.replicated_integration.api.model.LiteResourceLocation +import com.p_nsk.replicated_integration.api.node.NodeKey +import java.util.function.Supplier + +data class MatterRule( + val selector: MatterRuleSelector, + val value: ExplicitMatterValue, +) { + val source = value.source +} + +class MatterRuleExpansion( + val expandTag: (type: LiteResourceLocation, id: LiteResourceLocation) -> Iterable, + val expandMod: (type: LiteResourceLocation, modId: String) -> Iterable, + val allNodes: () -> Iterable, +) + +sealed interface MatterRuleSelector { + val staticKey: MatterSelectorKey? + + fun expand(expansion: MatterRuleExpansion): Set +} + +data class StaticMatterRuleSelector( + val key: MatterSelectorKey, +) : MatterRuleSelector { + override val staticKey: MatterSelectorKey = key + + override fun expand(expansion: MatterRuleExpansion): Set = + when (key.kind) { + MatterSelectorKind.NODE -> linkedSetOf(NodeKey(key.type, key.id)) + MatterSelectorKind.TAG -> expansion.expandTag(key.type, key.id).toCollection(linkedSetOf()) + MatterSelectorKind.MOD -> expansion.expandMod(key.type, key.id.path).toCollection(linkedSetOf()) + } +} + +data class AnyMatterRuleSelector( + val children: List, +) : MatterRuleSelector { + override val staticKey: MatterSelectorKey? = null + + override fun expand(expansion: MatterRuleExpansion): Set = + children.flatMapTo(linkedSetOf()) { it.expand(expansion) } +} + +data class AllMatterRuleSelector( + val children: List, +) : MatterRuleSelector { + override val staticKey: MatterSelectorKey? = null + + override fun expand(expansion: MatterRuleExpansion): Set { + if (children.isEmpty()) { + return emptySet() + } + val result = children.first().expand(expansion).toMutableSet() + for (child in children.drop(1)) { + result.retainAll(child.expand(expansion)) + } + return result + } +} + +data class NotMatterRuleSelector( + val child: MatterRuleSelector, +) : MatterRuleSelector { + override val staticKey: MatterSelectorKey? = null + + override fun expand(expansion: MatterRuleExpansion): Set { + val excluded = child.expand(expansion) + return expansion.allNodes().filterTo(linkedSetOf()) { it !in excluded } + } +} + +class PredicateMatterRuleSelector( + val name: String, + private val predicate: (NodeView) -> Boolean, +) : MatterRuleSelector { + override val staticKey: MatterSelectorKey? = null + + override fun expand(expansion: MatterRuleExpansion): Set = + expansion.allNodes().filterTo(linkedSetOf()) { node -> + predicate(NodeView(node)) + } + + override fun toString(): String = "whereNode($name)" +} + +class NodeView( + val key: NodeKey, + private val raw: Supplier = Supplier { null }, +) { + val type: String = key.type.toString() + val id: String = key.id.toString() + val modId: String = key.id.namespace + + @Suppress("UNCHECKED_CAST") + fun getRaw(): T? = raw as? T +} + +fun MatterSelectorKey.asRuleSelector(): MatterRuleSelector = + StaticMatterRuleSelector(this) diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorFormatter.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorFormatter.kt index cc089ae..b1f857d 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorFormatter.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorFormatter.kt @@ -12,4 +12,7 @@ fun MatterNodeRegistry<*>.formatSelector(selector: MatterSelectorKey): String = MatterSelectorKind.TAG -> "#${selector.id} [${formatType(selector.type)} tag]" + + MatterSelectorKind.MOD -> + "${selector.id.path} [${formatType(selector.type)} mod]" } diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorKind.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorKind.kt index 7b658ce..abc325c 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorKind.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorKind.kt @@ -3,4 +3,5 @@ package com.p_nsk.replicated_integration.api.selector enum class MatterSelectorKind { NODE, TAG, + MOD, } diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorMaterializer.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorMaterializer.kt index 2310135..7af8439 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorMaterializer.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorMaterializer.kt @@ -8,51 +8,30 @@ object MatterSelectorMaterializer { fun materialize( selectors: Map, expandTag: (type: LiteResourceLocation, id: LiteResourceLocation) -> Iterable, + expandMod: (type: LiteResourceLocation, modId: String) -> Iterable = { _, _ -> emptyList() }, + allNodes: () -> Iterable = { emptyList() }, + ): Map = + materialize( + selectors.entries.map { (selector, value) -> MatterRule(selector.asRuleSelector(), value) }, + expandTag, + expandMod, + allNodes, + ) + + fun materialize( + rules: List, + expandTag: (type: LiteResourceLocation, id: LiteResourceLocation) -> Iterable, + expandMod: (type: LiteResourceLocation, modId: String) -> Iterable = { _, _ -> emptyList() }, + allNodes: () -> Iterable = { emptyList() }, ): Map { - val values = linkedMapOf() - for ((selector, explicitValue) in selectors.entries.sortedBy { it.key }) { - val targets = - when (selector.kind) { - MatterSelectorKind.NODE -> listOf(NodeKey(selector.type, selector.id)) - MatterSelectorKind.TAG -> expandTag(selector.type, selector.id).toList() - } - val specificity = - when (selector.kind) { - MatterSelectorKind.NODE -> 1 - MatterSelectorKind.TAG -> 0 - } + val values = linkedMapOf() + val expansion = MatterRuleExpansion(expandTag, expandMod, allNodes) + for (rule in rules.sortedWith(compareBy { it.source.priority })) { + val targets = rule.selector.expand(expansion) for (target in targets) { - val current = values[target] - if (current == null || shouldReplace(current, explicitValue, specificity)) { - values[target] = AppliedValue(explicitValue, specificity) - } + values[target] = rule.value } } - return values.mapValues { it.value.explicitValue } + return values } - - private fun shouldReplace( - current: AppliedValue, - candidate: ExplicitMatterValue, - specificity: Int, - ): Boolean { - if (candidate.source.priority != current.explicitValue.source.priority) { - return candidate.source.priority > current.explicitValue.source.priority - } - if (specificity != current.specificity) { - return specificity > current.specificity - } - return when { - current.explicitValue is ExplicitMatterValue.Deny -> false - candidate is ExplicitMatterValue.Deny -> true - current.explicitValue is ExplicitMatterValue.Set && candidate is ExplicitMatterValue.Set -> - candidate.compound.isBetterThan(current.explicitValue.compound) - else -> false - } - } - - private data class AppliedValue( - val explicitValue: ExplicitMatterValue, - val specificity: Int, - ) } diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorStorageCodec.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorStorageCodec.kt index 30d2511..0fd7fbc 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorStorageCodec.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorStorageCodec.kt @@ -19,24 +19,43 @@ object MatterSelectorStorageCodec { path: Path, source: ExplicitMatterSource, ): Map { + val values = linkedMapOf() + for (rule in loadRules(path, source)) { + val key = rule.selector.staticKey ?: continue + values[key] = rule.value + } + return values + } + + fun loadRules( + path: Path, + source: ExplicitMatterSource, + ): List { if (!Files.exists(path)) { - return emptyMap() + return emptyList() } val root = JsonParser.parseString(Files.readString(path)).asJsonObject val entries = root.getAsJsonArray("entries") ?: JsonArray() - val parsed = linkedMapOf() + val parsed = mutableListOf() for (element in entries) { val json = element.asJsonObject + val kind = + when (json["selector"]?.asString ?: "node") { + "node" -> MatterSelectorKind.NODE + "tag" -> MatterSelectorKind.TAG + "mod" -> MatterSelectorKind.MOD + else -> throw IllegalArgumentException("Unknown selector kind: ${json["selector"]}") + } val selector = MatterSelectorKey( - kind = - when (json["selector"]?.asString ?: "node") { - "node" -> MatterSelectorKind.NODE - "tag" -> MatterSelectorKind.TAG - else -> throw IllegalArgumentException("Unknown selector kind: ${json["selector"]}") - }, + kind = kind, type = json["type"].asString.toLite(), - id = json["id"].asString.toLite(), + id = + if (kind == MatterSelectorKind.MOD) { + LiteResourceLocation.of("mod", json["id"].asString) + } else { + json["id"].asString.toLite() + }, ) val value = if (json["deny"]?.asBoolean == true) { @@ -50,7 +69,7 @@ object MatterSelectorStorageCodec { } ExplicitMatterValue.Set(LiteMatterCompound(compound), source) } - parsed[selector] = value + parsed += MatterRule(selector.asRuleSelector(), value) } return parsed } @@ -58,15 +77,30 @@ object MatterSelectorStorageCodec { fun save( path: Path, values: Map, + ) = saveRules( + path, + values.entries.sortedBy { it.key }.map { (selector, value) -> + MatterRule(selector.asRuleSelector(), value) + }, + ) + + fun saveRules( + path: Path, + rules: List, ) { Files.createDirectories(path.parent) val root = JsonObject() val entries = JsonArray() - for ((selector, value) in values.entries.sortedBy { it.key }) { + for (rule in rules) { + val selector = rule.selector.staticKey ?: continue + val value = rule.value val json = JsonObject() json.addProperty("selector", selector.kind.name.lowercase()) json.addProperty("type", selector.type.toString()) - json.addProperty("id", selector.id.toString()) + json.addProperty( + "id", + if (selector.kind == MatterSelectorKind.MOD) selector.id.path else selector.id.toString(), + ) when (value) { is ExplicitMatterValue.Deny -> json.addProperty("deny", true) is ExplicitMatterValue.Set -> { diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MutableMatterSelectors.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MutableMatterSelectors.kt index 91b420d..bbef69d 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MutableMatterSelectors.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MutableMatterSelectors.kt @@ -5,47 +5,46 @@ import com.p_nsk.replicated_integration.api.model.ExplicitMatterValue import com.p_nsk.replicated_integration.api.model.LiteMatterCompound class MutableMatterSelectors { - private val values = linkedMapOf() + private val rules = mutableListOf() fun put(selector: MatterSelectorKey, compound: LiteMatterCompound, source: ExplicitMatterSource) { - val current = values[selector] - val candidate = ExplicitMatterValue.Set(compound, source) - if (current == null || shouldReplace(current, candidate)) { - values[selector] = candidate - } + put(selector.asRuleSelector(), compound, source) + } + + fun put(selector: MatterRuleSelector, compound: LiteMatterCompound, source: ExplicitMatterSource) { + rules += MatterRule(selector, ExplicitMatterValue.Set(compound, source)) } fun deny(selector: MatterSelectorKey, source: ExplicitMatterSource) { - val current = values[selector] - val candidate = ExplicitMatterValue.Deny(source) - if (current == null || shouldReplace(current, candidate)) { - values[selector] = candidate - } + deny(selector.asRuleSelector(), source) + } + + fun deny(selector: MatterRuleSelector, source: ExplicitMatterSource) { + rules += MatterRule(selector, ExplicitMatterValue.Deny(source)) } fun putAll(other: Map) { other.forEach { (selector, value) -> - when (value) { - is ExplicitMatterValue.Deny -> deny(selector, value.source) - is ExplicitMatterValue.Set -> put(selector, value.compound, value.source) - } + putRule(MatterRule(selector.asRuleSelector(), value)) } } - fun snapshot(): Map = values.toMap() + fun putRule(rule: MatterRule) { + rules += rule + } - private fun shouldReplace( - current: ExplicitMatterValue, - candidate: ExplicitMatterValue, - ): Boolean { - if (candidate.source.priority != current.source.priority) { - return candidate.source.priority > current.source.priority - } - return when { - current is ExplicitMatterValue.Deny -> false - candidate is ExplicitMatterValue.Deny -> true - current is ExplicitMatterValue.Set && candidate is ExplicitMatterValue.Set -> candidate.compound.isBetterThan(current.compound) - else -> false + fun putRules(other: Iterable) { + rules += other + } + + fun snapshotRules(): List = rules.toList() + + fun snapshot(): Map { + val values = linkedMapOf() + for (rule in rules.sortedWith(compareBy { it.source.priority })) { + val key = rule.selector.staticKey ?: continue + values[key] = rule.value } + return values } } diff --git a/common/src/test/kotlin/SimpleConversionSolverTest.kt b/common/src/test/kotlin/SimpleConversionSolverTest.kt index 085dd15..9673260 100644 --- a/common/src/test/kotlin/SimpleConversionSolverTest.kt +++ b/common/src/test/kotlin/SimpleConversionSolverTest.kt @@ -12,6 +12,7 @@ import com.p_nsk.replicated_integration.api.node.MatterNodes import com.p_nsk.replicated_integration.api.selector.MatterSelectorKey import com.p_nsk.replicated_integration.api.selector.MatterSelectorKind import com.p_nsk.replicated_integration.api.selector.MatterSelectorMaterializer +import com.p_nsk.replicated_integration.api.selector.asRuleSelector import kotlin.test.Test import kotlin.test.assertEquals @@ -189,4 +190,38 @@ class SimpleConversionSolverTest { assertEquals(20.0, (materialized[oakLog] as ExplicitMatterValue.Set).compound.values[earth]) } + + @Test + fun selectorMaterializerUsesLastRuleWithinSameSourcePriority() { + val stick = MatterNodes.item(LiteResourceLocation.of("minecraft", "stick")) + val earth = LiteResourceLocation.of("replication", "earth") + + val materialized = + MatterSelectorMaterializer.materialize( + rules = + listOf( + com.p_nsk.replicated_integration.api.selector.MatterRule( + MatterSelectorKey( + MatterSelectorKind.MOD, + MatterNodes.ITEM, + LiteResourceLocation.of("mod", "minecraft"), + ).asRuleSelector(), + ExplicitMatterValue.Deny(ExplicitMatterSource.CONFIG), + ), + com.p_nsk.replicated_integration.api.selector.MatterRule( + MatterSelectorKey( + MatterSelectorKind.NODE, + MatterNodes.ITEM, + LiteResourceLocation.of("minecraft", "stick"), + ).asRuleSelector(), + ExplicitMatterValue.Set(LiteMatterCompound.single(earth, 4.0), ExplicitMatterSource.CONFIG), + ), + ), + expandTag = { _, _ -> emptyList() }, + expandMod = { _, _ -> listOf(stick) }, + allNodes = { listOf(stick) }, + ) + + assertEquals(4.0, (materialized[stick] as ExplicitMatterValue.Set).compound.values[earth]) + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4cea0b2..ea4069b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,12 +52,15 @@ configured-mc1201 = "5180900" configured-mc1211 = "7276577" kubejs-mc1201 = "2001.6.4-build.120" -kubejs-mc1211 = "2101.7.2-build.285" +kubejs-mc1211 = "2101.7.2-build.365" architectury-mc1201 = "9.2.14" architectury-mc1211 = "13.0.8" rhino-mc1201 = "2001.2.3-build.10" rhino-mc1211 = "2101.2.7-build.82" +probejsLegacy-mc1201 = "8087580" +probejs-mc1211 = "8122173" + [libraries] slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" } @@ -114,5 +117,8 @@ architectury-mc1211 = { module = "dev.architectury:architectury-neoforge", versi rhino-mc1201 = { module = "dev.latvian.mods:rhino-forge", version.ref = "rhino-mc1201" } rhino-mc1211 = { module = "dev.latvian.mods:rhino", version.ref = "rhino-mc1211" } +probejsLegacy-mc1201 = { module = "curse.maven:probejs-legacy-956446", version.ref = "probejsLegacy-mc1201" } +probejs-mc1211 = { module = "curse.maven:probejs-585406", version.ref = "probejs-mc1211" } + [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } From b3ace27b68bc9c841596c569eb4fac24a456f923 Mon Sep 17 00:00:00 2001 From: P-nutsK <107553542+P-nutsK@users.noreply.github.com> Date: Fri, 22 May 2026 19:34:36 +0900 Subject: [PATCH 07/12] =?UTF-8?q?wip:=20kjs=E5=AF=BE=E5=BF=9C=20=E5=8B=95?= =?UTF-8?q?=E4=BD=9C=E7=A2=BA=E8=AA=8D=E3=81=AF1.21.1=E3=81=AE=E3=81=BF.?= =?UTF-8?q?=20ProbeJS=E5=AF=BE=E5=BF=9C=E3=82=82=E3=81=82=E3=81=BE?= =?UTF-8?q?=E3=82=8A=E3=82=88=E3=82=8D=E3=81=97=E3=81=8F=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/vanilla/BuiltinNodeResolver.kt | 9 ++++ .../vanilla/ReplicationVanillaAddon.kt | 2 + .../replicated_integration/core/Aliases.kt | 1 + .../core/ForgeMatterNodeBuilder.kt | 7 ++++ .../ForgeReplicationCalculationService.kt | 13 +++++- .../kubejs/ForgeRepInt.kt | 42 +++++++++++++++++++ .../kubejs/ForgeRepIntKubeJSPlugin.kt | 10 ++++- .../adapter/mekanism/MekanismNodeResolver.kt | 7 ++++ .../mekanism/ReplicationMekanismAddon.kt | 1 + .../adapter/vanilla/BuiltinNodeResolver.kt | 9 ++++ .../vanilla/ReplicationVanillaAddon.kt | 2 + .../replicated_integration/core/Aliases.kt | 1 + .../core/NeoMatterNodeBuilder.kt | 7 ++++ .../core/NeoReplicationCalculationService.kt | 13 +++++- .../kubejs/NeoRepInt.kt | 42 +++++++++++++++++++ .../kubejs/NeoRepIntKubeJSPlugin.kt | 9 +++- .../api/kubejs/RepInt.kt | 3 -- .../api/node/MatterNodeDef.kt | 1 + .../api/node/MatterNodeRegistry.kt | 2 + .../api/selector/MatterRule.kt | 5 ++- .../selector/MatterSelectorMaterializer.kt | 5 ++- gradle/libs.versions.toml | 2 +- 22 files changed, 180 insertions(+), 13 deletions(-) create mode 100644 1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt create mode 100644 1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt index 6b50d8c..9314bcf 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt @@ -24,6 +24,9 @@ object BuiltinNodeResolver { fun itemNode(id: ResourceLocation): NodeKey = MatterNodes.item(id.toLite()) + fun item(id: LiteResourceLocation) = + BuiltInRegistries.ITEM.get(id.toMcResourceLocation()) + fun fluidNode(stack: FluidStack): NodeKey? { if (stack.isEmpty) { return null @@ -37,6 +40,9 @@ object BuiltinNodeResolver { if (fluid == net.minecraft.world.level.material.Fluids.EMPTY) null else MatterNodes.fluid(id.toLite()) } + fun fluid(id: LiteResourceLocation) = + ForgeRegistries.FLUIDS.getValue(id.toMcResourceLocation()) + fun itemNodesInTag(id: ResourceLocation): List = BuiltInRegistries.ITEM.getTag(TagKey.create(Registries.ITEM, id)) .map { holders -> @@ -77,4 +83,7 @@ object BuiltinNodeResolver { private fun ResourceLocation.toLite(): LiteResourceLocation = LiteResourceLocation.of(namespace, path) + + private fun LiteResourceLocation.toMcResourceLocation(): ResourceLocation = + ResourceLocation.fromNamespaceAndPath(namespace, path) } diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt index d1a494c..6d70ddd 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt @@ -42,6 +42,7 @@ object ReplicationVanillaAddon : ForgeReplicationAddon { override fun registerMatterNodes(registry: ForgeMatterNodeRegistry) = with(registry) { node(MatterNodes.ITEM, "Item") { enumerate(BuiltinNodeResolver::allItemNodes) + rawValue(BuiltinNodeResolver::item) value( literal = "item", @@ -58,6 +59,7 @@ object ReplicationVanillaAddon : ForgeReplicationAddon { node(MatterNodes.FLUID, "Fluid") { enumerate(BuiltinNodeResolver::allFluidNodes) + rawValue(BuiltinNodeResolver::fluid) value( literal = "fluid", diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt index 7807835..0eccacf 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt @@ -23,6 +23,7 @@ fun ForgeMatterNodeRegistry.node( displayName = displayName, formatter = formatter, enumerateNodes = builder.buildEnumerator(), + rawValue = builder.buildRawValue(), commands = builder.buildCommands(), ) ) diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeMatterNodeBuilder.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeMatterNodeBuilder.kt index 4a1ec1a..1f64055 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeMatterNodeBuilder.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeMatterNodeBuilder.kt @@ -15,6 +15,7 @@ class ForgeMatterNodeBuilder( private var valueCommand: ForgeMatterCommand? = null private var tagCommand: ForgeMatterCommand? = null private var enumerateNodes: () -> Iterable = { emptyList() } + private var rawValue: (LiteResourceLocation) -> Any? = { null } fun value( literal: String, @@ -56,6 +57,10 @@ class ForgeMatterNodeBuilder( enumerateNodes = nodes } + fun rawValue(value: (LiteResourceLocation) -> Any?) { + rawValue = value + } + fun buildCommands(): MatterNodeCommands = MatterNodeCommands( value = valueCommand, @@ -63,4 +68,6 @@ class ForgeMatterNodeBuilder( ) fun buildEnumerator(): () -> Iterable = enumerateNodes + + fun buildRawValue(): (LiteResourceLocation) -> Any? = rawValue } diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt index 5623a3b..9b402cf 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt @@ -73,7 +73,15 @@ object ForgeReplicationCalculationService { val selectorSnapshot = selectors.snapshot() val selectorRules = selectors.snapshotRules() - defaults.putAll(MatterSelectorMaterializer.materialize(selectorRules, ::expandSelectorTag, ::expandSelectorMod, ::allMatterNodes)) + defaults.putAll( + MatterSelectorMaterializer.materialize( + selectorRules, + ::expandSelectorTag, + ::expandSelectorMod, + ::allMatterNodes, + ::rawValue, + ), + ) val explicitSnapshot = defaults.snapshot() for (addon in activeAddons) { addon.collectConversions(context, builder) @@ -213,6 +221,9 @@ object ForgeReplicationCalculationService { private fun allMatterNodes(): Iterable = matterNodeRegistry().all().flatMap { it.enumerateNodes() } + private fun rawValue(node: NodeKey): Any? = + matterNodeRegistry().get(node.type)?.rawValue?.invoke(node.id) + private fun warnKubeJSConfigOverlap( configRules: List, kubeJSRules: List, diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt new file mode 100644 index 0000000..4116ce7 --- /dev/null +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt @@ -0,0 +1,42 @@ +package com.p_nsk.replicated_integration.kubejs + +import com.p_nsk.replicated_integration.adapter.vanilla.BuiltinNodeResolver +import com.p_nsk.replicated_integration.api.kubejs.RepInt +import com.p_nsk.replicated_integration.api.node.MatterNodes +import com.p_nsk.replicated_integration.api.selector.* +import net.minecraft.tags.TagKey +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.material.Fluid +import net.minecraftforge.fluids.FluidStack +import java.util.function.Predicate + +object ForgeRepInt { + @JvmStatic fun node(type: String, id: String): MatterRuleSelector = RepInt.node(type, id) + @JvmStatic fun nodeTag(type: String, tag: String): MatterRuleSelector = RepInt.nodeTag(type, tag) + @JvmStatic fun nodeMod(type: String, modId: String): MatterRuleSelector = RepInt.nodeMod(type, modId) + @JvmStatic fun item(stack: ItemStack): MatterRuleSelector = + BuiltinNodeResolver.itemNode(stack) + ?.let { MatterSelectorKey(MatterSelectorKind.NODE, it.type, it.id).asRuleSelector() } + ?: throw IllegalArgumentException("Item selector cannot be created from an empty ItemStack") + + @JvmStatic fun itemTag(tag: TagKey): MatterRuleSelector = RepInt.itemTag(tag.location().toString()) + @JvmStatic fun itemMod(modId: String): MatterRuleSelector = RepInt.itemMod(modId) + @JvmStatic fun fluid(stack: FluidStack): MatterRuleSelector = + BuiltinNodeResolver.fluidNode(stack) + ?.let { MatterSelectorKey(MatterSelectorKind.NODE, it.type, it.id).asRuleSelector() } + ?: throw IllegalArgumentException("Fluid selector cannot be created from an empty FluidStack") + + @JvmStatic fun fluidTag(tag: TagKey): MatterRuleSelector = RepInt.fluidTag(tag.location().toString()) + @JvmStatic fun fluidMod(modId: String): MatterRuleSelector = RepInt.fluidMod(modId) + @JvmStatic fun anyOf(vararg selectors: Any): MatterRuleSelector = RepInt.anyOf(*selectors) + @JvmStatic fun allOf(vararg selectors: Any): MatterRuleSelector = RepInt.allOf(*selectors) + @JvmStatic fun not(selector: Any): MatterRuleSelector = RepInt.not(selector) + @JvmStatic fun whereNode(name: String, predicate: Predicate): MatterRuleSelector = RepInt.whereNode(name, predicate) + + @JvmStatic + fun whereItem(name: String, predicate: Predicate): MatterRuleSelector = + RepInt.whereNode(name, Predicate { view -> + view.key.type == MatterNodes.ITEM && view.getRaw()?.let(predicate::test) == true + }) +} diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntKubeJSPlugin.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntKubeJSPlugin.kt index 4bb57b0..18fd487 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntKubeJSPlugin.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntKubeJSPlugin.kt @@ -1,20 +1,26 @@ package com.p_nsk.replicated_integration.kubejs -import com.p_nsk.replicated_integration.api.kubejs.RepInt import com.p_nsk.replicated_integration.api.kubejs.RepIntMatterEvent import com.p_nsk.replicated_integration.api.selector.MatterRule import dev.latvian.mods.kubejs.KubeJSPlugin import dev.latvian.mods.kubejs.event.EventGroup +import dev.latvian.mods.kubejs.event.EventGroupWrapper import dev.latvian.mods.kubejs.event.EventJS import dev.latvian.mods.kubejs.script.BindingsEvent +import dev.latvian.mods.kubejs.script.ScriptType +import dev.latvian.mods.kubejs.util.ClassFilter class ForgeRepIntKubeJSPlugin : KubeJSPlugin() { + override fun registerClasses(type: ScriptType?, filter: ClassFilter) { + filter.allow("com.p_nsk.replicated_integration") + } + override fun registerEvents() { ForgeRepIntKubeJSEvents.GROUP.register() } override fun registerBindings(event: BindingsEvent) { - event.add("RepInt", RepInt) + event.add("RepInt", ForgeRepInt::class.java) } } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt index e6921e6..bbf1c85 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt @@ -44,6 +44,13 @@ object MekanismNodeResolver { fun chemicalNodesInMod(modId: String): List = allChemicalNodes().filter { it.id.namespace == modId } + @Suppress("DEPRECATION") + fun chemical(id: LiteResourceLocation) = + MekanismAPI.CHEMICAL_REGISTRY.get(id.toMcResourceLocation()) + private fun ResourceLocation.toLite(): LiteResourceLocation = LiteResourceLocation.of(namespace, path) + + private fun LiteResourceLocation.toMcResourceLocation(): ResourceLocation = + ResourceLocation.fromNamespaceAndPath(namespace, path) } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt index c000c19..4bacfed 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt @@ -32,6 +32,7 @@ object ReplicationMekanismAddon : NeoReplicationAddon { override fun registerMatterNodes(registry: NeoMatterNodeRegistry) = with(registry) { node(MatterNodes.CHEMICAL, "Chemical") { enumerate(MekanismNodeResolver::allChemicalNodes) + rawValue(MekanismNodeResolver::chemical) value( literal = "chemical", diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt index f64f26a..5fad1cf 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt @@ -23,6 +23,9 @@ object BuiltinNodeResolver { fun itemNode(id: ResourceLocation): NodeKey = MatterNodes.item(id.toLite()) + fun item(id: LiteResourceLocation) = + BuiltInRegistries.ITEM.get(id.toMcResourceLocation()) + fun fluidNode(stack: FluidStack): NodeKey? { if (stack.isEmpty) { return null @@ -36,6 +39,9 @@ object BuiltinNodeResolver { if (fluid == net.minecraft.world.level.material.Fluids.EMPTY) null else MatterNodes.fluid(id.toLite()) } + fun fluid(id: LiteResourceLocation) = + BuiltInRegistries.FLUID.get(id.toMcResourceLocation()) + fun itemNodesInTag(id: ResourceLocation): List = BuiltInRegistries.ITEM.getTag(TagKey.create(Registries.ITEM, id)) .map { holders -> @@ -76,4 +82,7 @@ object BuiltinNodeResolver { private fun ResourceLocation.toLite(): LiteResourceLocation = LiteResourceLocation.of(namespace, path) + + private fun LiteResourceLocation.toMcResourceLocation(): ResourceLocation = + ResourceLocation.fromNamespaceAndPath(namespace, path) } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt index 441a6e5..55aa95d 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/ReplicationVanillaAddon.kt @@ -40,6 +40,7 @@ object ReplicationVanillaAddon : NeoReplicationAddon { override fun registerMatterNodes(registry: NeoMatterNodeRegistry) = with(registry) { node(MatterNodes.ITEM, "Item") { enumerate(BuiltinNodeResolver::allItemNodes) + rawValue(BuiltinNodeResolver::item) value( literal = "item", @@ -56,6 +57,7 @@ object ReplicationVanillaAddon : NeoReplicationAddon { node(MatterNodes.FLUID, "Fluid") { enumerate(BuiltinNodeResolver::allFluidNodes) + rawValue(BuiltinNodeResolver::fluid) value( literal = "fluid", diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt index 85a1f6c..c207b44 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/Aliases.kt @@ -23,6 +23,7 @@ fun NeoMatterNodeRegistry.node( displayName = displayName, formatter = formatter, enumerateNodes = builder.buildEnumerator(), + rawValue = builder.buildRawValue(), commands = builder.buildCommands(), ) ) diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoMatterNodeBuilder.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoMatterNodeBuilder.kt index ed08849..4a8b941 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoMatterNodeBuilder.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoMatterNodeBuilder.kt @@ -15,6 +15,7 @@ class NeoMatterNodeBuilder( private var valueCommand: NeoMatterCommand? = null private var tagCommand: NeoMatterCommand? = null private var enumerateNodes: () -> Iterable = { emptyList() } + private var rawValue: (LiteResourceLocation) -> Any? = { null } fun value( literal: String, @@ -56,6 +57,10 @@ class NeoMatterNodeBuilder( enumerateNodes = nodes } + fun rawValue(value: (LiteResourceLocation) -> Any?) { + rawValue = value + } + fun buildCommands(): MatterNodeCommands = MatterNodeCommands( value = valueCommand, @@ -63,4 +68,6 @@ class NeoMatterNodeBuilder( ) fun buildEnumerator(): () -> Iterable = enumerateNodes + + fun buildRawValue(): (LiteResourceLocation) -> Any? = rawValue } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoReplicationCalculationService.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoReplicationCalculationService.kt index 55234e1..ed3c7d1 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoReplicationCalculationService.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/core/NeoReplicationCalculationService.kt @@ -80,7 +80,15 @@ object NeoReplicationCalculationService { val selectorSnapshot = selectors.snapshot() val selectorRules = selectors.snapshotRules() - defaults.putAll(MatterSelectorMaterializer.materialize(selectorRules, ::expandSelectorTag, ::expandSelectorMod, ::allMatterNodes)) + defaults.putAll( + MatterSelectorMaterializer.materialize( + selectorRules, + ::expandSelectorTag, + ::expandSelectorMod, + ::allMatterNodes, + ::rawValue, + ), + ) val explicitSnapshot = defaults.snapshot() for (addon in activeAddons) { addon.collectConversions(context, builder) @@ -202,6 +210,9 @@ object NeoReplicationCalculationService { private fun allMatterNodes(): Iterable = matterNodeRegistry().all().flatMap { it.enumerateNodes() } + private fun rawValue(node: NodeKey): Any? = + matterNodeRegistry().get(node.type)?.rawValue?.invoke(node.id) + private fun warnKubeJSConfigOverlap( configRules: List, kubeJSRules: List, diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt new file mode 100644 index 0000000..9a73161 --- /dev/null +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt @@ -0,0 +1,42 @@ +package com.p_nsk.replicated_integration.kubejs + +import com.p_nsk.replicated_integration.adapter.vanilla.BuiltinNodeResolver +import com.p_nsk.replicated_integration.api.kubejs.RepInt +import com.p_nsk.replicated_integration.api.node.MatterNodes +import com.p_nsk.replicated_integration.api.selector.* +import net.minecraft.tags.TagKey +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.material.Fluid +import net.neoforged.neoforge.fluids.FluidStack +import java.util.function.Predicate + +object NeoRepInt { + @JvmStatic fun node(type: String, id: String): MatterRuleSelector = RepInt.node(type, id) + @JvmStatic fun nodeTag(type: String, tag: String): MatterRuleSelector = RepInt.nodeTag(type, tag) + @JvmStatic fun nodeMod(type: String, modId: String): MatterRuleSelector = RepInt.nodeMod(type, modId) + @JvmStatic fun item(stack: ItemStack): MatterRuleSelector = + BuiltinNodeResolver.itemNode(stack) + ?.let { MatterSelectorKey(MatterSelectorKind.NODE, it.type, it.id).asRuleSelector() } + ?: throw IllegalArgumentException("Item selector cannot be created from an empty ItemStack") + + @JvmStatic fun itemTag(tag: TagKey): MatterRuleSelector = RepInt.itemTag(tag.location().toString()) + @JvmStatic fun itemMod(modId: String): MatterRuleSelector = RepInt.itemMod(modId) + @JvmStatic fun fluid(stack: FluidStack): MatterRuleSelector = + BuiltinNodeResolver.fluidNode(stack) + ?.let { MatterSelectorKey(MatterSelectorKind.NODE, it.type, it.id).asRuleSelector() } + ?: throw IllegalArgumentException("Fluid selector cannot be created from an empty FluidStack") + + @JvmStatic fun fluidTag(tag: TagKey): MatterRuleSelector = RepInt.fluidTag(tag.location().toString()) + @JvmStatic fun fluidMod(modId: String): MatterRuleSelector = RepInt.fluidMod(modId) + @JvmStatic fun anyOf(vararg selectors: Any): MatterRuleSelector = RepInt.anyOf(*selectors) + @JvmStatic fun allOf(vararg selectors: Any): MatterRuleSelector = RepInt.allOf(*selectors) + @JvmStatic fun not(selector: Any): MatterRuleSelector = RepInt.not(selector) + @JvmStatic fun whereNode(name: String, predicate: Predicate): MatterRuleSelector = RepInt.whereNode(name, predicate) + + @JvmStatic + fun whereItem(name: String, predicate: Predicate): MatterRuleSelector = + RepInt.whereNode(name, Predicate { view -> + view.key.type == MatterNodes.ITEM && view.getRaw()?.let(predicate::test) == true + }) +} diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt index b8e81cf..351decd 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt @@ -1,21 +1,26 @@ package com.p_nsk.replicated_integration.kubejs -import com.p_nsk.replicated_integration.api.kubejs.RepInt import com.p_nsk.replicated_integration.api.kubejs.RepIntMatterEvent import com.p_nsk.replicated_integration.api.selector.MatterRule import dev.latvian.mods.kubejs.event.EventGroup import dev.latvian.mods.kubejs.event.EventGroupRegistry +import dev.latvian.mods.kubejs.event.EventGroupWrapper import dev.latvian.mods.kubejs.event.KubeEvent +import dev.latvian.mods.kubejs.plugin.ClassFilter import dev.latvian.mods.kubejs.plugin.KubeJSPlugin import dev.latvian.mods.kubejs.script.BindingRegistry class NeoRepIntKubeJSPlugin : KubeJSPlugin { + override fun registerClasses(filter: ClassFilter) { + filter.allow("com.p_nsk.replicated_integration") + } + override fun registerEvents(registry: EventGroupRegistry) { registry.register(NeoRepIntKubeJSEvents.GROUP) } override fun registerBindings(registry: BindingRegistry) { - registry.add("RepInt", RepInt::class.java) + registry.add("RepInt", NeoRepInt::class.java) } } diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/kubejs/RepInt.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/kubejs/RepInt.kt index ecadf10..d4cefd8 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/kubejs/RepInt.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/kubejs/RepInt.kt @@ -57,9 +57,6 @@ object RepInt { @JvmStatic fun whereNode(name: String, predicate: Predicate): MatterRuleSelector = PredicateMatterRuleSelector(name) { predicate.test(it) } - @JvmStatic fun whereItem(name: String, predicate: Predicate): MatterRuleSelector = - whereNode(name, Predicate { it.key.type == MatterNodes.ITEM && predicate.test(it) }) - internal fun selector(value: Any): MatterRuleSelector = when (value) { is MatterRuleSelector -> value diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeDef.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeDef.kt index f25a295..22239aa 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeDef.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeDef.kt @@ -8,6 +8,7 @@ data class MatterNodeDef( val displayName: String, val formatter: (LiteResourceLocation) -> String = LiteResourceLocation::toString, val enumerateNodes: () -> Iterable = { emptyList() }, + val rawValue: (LiteResourceLocation) -> Any? = { null }, val commands: MatterNodeCommands = MatterNodeCommands(), ) diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeRegistry.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeRegistry.kt index 308a409..dd4ef58 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeRegistry.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/node/MatterNodeRegistry.kt @@ -7,6 +7,7 @@ class MatterNodeRegistry { private val nodes = linkedMapOf>() private val commandsByLiteral = linkedMapOf() private val emptyEnumerator: () -> Iterable = { emptyList() } + private val emptyRawValue: (LiteResourceLocation) -> Any? = { null } fun register(node: MatterNodeDef) { val previous = nodes[node.id] @@ -43,5 +44,6 @@ class MatterNodeRegistry { copy( commands = MatterNodeCommands(), enumerateNodes = emptyEnumerator, + rawValue = emptyRawValue, ) } diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterRule.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterRule.kt index 2ac3619..520effb 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterRule.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterRule.kt @@ -16,6 +16,7 @@ class MatterRuleExpansion( val expandTag: (type: LiteResourceLocation, id: LiteResourceLocation) -> Iterable, val expandMod: (type: LiteResourceLocation, modId: String) -> Iterable, val allNodes: () -> Iterable, + val rawValue: (NodeKey) -> Any?, ) sealed interface MatterRuleSelector { @@ -82,7 +83,7 @@ class PredicateMatterRuleSelector( override fun expand(expansion: MatterRuleExpansion): Set = expansion.allNodes().filterTo(linkedSetOf()) { node -> - predicate(NodeView(node)) + predicate(NodeView(node, Supplier { expansion.rawValue(node) })) } override fun toString(): String = "whereNode($name)" @@ -97,7 +98,7 @@ class NodeView( val modId: String = key.id.namespace @Suppress("UNCHECKED_CAST") - fun getRaw(): T? = raw as? T + fun getRaw(): T? = raw.get() as? T } fun MatterSelectorKey.asRuleSelector(): MatterRuleSelector = diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorMaterializer.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorMaterializer.kt index 7af8439..be56c14 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorMaterializer.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorMaterializer.kt @@ -10,12 +10,14 @@ object MatterSelectorMaterializer { expandTag: (type: LiteResourceLocation, id: LiteResourceLocation) -> Iterable, expandMod: (type: LiteResourceLocation, modId: String) -> Iterable = { _, _ -> emptyList() }, allNodes: () -> Iterable = { emptyList() }, + rawValue: (NodeKey) -> Any? = { null }, ): Map = materialize( selectors.entries.map { (selector, value) -> MatterRule(selector.asRuleSelector(), value) }, expandTag, expandMod, allNodes, + rawValue, ) fun materialize( @@ -23,9 +25,10 @@ object MatterSelectorMaterializer { expandTag: (type: LiteResourceLocation, id: LiteResourceLocation) -> Iterable, expandMod: (type: LiteResourceLocation, modId: String) -> Iterable = { _, _ -> emptyList() }, allNodes: () -> Iterable = { emptyList() }, + rawValue: (NodeKey) -> Any? = { null }, ): Map { val values = linkedMapOf() - val expansion = MatterRuleExpansion(expandTag, expandMod, allNodes) + val expansion = MatterRuleExpansion(expandTag, expandMod, allNodes, rawValue) for (rule in rules.sortedWith(compareBy { it.source.priority })) { val targets = rule.selector.expand(expansion) for (target in targets) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ea4069b..1476913 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -58,7 +58,7 @@ architectury-mc1211 = "13.0.8" rhino-mc1201 = "2001.2.3-build.10" rhino-mc1211 = "2101.2.7-build.82" -probejsLegacy-mc1201 = "8087580" +probejsLegacy-mc1201 = "7615033" # 8087580 probejs-mc1211 = "8122173" [libraries] From 73cb7f01a7ff73800c75ecd40c085e4aa2ad51a5 Mon Sep 17 00:00:00 2001 From: P-nutsK <107553542+P-nutsK@users.noreply.github.com> Date: Sat, 23 May 2026 05:04:39 +0900 Subject: [PATCH 08/12] enhanced probejs support --- 1.20.1-forge/build.gradle.kts | 2 +- .../replicated_integration/lang/en_us.json | 2 + .../replicated_integration/lang/ja_jp.json | 2 + .../generated/resources/probejs.documents.txt | 1 + .../probejs/replicated_integration.json | 614 ++++++++++++++++++ .../adapter/mekanism/MekanismNodeResolver.kt | 49 ++ .../mekanism/ReplicationMekanismAddon.kt | 18 + .../ForgeReplicationCalculationService.kt | 9 + .../data/ProbeJSDocumentProvider.kt | 147 +++++ .../data/ReplicatedIntegrationDataGen.kt | 4 + .../kubejs/ForgeRepInt.kt | 24 +- .../kubejs/ForgeRepIntMek.kt | 23 + .../kubejs/ForgeRepIntMekKubeJSPlugin.kt | 10 + .../src/main/resources/kubejs.plugins.txt | 1 + 1.21.1-neo/build.gradle.kts | 1 + .../kubejs/NeoRepInt.kt | 24 +- .../kubejs/NeoRepIntMek.kt | 11 + .../kubejs/NeoRepIntMekKubeJSPlugin.kt | 10 + .../kubejs/NeoRepIntProbeJSPlugin.kt | 61 ++ .../src/main/resources/kubejs.plugins.txt | 2 + .../legacyforge-mod-conventions.gradle.kts | 2 + gradle.properties | 2 +- gradle/libs.versions.toml | 6 +- 23 files changed, 985 insertions(+), 40 deletions(-) create mode 100644 1.20.1-forge/src/generated/resources/probejs.documents.txt create mode 100644 1.20.1-forge/src/generated/resources/probejs/replicated_integration.json create mode 100644 1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ProbeJSDocumentProvider.kt create mode 100644 1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntMek.kt create mode 100644 1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntMekKubeJSPlugin.kt create mode 100644 1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMek.kt create mode 100644 1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMekKubeJSPlugin.kt create mode 100644 1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntProbeJSPlugin.kt diff --git a/1.20.1-forge/build.gradle.kts b/1.20.1-forge/build.gradle.kts index 51b574d..bb6e36d 100644 --- a/1.20.1-forge/build.gradle.kts +++ b/1.20.1-forge/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { modRuntimeOnly(libs.emi.mc1201) modRuntimeOnly(libs.jade.mc1201) modRuntimeOnly(libs.configured.mc1201) - modRuntimeOnly(libs.probejsLegacy.mc1201) + modRuntimeOnly(libs.probejs.mc1201) // ae2 dependency modRuntimeOnly(libs.guideme.mc1201) diff --git a/1.20.1-forge/src/generated/resources/assets/replicated_integration/lang/en_us.json b/1.20.1-forge/src/generated/resources/assets/replicated_integration/lang/en_us.json index 92cbe4a..cebb84b 100644 --- a/1.20.1-forge/src/generated/resources/assets/replicated_integration/lang/en_us.json +++ b/1.20.1-forge/src/generated/resources/assets/replicated_integration/lang/en_us.json @@ -1,4 +1,6 @@ { + "key.categories.replicated_integration": "Replicated Integration", + "key.replicated_integration.open_replication_index": "Open Replication Index", "replicated_integration.chat.notice.config_path": "Open %s to adjust optional integration behavior.", "replicated_integration.chat.notice.configurable_integrations": "You can fine-tune these integrations in the compatibility config:", "replicated_integration.chat.notice.supported_integrations": "has tuned support for these installed mods:", diff --git a/1.20.1-forge/src/generated/resources/assets/replicated_integration/lang/ja_jp.json b/1.20.1-forge/src/generated/resources/assets/replicated_integration/lang/ja_jp.json index f308816..aeb1d2c 100644 --- a/1.20.1-forge/src/generated/resources/assets/replicated_integration/lang/ja_jp.json +++ b/1.20.1-forge/src/generated/resources/assets/replicated_integration/lang/ja_jp.json @@ -1,4 +1,6 @@ { + "key.categories.replicated_integration": "Replicated Integration", + "key.replicated_integration.open_replication_index": "Replication Index を開く", "replicated_integration.chat.notice.config_path": "任意の連携動作は %s から変更できます。", "replicated_integration.chat.notice.configurable_integrations": "さらに、以下の統合は互換性コンフィグで調整できます:", "replicated_integration.chat.notice.supported_integrations": "は、導入済みの以下 mod と連携できます:", diff --git a/1.20.1-forge/src/generated/resources/probejs.documents.txt b/1.20.1-forge/src/generated/resources/probejs.documents.txt new file mode 100644 index 0000000..3421512 --- /dev/null +++ b/1.20.1-forge/src/generated/resources/probejs.documents.txt @@ -0,0 +1 @@ +probejs/replicated_integration.json diff --git a/1.20.1-forge/src/generated/resources/probejs/replicated_integration.json b/1.20.1-forge/src/generated/resources/probejs/replicated_integration.json new file mode 100644 index 0000000..75bc5a2 --- /dev/null +++ b/1.20.1-forge/src/generated/resources/probejs/replicated_integration.json @@ -0,0 +1,614 @@ +{ + "classes": [ + { + "type": "document:class", + "className": "com.p_nsk.replicated_integration.kubejs.ForgeRepInt", + "methods": [ + { + "type": "document:method", + "abstract": false, + "name": "nodeMod", + "params": [ + { + "type": "property:param", + "name": "type", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + }, + { + "type": "property:param", + "name": "modId", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 1, + "newType": { + "type": "type:primitive", + "name": "Special.Mod" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "item", + "params": [ + { + "type": "property:param", + "name": "id", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Item" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "itemTag", + "params": [ + { + "type": "property:param", + "name": "tag", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.ItemTag" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "itemMod", + "params": [ + { + "type": "property:param", + "name": "modId", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Mod" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "fluid", + "params": [ + { + "type": "property:param", + "name": "id", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Fluid" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "fluidTag", + "params": [ + { + "type": "property:param", + "name": "tag", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.FluidTag" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "fluidMod", + "params": [ + { + "type": "property:param", + "name": "modId", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Mod" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + } + ] + }, + { + "type": "document:class", + "className": "com.p_nsk.replicated_integration.kubejs.ForgeRepIntMek", + "methods": [ + { + "type": "document:method", + "abstract": false, + "name": "gas", + "params": [ + { + "type": "property:param", + "name": "id", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Gas" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "gasTag", + "params": [ + { + "type": "property:param", + "name": "tag", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.GasTag" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "gasMod", + "params": [ + { + "type": "property:param", + "name": "modId", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Mod" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "infuseType", + "params": [ + { + "type": "property:param", + "name": "id", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.InfuseType" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "infuseTypeTag", + "params": [ + { + "type": "property:param", + "name": "tag", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.InfuseTypeTag" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "infuseTypeMod", + "params": [ + { + "type": "property:param", + "name": "modId", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Mod" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "pigment", + "params": [ + { + "type": "property:param", + "name": "id", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Pigment" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "pigmentTag", + "params": [ + { + "type": "property:param", + "name": "tag", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.PigmentTag" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "pigmentMod", + "params": [ + { + "type": "property:param", + "name": "modId", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Mod" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "slurry", + "params": [ + { + "type": "property:param", + "name": "id", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Slurry" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "slurryTag", + "params": [ + { + "type": "property:param", + "name": "tag", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.SlurryTag" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + }, + { + "type": "document:method", + "abstract": false, + "name": "slurryMod", + "params": [ + { + "type": "property:param", + "name": "modId", + "paramType": { + "type": "type:class", + "name": "java.lang.String" + }, + "varArg": false + } + ], + "properties": [ + { + "type": "property:modify", + "index": 0, + "newType": { + "type": "type:primitive", + "name": "Special.Mod" + } + } + ], + "returns": { + "type": "type:class", + "name": "com.p_nsk.replicated_integration.api.selector.MatterRuleSelector" + }, + "static": true + } + ] + } + ] +} \ No newline at end of file diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt index da0cb69..b729b7d 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/MekanismNodeResolver.kt @@ -4,12 +4,14 @@ import com.p_nsk.replicated_integration.api.model.LiteResourceLocation import com.p_nsk.replicated_integration.api.model.NodeAmount import com.p_nsk.replicated_integration.api.node.NodeKey import com.p_nsk.replicated_integration.api.node.MatterNodes +import mekanism.api.MekanismAPI import mekanism.api.chemical.ChemicalStack import mekanism.api.chemical.gas.GasStack import mekanism.api.chemical.infuse.InfusionStack import mekanism.api.chemical.pigment.PigmentStack import mekanism.api.chemical.slurry.SlurryStack import net.minecraft.resources.ResourceLocation +import net.minecraftforge.registries.IForgeRegistry object MekanismNodeResolver { fun chemicalNode(stack: ChemicalStack<*>): NodeKey? { @@ -30,6 +32,53 @@ object MekanismNodeResolver { fun chemicalAmount(stack: ChemicalStack<*>): NodeAmount? = chemicalNode(stack)?.takeIf { stack.amount > 0L }?.let { NodeAmount(it, stack.amount) } + fun gasNodesInTag(id: ResourceLocation): List = + nodesInTag(MekanismAPI.gasRegistry(), id, MatterNodes::gas) + + fun gasNodesInMod(modId: String): List = + nodesInMod(MekanismAPI.gasRegistry(), modId, MatterNodes::gas) + + fun infuseTypeNodesInTag(id: ResourceLocation): List = + nodesInTag(MekanismAPI.infuseTypeRegistry(), id, MatterNodes::infuseType) + + fun infuseTypeNodesInMod(modId: String): List = + nodesInMod(MekanismAPI.infuseTypeRegistry(), modId, MatterNodes::infuseType) + + fun pigmentNodesInTag(id: ResourceLocation): List = + nodesInTag(MekanismAPI.pigmentRegistry(), id, MatterNodes::pigment) + + fun pigmentNodesInMod(modId: String): List = + nodesInMod(MekanismAPI.pigmentRegistry(), modId, MatterNodes::pigment) + + fun slurryNodesInTag(id: ResourceLocation): List = + nodesInTag(MekanismAPI.slurryRegistry(), id, MatterNodes::slurry) + + fun slurryNodesInMod(modId: String): List = + nodesInMod(MekanismAPI.slurryRegistry(), modId, MatterNodes::slurry) + + private fun nodesInTag( + registry: IForgeRegistry, + id: ResourceLocation, + nodeOf: (LiteResourceLocation) -> NodeKey, + ): List { + val tags = registry.tags() ?: return emptyList() + return tags.getTag(tags.createTagKey(id)).mapNotNull { value -> + registry.getKey(value)?.toLite()?.let(nodeOf) + } + } + + private fun nodesInMod( + registry: IForgeRegistry, + modId: String, + nodeOf: (LiteResourceLocation) -> NodeKey, + ): List = + registry.mapNotNull { value -> + registry.getKey(value) + ?.takeIf { it.namespace == modId } + ?.toLite() + ?.let(nodeOf) + } + private fun ResourceLocation.toLite(): LiteResourceLocation = LiteResourceLocation.of(namespace, path) } diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt index 64cee45..d5a6007 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/mekanism/ReplicationMekanismAddon.kt @@ -51,6 +51,12 @@ object ReplicationMekanismAddon : ForgeReplicationAddon { suggestions = MekanismMatterCommandInputs.infuseTypeSuggestions(), validate = MekanismMatterCommandInputs.infuseTypeValidator(), ) + + tag( + literal = "infuse_type_tag", + suggestions = MekanismMatterCommandInputs.infuseTypeTagSuggestions(), + validate = MekanismMatterCommandInputs.infuseTypeTagValidator(), + ) } node(MekanismMatterNodes.PIGMENT, "Pigment") { @@ -59,6 +65,12 @@ object ReplicationMekanismAddon : ForgeReplicationAddon { suggestions = MekanismMatterCommandInputs.pigmentSuggestions(), validate = MekanismMatterCommandInputs.pigmentValidator(), ) + + tag( + literal = "pigment_tag", + suggestions = MekanismMatterCommandInputs.pigmentTagSuggestions(), + validate = MekanismMatterCommandInputs.pigmentTagValidator(), + ) } node(MekanismMatterNodes.SLURRY, "Slurry") { @@ -67,6 +79,12 @@ object ReplicationMekanismAddon : ForgeReplicationAddon { suggestions = MekanismMatterCommandInputs.slurrySuggestions(), validate = MekanismMatterCommandInputs.slurryValidator(), ) + + tag( + literal = "slurry_tag", + suggestions = MekanismMatterCommandInputs.slurryTagSuggestions(), + validate = MekanismMatterCommandInputs.slurryTagValidator(), + ) } } diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt index 9b402cf..33258a0 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/core/ForgeReplicationCalculationService.kt @@ -6,6 +6,7 @@ import com.buuz135.replication.calculation.MatterValue import com.buuz135.replication.recipe.MatterValueRecipe import com.p_nsk.replicated_integration.Constants import com.p_nsk.replicated_integration.adapter.vanilla.BuiltinNodeResolver +import com.p_nsk.replicated_integration.adapter.mekanism.MekanismNodeResolver import com.p_nsk.replicated_integration.api.addon.ReplicationAddonLoadSafetyContract import com.p_nsk.replicated_integration.api.addon.ReplicationAddonRegistry import com.p_nsk.replicated_integration.api.graph.ConversionGraphBuilder @@ -207,6 +208,10 @@ object ForgeReplicationCalculationService { return when (type) { MatterNodes.ITEM -> BuiltinNodeResolver.itemNodesInTag(resourceId) MatterNodes.FLUID -> BuiltinNodeResolver.fluidNodesInTag(resourceId) + MatterNodes.GAS -> MekanismNodeResolver.gasNodesInTag(resourceId) + MatterNodes.INFUSE_TYPE -> MekanismNodeResolver.infuseTypeNodesInTag(resourceId) + MatterNodes.PIGMENT -> MekanismNodeResolver.pigmentNodesInTag(resourceId) + MatterNodes.SLURRY -> MekanismNodeResolver.slurryNodesInTag(resourceId) else -> emptyList() } } @@ -215,6 +220,10 @@ object ForgeReplicationCalculationService { when (type) { MatterNodes.ITEM -> BuiltinNodeResolver.itemNodesInMod(modId) MatterNodes.FLUID -> BuiltinNodeResolver.fluidNodesInMod(modId) + MatterNodes.GAS -> MekanismNodeResolver.gasNodesInMod(modId) + MatterNodes.INFUSE_TYPE -> MekanismNodeResolver.infuseTypeNodesInMod(modId) + MatterNodes.PIGMENT -> MekanismNodeResolver.pigmentNodesInMod(modId) + MatterNodes.SLURRY -> MekanismNodeResolver.slurryNodesInMod(modId) else -> emptyList() } diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ProbeJSDocumentProvider.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ProbeJSDocumentProvider.kt new file mode 100644 index 0000000..38fe508 --- /dev/null +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ProbeJSDocumentProvider.kt @@ -0,0 +1,147 @@ +package com.p_nsk.replicated_integration.data + +import com.google.common.hash.Hashing +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import net.minecraft.data.CachedOutput +import net.minecraft.data.DataProvider +import net.minecraft.data.PackOutput +import java.nio.charset.StandardCharsets +import java.nio.file.Path +import java.util.concurrent.CompletableFuture + +class ProbeJSDocumentProvider( + output: PackOutput, +) : DataProvider { + private val root = output.outputFolder + + override fun run(cache: CachedOutput): CompletableFuture<*> { + val documentsPath = root.resolve("probejs.documents.txt") + val documentJsonPath = root.resolve("probejs/replicated_integration.json") + return CompletableFuture.allOf( + writeText(cache, documentsPath, "probejs/replicated_integration.json\n"), + DataProvider.saveStable(cache, documentJson(), documentJsonPath), + ) + } + + override fun getName(): String = + "Replicated Integration ProbeJS Documents" + + private fun documentJson(): JsonObject = + jsonObject( + "classes" to jsonArray( + classDocument( + "com.p_nsk.replicated_integration.kubejs.ForgeRepInt", + method("nodeMod", listOf("type" to "java.lang.String", "modId" to "java.lang.String"), 1, "Special.Mod"), + method("item", "id", "Special.Item"), + method("itemTag", "tag", "Special.ItemTag"), + method("itemMod", "modId", "Special.Mod"), + method("fluid", "id", "Special.Fluid"), + method("fluidTag", "tag", "Special.FluidTag"), + method("fluidMod", "modId", "Special.Mod"), + ), + classDocument( + "com.p_nsk.replicated_integration.kubejs.ForgeRepIntMek", + method("gas", "id", "Special.Gas"), + method("gasTag", "tag", "Special.GasTag"), + method("gasMod", "modId", "Special.Mod"), + method("infuseType", "id", "Special.InfuseType"), + method("infuseTypeTag", "tag", "Special.InfuseTypeTag"), + method("infuseTypeMod", "modId", "Special.Mod"), + method("pigment", "id", "Special.Pigment"), + method("pigmentTag", "tag", "Special.PigmentTag"), + method("pigmentMod", "modId", "Special.Mod"), + method("slurry", "id", "Special.Slurry"), + method("slurryTag", "tag", "Special.SlurryTag"), + method("slurryMod", "modId", "Special.Mod"), + ), + ), + ) + + private fun classDocument( + className: String, + vararg methods: JsonObject, + ): JsonObject = + jsonObject( + "type" to "document:class", + "className" to className, + "methods" to jsonArray(*methods), + ) + + private fun method( + name: String, + paramName: String, + specialType: String, + ): JsonObject = + method(name, listOf(paramName to "java.lang.String"), 0, specialType) + + private fun method( + name: String, + params: List>, + overrideIndex: Int, + specialType: String, + ): JsonObject = + jsonObject( + "type" to "document:method", + "name" to name, + "static" to true, + "abstract" to false, + "params" to jsonArray( + *params.map { (paramName, paramType) -> + jsonObject( + "type" to "property:param", + "name" to paramName, + "paramType" to classType(paramType), + "varArg" to false, + ) + }.toTypedArray(), + ), + "returns" to classType("com.p_nsk.replicated_integration.api.selector.MatterRuleSelector"), + "properties" to jsonArray( + jsonObject( + "type" to "property:modify", + "index" to overrideIndex, + "newType" to jsonObject( + "type" to "type:primitive", + "name" to specialType, + ), + ), + ), + ) + + private fun classType(name: String): JsonObject = + jsonObject( + "type" to "type:class", + "name" to name, + ) + + private fun jsonObject(vararg entries: Pair): JsonObject = + JsonObject().apply { + for ((key, value) in entries) { + when (value) { + null -> add(key, null) + is Boolean -> addProperty(key, value) + is Number -> addProperty(key, value) + is String -> addProperty(key, value) + is JsonArray -> add(key, value) + is JsonObject -> add(key, value) + else -> error("Unsupported JSON value: $value") + } + } + } + + private fun jsonArray(vararg values: JsonObject): JsonArray = + JsonArray().apply { + values.forEach(::add) + } + + private fun writeText( + cache: CachedOutput, + path: Path, + text: String, + ): CompletableFuture<*> = + CompletableFuture.runAsync { + val bytes = text.toByteArray(StandardCharsets.UTF_8) + cache.writeIfNeeded(path, bytes, Hashing.sha1().hashBytes(bytes)) + } +} diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ReplicatedIntegrationDataGen.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ReplicatedIntegrationDataGen.kt index 186611a..4823de0 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ReplicatedIntegrationDataGen.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/data/ReplicatedIntegrationDataGen.kt @@ -13,6 +13,10 @@ object ReplicatedIntegrationDataGen { true, ReplicatedIntegrationLanguageProvider(event.generator.packOutput, "ja_jp"), ) + event.generator.addProvider( + true, + ProbeJSDocumentProvider(event.generator.packOutput), + ) } if (event.includeServer()) { event.generator.addProvider( diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt index 4116ce7..a30cc68 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt @@ -1,33 +1,21 @@ package com.p_nsk.replicated_integration.kubejs -import com.p_nsk.replicated_integration.adapter.vanilla.BuiltinNodeResolver import com.p_nsk.replicated_integration.api.kubejs.RepInt import com.p_nsk.replicated_integration.api.node.MatterNodes -import com.p_nsk.replicated_integration.api.selector.* -import net.minecraft.tags.TagKey +import com.p_nsk.replicated_integration.api.selector.MatterRuleSelector +import com.p_nsk.replicated_integration.api.selector.NodeView import net.minecraft.world.item.Item -import net.minecraft.world.item.ItemStack -import net.minecraft.world.level.material.Fluid -import net.minecraftforge.fluids.FluidStack import java.util.function.Predicate object ForgeRepInt { @JvmStatic fun node(type: String, id: String): MatterRuleSelector = RepInt.node(type, id) @JvmStatic fun nodeTag(type: String, tag: String): MatterRuleSelector = RepInt.nodeTag(type, tag) @JvmStatic fun nodeMod(type: String, modId: String): MatterRuleSelector = RepInt.nodeMod(type, modId) - @JvmStatic fun item(stack: ItemStack): MatterRuleSelector = - BuiltinNodeResolver.itemNode(stack) - ?.let { MatterSelectorKey(MatterSelectorKind.NODE, it.type, it.id).asRuleSelector() } - ?: throw IllegalArgumentException("Item selector cannot be created from an empty ItemStack") - - @JvmStatic fun itemTag(tag: TagKey): MatterRuleSelector = RepInt.itemTag(tag.location().toString()) + @JvmStatic fun item(id: String): MatterRuleSelector = RepInt.item(id) + @JvmStatic fun itemTag(tag: String): MatterRuleSelector = RepInt.itemTag(tag) @JvmStatic fun itemMod(modId: String): MatterRuleSelector = RepInt.itemMod(modId) - @JvmStatic fun fluid(stack: FluidStack): MatterRuleSelector = - BuiltinNodeResolver.fluidNode(stack) - ?.let { MatterSelectorKey(MatterSelectorKind.NODE, it.type, it.id).asRuleSelector() } - ?: throw IllegalArgumentException("Fluid selector cannot be created from an empty FluidStack") - - @JvmStatic fun fluidTag(tag: TagKey): MatterRuleSelector = RepInt.fluidTag(tag.location().toString()) + @JvmStatic fun fluid(id: String): MatterRuleSelector = RepInt.fluid(id) + @JvmStatic fun fluidTag(tag: String): MatterRuleSelector = RepInt.fluidTag(tag) @JvmStatic fun fluidMod(modId: String): MatterRuleSelector = RepInt.fluidMod(modId) @JvmStatic fun anyOf(vararg selectors: Any): MatterRuleSelector = RepInt.anyOf(*selectors) @JvmStatic fun allOf(vararg selectors: Any): MatterRuleSelector = RepInt.allOf(*selectors) diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntMek.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntMek.kt new file mode 100644 index 0000000..582e23f --- /dev/null +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntMek.kt @@ -0,0 +1,23 @@ +package com.p_nsk.replicated_integration.kubejs + +import com.p_nsk.replicated_integration.api.kubejs.RepInt +import com.p_nsk.replicated_integration.api.node.MatterNodes +import com.p_nsk.replicated_integration.api.selector.MatterRuleSelector + +object ForgeRepIntMek { + @JvmStatic fun gas(id: String): MatterRuleSelector = RepInt.node(MatterNodes.GAS.toString(), id) + @JvmStatic fun gasTag(tag: String): MatterRuleSelector = RepInt.nodeTag(MatterNodes.GAS.toString(), tag) + @JvmStatic fun gasMod(modId: String): MatterRuleSelector = RepInt.nodeMod(MatterNodes.GAS.toString(), modId) + + @JvmStatic fun infuseType(id: String): MatterRuleSelector = RepInt.node(MatterNodes.INFUSE_TYPE.toString(), id) + @JvmStatic fun infuseTypeTag(tag: String): MatterRuleSelector = RepInt.nodeTag(MatterNodes.INFUSE_TYPE.toString(), tag) + @JvmStatic fun infuseTypeMod(modId: String): MatterRuleSelector = RepInt.nodeMod(MatterNodes.INFUSE_TYPE.toString(), modId) + + @JvmStatic fun pigment(id: String): MatterRuleSelector = RepInt.node(MatterNodes.PIGMENT.toString(), id) + @JvmStatic fun pigmentTag(tag: String): MatterRuleSelector = RepInt.nodeTag(MatterNodes.PIGMENT.toString(), tag) + @JvmStatic fun pigmentMod(modId: String): MatterRuleSelector = RepInt.nodeMod(MatterNodes.PIGMENT.toString(), modId) + + @JvmStatic fun slurry(id: String): MatterRuleSelector = RepInt.node(MatterNodes.SLURRY.toString(), id) + @JvmStatic fun slurryTag(tag: String): MatterRuleSelector = RepInt.nodeTag(MatterNodes.SLURRY.toString(), tag) + @JvmStatic fun slurryMod(modId: String): MatterRuleSelector = RepInt.nodeMod(MatterNodes.SLURRY.toString(), modId) +} diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntMekKubeJSPlugin.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntMekKubeJSPlugin.kt new file mode 100644 index 0000000..11b5fe9 --- /dev/null +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepIntMekKubeJSPlugin.kt @@ -0,0 +1,10 @@ +package com.p_nsk.replicated_integration.kubejs + +import dev.latvian.mods.kubejs.KubeJSPlugin +import dev.latvian.mods.kubejs.script.BindingsEvent + +class ForgeRepIntMekKubeJSPlugin : KubeJSPlugin() { + override fun registerBindings(event: BindingsEvent) { + event.add("RepIntMek", ForgeRepIntMek::class.java) + } +} diff --git a/1.20.1-forge/src/main/resources/kubejs.plugins.txt b/1.20.1-forge/src/main/resources/kubejs.plugins.txt index ec3fe48..7117a9b 100644 --- a/1.20.1-forge/src/main/resources/kubejs.plugins.txt +++ b/1.20.1-forge/src/main/resources/kubejs.plugins.txt @@ -1 +1,2 @@ com.p_nsk.replicated_integration.kubejs.ForgeRepIntKubeJSPlugin +com.p_nsk.replicated_integration.kubejs.ForgeRepIntMekKubeJSPlugin mekanism diff --git a/1.21.1-neo/build.gradle.kts b/1.21.1-neo/build.gradle.kts index b044eef..eb2175b 100644 --- a/1.21.1-neo/build.gradle.kts +++ b/1.21.1-neo/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { // depend (optional) compileOnly(mekclassifier("api")) compileOnly(libs.ae2addonlib.mc1211) + compileOnly(libs.probejs.mc1211) // for develop runtimeOnly(libs.mekanism.mc1211) diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt index 9a73161..7b3cadf 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt @@ -1,33 +1,21 @@ package com.p_nsk.replicated_integration.kubejs -import com.p_nsk.replicated_integration.adapter.vanilla.BuiltinNodeResolver import com.p_nsk.replicated_integration.api.kubejs.RepInt import com.p_nsk.replicated_integration.api.node.MatterNodes -import com.p_nsk.replicated_integration.api.selector.* -import net.minecraft.tags.TagKey +import com.p_nsk.replicated_integration.api.selector.MatterRuleSelector +import com.p_nsk.replicated_integration.api.selector.NodeView import net.minecraft.world.item.Item -import net.minecraft.world.item.ItemStack -import net.minecraft.world.level.material.Fluid -import net.neoforged.neoforge.fluids.FluidStack import java.util.function.Predicate object NeoRepInt { @JvmStatic fun node(type: String, id: String): MatterRuleSelector = RepInt.node(type, id) @JvmStatic fun nodeTag(type: String, tag: String): MatterRuleSelector = RepInt.nodeTag(type, tag) @JvmStatic fun nodeMod(type: String, modId: String): MatterRuleSelector = RepInt.nodeMod(type, modId) - @JvmStatic fun item(stack: ItemStack): MatterRuleSelector = - BuiltinNodeResolver.itemNode(stack) - ?.let { MatterSelectorKey(MatterSelectorKind.NODE, it.type, it.id).asRuleSelector() } - ?: throw IllegalArgumentException("Item selector cannot be created from an empty ItemStack") - - @JvmStatic fun itemTag(tag: TagKey): MatterRuleSelector = RepInt.itemTag(tag.location().toString()) + @JvmStatic fun item(id: String): MatterRuleSelector = RepInt.item(id) + @JvmStatic fun itemTag(tag: String): MatterRuleSelector = RepInt.itemTag(tag) @JvmStatic fun itemMod(modId: String): MatterRuleSelector = RepInt.itemMod(modId) - @JvmStatic fun fluid(stack: FluidStack): MatterRuleSelector = - BuiltinNodeResolver.fluidNode(stack) - ?.let { MatterSelectorKey(MatterSelectorKind.NODE, it.type, it.id).asRuleSelector() } - ?: throw IllegalArgumentException("Fluid selector cannot be created from an empty FluidStack") - - @JvmStatic fun fluidTag(tag: TagKey): MatterRuleSelector = RepInt.fluidTag(tag.location().toString()) + @JvmStatic fun fluid(id: String): MatterRuleSelector = RepInt.fluid(id) + @JvmStatic fun fluidTag(tag: String): MatterRuleSelector = RepInt.fluidTag(tag) @JvmStatic fun fluidMod(modId: String): MatterRuleSelector = RepInt.fluidMod(modId) @JvmStatic fun anyOf(vararg selectors: Any): MatterRuleSelector = RepInt.anyOf(*selectors) @JvmStatic fun allOf(vararg selectors: Any): MatterRuleSelector = RepInt.allOf(*selectors) diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMek.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMek.kt new file mode 100644 index 0000000..4372211 --- /dev/null +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMek.kt @@ -0,0 +1,11 @@ +package com.p_nsk.replicated_integration.kubejs + +import com.p_nsk.replicated_integration.api.kubejs.RepInt +import com.p_nsk.replicated_integration.api.node.MatterNodes +import com.p_nsk.replicated_integration.api.selector.MatterRuleSelector + +object NeoRepIntMek { + @JvmStatic fun chemical(id: String): MatterRuleSelector = RepInt.node(MatterNodes.CHEMICAL.toString(), id) + @JvmStatic fun chemicalTag(tag: String): MatterRuleSelector = RepInt.nodeTag(MatterNodes.CHEMICAL.toString(), tag) + @JvmStatic fun chemicalMod(modId: String): MatterRuleSelector = RepInt.nodeMod(MatterNodes.CHEMICAL.toString(), modId) +} diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMekKubeJSPlugin.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMekKubeJSPlugin.kt new file mode 100644 index 0000000..727df7a --- /dev/null +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMekKubeJSPlugin.kt @@ -0,0 +1,10 @@ +package com.p_nsk.replicated_integration.kubejs + +import dev.latvian.mods.kubejs.plugin.KubeJSPlugin +import dev.latvian.mods.kubejs.script.BindingRegistry + +class NeoRepIntMekKubeJSPlugin : KubeJSPlugin { + override fun registerBindings(registry: BindingRegistry) { + registry.add("RepIntMek", NeoRepIntMek::class.java) + } +} diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntProbeJSPlugin.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntProbeJSPlugin.kt new file mode 100644 index 0000000..f8f0d85 --- /dev/null +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntProbeJSPlugin.kt @@ -0,0 +1,61 @@ +package com.p_nsk.replicated_integration.kubejs + +import moe.wolfgirl.probejs.plugin.ProbeJSPlugin +import moe.wolfgirl.probejs.plugin.builtins.alias.RegistryTypes +import moe.wolfgirl.probejs.plugin.builtins.alias.SpecialTypes +import moe.wolfgirl.probejs.typescript.Documents +import moe.wolfgirl.probejs.typescript.document.base.Type +import moe.wolfgirl.probejs.typescript.document.members.MethodDecl +import moe.wolfgirl.probejs.typescript.document.members.ParamDecl + +class NeoRepIntProbeJSPlugin : ProbeJSPlugin() { + override fun transformClass(document: Documents.ClassDocument) { + val className = document.classInfo().clazz().name + if (className !in PROBE_CLASS_NAMES) return + + document.methodDocs() + .map { it.second } + .forEach { method -> + PARAM_OVERRIDES[className]?.get(method.name)?.let { paramOverride -> + replaceParam(method, paramOverride.index, paramOverride.type) + } + } + } + + private fun replaceParam(method: MethodDecl, index: Int, type: Type) { + if (index !in method.params.indices) return + method.params = method.params.mapIndexed { paramIndex, param -> + if (paramIndex == index) { + ParamDecl(param.name, type, param.isRest, param.optional) + } else { + param + } + } + } + + companion object { + private data class ParamOverride(val index: Int, val type: Type) + + private const val REP_INT_CLASS_NAME = "com.p_nsk.replicated_integration.kubejs.NeoRepInt" + private const val REP_INT_MEK_CLASS_NAME = "com.p_nsk.replicated_integration.kubejs.NeoRepIntMek" + + private val PROBE_CLASS_NAMES = setOf(REP_INT_CLASS_NAME, REP_INT_MEK_CLASS_NAME) + + private val PARAM_OVERRIDES: Map> = mapOf( + REP_INT_CLASS_NAME to mapOf( + "nodeMod" to ParamOverride(1, SpecialTypes.MOD_ID), + "item" to ParamOverride(0, RegistryTypes.`object`("Item")), + "itemTag" to ParamOverride(0, RegistryTypes.tag("Item")), + "itemMod" to ParamOverride(0, SpecialTypes.MOD_ID), + "fluid" to ParamOverride(0, RegistryTypes.`object`("Fluid")), + "fluidTag" to ParamOverride(0, RegistryTypes.tag("Fluid")), + "fluidMod" to ParamOverride(0, SpecialTypes.MOD_ID), + ), + REP_INT_MEK_CLASS_NAME to mapOf( + "chemical" to ParamOverride(0, RegistryTypes.`object`("MekanismChemical")), + "chemicalTag" to ParamOverride(0, RegistryTypes.tag("MekanismChemical")), + "chemicalMod" to ParamOverride(0, SpecialTypes.MOD_ID), + ), + ) + } +} diff --git a/1.21.1-neo/src/main/resources/kubejs.plugins.txt b/1.21.1-neo/src/main/resources/kubejs.plugins.txt index 6d05038..571da96 100644 --- a/1.21.1-neo/src/main/resources/kubejs.plugins.txt +++ b/1.21.1-neo/src/main/resources/kubejs.plugins.txt @@ -1 +1,3 @@ com.p_nsk.replicated_integration.kubejs.NeoRepIntKubeJSPlugin +com.p_nsk.replicated_integration.kubejs.NeoRepIntProbeJSPlugin probejs +com.p_nsk.replicated_integration.kubejs.NeoRepIntMekKubeJSPlugin mekanism diff --git a/buildSrc/src/main/kotlin/legacyforge-mod-conventions.gradle.kts b/buildSrc/src/main/kotlin/legacyforge-mod-conventions.gradle.kts index ebd047a..fa1f4fd 100644 --- a/buildSrc/src/main/kotlin/legacyforge-mod-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/legacyforge-mod-conventions.gradle.kts @@ -76,12 +76,14 @@ legacyForge { create("server") { server() + gameDirectory = project.file("run-server") programArgument("--nogui") systemProperty("forge.enabledGameTestNamespaces", modId) } create("gameTestServer") { type = "gameTestServer" + gameDirectory = project.file("run-server") systemProperty("forge.enabledGameTestNamespaces", modId) } diff --git a/gradle.properties b/gradle.properties index 2c18508..37a87e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ org.gradle.configuration-cache=true modId=replicated_integration modName=Replicated Integration modLicense=MIT -modVersion=0.1.1 +modVersion=0.2.0 modGroupId=com.p_nsk modAuthors=p_nsk modDescription=Optional compatibility addon for Replication across Forge and NeoForge. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1476913..077d811 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,9 +57,10 @@ architectury-mc1201 = "9.2.14" architectury-mc1211 = "13.0.8" rhino-mc1201 = "2001.2.3-build.10" rhino-mc1211 = "2101.2.7-build.82" - -probejsLegacy-mc1201 = "7615033" # 8087580 +# probejsLegacyはバグがあって使えないので一旦封印 +probejsLegacy-mc1201 = "N/A" # 8087580 # 7615033 probejs-mc1211 = "8122173" +probejs-mc1201 = "5227399" [libraries] slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" } @@ -118,6 +119,7 @@ rhino-mc1201 = { module = "dev.latvian.mods:rhino-forge", version.ref = "rhino-m rhino-mc1211 = { module = "dev.latvian.mods:rhino", version.ref = "rhino-mc1211" } probejsLegacy-mc1201 = { module = "curse.maven:probejs-legacy-956446", version.ref = "probejsLegacy-mc1201" } +probejs-mc1201 = { module = "curse.maven:probejs-585406", version.ref = "probejs-mc1201" } probejs-mc1211 = { module = "curse.maven:probejs-585406", version.ref = "probejs-mc1211" } [plugins] From a157adfad723a6af51dda99b422867c2d818e4c2 Mon Sep 17 00:00:00 2001 From: P-nutsK <107553542+P-nutsK@users.noreply.github.com> Date: Sat, 23 May 2026 05:08:16 +0900 Subject: [PATCH 09/12] fix warnings --- .../adapter/vanilla/BuiltinNodeResolver.kt | 5 +++-- .../com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt | 6 +++--- .../com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt | 6 +++--- .../replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt | 3 +-- .../kubejs/NeoRepIntMekKubeJSPlugin.kt | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt index 9314bcf..00296ea 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt @@ -2,16 +2,17 @@ package com.p_nsk.replicated_integration.adapter.vanilla import com.p_nsk.replicated_integration.api.model.LiteResourceLocation import com.p_nsk.replicated_integration.api.model.NodeAmount -import com.p_nsk.replicated_integration.api.node.NodeKey import com.p_nsk.replicated_integration.api.node.MatterNodes -import net.minecraft.core.registries.Registries +import com.p_nsk.replicated_integration.api.node.NodeKey import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.core.registries.Registries import net.minecraft.resources.ResourceLocation import net.minecraft.tags.TagKey import net.minecraft.world.item.ItemStack import net.minecraftforge.fluids.FluidStack import net.minecraftforge.registries.ForgeRegistries +@Suppress("unused") object BuiltinNodeResolver { fun itemNode(stack: ItemStack): NodeKey? { if (stack.isEmpty) { diff --git a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt index a30cc68..2a88c48 100644 --- a/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt +++ b/1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/ForgeRepInt.kt @@ -6,7 +6,7 @@ import com.p_nsk.replicated_integration.api.selector.MatterRuleSelector import com.p_nsk.replicated_integration.api.selector.NodeView import net.minecraft.world.item.Item import java.util.function.Predicate - +@Suppress("unused") object ForgeRepInt { @JvmStatic fun node(type: String, id: String): MatterRuleSelector = RepInt.node(type, id) @JvmStatic fun nodeTag(type: String, tag: String): MatterRuleSelector = RepInt.nodeTag(type, tag) @@ -24,7 +24,7 @@ object ForgeRepInt { @JvmStatic fun whereItem(name: String, predicate: Predicate): MatterRuleSelector = - RepInt.whereNode(name, Predicate { view -> + RepInt.whereNode(name) { view -> view.key.type == MatterNodes.ITEM && view.getRaw()?.let(predicate::test) == true - }) + } } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt index 7b3cadf..0e26e51 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepInt.kt @@ -6,7 +6,7 @@ import com.p_nsk.replicated_integration.api.selector.MatterRuleSelector import com.p_nsk.replicated_integration.api.selector.NodeView import net.minecraft.world.item.Item import java.util.function.Predicate - +@Suppress("unused") object NeoRepInt { @JvmStatic fun node(type: String, id: String): MatterRuleSelector = RepInt.node(type, id) @JvmStatic fun nodeTag(type: String, tag: String): MatterRuleSelector = RepInt.nodeTag(type, tag) @@ -24,7 +24,7 @@ object NeoRepInt { @JvmStatic fun whereItem(name: String, predicate: Predicate): MatterRuleSelector = - RepInt.whereNode(name, Predicate { view -> + RepInt.whereNode(name) { view -> view.key.type == MatterNodes.ITEM && view.getRaw()?.let(predicate::test) == true - }) + } } diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt index 351decd..8f58470 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntKubeJSPlugin.kt @@ -4,7 +4,6 @@ import com.p_nsk.replicated_integration.api.kubejs.RepIntMatterEvent import com.p_nsk.replicated_integration.api.selector.MatterRule import dev.latvian.mods.kubejs.event.EventGroup import dev.latvian.mods.kubejs.event.EventGroupRegistry -import dev.latvian.mods.kubejs.event.EventGroupWrapper import dev.latvian.mods.kubejs.event.KubeEvent import dev.latvian.mods.kubejs.plugin.ClassFilter import dev.latvian.mods.kubejs.plugin.KubeJSPlugin @@ -34,7 +33,7 @@ object NeoRepIntKubeJSEvents { return event.rules } } - +@Suppress("unused") class NeoRepIntMatterEvent : KubeEvent { private val delegate = RepIntMatterEvent() diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMekKubeJSPlugin.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMekKubeJSPlugin.kt index 727df7a..74e8613 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMekKubeJSPlugin.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/kubejs/NeoRepIntMekKubeJSPlugin.kt @@ -2,7 +2,7 @@ package com.p_nsk.replicated_integration.kubejs import dev.latvian.mods.kubejs.plugin.KubeJSPlugin import dev.latvian.mods.kubejs.script.BindingRegistry - +@Suppress("unused") class NeoRepIntMekKubeJSPlugin : KubeJSPlugin { override fun registerBindings(registry: BindingRegistry) { registry.add("RepIntMek", NeoRepIntMek::class.java) From 2349118e0fc0dd8f0237c774532209ba93204403 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 20:39:45 +0000 Subject: [PATCH 10/12] Add Neo UI config translation for matter amount notation Agent-Logs-Url: https://github.com/P-nutsK/ReplicatedIntegration/sessions/d7a45117-7699-4db9-9ca6-afbbbf75ebc1 Co-authored-by: P-nutsK <107553542+P-nutsK@users.noreply.github.com> --- .../resources/assets/replicated_integration/lang/en_us.json | 1 + .../resources/assets/replicated_integration/lang/ja_jp.json | 1 + .../data/NeoReplicatedIntegrationLanguageProvider.kt | 2 ++ 3 files changed, 4 insertions(+) diff --git a/1.21.1-neo/src/generated/resources/assets/replicated_integration/lang/en_us.json b/1.21.1-neo/src/generated/resources/assets/replicated_integration/lang/en_us.json index 92cbe4a..7e74cb2 100644 --- a/1.21.1-neo/src/generated/resources/assets/replicated_integration/lang/en_us.json +++ b/1.21.1-neo/src/generated/resources/assets/replicated_integration/lang/en_us.json @@ -4,5 +4,6 @@ "replicated_integration.chat.notice.supported_integrations": "has tuned support for these installed mods:", "replicated_integration.chat.notice.title": "Replicated Integration", "replicated_integration.config.enable_nuclear_recipe": "Enable Mekanism nuclear recipe support", + "replicated_integration.config.ui.matter_amount_notation": "Matter amount notation", "replicated_integration.config.notice.compatibility_summary_shown": "Compatibility summary notice shown" } \ No newline at end of file diff --git a/1.21.1-neo/src/generated/resources/assets/replicated_integration/lang/ja_jp.json b/1.21.1-neo/src/generated/resources/assets/replicated_integration/lang/ja_jp.json index f308816..4d03627 100644 --- a/1.21.1-neo/src/generated/resources/assets/replicated_integration/lang/ja_jp.json +++ b/1.21.1-neo/src/generated/resources/assets/replicated_integration/lang/ja_jp.json @@ -4,5 +4,6 @@ "replicated_integration.chat.notice.supported_integrations": "は、導入済みの以下 mod と連携できます:", "replicated_integration.chat.notice.title": "Replicated Integration", "replicated_integration.config.enable_nuclear_recipe": "Mekanism の核分裂レシピ対応を有効化", + "replicated_integration.config.ui.matter_amount_notation": "Matter 量の表記", "replicated_integration.config.notice.compatibility_summary_shown": "互換性サマリー通知を表示済み" } \ No newline at end of file diff --git a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoReplicatedIntegrationLanguageProvider.kt b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoReplicatedIntegrationLanguageProvider.kt index 099fade..15fe43b 100644 --- a/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoReplicatedIntegrationLanguageProvider.kt +++ b/1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/data/NeoReplicatedIntegrationLanguageProvider.kt @@ -17,6 +17,7 @@ class NeoReplicatedIntegrationLanguageProvider( private fun addEnglishTranslations() { add("replicated_integration.config.enable_nuclear_recipe", "Enable Mekanism nuclear recipe support") + add("replicated_integration.config.ui.matter_amount_notation", "Matter amount notation") add("replicated_integration.config.notice.compatibility_summary_shown", "Compatibility summary notice shown") add("key.categories.replicated_integration", "Replicated Integration") add("key.replicated_integration.open_replication_index", "Open Replication Index") @@ -37,6 +38,7 @@ class NeoReplicatedIntegrationLanguageProvider( private fun addJapaneseTranslations() { add("replicated_integration.config.enable_nuclear_recipe", "Mekanism の核分裂レシピ対応を有効化") + add("replicated_integration.config.ui.matter_amount_notation", "Matter 量の表記") add("replicated_integration.config.notice.compatibility_summary_shown", "互換性サマリー通知を表示済み") add("key.categories.replicated_integration", "Replicated Integration") add("key.replicated_integration.open_replication_index", "Replication Index を開く") From baf371cc9c74e19c56d94ea9bf0e55aaa09dbe80 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 20:41:18 +0000 Subject: [PATCH 11/12] fix: make MatterAmountFormatter exact formatting locale-stable Agent-Logs-Url: https://github.com/P-nutsK/ReplicatedIntegration/sessions/d250a680-10f0-49b9-94b2-752c66825158 Co-authored-by: P-nutsK <107553542+P-nutsK@users.noreply.github.com> --- .../replicated_integration/api/client/MatterAmountFormatter.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterAmountFormatter.kt b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterAmountFormatter.kt index 365e59a..ac67954 100644 --- a/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterAmountFormatter.kt +++ b/common/src/main/kotlin/com/p_nsk/replicated_integration/api/client/MatterAmountFormatter.kt @@ -1,6 +1,7 @@ package com.p_nsk.replicated_integration.api.client import java.text.DecimalFormat +import java.text.DecimalFormatSymbols import java.util.Locale import kotlin.math.abs import kotlin.math.floor @@ -13,7 +14,7 @@ enum class MatterAmountNotation { } object MatterAmountFormatter { - private val exactFormat = DecimalFormat("#,##0.##") + private val exactFormat = DecimalFormat("#,##0.##", DecimalFormatSymbols(Locale.ROOT)) private val siSuffixes = listOf("", "K", "M", "G", "T", "P", "E") private val shortScaleSuffixes = listOf("", "K", "M", "B", "T", "Qa", "Qi") From ceff00df69b8efadbc49943d5bab59047a9d0bf4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 20:52:04 +0000 Subject: [PATCH 12/12] docs: add parked list of unresolved PR4 review items Agent-Logs-Url: https://github.com/P-nutsK/ReplicatedIntegration/sessions/f1dd3ce3-6124-4066-bdfc-1e5b2e79cb98 Co-authored-by: P-nutsK <107553542+P-nutsK@users.noreply.github.com> --- docs/pr4-unresolved-review-items.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 docs/pr4-unresolved-review-items.md diff --git a/docs/pr4-unresolved-review-items.md b/docs/pr4-unresolved-review-items.md new file mode 100644 index 0000000..7f9e8fa --- /dev/null +++ b/docs/pr4-unresolved-review-items.md @@ -0,0 +1,12 @@ +# PR #4 unresolved review items (parked) + +The following review threads are intentionally left unresolved for now. + +- `common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorStorageCodec.kt` (`saveRules()` static selector handling) +- `common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MatterSelectorMaterializer.kt` (explicit tie-break for equal priority rules) +- `common/src/main/kotlin/com/p_nsk/replicated_integration/api/selector/MutableMatterSelectors.kt` (explicit tie-break for equal priority rules in `snapshot()`) +- `1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt` (`searchBox.tick()` in NeoForge `tick()`) +- `1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt` (cache/index for mod-scoped node lookups) +- `1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/adapter/vanilla/BuiltinNodeResolver.kt` (cache/index for mod-scoped node lookups) +- `1.21.1-neo/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt` (avoid rebuilding record/index maps in `visibleEntries()`) +- `1.20.1-forge/src/main/kotlin/com/p_nsk/replicated_integration/client/MatterIndexScreen.kt` (avoid rebuilding record/index maps in `visibleEntries()` / `equivalentEntries()`)