Compare commits

...

29 Commits

Author SHA1 Message Date
01d3cbf7b2
Add "Smart exchange" button to quickstack controls 2025-03-21 21:58:56 +07:00
be2011cf96
Merge branch '1.21' into new-container-api 2025-03-21 21:24:26 +07:00
ab2cc33b7a
"Smart storage exchange icon" 2025-03-21 21:24:16 +07:00
9413ae5f2b
Shrink effect list panel so it no longer overlaps "quick stack" controls 2025-03-21 21:17:50 +07:00
dc73ac7b55
Merge branch '1.21' into new-container-api 2025-03-21 21:11:30 +07:00
bfb8f0380a
Panel debug rendering 2025-03-21 21:10:46 +07:00
a6361f10f2
Final improvements to quick stack controls 2025-03-21 18:35:25 +07:00
f16b003cdf
Merge branch '1.21' into new-container-api 2025-03-21 18:18:18 +07:00
06f109575d
Fix children not getting re-sorted upon visibility changes 2025-03-21 18:18:07 +07:00
a2d9f43a2e
Fix grid panel layouts, introduce column major order 2025-03-21 18:17:50 +07:00
07e0c73479
Fixes for quick stack controls 2025-03-21 15:19:07 +07:00
2547478914
Merge branch '1.21' into new-container-api 2025-03-21 15:11:17 +07:00
7c028b1fa6
Im with stupid 2025-03-21 15:11:09 +07:00
dcff861e71
More quickstack controls 2025-03-21 14:31:59 +07:00
9be8596a5f
Merge branch '1.21' into new-container-api 2025-03-21 14:05:50 +07:00
57c6bbb795
Update cases where wrong random being used 2025-03-21 14:00:19 +07:00
29c51617b9
Merge branch '1.21' into new-container-api 2025-03-21 13:52:56 +07:00
3e92c5272d
Move panels additional types to separate file 2025-03-21 13:52:00 +07:00
f9821aa552
Use GJRAND64 in menus 2025-03-21 13:36:46 +07:00
b921658eb2
Remove randomGenerator from editable panel 2025-03-21 13:34:53 +07:00
9b27ed7fb6
Add netFloat and nextDouble helper methods to RandomSource 2025-03-21 13:33:53 +07:00
269227f6cf
Add helper methods for fast DockProperty creation 2025-03-21 12:58:13 +07:00
27834cc595
Fix wrong layout final iteration value 2025-03-21 12:23:08 +07:00
70ed0cfcba
Merge branch '1.21' into new-container-api 2025-03-21 10:46:03 +07:00
c343de6031
Parenting callbacks in panels 2025-03-21 10:45:07 +07:00
65bf4dc9d0
Merge branch '1.21' into new-container-api 2025-03-21 08:07:52 +07:00
e6c9708652
Add layout modes to grid panel 2025-03-21 08:07:43 +07:00
a48aaf52ae
Add DockProperty#all variant 2025-03-21 07:19:03 +07:00
9b384f2213
Implement sizeToContents() for GridPanel 2025-03-21 07:18:42 +07:00
25 changed files with 664 additions and 172 deletions

View File

@ -988,12 +988,14 @@ private fun androidFeatures(provider: MatteryLanguageProvider) {
private fun gui(provider: MatteryLanguageProvider) {
with(provider.english) {
gui("quickmove_from.restock", "Restock from storage")
gui("quickmove_from.restock_with_move", "Full restock from storage")
gui("quickmove_from.restock_with_move", "Quickstack from storage")
gui("quickmove_from.move", "Take all")
gui("quickmove_to.restock", "Restock to storage")
gui("quickmove_to.restock_with_move", "Full restock to storage")
gui("quickmove_to.restock_with_move", "Quickstack to storage")
gui("quickmove_to.move", "Deposit all")
gui("quickmove.exchange", "Smart exchange with storage")
gui("quickmove.exchange.desc", "Filtered slots get restocked, everything else gets quickstacked from Exopack")
gui("quickmove_hint", "Right click to show all variants")

View File

@ -981,12 +981,14 @@ private fun androidFeatures(provider: MatteryLanguageProvider) {
private fun gui(provider: MatteryLanguageProvider) {
with(provider.russian) {
gui("quickmove_from.restock", "Быстрое пополнение из хранилища")
gui("quickmove_from.restock_with_move", "Быстрое перемещение из хранилища")
gui("quickmove_from.restock_with_move", "Быстрое складирование из хранилища")
gui("quickmove_from.move", "Взять всё")
gui("quickmove_to.restock", "Быстрое пополнение в хранилище")
gui("quickmove_to.restock_with_move", "Быстрое перемещение в хранилище")
gui("quickmove_to.restock_with_move", "Быстрое складирование в хранилище")
gui("quickmove_to.move", "Переместить всё")
gui("quickmove.exchange", "Умный обмен с хранилищем")
gui("quickmove.exchange.desc", "Слоты с фильтрами заполняются до максимума, всё остальное быстро складируется из экзопака")
gui("quickmove_hint", "Правый клик чтоб увидеть все варианты")

View File

@ -14,6 +14,8 @@ 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
import ru.dbotthepony.mc.otm.client.screen.panels.ScreenPos
import java.util.Arrays
import kotlin.math.PI
import kotlin.math.roundToInt
@ -45,6 +47,73 @@ class MGUIGraphics(val parent: GuiGraphics) {
drawLine(pose.last().pose(), startX, startY, endX, endY, width, z, color)
}
fun drawLine(
points: Iterable<LinePoint>,
width: Float,
color: RGBAColor = RGBAColor.WHITE
) {
drawLine(pose.last().pose(), points, width, color)
}
fun drawLine(
points: Array<out LinePoint>,
width: Float,
color: RGBAColor = RGBAColor.WHITE
) {
drawLine(pose.last().pose(), points, width, color)
}
fun drawLine(
width: Float,
color: RGBAColor,
vararg points: LinePoint,
) {
drawLine(pose.last().pose(), points, width, color)
}
fun drawLine(
width: Float,
color: RGBAColor,
points: Iterable<LinePoint>,
) {
drawLine(pose.last().pose(), points, width, color)
}
fun drawLine(
width: Float,
color: RGBAColor,
z: Float,
points: List<ScreenPos>,
) {
require(points.size >= 2) { "Degenerate point list: only ${points.size} defined" }
val result = ArrayList<LinePoint>(points.size)
for (i in 1 until points.size) {
val (x0, y0) = points[i - 1]
val (x1, y1) = points[i]
result.add(
LinePoint(
x0, y0,
x1, y1,
z
)
)
}
drawLine(pose.last().pose(), result, width, color)
}
fun drawLine(
width: Float,
color: RGBAColor,
z: Float,
vararg points: ScreenPos,
) {
drawLine(width, color, z, Arrays.asList(*points))
}
fun renderRect(
x: Float,
y: Float,

View File

@ -294,6 +294,65 @@ fun renderColoredSphere(pose: PoseStack, radius: Float, color: RGBAColor = RGBAC
BufferUploader.drawWithShader(builder.buildOrThrow())
}
private fun uploadLineSegment(
builder: BufferBuilder,
matrix: Matrix4f,
startX: Float,
startY: Float,
endX: Float,
endY: Float,
width: Float,
z: Float,
color: RGBAColor = RGBAColor.WHITE
) {
val length = ((startX - endX).pow(2f) + (startY - endY).pow(2f)).pow(0.5f)
var angle = acos((endX - startX) / length)
if (startY > endY)
angle *= -1f
val cos = cos(angle)
val sin = sin(angle)
val y0 = -width
val y1 = width
val x2 = length
val y2 = width
val x3 = length
val y3 = -width
builder.vertex(matrix,
startX - y0 * sin,
startY + y0 * cos,
z).color(color)
builder.vertex(matrix,
startX - y1 * sin,
startY + y1 * cos,
z).color(color)
builder.vertex(matrix,
startX + x2 * cos - y2 * sin,
startY + x2 * sin + y2 * cos,
z).color(color)
builder.vertex(matrix,
startX + x3 * cos - y3 * sin,
startY + x3 * sin + y3 * cos,
z).color(color)
}
data class LinePoint(
val startX: Float,
val startY: Float,
val endX: Float,
val endY: Float,
val z: Float = 0f,
)
fun drawLine(
matrix: Matrix4f,
startX: Float,
@ -312,46 +371,46 @@ fun drawLine(
RenderSystem.depthFunc(GL_ALWAYS)
val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
uploadLineSegment(builder, matrix, startX, startY, endX, endY, width, z, color)
BufferUploader.drawWithShader(builder.buildOrThrow())
}
val length = ((startX - endX).pow(2f) + (startY - endY).pow(2f)).pow(0.5f)
val angle = acos((endX - startX) / length)
fun drawLine(
matrix: Matrix4f,
points: Iterable<LinePoint>,
width: Float,
color: RGBAColor = RGBAColor.WHITE
) {
val itr = points.iterator()
val cos = cos(angle)
val sin = sin(angle)
if (!itr.hasNext()) {
throw IllegalArgumentException("No line points were provided")
}
val y0 = -width
RenderSystem.setShader(GameRenderer::getPositionColorShader)
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
val y1 = width
if (!is3DContext)
RenderSystem.depthFunc(GL_ALWAYS)
val x2 = length
val y2 = width
val builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR)
val x3 = length
val y3 = -width
builder.vertex(matrix,
startX - y0 * sin,
startY + y0 * cos,
z).color(color)
builder.vertex(matrix,
startX - y1 * sin,
startY + y1 * cos,
z).color(color)
builder.vertex(matrix,
startX + x2 * cos - y2 * sin,
startY + x2 * sin + y2 * cos,
z).color(color)
builder.vertex(matrix,
startX + x3 * cos - y3 * sin,
startY + x3 * sin + y3 * cos,
z).color(color)
for ((startX, startY, endX, endY, z) in itr)
uploadLineSegment(builder, matrix, startX, startY, endX, endY, width, z, color)
BufferUploader.drawWithShader(builder.buildOrThrow())
}
fun drawLine(
matrix: Matrix4f,
points: Array<out LinePoint>,
width: Float,
color: RGBAColor = RGBAColor.WHITE
) {
return drawLine(matrix, points.asIterable(), width, color)
}
data class ScissorRect(val xStart: Int, val yStart: Int, val xEnd: Int, val yEnd: Int, val lock: Boolean = false) {
val width: Int
get() = (xEnd - xStart).coerceAtLeast(0)

View File

@ -30,6 +30,7 @@ object Widgets18 {
val RESTOCK_FROM_STORAGE = storageGrid.next()
val RESTOCK_WITH_MOVE_TO_STORAGE = storageGrid.next()
val RESTOCK_WITH_MOVE_FROM_STORAGE = storageGrid.next()
val SMART_STORAGE_EXCHANGE = storageGrid.next()
private val miscGrid = WidgetLocation.WIDGET_18.grid(4, 4)

View File

@ -20,12 +20,13 @@ import ru.dbotthepony.mc.otm.core.math.asAngle
import ru.dbotthepony.mc.otm.core.math.clusterize
import ru.dbotthepony.mc.otm.core.util.formatPower
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.util.GJRAND64RandomSource
import java.util.Random
import kotlin.math.PI
import kotlin.math.absoluteValue
class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer<EnergyCounterBlockEntity> {
private val random = Random()
private val random = GJRAND64RandomSource()
override fun render(
tile: EnergyCounterBlockEntity,

View File

@ -284,18 +284,9 @@ class ExopackInventoryScreen(menu: ExopackInventoryMenu) : MatteryScreen<Exopack
curios.x = x
}
EffectListPanel(this, frame, menu.player, x - 56f, gridHeight = 6).tick()
EffectListPanel(this, frame, menu.player, x - 56f, gridHeight = 4).tick()
val leftControls = DeviceControls(this, frame)
leftControls.dockOnLeft = true
leftControls.dockTop = 96f
leftControls.addButton(ButtonPanel.square18(
this, leftControls,
icon = Widgets18.RESTOCK_WITH_MOVE_TO_STORAGE,
onPress = IntConsumer { PacketDistributor.sendToServer(QuickStackPacket(QuickMoveInput.Mode.RESTOCK_WITH_MOVE, true)) }).also {
it.tooltips.add(QuickMoveInput.Mode.RESTOCK_WITH_MOVE.textToStorage)
})
QuickStackControlsPanel(this, frame, x = -19f, y = 96f + 18f)
return frame
}

View File

@ -14,9 +14,13 @@ import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.client.event.ContainerScreenEvent
import net.neoforged.neoforge.common.NeoForge
import org.lwjgl.opengl.GL11
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.player.matteryPlayer
import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
import ru.dbotthepony.mc.otm.client.moveMousePosScaled
import ru.dbotthepony.mc.otm.client.render.LinePoint
import ru.dbotthepony.mc.otm.client.render.WidgetLocation
import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.render.translation
@ -52,6 +56,7 @@ import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.collections.ArrayList
import kotlin.collections.List
import kotlin.collections.MutableSet
import kotlin.collections.isNotEmpty
@ -685,7 +690,13 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
for (panel in panelsReversed) {
RenderSystem.depthFunc(GL11.GL_ALWAYS)
RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
panel.render(wrap, mouseXf, mouseYf, partialTick)
val segments = ArrayList<LinePoint>()
panel.render(wrap, mouseXf, mouseYf, partialTick, segments)
if (segments.isNotEmpty()) {
wrap.drawLine(0.5f, RGBAColor.GOLD, segments)
}
}
if (!panelsReversed.any { it.updateCursor0() })

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.client.screen.decorative
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls
@ -28,11 +29,11 @@ class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Compon
val leftControls = DeviceControls(this, frame)
leftControls.dockOnLeft = true
leftControls.quickMoveButtons(menu.quickMoveFromStorage, QuickMoveInput.Mode.RESTOCK, true)
leftControls.quickMoveButtons(menu.quickMoveFromStorage)
val leftInventoryControls = DeviceControls(this, inventoryFrame!!)
leftInventoryControls.dockOnLeft = true
leftInventoryControls.quickMoveButtons(menu.quickMoveToStorage, QuickMoveInput.Mode.RESTOCK_WITH_MOVE, false)
leftInventoryControls.quickMoveButtons(menu.quickMoveToStorage, menu.quickMoveFromStorage)
return frame
}

View File

@ -52,7 +52,7 @@ open class DecimalHistoryChartPanel<out S : MatteryScreen<*>>(
map[1f] = formatText(maximum)
for (cluster in chart.asIterable().clusterize(randomGenerator)) {
for (cluster in chart.asIterable().clusterize(random)) {
val perc = (cluster.center / maximum).toFloat()
if (map.keys.none { (it - perc).absoluteValue < 0.08f }) {

View File

@ -11,94 +11,31 @@ import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.navigation.FocusNavigationEvent
import net.minecraft.client.gui.navigation.ScreenRectangle
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.renderer.Rect2i
import net.minecraft.network.chat.Component
import net.minecraft.util.RandomSource
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.SystemTime
import ru.dbotthepony.mc.otm.client.CursorType
import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.moveMousePosScaled
import ru.dbotthepony.mc.otm.client.render.LinePoint
import ru.dbotthepony.mc.otm.client.render.RenderGravity
import ru.dbotthepony.mc.otm.client.render.currentScissorRect
import ru.dbotthepony.mc.otm.client.render.popScissorRect
import ru.dbotthepony.mc.otm.client.render.pushScissorRect
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.input.QueryUserPanel
import ru.dbotthepony.mc.otm.core.RandomSource2Generator
import ru.dbotthepony.mc.otm.core.collect.concatIterators
import ru.dbotthepony.mc.otm.core.collect.flatMap
import ru.dbotthepony.mc.otm.core.util.GJRAND64RandomSource
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
import java.util.function.Predicate
import java.util.random.RandomGenerator
import kotlin.collections.ArrayList
import kotlin.math.max
import kotlin.math.roundToInt
data class ScreenPos(val x: Float, val y: Float)
data class DockProperty(val left: Float = 0f, val top: Float = 0f, val right: Float = 0f, val bottom: Float = 0f) {
val isEmpty get() = left == 0f && right == 0f && top == 0f && bottom == 0f
val horizontal get() = left + right
val vertical get() = top + bottom
operator fun plus(other: DockProperty): DockProperty {
return DockProperty(
left + other.left,
top + other.top,
right + other.right,
bottom + other.bottom
)
}
operator fun minus(other: DockProperty): DockProperty {
return DockProperty(
left - other.left,
top - other.top,
right - other.right,
bottom - other.bottom
)
}
operator fun plus(other: Float): DockProperty {
return DockProperty(
left + other,
top + other,
right + other,
bottom + other
)
}
operator fun minus(other: Float): DockProperty {
return DockProperty(
left - other,
top - other,
right - other,
bottom - other
)
}
companion object {
val EMPTY = DockProperty()
}
}
enum class Dock {
NONE, LEFT, RIGHT, TOP, BOTTOM, FILL
}
enum class DockResizeMode(val changeWidth: Boolean, val changeHeight: Boolean) {
ALL(true, true), NONE(false, false), WIDTH(true, false), HEIGHT(false, true)
}
data class Rect2f(val x: Float, val y: Float, val width: Float, val height: Float) {
fun toIntRect(): Rect2i {
return Rect2i(x.roundToInt(), y.roundToInt(), width.roundToInt(), height.roundToInt())
}
}
open class EditablePanel<out S : Screen>(
val screen: S,
parent: EditablePanel<*>?,
@ -175,18 +112,14 @@ open class EditablePanel<out S : Screen>(
}
}
val random: RandomSource by lazy {
val random: GJRAND64RandomSource by lazy {
if (screen is MatteryScreen<*>) {
screen.menu.random
} else {
RandomSource.create()
GJRAND64RandomSource()
}
}
val randomGenerator: RandomGenerator by lazy {
RandomSource2Generator(random)
}
/**
* Bigger values means lesser priority while docking, rendering and processing inputs.
*/
@ -350,11 +283,13 @@ open class EditablePanel<out S : Screen>(
if (visibleChildrenParent?.contains(this) == false) {
visibleChildrenParent.add(this)
parent?.invalidateChildrenSorting()
parent?.layoutInvalidated = true
}
} else {
if (visibleChildrenParent?.contains(this) == true) {
visibleChildrenParent.remove(this)
parent?.invalidateChildrenSorting()
parent?.layoutInvalidated = true
}
}
@ -843,6 +778,12 @@ open class EditablePanel<out S : Screen>(
childrenSortingInvalidated = true
}
protected open fun onChildrenAdded(child: EditablePanel<*>) {}
protected open fun onChildrenRemoved(child: EditablePanel<*>) {}
protected open fun onParented(parent: EditablePanel<*>) {}
protected open fun onUnParented(parent: EditablePanel<*>) {}
private fun onParent(child: EditablePanel<*>) {
if (childrenInternal.contains(child)) throw IllegalStateException("Already containing $child")
childrenInternal.add(child)
@ -860,11 +801,15 @@ open class EditablePanel<out S : Screen>(
else
updateVisibility = true
}
onChildrenAdded(child)
child.onParented(this)
}
private fun onUnParent(child: EditablePanel<*>) {
val indexOf = childrenInternal.indexOf(child)
if (indexOf == -1) throw IllegalStateException("Already not containing $child")
child.onUnParented(this)
childrenInternal.removeAt(indexOf)
invalidateChildrenSorting()
@ -876,6 +821,8 @@ open class EditablePanel<out S : Screen>(
if (child.isVisible() != isVisible()) {
updateVisible()
}
onChildrenRemoved(child)
}
private fun sortChildren() {
@ -944,12 +891,11 @@ open class EditablePanel<out S : Screen>(
}
}
fun render(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
fun render(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float, debugSegments: MutableList<LinePoint>) {
once = true
if (!isVisible()) {
if (!isVisible())
return
}
val poseStack = graphics.pose
@ -997,12 +943,19 @@ open class EditablePanel<out S : Screen>(
child.absoluteX = absoluteX + child.x + xOffset
child.absoluteY = absoluteY + child.y + yOffset
child.render(graphics, mouseX, mouseY, partialTick)
child.render(graphics, mouseX, mouseY, partialTick, debugSegments)
}
if (scissor) {
popScissorRect()
}
if (minecraft.entityRenderDispatcher.shouldRenderHitBoxes()) {
debugSegments.add(LinePoint(absoluteX, absoluteY, absoluteX + width, absoluteY))
debugSegments.add(LinePoint(absoluteX + width, absoluteY, absoluteX + width, absoluteY + height))
debugSegments.add(LinePoint(absoluteX + width, absoluteY + height, absoluteX, absoluteY + height))
debugSegments.add(LinePoint(absoluteX, absoluteY + height, absoluteX, absoluteY))
}
}
fun updateCursor0(): Boolean {
@ -1508,6 +1461,7 @@ open class EditablePanel<out S : Screen>(
if (old != new) {
child.visibilityChanges(new, old)
child.invalidateChildrenSorting()
}
child.updateVisible()

View File

@ -62,7 +62,7 @@ open class EffectListPanel<out S : Screen> @JvmOverloads constructor(
init {
scroll.visible = false
//scissor = true
scissor = true
}
open inner class EffectSquare(

View File

@ -4,6 +4,8 @@ import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Renderable
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.screens.Screen
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.client.render.LinePoint
import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
// before 1.19.3 Renderable was Widget
@ -21,11 +23,17 @@ class Panel2Widget<out S: Screen, out P : EditablePanel<S>>(
val yFloat = mouseY.toFloat()
val wrap = MGUIGraphics(graphics)
val segments = ArrayList<LinePoint>()
panel.tickHovered0(xFloat, yFloat, false)
panel.tickHovered1()
panel.tickHovered2()
panel.render(wrap, xFloat, yFloat, partialTick)
panel.render(wrap, xFloat, yFloat, partialTick, segments)
if (segments.isNotEmpty()) {
wrap.drawLine(0.5f, RGBAColor.GOLD, segments)
}
panel.renderTooltips(wrap, xFloat, yFloat, partialTick)
}

View File

@ -0,0 +1,120 @@
package ru.dbotthepony.mc.otm.client.screen.panels
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.screens.Screen
import net.neoforged.neoforge.network.PacketDistributor
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.menu.QuickMoveInput
import ru.dbotthepony.mc.otm.network.QuickStackPacket
import java.util.function.IntConsumer
class QuickStackControlsPanel<out S : Screen>(
screen: S,
parent: EditablePanel<*>? = null,
x: Float = 0f,
y: Float = 0f
) : EditablePanel<S>(screen, parent, x, y, 18f, 18f) {
private val grid = GridPanel(screen, this, columns = 1, rows = 1)
private val buttons = ArrayList<EditablePanel<S>>()
init {
grid.dock = Dock.FILL
grid.layout = GridPanel.Layout.TOP_RIGHT
grid.columnMajorOrder = true
// so row with main button contains only that button (visibly)
val fill = EditablePanel(screen, grid, width = 18f, height = 18f)
fill.childrenOrder = 999
fill.visible = false
fill.dockMargin = DockProperty.bottomRight(1f)
buttons.add(fill)
}
private inner class MainButton : ButtonPanel<S>(screen, grid, width = 18f, height = 18f) {
init {
dockMargin = DockProperty.bottom(1f)
childrenOrder = -100000
tooltips.add(TranslatableComponent("otm.gui.quickmove.exchange"))
tooltips.add(TranslatableComponent("otm.gui.quickmove.exchange.desc").withStyle(ChatFormatting.GRAY))
tooltips.add(TextComponent(""))
tooltips.add(TranslatableComponent("otm.gui.quickmove_hint").withStyle(ChatFormatting.GRAY))
}
override var isDisabled: Boolean
get() = minecraft.player!!.isSpectator
set(value) {}
override val icon: IGUIRenderable
get() = Widgets18.SMART_STORAGE_EXCHANGE
override fun test(value: Int): Boolean {
return value == InputConstants.MOUSE_BUTTON_LEFT || value == InputConstants.MOUSE_BUTTON_RIGHT
}
override fun onClick(mouseButton: Int) {
if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) {
PacketDistributor.sendToServer(QuickStackPacket(QuickMoveInput.Mode.RESTOCK_WITH_MOVE, true))
PacketDistributor.sendToServer(QuickStackPacket(QuickMoveInput.Mode.RESTOCK, false))
} else {
buttons.forEach { it.visible = !it.visible }
if (buttons[0].visible) {
grid.columns = 2
grid.rows = 4
} else {
grid.columns = 1
grid.rows = 1
}
this@QuickStackControlsPanel.sizeToContents()
}
}
}
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
return false
}
override fun sizeToContents() {
val oldWidth = width
super.sizeToContents()
x -= width - oldWidth
}
init {
for ((i, mode) in QuickMoveInput.Mode.entries.withIndex()) {
val button = ButtonPanel.square18(
screen, grid,
mode.iconFromStorage,
onPress = IntConsumer { PacketDistributor.sendToServer(QuickStackPacket(mode, false)) })
button.childrenOrder = 1000 + i
button.dockMargin = DockProperty.bottomRight(1f)
button.visible = false
button.tooltips.add(mode.textFromStorage)
buttons.add(button)
}
for ((i, mode) in QuickMoveInput.Mode.entries.withIndex()) {
val button = ButtonPanel.square18(
screen, grid,
mode.iconToStorage,
onPress = IntConsumer { PacketDistributor.sendToServer(QuickStackPacket(mode, true)) })
button.childrenOrder = i
button.dockMargin = DockProperty.bottom(1f)
button.visible = false
button.tooltips.add(mode.textToStorage)
buttons.add(button)
}
MainButton()
}
}

View File

@ -0,0 +1,115 @@
package ru.dbotthepony.mc.otm.client.screen.panels
import net.minecraft.client.renderer.Rect2i
import kotlin.math.roundToInt
data class ScreenPos(val x: Float, val y: Float)
data class DockProperty(val left: Float = 0f, val top: Float = 0f, val right: Float = 0f, val bottom: Float = 0f) {
val isEmpty get() = left == 0f && right == 0f && top == 0f && bottom == 0f
val horizontal get() = left + right
val vertical get() = top + bottom
operator fun plus(other: DockProperty): DockProperty {
return DockProperty(
left + other.left,
top + other.top,
right + other.right,
bottom + other.bottom
)
}
operator fun minus(other: DockProperty): DockProperty {
return DockProperty(
left - other.left,
top - other.top,
right - other.right,
bottom - other.bottom
)
}
operator fun plus(other: Float): DockProperty {
return DockProperty(
left + other,
top + other,
right + other,
bottom + other
)
}
operator fun minus(other: Float): DockProperty {
return DockProperty(
left - other,
top - other,
right - other,
bottom - other
)
}
companion object {
val EMPTY = DockProperty()
@JvmStatic
fun all(value: Float): DockProperty {
return DockProperty(value, value, value, value)
}
@JvmStatic
@JvmOverloads
fun topLeft(top: Float, left: Float = top): DockProperty {
return DockProperty(left = left, top = top)
}
@JvmStatic
fun left(value: Float): DockProperty {
return DockProperty(left = value)
}
@JvmStatic
@JvmOverloads
fun topRight(top: Float, right: Float = top): DockProperty {
return DockProperty(right = right, top = top)
}
@JvmStatic
fun right(value: Float): DockProperty {
return DockProperty(right = value)
}
@JvmStatic
fun top(value: Float): DockProperty {
return DockProperty(top = value)
}
@JvmStatic
@JvmOverloads
fun bottomLeft(bottom: Float, left: Float = bottom): DockProperty {
return DockProperty(left = left, bottom = bottom)
}
@JvmStatic
@JvmOverloads
fun bottomRight(bottom: Float, right: Float = bottom): DockProperty {
return DockProperty(right = right, bottom = bottom)
}
@JvmStatic
fun bottom(value: Float): DockProperty {
return DockProperty(bottom = value)
}
}
}
enum class Dock {
NONE, LEFT, RIGHT, TOP, BOTTOM, FILL
}
enum class DockResizeMode(val changeWidth: Boolean, val changeHeight: Boolean) {
ALL(true, true), NONE(false, false), WIDTH(true, false), HEIGHT(false, true)
}
data class Rect2f(val x: Float, val y: Float, val width: Float, val height: Float) {
fun toIntRect(): Rect2i {
return Rect2i(x.roundToInt(), y.roundToInt(), width.roundToInt(), height.roundToInt())
}
}

View File

@ -6,6 +6,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import ru.dbotthepony.kommons.util.Delegate
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.util.value
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting
@ -542,33 +543,43 @@ class DeviceControls<out S : MatteryScreen<*>>(
}
}
fun quickMoveButtons(buttons: Map<QuickMoveInput.Mode, QuickMoveInput>, mainMode: QuickMoveInput.Mode, isFromStorage: Boolean, unfoldableSettings: Boolean = true) {
fun quickMoveButtons(
buttons: Map<QuickMoveInput.Mode, QuickMoveInput>,
fromStorage: Map<QuickMoveInput.Mode, QuickMoveInput>? = null
) {
object : FoldableButtonPanel() {
private val input = buttons[mainMode]!!
override fun performAction() {
input.clientInput()
if (fromStorage == null) {
buttons[QuickMoveInput.Mode.MOVE]!!.clientInput()
} else {
buttons[QuickMoveInput.Mode.RESTOCK_WITH_MOVE]!!.clientInput()
fromStorage[QuickMoveInput.Mode.RESTOCK]!!.clientInput()
}
}
override var isDisabled: Boolean
get() { return !input.input.test(minecraft.player ?: return false) }
get() { return !buttons.values.first().input.test(minecraft.player ?: return false) }
set(value) {}
override val hasExtraButtons: Boolean
get() = unfoldableSettings
get() = true
override fun doMakeButtons(): List<EditablePanel<S>> {
return buttons.entries
var stream = buttons.entries
.stream()
.filter { (m, _) -> m != input.mode }
if (fromStorage == null)
stream = stream.filter { (m) -> m != QuickMoveInput.Mode.MOVE }
return stream
.map { (m, b) ->
square18(
screen,
this,
icon = if (isFromStorage) m.iconFromStorage else m.iconToStorage,
icon = if (fromStorage == null) m.iconFromStorage else m.iconToStorage,
onPress = { b.clientInput() }
).also {
if (isFromStorage)
if (fromStorage == null)
it.tooltips.add(m.textFromStorage)
else
it.tooltips.add(m.textToStorage)
@ -578,21 +589,19 @@ class DeviceControls<out S : MatteryScreen<*>>(
}
init {
if (isFromStorage)
tooltips.add(input.mode.textFromStorage)
else
tooltips.add(input.mode.textToStorage)
if (unfoldableSettings) {
tooltips.add(TextComponent(""))
tooltips.add(TranslatableComponent("otm.gui.quickmove_hint").withStyle(ChatFormatting.GRAY))
} else {
makeButtons()
if (fromStorage == null)
tooltips.add(QuickMoveInput.Mode.MOVE.textFromStorage)
else {
tooltips.add(TranslatableComponent("otm.gui.quickmove.exchange"))
tooltips.add(TranslatableComponent("otm.gui.quickmove.exchange.desc").withStyle(ChatFormatting.GRAY))
}
tooltips.add(TextComponent(""))
tooltips.add(TranslatableComponent("otm.gui.quickmove_hint").withStyle(ChatFormatting.GRAY))
}
override val icon: IGUIRenderable
get() = if (isFromStorage) input.mode.iconFromStorage else input.mode.iconToStorage
get() = if (fromStorage == null) QuickMoveInput.Mode.MOVE.iconFromStorage else Widgets18.SMART_STORAGE_EXCHANGE
}
}

View File

@ -7,6 +7,9 @@ import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.maybe
import java.util.stream.IntStream
import java.util.stream.Stream
import kotlin.math.max
open class GridPanel<out S : Screen>(
screen: S,
@ -20,25 +23,150 @@ open class GridPanel<out S : Screen>(
) : EditablePanel<S>(screen, parent, x, y, width, height) {
var columns: Int = columns
set(value) {
field = value
invalidateLayout()
if (field != value) {
field = value
invalidateLayout()
}
}
var rows: Int = rows
set(value) {
field = value
invalidateLayout()
if (field != value) {
field = value
invalidateLayout()
}
}
var gravity: RenderGravity = RenderGravity.CENTER_CENTER
set(value) {
field = value
invalidateLayout()
if (field != value) {
field = value
invalidateLayout()
}
}
var layout: Layout = Layout.TOP_LEFT
set(value) {
if (field != value) {
field = value
invalidateLayout()
}
}
var columnMajorOrder = false
set(value) {
if (field != value) {
field = value
invalidateLayout()
}
}
enum class Layout {
TOP_LEFT {
override fun get(
list: List<EditablePanel<*>>,
row: Int,
column: Int,
rows: Int,
columns: Int,
columnMajorOrder: Boolean
): EditablePanel<*>? {
if (columnMajorOrder) {
return list.getOrNull(row + column * rows)
} else {
return list.getOrNull(column + row * columns)
}
}
},
TOP_RIGHT {
override fun get(
list: List<EditablePanel<*>>,
row: Int,
column: Int,
rows: Int,
columns: Int,
columnMajorOrder: Boolean
): EditablePanel<*>? {
if (columnMajorOrder) {
return list.getOrNull(row + (columns - column - 1) * rows)
} else {
return list.getOrNull((columns - column - 1) + row * columns)
}
}
},
BOTTOM_LEFT {
override fun get(
list: List<EditablePanel<*>>,
row: Int,
column: Int,
rows: Int,
columns: Int,
columnMajorOrder: Boolean
): EditablePanel<*>? {
if (columnMajorOrder) {
return list.getOrNull((rows - row - 1) + column * rows)
} else {
return list.getOrNull(column + (rows - row - 1) * columns)
}
}
},
BOTTOM_RIGHT {
override fun get(
list: List<EditablePanel<*>>,
row: Int,
column: Int,
rows: Int,
columns: Int,
columnMajorOrder: Boolean
): EditablePanel<*>? {
if (columnMajorOrder) {
return list.getOrNull((rows - row - 1) + (columns - column - 1) * rows)
} else {
return list.getOrNull((columns - column - 1) + (rows - row - 1) * columns)
}
}
};
abstract fun get(list: List<EditablePanel<*>>, row: Int, column: Int, rows: Int, columns: Int, columnMajorOrder: Boolean): EditablePanel<*>?
}
override fun sizeToContents() {
if (visibleChildren.isEmpty()) {
// nothing to size against
return
}
visibleChildren.forEach { it.sizeToContents() }
var width = 0f
var height = 0f
val children = visibleChildren.filter { it.dock == Dock.NONE }
for (row in 0 until rows) {
var maxHeight = 0f
var rowWidth = 0f
for (column in 0 until columns) {
val nextChild = layout.get(children, row, column, rows, columns, columnMajorOrder) ?: continue
rowWidth += nextChild.width + nextChild.dockMargin.horizontal
maxHeight = max(maxHeight, nextChild.height + nextChild.dockMargin.vertical)
}
height += maxHeight
width = max(width, rowWidth)
}
this.width = width
this.height = height
}
override fun performLayout() {
super.performLayout()
var children = visibleChildren.iterator().filter { it.dock == Dock.NONE }
val children = visibleChildren.filter { it.dock == Dock.NONE }
var totalWidth = 0f
var totalHeight = 0f
@ -48,7 +176,7 @@ open class GridPanel<out S : Screen>(
var width = 0f
for (column in 0 until columns) {
val child = children.maybe() ?: break
val child = layout.get(children, row, column, rows, columns, columnMajorOrder) ?: continue
width += child.dockedWidth
maxHeight = maxHeight.coerceAtLeast(child.dockedHeight)
}
@ -59,7 +187,6 @@ open class GridPanel<out S : Screen>(
val alignX = gravity.repositionX(width, totalWidth)
val alignY = gravity.repositionY(height, totalHeight)
children = visibleChildren.iterator().filter { it.dock == Dock.NONE }
totalWidth = 0f
totalHeight = 0f
@ -69,7 +196,7 @@ open class GridPanel<out S : Screen>(
var width = 0f
for (column in 0 until columns) {
val child = children.maybe() ?: break
val child = layout.get(children, row, column, rows, columns, columnMajorOrder) ?: continue
child.x = alignX + width
child.y = alignY + totalHeight
width += child.dockedWidth

View File

@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import net.minecraft.ChatFormatting
import net.minecraft.client.Minecraft
import net.minecraft.network.chat.Component
import net.minecraft.util.RandomSource
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.network.PacketDistributor
@ -34,6 +35,7 @@ import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.player.matteryPlayer
import ru.dbotthepony.mc.otm.client.screen.panels.button.BooleanButtonPanel
import ru.dbotthepony.mc.otm.core.nextFloat
import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
import ru.dbotthepony.mc.otm.network.AndroidResearchRequestPacket
import java.util.*
@ -540,14 +542,14 @@ class AndroidStationScreen(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_
if (isPreview && !layoutInvalidated) {
if (firstTick) {
scroller.init.invoke(this, randomGenerator)
scroller.init.invoke(this, random)
}
val status = scroller.scroll.invoke(this, randomGenerator)
val status = scroller.scroll.invoke(this, random)
if (!status) {
scroller = PreviewScrollers.entries.let { it[random.nextInt(it.size)] }
scroller.init.invoke(this, randomGenerator)
scroller.init.invoke(this, random)
}
}
}

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.compat.vanilla
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.mc.otm.client.render.RenderGravity
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
@ -31,11 +32,11 @@ class VanillaChestScreen(menu: AbstractVanillaChestMenu, inventory: Inventory, t
val leftControls = DeviceControls(this, frame)
leftControls.dockOnLeft = true
leftControls.quickMoveButtons(menu.quickMoveFromStorage, QuickMoveInput.Mode.RESTOCK, true)
leftControls.quickMoveButtons(menu.quickMoveFromStorage)
val leftInventoryControls = DeviceControls(this, inventoryFrame!!)
leftInventoryControls.dockOnLeft = true
leftInventoryControls.quickMoveButtons(menu.quickMoveToStorage, QuickMoveInput.Mode.RESTOCK_WITH_MOVE, false)
leftInventoryControls.quickMoveButtons(menu.quickMoveToStorage, menu.quickMoveFromStorage)
return frame
}

View File

@ -680,3 +680,21 @@ fun <K : Any, V> SimpleCache(size: Long, freshness: Duration): Cache<K, V> {
.expireAfterWrite(freshness)
.build()
}
fun RandomSource.nextFloat(min: Float, max: Float): Float {
if (this is RandomGenerator)
return nextFloat(min, max)
require(max >= min) { "Min is bigger than max: $min vs $max" }
if (min == max) return min
return min + nextFloat() * (max - min)
}
fun RandomSource.nextDouble(min: Double, max: Double): Double {
if (this is RandomGenerator)
return nextDouble(min, max)
require(max >= min) { "Min is bigger than max: $min vs $max" }
if (min == max) return min
return min + nextDouble() * (max - min)
}

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.mc.otm.core.math
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import net.minecraft.util.RandomSource
import ru.dbotthepony.mc.otm.core.random
import java.util.random.RandomGenerator
import kotlin.math.min
@ -88,7 +89,7 @@ private class MutableCluster<V : Comparable<V>>(var center: V) {
}
private fun <V : Comparable<V>> Iterable<V>.clusterize(
random: RandomGenerator,
random: RandomSource,
initialClusters: Int = 1,
zeroBound: Boolean = false,
identity: V,
@ -202,7 +203,7 @@ private fun <V : Comparable<V>> Iterable<V>.clusterize(
// TODO: could use some tweaking
private val DECIMAL_ERROR_TOLERANCE = Decimal("0.02")
fun Iterable<Decimal>.clusterize(random: RandomGenerator, clusters: Int? = null, zeroBound: Boolean = false): List<Cluster<Decimal>> {
fun Iterable<Decimal>.clusterize(random: RandomSource, clusters: Int? = null, zeroBound: Boolean = false): List<Cluster<Decimal>> {
return clusterize(random, clusters ?: 1, zeroBound, Decimal.ZERO, Decimal::plus, Decimal::minus, Decimal::div, Decimal::absoluteValue) { min, max, error ->
if (clusters != null)
false

View File

@ -9,6 +9,7 @@ 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.THREAD_LOCAL_RANDOM
import ru.dbotthepony.mc.otm.client.render.ChartLevelLabels
import ru.dbotthepony.mc.otm.client.render.ChartTooltipElement
import ru.dbotthepony.mc.otm.config.ClientConfig
@ -453,10 +454,8 @@ private fun formatHistoryChart(
labelNames[0f] = (-maxTransferred).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
labelNames[1f] = maxReceived.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
val rand = java.util.Random()
if (maxTransferred.isNotZero && transferredMult > 0.2f) {
for (cluster in widget.transferred.clusterize(rand, zeroBound = true)) {
for (cluster in widget.transferred.clusterize(THREAD_LOCAL_RANDOM, zeroBound = true)) {
val perc = (cluster.center / maxTransferred).toFloat() * transferredMult
if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f })
@ -465,7 +464,7 @@ private fun formatHistoryChart(
}
if (maxReceived.isNotZero && receivedMult > 0.2f) {
for (cluster in widget.received.clusterize(rand, zeroBound = true)) {
for (cluster in widget.received.clusterize(THREAD_LOCAL_RANDOM, zeroBound = true)) {
val perc = zero + (cluster.center / maxReceived).toFloat() * receivedMult
if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f })
@ -473,7 +472,7 @@ private fun formatHistoryChart(
}
}
val clusters = diff.asIterable().clusterize(rand, zeroBound = true)
val clusters = diff.asIterable().clusterize(THREAD_LOCAL_RANDOM, zeroBound = true)
for (cluster in clusters) {
val perc: Float

View File

@ -43,6 +43,7 @@ import ru.dbotthepony.mc.otm.core.ResourceLocation
import ru.dbotthepony.mc.otm.core.collect.ConditionalSet
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.entity.checkCanInteract
import ru.dbotthepony.mc.otm.core.util.GJRAND64RandomSource
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
@ -81,7 +82,7 @@ abstract class MatteryMenu(
val mSynchronizer = SynchableGroup()
val synchronizerRemote = mSynchronizer.Remote()
val player: Player get() = inventory.player
val random: RandomSource = RandomSource.create()
val random = GJRAND64RandomSource()
private val _playerInventorySlots = ArrayList<InventorySlot>()
private val _playerHotbarSlots = ArrayList<InventorySlot>()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB