Chart rendering right on energy counter

This commit is contained in:
DBotThePony 2024-10-05 20:43:00 +07:00
parent 35769e5b57
commit 7d0b257928
Signed by: DBot
GPG Key ID: DCC23B5715498507
15 changed files with 313 additions and 90 deletions

View File

@ -541,6 +541,7 @@ private fun blocks(provider: MatteryLanguageProvider) {
add(MBlocks.ENERGY_COUNTER[null]!!, "facing", "Input facing: %s") add(MBlocks.ENERGY_COUNTER[null]!!, "facing", "Input facing: %s")
add(MBlocks.ENERGY_COUNTER[null]!!, "switch", "Switch input facing") add(MBlocks.ENERGY_COUNTER[null]!!, "switch", "Switch input facing")
add(MBlocks.ENERGY_COUNTER[null]!!, "limit", "I/O Limit. -1 means no limit") add(MBlocks.ENERGY_COUNTER[null]!!, "limit", "I/O Limit. -1 means no limit")
add(MBlocks.ENERGY_COUNTER[null]!!, "display_this", "Display this information on block's screen")
addBlock(MBlocks.CHEMICAL_GENERATOR.values, "Chemical Generator") addBlock(MBlocks.CHEMICAL_GENERATOR.values, "Chemical Generator")
addBlock(MBlocks.CHEMICAL_GENERATOR.values, "desc", "Generates power by burning solid fuels") addBlock(MBlocks.CHEMICAL_GENERATOR.values, "desc", "Generates power by burning solid fuels")
@ -840,6 +841,7 @@ private fun gui(provider: MatteryLanguageProvider) {
gui("time.short.5s", "5s") gui("time.short.5s", "5s")
gui("time.short.15s", "15s") gui("time.short.15s", "15s")
gui("time.short.1m", "1m") gui("time.short.1m", "1m")
gui("time.short.10m", "10m")
gui("time.short.1h", "1h") gui("time.short.1h", "1h")
gui("time.short.6h", "6h") gui("time.short.6h", "6h")
gui("time.short.24h", "24h") gui("time.short.24h", "24h")

View File

@ -549,6 +549,7 @@ private fun blocks(provider: MatteryLanguageProvider) {
add(MBlocks.ENERGY_COUNTER[null]!!, "facing", "Сторона входа: %s") add(MBlocks.ENERGY_COUNTER[null]!!, "facing", "Сторона входа: %s")
add(MBlocks.ENERGY_COUNTER[null]!!, "switch", "Сменить сторону входа") add(MBlocks.ENERGY_COUNTER[null]!!, "switch", "Сменить сторону входа")
add(MBlocks.ENERGY_COUNTER[null]!!, "limit", "Лимит ввода/вывода. -1 для отключения лимитов") add(MBlocks.ENERGY_COUNTER[null]!!, "limit", "Лимит ввода/вывода. -1 для отключения лимитов")
add(MBlocks.ENERGY_COUNTER[null]!!, "display_this", "Отображать эти данные на экране блока")
addBlock(MBlocks.CHEMICAL_GENERATOR.values, "Химический генератор") addBlock(MBlocks.CHEMICAL_GENERATOR.values, "Химический генератор")
addBlock(MBlocks.CHEMICAL_GENERATOR.values, "desc", "Генерирует энергию сжигая твёрдое топливо") addBlock(MBlocks.CHEMICAL_GENERATOR.values, "desc", "Генерирует энергию сжигая твёрдое топливо")
@ -848,6 +849,7 @@ private fun gui(provider: MatteryLanguageProvider) {
gui("time.short.5s", "5с") gui("time.short.5s", "5с")
gui("time.short.15s", "15с") gui("time.short.15s", "15с")
gui("time.short.1m", "") gui("time.short.1m", "")
gui("time.short.10m", "10м")
gui("time.short.1h", "") gui("time.short.1h", "")
gui("time.short.6h", "") gui("time.short.6h", "")
gui("time.short.24h", "24ч") gui("time.short.24h", "24ч")

View File

@ -10,6 +10,7 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import net.neoforged.neoforge.capabilities.Capabilities import net.neoforged.neoforge.capabilities.Capabilities
import ru.dbotthepony.kommons.util.DelegateSetter
import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock
@ -21,7 +22,6 @@ import ru.dbotthepony.mc.otm.core.math.BlockRotation
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.math.getDecimal import ru.dbotthepony.mc.otm.core.math.getDecimal
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.mapPresent import ru.dbotthepony.mc.otm.core.nbt.mapPresent
import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.util.countingLazy import ru.dbotthepony.mc.otm.core.util.countingLazy
@ -43,22 +43,24 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
val history6h = DecimalHistoryChart(720 * 6, 100) val history6h = DecimalHistoryChart(720 * 6, 100)
val history24h = DecimalHistoryChart(720 * 24, 100) val history24h = DecimalHistoryChart(720 * 24, 100)
private val graphs = ImmutableList.of( val charts: ImmutableList<DecimalHistoryChart> = ImmutableList.of(
history5s, history15s, history1m, history10m, history1h, history6h, history24h, history5s, history15s, history1m, history10m, history1h, history6h, history24h,
) )
init { init {
syncher.add(history5s) charts.forEach { syncher.add(it) }
} }
private var thisTick = Decimal.ZERO private var thisTick = Decimal.ZERO
var lastTick by syncher.decimal() var lastTick by syncher.decimal()
internal set internal set
var displayChartOnBlock by syncher.int(0, setter = DelegateSetter { field, value -> if (value in 0 .. charts.size + 1) field.accept(value) })
var ioLimit: Decimal? = null var ioLimit: Decimal? = null
fun resetStats() { fun resetStats() {
graphs.forEach { it.clear() } charts.forEach { it.clear() }
lastTick = Decimal.ZERO lastTick = Decimal.ZERO
passed = Decimal.ZERO passed = Decimal.ZERO
} }
@ -69,16 +71,19 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
ioLimit?.let { nbt[IO_LIMIT_KEY] = it.serializeNBT() } ioLimit?.let { nbt[IO_LIMIT_KEY] = it.serializeNBT() }
} }
init {
savetables.int(::displayChartOnBlock)
savetables.stateful(::history5s)
savetables.stateful(::history15s)
savetables.stateful(::history1m)
savetables.stateful(::history10m)
savetables.stateful(::history1h)
savetables.stateful(::history6h)
savetables.stateful(::history24h)
}
override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) { override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) {
super.saveLevel(nbt, registry) super.saveLevel(nbt, registry)
nbt["history5s"] = history5s.serializeNBT(registry)
nbt["history15s"] = history15s.serializeNBT(registry)
nbt["history1m"] = history1m.serializeNBT(registry)
nbt["history10m"] = history10m.serializeNBT(registry)
nbt["history1h"] = history1h.serializeNBT(registry)
nbt["history6h"] = history6h.serializeNBT(registry)
nbt["history24h"] = history24h.serializeNBT(registry)
} }
override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) { override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
@ -86,14 +91,6 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
passed = nbt.getDecimal(PASSED_ENERGY_KEY) passed = nbt.getDecimal(PASSED_ENERGY_KEY)
ioLimit = nbt.mapPresent(IO_LIMIT_KEY, Decimal.Companion::deserializeNBT) ioLimit = nbt.mapPresent(IO_LIMIT_KEY, Decimal.Companion::deserializeNBT)
nbt.map("history5s") { it: CompoundTag -> history5s.deserializeNBT(registry, it) }
nbt.map("history15s") { it: CompoundTag -> history15s.deserializeNBT(registry, it) }
nbt.map("history1m") { it: CompoundTag -> history1m.deserializeNBT(registry, it) }
nbt.map("history10m") { it: CompoundTag -> history10m.deserializeNBT(registry, it) }
nbt.map("history1h") { it: CompoundTag -> history1h.deserializeNBT(registry, it) }
nbt.map("history6h") { it: CompoundTag -> history6h.deserializeNBT(registry, it) }
nbt.map("history24h") { it: CompoundTag -> history24h.deserializeNBT(registry, it) }
} }
override val defaultDisplayName: Component override val defaultDisplayName: Component
@ -265,7 +262,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
super.tick() super.tick()
lastTick = thisTick lastTick = thisTick
graphs.forEach { it.add(thisTick) } charts.forEach { it.add(thisTick) }
thisTick = Decimal.ZERO thisTick = Decimal.ZERO
} }

View File

@ -1,16 +1,18 @@
package ru.dbotthepony.mc.otm.client.render package ru.dbotthepony.mc.otm.client.render
import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.BufferBuilder
import com.mojang.blaze3d.vertex.BufferUploader
import com.mojang.blaze3d.vertex.ByteBufferBuilder import com.mojang.blaze3d.vertex.ByteBufferBuilder
import com.mojang.blaze3d.vertex.DefaultVertexFormat import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexFormat import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.blaze3d.vertex.VertexSorting
import net.minecraft.client.gui.Font import net.minecraft.client.gui.Font
import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent
import net.minecraft.client.renderer.GameRenderer import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.world.inventory.tooltip.TooltipComponent import net.minecraft.world.inventory.tooltip.TooltipComponent
import net.neoforged.neoforge.client.event.RegisterClientTooltipComponentFactoriesEvent import net.neoforged.neoforge.client.event.RegisterClientTooltipComponentFactoriesEvent
@ -68,7 +70,10 @@ class ChartTooltipElement(
} }
override fun renderImage(font: Font, x: Int, y: Int, graphics: GuiGraphics) { 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) graphics.pose().pushPose()
graphics.pose().translate(0f, 0f, 0.1f)
renderChart(graphics.pose(), normalized, this.width, this.height - 10f, levelLabels = this.levelLabels, x = x.toFloat(), y = y.toFloat() + 10f, buffer = graphics.bufferSource())
graphics.pose().popPose()
} }
companion object { companion object {
@ -78,6 +83,32 @@ class ChartTooltipElement(
} }
} }
val CHART_RENDER_TYPE = run {
val builder = RenderType.CompositeState.builder()
builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorShader))
builder.setDepthTestState(RenderStateShard.DepthTestStateShard("always", GL_LESS))
builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", {
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
}, { RenderSystem.disableBlend() }))
@Suppress("INACCESSIBLE_TYPE")
RenderType.create(
"otm_chart",
DefaultVertexFormat.POSITION_COLOR,
VertexFormat.Mode.QUADS,
16_000,
false,
true,
builder.createCompositeState(false)
) as RenderType
}
private val buffer = DynamicBufferSource(vertexSorting = VertexSorting.ORTHOGRAPHIC_Z)
private fun buffer() = buffer
fun renderChart( fun renderChart(
poseStack: PoseStack, poseStack: PoseStack,
charts: List<Pair<FloatArray, RGBAColor>>, charts: List<Pair<FloatArray, RGBAColor>>,
@ -85,16 +116,20 @@ fun renderChart(
height: Float, height: Float,
labels: ChartMouseLabels? = null, labels: ChartMouseLabels? = null,
levelLabels: ChartLevelLabels? = null, levelLabels: ChartLevelLabels? = null,
buffer: MultiBufferSource = buffer(),
x: Float = 0f, x: Float = 0f,
y: Float = 0f, y: Float = 0f,
textScale: Float = 1f,
lineWidth: Float = LINE_WIDTH
) { ) {
require(charts.all { it.first.size == charts[0].first.size }) { "Provided chart data is not of same width" } 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 builder = buffer.getBuffer(CHART_RENDER_TYPE)
val step = width / charts[0].first.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 val checkLabels = labels != null && labels.mouseY in 0f .. height && labels.mouseX in 0f .. width
poseStack.pushPose()
var drawLabel = -1 var drawLabel = -1
var drawPointX = 0f var drawPointX = 0f
var drawPointY = 0f var drawPointY = 0f
@ -103,13 +138,15 @@ fun renderChart(
for ((level, label) in levelLabels.labels) { for ((level, label) in levelLabels.labels) {
val y0 = y + (1f - level) * height val y0 = y + (1f - level) * height
builder.vertex(pose, x + width, LINE_WIDTH / 2f + y0, 0f).color(levelLabels.lineColor) builder.vertex(poseStack, x + width, lineWidth / 2f + y0, 0f).color(levelLabels.lineColor)
builder.vertex(pose, x + width, -LINE_WIDTH / 2f + y0, 0f).color(levelLabels.lineColor) builder.vertex(poseStack, x + width, -lineWidth / 2f + y0, 0f).color(levelLabels.lineColor)
builder.vertex(pose, x, -LINE_WIDTH / 2f + y0, 0f).color(levelLabels.lineColor) builder.vertex(poseStack, x, -lineWidth / 2f + y0, 0f).color(levelLabels.lineColor)
builder.vertex(pose, x, LINE_WIDTH / 2f + y0, 0f).color(levelLabels.lineColor) builder.vertex(poseStack, x, lineWidth / 2f + y0, 0f).color(levelLabels.lineColor)
} }
} }
poseStack.translate(0f, 0f, 0.2f)
for ((normalized, color) in charts) { for ((normalized, color) in charts) {
for (i in 0 until normalized.size - 1) { for (i in 0 until normalized.size - 1) {
val y0 = y + (1f - normalized[i]) * height val y0 = y + (1f - normalized[i]) * height
@ -122,21 +159,21 @@ fun renderChart(
val yDiff = y1 - y0 val yDiff = y1 - y0
if (yDiff.sign == 0f) { if (yDiff.sign == 0f) {
builder.vertex(pose, x0, y0 + LINE_WIDTH / 2f, 0f).color(color) builder.vertex(poseStack, x0, y0 + lineWidth / 2f, 0f).color(color)
builder.vertex(pose, x0, y0 - LINE_WIDTH / 2f, 0f).color(color) builder.vertex(poseStack, x0, y0 - lineWidth / 2f, 0f).color(color)
builder.vertex(pose, x1, y1 - LINE_WIDTH / 2f, 0f).color(color) builder.vertex(poseStack, x1, y1 - lineWidth / 2f, 0f).color(color)
builder.vertex(pose, x1, y1 + LINE_WIDTH / 2f, 0f).color(color) builder.vertex(poseStack, x1, y1 + lineWidth / 2f, 0f).color(color)
} else { } else {
val length = (xDiff.pow(2f) + yDiff.pow(2f)).pow(0.5f) val length = (xDiff.pow(2f) + yDiff.pow(2f)).pow(0.5f)
val angle = acos(xDiff / length) * yDiff.sign - PI / 2f // rotate 90 deg val angle = acos(xDiff / length) * yDiff.sign - PI / 2f // rotate 90 deg
val xDisp = cos(angle).toFloat() * LINE_WIDTH / 2f val xDisp = cos(angle).toFloat() * lineWidth / 2f
val yDisp = sin(angle).toFloat() * LINE_WIDTH / 2f val yDisp = sin(angle).toFloat() * lineWidth / 2f
builder.vertex(pose, x0 + xDisp, y0 + yDisp, 0f).color(color) builder.vertex(poseStack, x0 + xDisp, y0 + yDisp, 0f).color(color)
builder.vertex(pose, x0 - xDisp, y0 - yDisp, 0f).color(color) builder.vertex(poseStack, x0 - xDisp, y0 - yDisp, 0f).color(color)
builder.vertex(pose, x1 - xDisp, y1 - yDisp, 0f).color(color) builder.vertex(poseStack, x1 - xDisp, y1 - yDisp, 0f).color(color)
builder.vertex(pose, x1 + xDisp, y1 + yDisp, 0f).color(color) builder.vertex(poseStack, x1 + xDisp, y1 + yDisp, 0f).color(color)
} }
//graphics.renderRect(x0, y0, LINE_WIDTH, LINE_WIDTH) //graphics.renderRect(x0, y0, LINE_WIDTH, LINE_WIDTH)
@ -149,57 +186,58 @@ fun renderChart(
} }
} }
RenderSystem.setShader(GameRenderer::getPositionColorShader)
// RenderSystem.setShaderColor(color.red, color.green, color.blue, color.alpha)
RenderSystem.disableDepthTest()
RenderSystem.disableBlend()
RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
BufferUploader.drawWithShader(builder.buildOrThrow())
RenderSystem.enableDepthTest()
RenderSystem.depthFunc(GL_LESS)
if (levelLabels != null && levelLabels.labels.isNotEmpty()) { if (levelLabels != null && levelLabels.labels.isNotEmpty()) {
poseStack.translate(0f, 0f, 0.2f)
val font = levelLabels.font ?: minecraft.font val font = levelLabels.font ?: minecraft.font
for ((level, label) in levelLabels.labels) { for ((level, label) in levelLabels.labels) {
val y0 = y + (1f - level) * height val y0 = y + (1f - level) * height
val tX = x + levelLabels.textGravity.repositionX(width, font.width(label).toFloat()) - 1f val tX = x + (levelLabels.textGravity.repositionX(width - 1f, font.width(label).toFloat() * textScale - 1f))
val tY = y0 - LINE_WIDTH - 1f - font.lineHeight + levelLabels.textGravity.repositionY(font.lineHeight * 2f + LINE_WIDTH + 2f, font.lineHeight.toFloat()) + 1f val tY = y0 - lineWidth - 1f - font.lineHeight * textScale + levelLabels.textGravity.repositionY(font.lineHeight * 2f * textScale + lineWidth + 2f, font.lineHeight.toFloat() * textScale) + 1f
font.draw( font.draw(
poseStack, poseStack,
label, label,
buffer = buffer,
x = tX, x = tX,
y = tY, y = tY,
color = levelLabels.textColor, color = levelLabels.textColor,
drawOutline = true, drawOutline = true,
outlineZ = -1f, outlineZ = -0.1f,
outlineColor = levelLabels.textOutline outlineColor = levelLabels.textOutline,
scale = textScale
) )
} }
} }
if (drawLabel != -1) { if (drawLabel != -1) {
renderRect(pose, drawPointX - LINE_WIDTH / 2f, y, LINE_WIDTH, height, color = labels!!.pillarColor) poseStack.translate(0f, 0f, 0.2f)
renderRect(pose, drawPointX - HIGHLIGHT_WIDTH / 2f, drawPointY - HIGHLIGHT_WIDTH / 2f, HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, color = labels.color)
uploadRect(poseStack, drawPointX - lineWidth / 2f, y, lineWidth, height, buffer = buffer, color = labels!!.pillarColor)
uploadRect(poseStack, drawPointX - HIGHLIGHT_WIDTH / 2f, drawPointY - HIGHLIGHT_WIDTH / 2f, HIGHLIGHT_WIDTH, HIGHLIGHT_WIDTH, buffer = buffer, color = labels.color)
val label = labels.labels(drawLabel) val label = labels.labels(drawLabel)
if (label.isNotEmpty()) { if (label.isNotEmpty()) {
val fWidth = label.maxOf { labels.font.width(it).toFloat() + 2f } val fWidth = label.maxOf { (labels.font.width(it).toFloat() + 2f) * textScale }
val fHeight = labels.font.lineHeight.toFloat() * label.size + 2f val fHeight = (labels.font.lineHeight.toFloat() * label.size + 2f) * textScale
val anchorX = labels.mouseX // drawPointX val anchorX = labels.mouseX // drawPointX
var anchorY = labels.mouseY - fHeight - 2f var anchorY = labels.mouseY - fHeight - 2f
renderRect(pose, anchorX - fWidth / 2f, anchorY - 2f, fWidth, fHeight + 4f, color = labels.textBackgroundColor) uploadRect(poseStack, anchorX - fWidth / 2f, anchorY - 2f, fWidth, fHeight + 4f, buffer = buffer, color = labels.textBackgroundColor)
label.forEach { label.forEach {
labels.font.draw(poseStack, it, anchorX, anchorY, gravity = RenderGravity.TOP_CENTER, color = labels.textColor) labels.font.draw(poseStack, it, anchorX, anchorY, buffer = buffer, gravity = RenderGravity.TOP_CENTER, color = labels.textColor, scale = textScale)
anchorY += labels.font.lineHeight anchorY += labels.font.lineHeight
} }
} }
} }
poseStack.popPose()
if (buffer === buffer()) {
buffer.endBatch()
}
} }

View File

@ -162,7 +162,15 @@ class DynamicBufferSource(
next = State(Sheets.shulkerBoxSheet(), ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(Sheets.shulkerBoxSheet(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
next = State(Sheets.signSheet(), ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(Sheets.signSheet(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
//next = State(Sheets.hangingSignSheet(), ImmutableList.of(next.type), immutableAfter = true, chained = false) //next = State(Sheets.hangingSignSheet(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
next = State(Sheets.chestSheet(), ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(Sheets.chestSheet(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
next = State(CHART_RENDER_TYPE, ImmutableList.of(next.type), immutableAfter = true, chained = true)
next = State(rectRenderType(true, false, true), ImmutableList.of(next.type), immutableAfter = true, chained = true)
next = State(rectRenderType(true, false, false), ImmutableList.of(next.type), immutableAfter = true, chained = true)
next = State(rectRenderType(false, false, true), ImmutableList.of(next.type), immutableAfter = true, chained = true)
next = State(rectRenderType(false, false, false), ImmutableList.of(next.type), immutableAfter = true, chained = true)
next = State(RenderType.armorEntityGlint(), ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(RenderType.armorEntityGlint(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
next = State(RenderType.glint(), ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(RenderType.glint(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
next = State(RenderType.glintTranslucent(), ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(RenderType.glintTranslucent(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
@ -170,6 +178,9 @@ class DynamicBufferSource(
next = State(RenderType.entityGlintDirect(), ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(RenderType.entityGlintDirect(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
next = State(RenderType.waterMask(), ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(RenderType.waterMask(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
next = State(rectRenderType(true, true, true), ImmutableList.of(next.type), immutableAfter = true, chained = true)
next = State(rectRenderType(true, true, false), ImmutableList.of(next.type), immutableAfter = true, chained = true)
ModelBakery.DESTROY_TYPES.forEach { ModelBakery.DESTROY_TYPES.forEach {
next = State(it, ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(it, ImmutableList.of(next.type), immutableAfter = true, chained = false)
} }

View File

@ -165,8 +165,8 @@ private fun Font.drawInternal(
drawInBatch( drawInBatch(
text, text,
(x + shadowX) * inv, (x + shadowX * scale) * inv,
(y + shadowY) * inv, (y + shadowY * scale) * inv,
shadowColor.toBGR(), shadowColor.toBGR(),
false, false,
poseStack.last().pose(), poseStack.last().pose(),
@ -189,8 +189,8 @@ private fun Font.drawInternal(
for ((_x, _y) in outlinePositions) { for ((_x, _y) in outlinePositions) {
drawInBatch( drawInBatch(
text, text,
(x + _x) * inv, (x + _x * scale) * inv,
(y + _y) * inv, (y + _y * scale) * inv,
outlineColor.toBGR(), outlineColor.toBGR(),
drawShadow, drawShadow,
poseStack.last().pose(), poseStack.last().pose(),

View File

@ -18,6 +18,7 @@ val tesselator: Tesselator get() = Tesselator.getInstance()
// what meth have you been smoking, mojang, with new names // what meth have you been smoking, mojang, with new names
fun VertexConsumer.vertex(matrix4f: Matrix4f, x: Float, y: Float, z: Float): VertexConsumer = addVertex(matrix4f, x, y, z) fun VertexConsumer.vertex(matrix4f: Matrix4f, x: Float, y: Float, z: Float): VertexConsumer = addVertex(matrix4f, x, y, z)
fun VertexConsumer.vertex(poseStack: PoseStack, x: Float, y: Float, z: Float): VertexConsumer = addVertex(poseStack.last.pose, x, y, z)
fun VertexConsumer.vertex(x: Float, y: Float, z: Float): VertexConsumer = addVertex(x, y, z) fun VertexConsumer.vertex(x: Float, y: Float, z: Float): VertexConsumer = addVertex(x, y, z)
fun VertexConsumer.uv(u: Float, v: Float): VertexConsumer = setUv(u, v) fun VertexConsumer.uv(u: Float, v: Float): VertexConsumer = setUv(u, v)
fun VertexConsumer.overlayCoords(coords: Int): VertexConsumer = setOverlay(coords) fun VertexConsumer.overlayCoords(coords: Int): VertexConsumer = setOverlay(coords)

View File

@ -4,6 +4,7 @@ import com.mojang.blaze3d.platform.GlStateManager
import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.* import com.mojang.blaze3d.vertex.*
import net.minecraft.client.renderer.GameRenderer import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderStateShard.LineStateShard import net.minecraft.client.renderer.RenderStateShard.LineStateShard
import net.minecraft.client.renderer.RenderType import net.minecraft.client.renderer.RenderType
@ -92,6 +93,76 @@ fun renderRect(
} }
} }
private const val IS_WHITE = 1
private const val IS_TRANSLUCENT = 2
private const val IS_3D = 4
private val rectRenderTypes = Array(1 shl 3) {
val isWhite = it and IS_WHITE != 0
val isTranslucent = it and IS_TRANSLUCENT != 0
val is3D = it and IS_3D != 0
val builder = RenderType.CompositeState.builder()
if (isWhite)
builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionShader))
else
builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorShader))
if (!is3D)
builder.setDepthTestState(RenderStateShard.DepthTestStateShard("always", GL_LESS))
if (isTranslucent) {
builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", {
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
}, { RenderSystem.disableBlend() }))
}
@Suppress("INACCESSIBLE_TYPE")
RenderType.create(
"otm_rect_${isWhite}_${isTranslucent}_$is3D",
if (isWhite) DefaultVertexFormat.POSITION else DefaultVertexFormat.POSITION_COLOR,
VertexFormat.Mode.QUADS,
256,
false,
true,
builder.createCompositeState(false)
) as RenderType
}
internal fun rectRenderType(isWhite: Boolean, isTranslucent: Boolean, is3D: Boolean): RenderType {
var index = 0
if (isWhite) index = IS_WHITE
if (isTranslucent) index = index or IS_TRANSLUCENT
if (is3D) index = index or IS_3D
return rectRenderTypes[index]
}
fun uploadRect(
poseStack: PoseStack,
x: Float,
y: Float,
width: Float,
height: Float,
buffer: MultiBufferSource,
z: Float = 0f,
color: RGBAColor = RGBAColor.WHITE
) {
val builder = buffer.getBuffer(rectRenderType(color.isWhite, color.alpha != 1f, is3DContext))
if (color.isWhite) {
builder.vertex(poseStack, x, y + height, z)
builder.vertex(poseStack, x + width, y + height, z)
builder.vertex(poseStack, x + width, y, z)
builder.vertex(poseStack, x, y, z)
} else {
builder.vertex(poseStack, x, y + height, z).color(color)
builder.vertex(poseStack, x + width, y + height, z).color(color)
builder.vertex(poseStack, x + width, y, z).color(color)
builder.vertex(poseStack, x, y, z).color(color)
}
}
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
fun renderCheckerboard( fun renderCheckerboard(
matrix: Matrix4f, matrix: Matrix4f,

View File

@ -1,10 +1,12 @@
package ru.dbotthepony.mc.otm.client.render.blockentity package ru.dbotthepony.mc.otm.client.render.blockentity
import com.mojang.blaze3d.vertex.PoseStack import com.mojang.blaze3d.vertex.PoseStack
import it.unimi.dsi.fastutil.floats.Float2ObjectArrayMap
import net.minecraft.client.renderer.MultiBufferSource import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.core.Direction import net.minecraft.core.Direction
import net.minecraft.network.chat.Component
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock
import ru.dbotthepony.mc.otm.block.entity.tech.EnergyCounterBlockEntity import ru.dbotthepony.mc.otm.block.entity.tech.EnergyCounterBlockEntity
@ -12,6 +14,7 @@ import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.* import ru.dbotthepony.mc.otm.client.render.*
import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.asAngle import ru.dbotthepony.mc.otm.core.math.asAngle
import ru.dbotthepony.mc.otm.core.util.formatPower import ru.dbotthepony.mc.otm.core.util.formatPower
import ru.dbotthepony.mc.otm.core.math.times import ru.dbotthepony.mc.otm.core.math.times
@ -47,23 +50,76 @@ class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Con
poseStack.rotateAroundPoint(poseStack.translation(), screenDir.asAngle().copy(pitch = PI)) poseStack.rotateAroundPoint(poseStack.translation(), screenDir.asAngle().copy(pitch = PI))
} }
var y = -16f poseStack.scale(1f, 1f, -1f)
val finalX = font.draw(poseStack, buffer = sorse, text = "00000000", gravity = RenderGravity.CENTER_CENTER, x = -4f, y = y, color = RGBAColor.GRAY) if (tile.displayChartOnBlock == 0) {
font.draw(poseStack, buffer = sorse, text = "00000000", gravity = RenderGravity.CENTER_CENTER, x = -4f, y = y + font.lineHeight, color = RGBAColor.GRAY) var y = -16f
font.draw(poseStack, buffer = sorse, text = "/t", gravity = RenderGravity.CENTER_LEFT, x = finalX, y = y, color = RGBAColor.GRAY)
font.draw(poseStack, buffer = sorse, text = "/s", gravity = RenderGravity.CENTER_LEFT, x = finalX, y = y + font.lineHeight, color = RGBAColor.GRAY)
poseStack.pushPose() val finalX = font.draw(poseStack, buffer = sorse, text = "00000000", gravity = RenderGravity.CENTER_CENTER, x = -4f, y = y, color = RGBAColor.GRAY)
poseStack.translate(-0.1, -0.1, -0.1) font.draw(poseStack, buffer = sorse, text = "00000000", gravity = RenderGravity.CENTER_CENTER, x = -4f, y = y + font.lineHeight, color = RGBAColor.GRAY)
font.draw(poseStack, buffer = sorse, text = tile.lastTick.toString(0), gravity = RenderGravity.CENTER_RIGHT, x = finalX, y = y, color = RGBAColor.WHITE) font.draw(poseStack, buffer = sorse, text = "/t", gravity = RenderGravity.CENTER_LEFT, x = finalX, y = y, color = RGBAColor.GRAY)
font.draw(poseStack, buffer = sorse, text = tile.history5s.calculateSum(20).toString(0), gravity = RenderGravity.CENTER_RIGHT, x = finalX, y = y + font.lineHeight, color = RGBAColor.WHITE) font.draw(poseStack, buffer = sorse, text = "/s", gravity = RenderGravity.CENTER_LEFT, x = finalX, y = y + font.lineHeight, color = RGBAColor.GRAY)
poseStack.popPose()
y += font.lineHeight * 3 poseStack.pushPose()
poseStack.translate(-0.1f, -0.1f, 0.1f)
font.draw(poseStack, buffer = sorse, text = tile.lastTick.toString(0), gravity = RenderGravity.CENTER_RIGHT, x = finalX, y = y, color = RGBAColor.WHITE)
font.draw(poseStack, buffer = sorse, text = tile.history5s.calculateSum(20).toString(0), gravity = RenderGravity.CENTER_RIGHT, x = finalX, y = y + font.lineHeight, color = RGBAColor.WHITE)
poseStack.popPose()
font.draw(poseStack, buffer = sorse, text = TOTAL, gravity = RenderGravity.CENTER_CENTER, x = 0f, y = y, color = RGBAColor.WHITE) y += font.lineHeight * 3
font.draw(poseStack, buffer = sorse,text = tile.passed.formatPower(), gravity = RenderGravity.CENTER_CENTER, x = 0f, y = y + font.lineHeight, color = RGBAColor.WHITE)
font.draw(poseStack, buffer = sorse, text = TOTAL, gravity = RenderGravity.CENTER_CENTER, x = 0f, y = y, color = RGBAColor.WHITE)
font.draw(poseStack, buffer = sorse, text = tile.passed.formatPower(), gravity = RenderGravity.CENTER_CENTER, x = 0f, y = y + font.lineHeight, color = RGBAColor.WHITE)
} else {
val chart = tile.charts.getOrNull(tile.displayChartOnBlock - 1)
if (chart != null) {
val maximum = chart.max()
val normalized: FloatArray
val levelLabels: ChartLevelLabels
if (maximum.isZero || maximum.isInfinite) {
normalized = FloatArray(chart.width) {
if (chart[it].isInfinite) 0.8f else 0.0f
}
levelLabels = ChartLevelLabels(
labels = mapOf(
1f to TextComponent(""),
0f to Decimal.ZERO.formatPower(),
),
font = font
)
} else {
normalized = FloatArray(chart.width) { (chart[it] / maximum).toFloat() }
val map = Float2ObjectArrayMap<Component>()
map[1f] = maximum.formatPower()
map[0.75f] = (maximum * 0.75).formatPower()
map[0.5f] = (maximum * 0.5).formatPower()
map[0.25f] = (maximum * 0.25).formatPower()
levelLabels = ChartLevelLabels(
labels = map,
font = font
)
}
renderChart(
poseStack,
listOf(normalized to RGBAColor.WHITE),
60f,
50f,
buffer = sorse,
levelLabels = levelLabels,
x = -30f,
y = -20f,
textScale = 0.3f,
lineWidth = 0.5f
)
}
}
poseStack.popPose() poseStack.popPose()
} }

View File

@ -193,9 +193,7 @@ sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords {
builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", { builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", {
RenderSystem.enableBlend() RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc() RenderSystem.defaultBlendFunc()
}, { }, { RenderSystem.disableBlend() }))
RenderSystem.disableBlend()
}))
@Suppress("INACCESSIBLE_TYPE") @Suppress("INACCESSIBLE_TYPE")
RenderType.create("otm_gui_element_no_depth", RenderType.create("otm_gui_element_no_depth",

View File

@ -35,7 +35,10 @@ open class DecimalHistoryChartPanel<out S : MatteryScreen<*>>(
} }
levelLabels = ChartLevelLabels( levelLabels = ChartLevelLabels(
labels = mapOf(0.8f to formatText(maximum)), labels = mapOf(
0.8f to TextComponent(""),
// 0f to formatText(Decimal.ZERO),
),
font = font font = font
) )
} else { } else {

View File

@ -10,6 +10,7 @@ import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.client.screen.panels.* import ru.dbotthepony.mc.otm.client.screen.panels.*
import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.input.NetworkNumberInputPanel import ru.dbotthepony.mc.otm.client.screen.panels.input.NetworkNumberInputPanel
import ru.dbotthepony.mc.otm.core.util.formatPower import ru.dbotthepony.mc.otm.core.util.formatPower
@ -19,22 +20,31 @@ class EnergyCounterScreen(menu: EnergyCounterMenu, inventory: Inventory, title:
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> { override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
val frame = super.makeMainFrame()!! val frame = super.makeMainFrame()!!
frame.height = 160f frame.height = 180f
frame.width += 60f frame.width += 80f
val graphs = linkedMapOf( val graphs = listOf(
menu.history5s to "otm.gui.time.short.5s", menu.history5s to "otm.gui.time.short.5s",
menu.history15s to "otm.gui.time.short.15s", menu.history15s to "otm.gui.time.short.15s",
menu.history1m to "otm.gui.time.short.1m", menu.history1m to "otm.gui.time.short.1m",
menu.history10m to "otm.gui.time.short.10m",
menu.history1h to "otm.gui.time.short.1h", menu.history1h to "otm.gui.time.short.1h",
menu.history6h to "otm.gui.time.short.6h", menu.history6h to "otm.gui.time.short.6h",
menu.history24h to "otm.gui.time.short.24h", menu.history24h to "otm.gui.time.short.24h",
) )
for ((graph, text) in graphs) { kotlin.run {
val tab = frame.Tab(activeIcon = TextIcon(color = RGBAColor.BLACK, font = font, text = TranslatableComponent(text))) var i = 1
val panel = DecimalHistoryChartPanel(this, tab.canvas, graph, formatText = { it.formatPower(formatAsReadable = ShiftPressedCond) })
panel.dock = Dock.FILL for ((graph, text) in graphs) {
val tab = frame.Tab(activeIcon = TextIcon(color = RGBAColor.BLACK, font = font, text = TranslatableComponent(text)))
val panel = DecimalHistoryChartPanel(this, tab.canvas, graph, formatText = { it.formatPower(formatAsReadable = ShiftPressedCond) })
panel.dock = Dock.FILL
val current = i++
val button = CheckBoxLabelInputPanel(this, tab.canvas, menu.displayChartOnBlock.map({ it == current }, { current }), TranslatableComponent("block.overdrive_that_matters.energy_counter.display_this"))
button.dock = Dock.BOTTOM
}
} }
val informationTab = frame.Tab(activeIcon = Widgets18.STATISTICS_TAB) val informationTab = frame.Tab(activeIcon = Widgets18.STATISTICS_TAB)
@ -51,6 +61,7 @@ class EnergyCounterScreen(menu: EnergyCounterMenu, inventory: Inventory, title:
labels.add(Label(this, informationTab.canvas, text = TranslatableComponent("otm.gui.power.last_tick"))) labels.add(Label(this, informationTab.canvas, text = TranslatableComponent("otm.gui.power.last_tick")))
labels.add(DynamicLabel(this, informationTab.canvas, textSupplier = { menu.lastTick.formatPower(formatAsReadable = ShiftPressedCond) })) labels.add(DynamicLabel(this, informationTab.canvas, textSupplier = { menu.lastTick.formatPower(formatAsReadable = ShiftPressedCond) }))
labels.add(DynamicLabel(this, informationTab.canvas, textSupplier = { TranslatableComponent("block.overdrive_that_matters.energy_counter.facing", menu.inputDirection) })) labels.add(DynamicLabel(this, informationTab.canvas, textSupplier = { TranslatableComponent("block.overdrive_that_matters.energy_counter.facing", menu.inputDirection) }))
labels.add(CheckBoxLabelInputPanel(this, informationTab.canvas, menu.displayChartOnBlock.map({ it == 0 }, { 0 }), TranslatableComponent("block.overdrive_that_matters.energy_counter.display_this")))
for ((i, label) in labels.withIndex()) { for ((i, label) in labels.withIndex()) {
if (i == 0) { if (i == 0) {

View File

@ -52,6 +52,14 @@ fun interface BlockPredicate {
return BlockPredicate { pos, access -> test(pos, access) || other.test(pos, access) } return BlockPredicate { pos, access -> test(pos, access) || other.test(pos, access) }
} }
fun and(other: BlockPredicate, vararg others: BlockPredicate): BlockPredicate {
return And(ImmutableSet.copyOf(listOf(other, *others)))
}
fun or(other: BlockPredicate, vararg others: BlockPredicate): BlockPredicate {
return Or(ImmutableSet.copyOf(listOf(other, *others)))
}
fun offset(offset: BlockPos): Positioned { fun offset(offset: BlockPos): Positioned {
return Positioned(offset, this) return Positioned(offset, this)
} }

View File

@ -15,7 +15,29 @@ import kotlin.reflect.KMutableProperty0
* *
* Getting and setting values should ONLY be done clientside * Getting and setting values should ONLY be done clientside
*/ */
interface IPlayerInputWithFeedback<V> : ListenableDelegate<V>, Predicate<Player> interface IPlayerInputWithFeedback<V> : ListenableDelegate<V>, Predicate<Player> {
private class Map<N, V>(private val parent: IPlayerInputWithFeedback<V>, private val from: (V) -> N, private val to: (N) -> V): IPlayerInputWithFeedback<N> {
override fun accept(t: N) {
parent.accept(to(t))
}
override fun addListener(listener: Consumer<N>): Listenable.L {
return parent.addListener(Consumer { listener.accept(from(it)) })
}
override fun get(): N {
return from(parent.get())
}
override fun test(t: Player): Boolean {
return parent.test(t)
}
}
fun <N> map(from: (V) -> N, to: (N) -> V): IPlayerInputWithFeedback<N> {
return Map(this, from, to)
}
}
/** /**
* Represents Server to Client synchronization and Client to Server input * Represents Server to Client synchronization and Client to Server input

View File

@ -13,6 +13,7 @@ import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.toDecimal import ru.dbotthepony.mc.otm.core.math.toDecimal
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.IntInputWithFeedback
import ru.dbotthepony.mc.otm.registry.MMenus import ru.dbotthepony.mc.otm.registry.MMenus
import java.math.BigDecimal import java.math.BigDecimal
@ -48,11 +49,13 @@ class EnergyCounterMenu(
tile.level?.setBlock(tile.blockPos, tile.blockState.setValue(EnergyCounterBlock.INPUT_DIRECTION, tile.blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION).opposite), Block.UPDATE_ALL) tile.level?.setBlock(tile.blockPos, tile.blockState.setValue(EnergyCounterBlock.INPUT_DIRECTION, tile.blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION).opposite), Block.UPDATE_ALL)
} }
val displayChartOnBlock = IntInputWithFeedback(this)
val redstone = EnumInputWithFeedback(this, RedstoneSetting::class.java) val redstone = EnumInputWithFeedback(this, RedstoneSetting::class.java)
init { init {
if (tile != null) { if (tile != null) {
redstone.with(tile.redstoneControl::redstoneSetting) redstone.with(tile.redstoneControl::redstoneSetting)
displayChartOnBlock.with(tile::displayChartOnBlock)
} }
} }