Compare commits

...

2 Commits

8 changed files with 289 additions and 54 deletions

View File

@ -32,6 +32,7 @@ import ru.dbotthepony.mc.otm.client.MatteryGUI;
import ru.dbotthepony.mc.otm.client.model.ExosuitModel;
import ru.dbotthepony.mc.otm.client.model.GravitationStabilizerModel;
import ru.dbotthepony.mc.otm.client.model.TritaniumArmorModel;
import ru.dbotthepony.mc.otm.client.render.ChartTooltipElement;
import ru.dbotthepony.mc.otm.client.render.ShockwaveRenderer;
import ru.dbotthepony.mc.otm.client.render.blockentity.BatteryBankRenderer;
import ru.dbotthepony.mc.otm.client.render.blockentity.MatterBatteryBankRenderer;
@ -159,6 +160,8 @@ public final class OverdriveThatMatters {
bus.addListener(EventPriority.NORMAL, BatteryBankRenderer.Companion::onRegisterAdditionalModels);
bus.addListener(EventPriority.NORMAL, MatterBatteryBankRenderer.Companion::onRegisterAdditionalModels);
bus.addListener(EventPriority.NORMAL, ChartTooltipElement.Companion::register);
MBlockColors.INSTANCE.register(bus);
}

View File

@ -7,12 +7,16 @@ import com.mojang.blaze3d.vertex.ByteBufferBuilder
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexFormat
import it.unimi.dsi.fastutil.floats.Float2ObjectMap
import net.minecraft.client.gui.Font
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.network.chat.Component
import net.minecraft.world.inventory.tooltip.TooltipComponent
import net.neoforged.neoforge.client.event.RegisterClientTooltipComponentFactoriesEvent
import org.lwjgl.opengl.GL11.GL_LESS
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.client.minecraft
import kotlin.math.PI
import kotlin.math.acos
import kotlin.math.cos
@ -40,7 +44,7 @@ data class ChartMouseLabels(
data class ChartLevelLabels(
val labels: Map<Float, Component>,
val font: Font,
val font: Font? = null,
val lineColor: RGBAColor = RGBAColor.DARK_GREEN,
val textColor: RGBAColor = RGBAColor.DARK_GREEN,
val textGravity: RenderGravity = RenderGravity.TOP_RIGHT,
@ -49,19 +53,44 @@ data class ChartLevelLabels(
private val BYTE_BUFFER_BUILDER = ByteBufferBuilder(786_432)
class ChartTooltipElement(
private val normalized: List<Pair<FloatArray, RGBAColor>>,
private val width: Float,
private val height: Float,
private val levelLabels: ChartLevelLabels? = null,
) : ClientTooltipComponent, TooltipComponent {
override fun getHeight(): Int {
return this.height.toInt()
}
override fun getWidth(font: Font): Int {
return this.width.toInt()
}
override fun renderImage(font: Font, x: Int, y: Int, graphics: GuiGraphics) {
renderChart(graphics.pose(), normalized, this.width, this.height - 10f, levelLabels = this.levelLabels, x = x.toFloat(), y = y.toFloat() + 10f)
}
companion object {
fun register(event: RegisterClientTooltipComponentFactoriesEvent) {
event.register(ChartTooltipElement::class.java) { it }
}
}
}
fun renderChart(
poseStack: PoseStack,
normalized: FloatArray,
charts: List<Pair<FloatArray, RGBAColor>>,
width: Float,
height: Float,
color: RGBAColor = RGBAColor.WHITE,
labels: ChartMouseLabels? = null,
levelLabels: ChartLevelLabels? = null,
x: Float = 0f,
y: Float = 0f,
) {
require(charts.all { it.first.size == charts[0].first.size }) { "Provided chart data is not of same width" }
val builder = BufferBuilder(BYTE_BUFFER_BUILDER, VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
val step = width / normalized.size
val step = width / charts[0].first.size
val pose = poseStack.last().pose
val checkLabels = labels != null && labels.mouseY in 0f .. height && labels.mouseX in 0f .. width
@ -81,40 +110,42 @@ fun renderChart(
}
}
for (i in 0 until normalized.size - 1) {
val y0 = y + (1f - normalized[i]) * height
val y1 = y + (1f - normalized[i + 1]) * height
for ((normalized, color) in charts) {
for (i in 0 until normalized.size - 1) {
val y0 = y + (1f - normalized[i]) * height
val y1 = y + (1f - normalized[i + 1]) * height
val x0 = x + width - (i + 0.5f) * step
val x1 = x + width - (i + 1.5f) * step
val x0 = x + width - (i + 0.5f) * step
val x1 = x + width - (i + 1.5f) * step
val xDiff = x1 - x0
val yDiff = y1 - y0
val xDiff = x1 - x0
val yDiff = y1 - y0
if (yDiff.sign == 0f) {
builder.vertex(pose, x0, y0 + LINE_WIDTH / 2f, 0f).color(color)
builder.vertex(pose, x0, y0 - LINE_WIDTH / 2f, 0f).color(color)
builder.vertex(pose, x1, y1 - LINE_WIDTH / 2f, 0f).color(color)
builder.vertex(pose, x1, y1 + LINE_WIDTH / 2f, 0f).color(color)
} else {
val length = (xDiff.pow(2f) + yDiff.pow(2f)).pow(0.5f)
val angle = acos(xDiff / length) * yDiff.sign - PI / 2f // rotate 90 deg
if (yDiff.sign == 0f) {
builder.vertex(pose, x0, y0 + LINE_WIDTH / 2f, 0f).color(color)
builder.vertex(pose, x0, y0 - LINE_WIDTH / 2f, 0f).color(color)
builder.vertex(pose, x1, y1 - LINE_WIDTH / 2f, 0f).color(color)
builder.vertex(pose, x1, y1 + LINE_WIDTH / 2f, 0f).color(color)
} else {
val length = (xDiff.pow(2f) + yDiff.pow(2f)).pow(0.5f)
val angle = acos(xDiff / length) * yDiff.sign - PI / 2f // rotate 90 deg
val xDisp = cos(angle).toFloat() * LINE_WIDTH / 2f
val yDisp = sin(angle).toFloat() * LINE_WIDTH / 2f
val xDisp = cos(angle).toFloat() * LINE_WIDTH / 2f
val yDisp = sin(angle).toFloat() * LINE_WIDTH / 2f
builder.vertex(pose, x0 + xDisp, y0 + yDisp, 0f).color(color)
builder.vertex(pose, x0 - xDisp, y0 - yDisp, 0f).color(color)
builder.vertex(pose, x1 - xDisp, y1 - yDisp, 0f).color(color)
builder.vertex(pose, x1 + xDisp, y1 + yDisp, 0f).color(color)
}
builder.vertex(pose, x0 + xDisp, y0 + yDisp, 0f).color(color)
builder.vertex(pose, x0 - xDisp, y0 - yDisp, 0f).color(color)
builder.vertex(pose, x1 - xDisp, y1 - yDisp, 0f).color(color)
builder.vertex(pose, x1 + xDisp, y1 + yDisp, 0f).color(color)
}
//graphics.renderRect(x0, y0, LINE_WIDTH, LINE_WIDTH)
//graphics.renderRect(x0, y0, LINE_WIDTH, LINE_WIDTH)
if (checkLabels && drawLabel == -1 && labels!!.mouseX in x1 .. x0) {
drawLabel = i
drawPointX = x0 // linearInterpolation(0.5f, x0, x1)
drawPointY = y0 // linearInterpolation(0.5f, y0, y1)
if (checkLabels && drawLabel == -1 && labels!!.mouseX in x1 .. x0) {
drawLabel = i
drawPointX = x0 // linearInterpolation(0.5f, x0, x1)
drawPointY = y0 // linearInterpolation(0.5f, y0, y1)
}
}
}
@ -130,13 +161,14 @@ fun renderChart(
RenderSystem.depthFunc(GL_LESS)
if (levelLabels != null && levelLabels.labels.isNotEmpty()) {
val font = levelLabels.font ?: minecraft.font
for ((level, label) in levelLabels.labels) {
val y0 = y + (1f - level) * height
val tX = x + levelLabels.textGravity.repositionX(width, levelLabels.font.width(label).toFloat()) - 1f
val tY = y0 - LINE_WIDTH - 1f - levelLabels.font.lineHeight + levelLabels.textGravity.repositionY(levelLabels.font.lineHeight * 2f + LINE_WIDTH + 2f, levelLabels.font.lineHeight.toFloat()) + 1f
val tX = x + levelLabels.textGravity.repositionX(width, font.width(label).toFloat()) - 1f
val tY = y0 - LINE_WIDTH - 1f - font.lineHeight + levelLabels.textGravity.repositionY(font.lineHeight * 2f + LINE_WIDTH + 2f, font.lineHeight.toFloat()) + 1f
levelLabels.font.draw(
font.draw(
poseStack,
label,
x = tX,

View File

@ -1,13 +1,16 @@
package ru.dbotthepony.mc.otm.client.render
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.datafixers.util.Either
import net.minecraft.client.gui.Font
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.FormattedText
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FormattedCharSequence
import net.minecraft.world.inventory.tooltip.TooltipComponent
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.kommons.math.RGBAColor
@ -78,10 +81,15 @@ class MGUIGraphics(val parent: GuiGraphics) {
renderCheckerboard(pose.last().pose(), x, y, width, height, z, color)
}
fun renderComponentTooltip(font: Font, lines: MutableList<Component>, x: Int, y: Int, itemStack: ItemStack = ItemStack.EMPTY) {
fun renderComponentTooltip(font: Font, lines: List<FormattedText>, x: Int, y: Int, itemStack: ItemStack = ItemStack.EMPTY) {
parent.renderComponentTooltip(font, lines, x, y, itemStack)
}
@JvmName("renderComponentTooltipFromElements")
fun renderComponentTooltip(font: Font, lines: List<Either<FormattedText, TooltipComponent>>, x: Int, y: Int, itemStack: ItemStack = ItemStack.EMPTY) {
parent.renderComponentTooltipFromElements(font, lines, x, y, itemStack)
}
fun flush() {
parent.flush()
}

View File

@ -58,7 +58,7 @@ open class DecimalHistoryChartPanel<out S : MatteryScreen<*>>(
renderChart(
graphics.pose,
normalized,
listOf(normalized to RGBAColor.WHITE),
width,
height,
labels = ChartMouseLabels(

View File

@ -4,11 +4,14 @@ import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.BufferUploader
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.datafixers.util.Either
import it.unimi.dsi.fastutil.ints.IntArrayList
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.FormattedText
import net.minecraft.world.inventory.tooltip.TooltipComponent
import org.lwjgl.opengl.GL11
import ru.dbotthepony.kommons.util.value
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
@ -52,13 +55,15 @@ open class MatterGaugePanel<out S : Screen> @JvmOverloads constructor(
private var lastDraw = nanoTime
private var lastLevel = 0f
protected open fun makeTooltip(): MutableList<Component> {
protected open fun makeTooltip(): MutableList<Either<FormattedText, TooltipComponent>> {
return mutableListOf(
TranslatableComponent(
"otm.gui.matter.percentage_level",
String.format("%.2f", widget.percentage * 100.0)
Either.left(
TranslatableComponent(
"otm.gui.matter.percentage_level",
String.format("%.2f", widget.percentage * 100.0)
)
),
formatMatterLevel(widget.level, widget.maxLevel, formatAsReadable = ShiftPressedCond)
Either.left(formatMatterLevel(widget.level, widget.maxLevel, formatAsReadable = ShiftPressedCond))
)
}
@ -145,7 +150,7 @@ open class ProfiledMatterGaugePanel<out S : Screen>(
x: Float = 0f,
y: Float = 0f
): MatterGaugePanel<S>(screen, parent, profiledWidget.gauge, x, y) {
override fun makeTooltip(): MutableList<Component> {
override fun makeTooltip(): MutableList<Either<FormattedText, TooltipComponent>> {
return super.makeTooltip().also {
formatHistory(
it,

View File

@ -1,7 +1,10 @@
package ru.dbotthepony.mc.otm.client.screen.widget
import com.mojang.datafixers.util.Either
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.FormattedText
import net.minecraft.world.inventory.tooltip.TooltipComponent
import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
import ru.dbotthepony.mc.otm.client.ShiftPressedCond
import ru.dbotthepony.mc.otm.core.TranslatableComponent
@ -25,10 +28,10 @@ open class PowerGaugePanel<out S : Screen>(
scissor = true
}
protected open fun makeTooltip(): MutableList<Component> {
protected open fun makeTooltip(): MutableList<Either<FormattedText, TooltipComponent>> {
return mutableListOf(
TranslatableComponent("otm.gui.power.percentage_level", String.format("%.2f", widget.percentage * 100.0)),
formatPowerLevel(widget.level, widget.maxLevel, formatAsReadable = ShiftPressedCond)
Either.left(TranslatableComponent("otm.gui.power.percentage_level", String.format("%.2f", widget.percentage * 100.0))),
Either.left(formatPowerLevel(widget.level, widget.maxLevel, formatAsReadable = ShiftPressedCond))
)
}
@ -95,7 +98,7 @@ open class ProfiledPowerGaugePanel<out S : Screen>(
width: Float = GAUGE_BACKGROUND.width,
height: Float = GAUGE_BACKGROUND.height
) : PowerGaugePanel<S>(screen, parent, profiledWidget.gauge, x, y, width, height) {
override fun makeTooltip(): MutableList<Component> {
override fun makeTooltip(): MutableList<Either<FormattedText, TooltipComponent>> {
return super.makeTooltip().also {
formatHistory(
it,

View File

@ -48,6 +48,11 @@ object ClientConfig : AbstractConfig("client", ModConfig.Type.CLIENT) {
.comment("For those who want it.")
.define("REDSTONE_CONTROLS_ITEM_ICONS", false)
var CHARTS_IN_TOOLTIPS: Boolean by builder
.comment("Draw charts in storage tooltips instead of list of last 20 values.")
.comment("Disable to get EnderIO-like experience")
.define("CHARTS_IN_TOOLTIPS", true)
init {
builder.pop()
}

View File

@ -1,9 +1,17 @@
package ru.dbotthepony.mc.otm.core.util
import com.mojang.datafixers.util.Either
import it.unimi.dsi.fastutil.chars.CharArrayList
import it.unimi.dsi.fastutil.floats.Float2ObjectArrayMap
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.FormattedText
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.inventory.tooltip.TooltipComponent
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.client.render.ChartLevelLabels
import ru.dbotthepony.mc.otm.client.render.ChartTooltipElement
import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.Decimal
@ -252,8 +260,179 @@ fun formatTickDuration(ticks: Int, longFormat: Boolean = false): String {
}
}
/**
* Clientside only function, do not call on shared/server realm.
*/
fun formatHistory(
result: MutableList<Component>,
result: MutableList<Either<FormattedText, TooltipComponent>>,
widget: ProfiledLevelGaugeWidget<*>,
bias: Int = 0,
decimals: Int = 3,
verbose: BooleanSupplier = never,
suffix: Any = "",
) {
if (!ClientConfig.GUI.CHARTS_IN_TOOLTIPS) {
formatHistoryLines(result, widget, bias, decimals, verbose, suffix)
} else {
formatHistoryChart(result, widget, bias, decimals, verbose, suffix)
}
}
private fun formatHistoryChart(
result: MutableList<Either<FormattedText, TooltipComponent>>,
widget: ProfiledLevelGaugeWidget<*>,
bias: Int = 0,
decimals: Int = 3,
verbose: BooleanSupplier = never,
suffix: Any = "",
) {
val diff = Array(widget.received.width) { widget.received[it] - widget.transferred[it] }
val normalizedDiff: FloatArray
val received: FloatArray
val transferred: FloatArray
val labels: ChartLevelLabels
val hasPositiveInfinity = diff.any { it.isInfinite && it.isPositive }
val hasNegativeInfinity = diff.any { it.isInfinite && it.isNegative }
if (hasPositiveInfinity && hasNegativeInfinity) {
normalizedDiff = FloatArray(diff.size) {
if (widget.received[it].isInfinite && widget.transferred[it].isInfinite)
0.5f
else if (diff[it].isInfinite && diff[it].isNegative)
0.1f
else if (diff[it].isInfinite && diff[it].isPositive)
0.9f
else
0.5f
}
received = FloatArray(diff.size) {
if (widget.received[it].isInfinite) 0.9f else 0.5f
}
transferred = FloatArray(diff.size) {
if (widget.transferred[it].isInfinite) 0.1f else 0.5f
}
labels = ChartLevelLabels(
labels = mapOf(
0.5f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
0.1f to TextComponent("-∞"),
0.9f to TextComponent(""),
)
)
} else if (hasPositiveInfinity) {
normalizedDiff = FloatArray(diff.size) {
if (diff[it].isInfinite)
0.9f
else
0.5f
}
received = FloatArray(diff.size) {
if (widget.received[it].isInfinite) 0.9f else 0.5f
}
transferred = FloatArray(diff.size)
transferred.fill(0.5f)
labels = ChartLevelLabels(
labels = mapOf(
0.5f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
0.9f to TextComponent(""),
)
)
} else if (hasNegativeInfinity) {
normalizedDiff = FloatArray(diff.size) {
if (diff[it].isInfinite)
0.1f
else
0.5f
}
received = FloatArray(diff.size)
received.fill(0.5f)
transferred = FloatArray(diff.size) {
if (widget.transferred[it].isInfinite) 0.1f else 0.5f
}
labels = ChartLevelLabels(
labels = mapOf(
0.5f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
0.9f to TextComponent("-∞"),
)
)
} else {
val max = maxOf(widget.received.max(), widget.transferred.max())
val labelNames = Float2ObjectArrayMap<Component>()
labelNames[0.5f] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
if (max.isZero) {
normalizedDiff = FloatArray(diff.size)
normalizedDiff.fill(0.5f)
received = FloatArray(diff.size)
received.fill(0.5f)
transferred = FloatArray(diff.size)
transferred.fill(0.5f)
labelNames[0.1f] = TextComponent("-∞")
labelNames[0.9f] = TextComponent("")
} else {
normalizedDiff = FloatArray(diff.size) {
(diff[it] / max).toFloat() * 0.4f + 0.5f
}
received = FloatArray(diff.size) {
(widget.received[it] / max).toFloat() * 0.4f + 0.5f
}
transferred = FloatArray(diff.size) {
(widget.transferred[it] / max).toFloat() * -0.4f + 0.5f
}
labelNames[0.1f] = (-max).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
if (verbose.asBoolean) labelNames[0.3f] = (-max * Decimal.ONE_HALF).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
labelNames[0.9f] = max.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
if (verbose.asBoolean) labelNames[0.7f] = (max * Decimal.ONE_HALF).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
}
labels = ChartLevelLabels(labels = labelNames)
}
val charts: List<Pair<FloatArray, RGBAColor>>
val receivedActivity = widget.received.any { it.isNotZero }
val transferredActivity = widget.received.any { it.isNotZero }
if (!receivedActivity && transferredActivity) {
charts = listOf(
received to RGBAColor.YELLOW,
normalizedDiff to RGBAColor.WHITE,
transferred to RGBAColor.RED,
)
} else if (!transferredActivity && receivedActivity) {
charts = listOf(
transferred to RGBAColor.RED,
normalizedDiff to RGBAColor.WHITE,
received to RGBAColor.YELLOW,
)
} else {
charts = listOf(
received to RGBAColor.YELLOW,
transferred to RGBAColor.RED,
normalizedDiff to RGBAColor.WHITE,
)
}
result.add(Either.right(ChartTooltipElement(charts, if (verbose.asBoolean) 200f else 100f, if (verbose.asBoolean) 120f else 60f, levelLabels = labels)))
}
private fun formatHistoryLines(
result: MutableList<Either<FormattedText, TooltipComponent>>,
widget: ProfiledLevelGaugeWidget<*>,
bias: Int = 0,
decimals: Int = 3,
@ -321,13 +500,13 @@ fun formatHistory(
)
}
result.add(TextComponent(""))
result.add(lines.removeFirst().format())
result.add(Either.left(TextComponent("")))
result.add(Either.left(lines.removeFirst().format()))
if (verbose.asBoolean) {
result.add(TextComponent("---"))
result.add(lines.removeFirst().format())
result.add(TextComponent("---"))
lines.forEach { result.add(it.format()) }
result.add(Either.left(TextComponent("---")))
result.add(Either.left(lines.removeFirst().format()))
result.add(Either.left(TextComponent("---")))
lines.forEach { result.add(Either.left(it.format())) }
}
}