Input balancing
This commit is contained in:
parent
2e37ff5de4
commit
4f7c9ea176
@ -680,6 +680,8 @@ private fun gui(provider: MatteryLanguageProvider) {
|
||||
gui("side_mode.pull", "Pull")
|
||||
gui("side_mode.push", "Push")
|
||||
|
||||
gui("balance_inputs", "Balance input slots")
|
||||
|
||||
gui("sorting.default", "Default sorting")
|
||||
gui("sorting.name", "Sort by name")
|
||||
gui("sorting.id", "Sort by ID")
|
||||
|
@ -685,6 +685,8 @@ private fun gui(provider: MatteryLanguageProvider) {
|
||||
gui("side_mode.pull", "Автоматическое вытягивание")
|
||||
gui("side_mode.push", "Автоматическое выталкивание")
|
||||
|
||||
gui("balance_inputs", "Балансировать входные слоты")
|
||||
|
||||
gui("sorting.default", "Сортировка по умолчанию")
|
||||
gui("sorting.name", "Сортировка по имени")
|
||||
gui("sorting.id", "Сортировка по ID")
|
||||
|
@ -7,13 +7,10 @@ import net.minecraft.data.recipes.FinishedRecipe
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.valueproviders.ConstantFloat
|
||||
import net.minecraft.util.valueproviders.FloatProvider
|
||||
import net.minecraft.util.valueproviders.UniformFloat
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer
|
||||
import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe
|
||||
import ru.dbotthepony.mc.otm.recipe.PlatePressRecipeFactory
|
||||
import ru.dbotthepony.mc.otm.core.set
|
||||
import ru.dbotthepony.mc.otm.core.toJsonStrict
|
||||
import ru.dbotthepony.mc.otm.data.getOrNull
|
||||
|
||||
class PlatePressFinishedRecipe(private val recipe: PlatePressRecipe) : FinishedRecipe {
|
||||
override fun serializeRecipeData(it: JsonObject) {
|
||||
@ -33,7 +30,7 @@ class PlatePressFinishedRecipe(private val recipe: PlatePressRecipe) : FinishedR
|
||||
}
|
||||
|
||||
override fun getType(): RecipeSerializer<*> {
|
||||
return PlatePressRecipeFactory
|
||||
return PlatePressRecipe.Companion
|
||||
}
|
||||
|
||||
override fun serializeAdvancement(): JsonObject? {
|
||||
@ -74,7 +71,7 @@ class PlatePressShallowFinishedRecipe(
|
||||
}
|
||||
|
||||
override fun getType(): RecipeSerializer<*> {
|
||||
return PlatePressRecipeFactory
|
||||
return PlatePressRecipe.Companion
|
||||
}
|
||||
|
||||
override fun serializeAdvancement(): JsonObject? {
|
||||
|
@ -61,6 +61,12 @@ abstract class MatteryWorkerBlockEntity<JobType : IMachineJob>(
|
||||
}
|
||||
}
|
||||
|
||||
var balanceInputs = false
|
||||
|
||||
init {
|
||||
savetables.bool(::balanceInputs)
|
||||
}
|
||||
|
||||
protected open fun jobUpdated(new: JobType?, old: JobType?, id: Int) {}
|
||||
protected abstract fun onJobFinish(job: JobType, id: Int): JobStatus
|
||||
protected abstract fun computeNextJob(id: Int): JobContainer<JobType>
|
||||
|
@ -17,6 +17,7 @@ import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.config.MachinesConfig
|
||||
import ru.dbotthepony.mc.otm.container.MatteryContainer
|
||||
import ru.dbotthepony.mc.otm.container.HandlerFilter
|
||||
import ru.dbotthepony.mc.otm.container.balance
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu
|
||||
import ru.dbotthepony.mc.otm.menu.tech.TwinPlatePressMenu
|
||||
@ -87,6 +88,14 @@ class PlatePressBlockEntity(
|
||||
return JobContainer.success(MachineItemJob(recipe.getResultItem(level.registryAccess()), recipe.workTime.toDouble(), BASELINE_CONSUMPTION, experience = recipe.experience.sample(level.random)))
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
if (isTwin && balanceInputs) {
|
||||
inputContainer.balance()
|
||||
}
|
||||
|
||||
super.tick()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val BASELINE_CONSUMPTION = Decimal(15)
|
||||
}
|
||||
|
@ -70,9 +70,8 @@ object Widgets18 {
|
||||
val REDSTONE_LOW = controlsGrid.next()
|
||||
val REDSTONE_HIGH = controlsGrid.next()
|
||||
|
||||
init {
|
||||
controlsGrid.jump()
|
||||
}
|
||||
val BALANCING_DISABLED = controlsGrid.next()
|
||||
val BALANCING_ENABLED = controlsGrid.next()
|
||||
|
||||
class SideControls {
|
||||
val disabled = controlsGrid.next()
|
||||
|
@ -274,11 +274,13 @@ class DeviceControls<out S : MatteryScreen<*>>(
|
||||
val itemConfig: ItemConfigPlayerInput? = null,
|
||||
val energyConfig: EnergyConfigPlayerInput? = null,
|
||||
val fluidConfig: FluidConfigPlayerInput? = null,
|
||||
val balanceInputs: BooleanInputWithFeedback? = null,
|
||||
) : EditablePanel<S>(screen, parent, x = parent.width + 3f, height = 0f, width = 0f) {
|
||||
val itemConfigButton: LargeRectangleButtonPanel<S>?
|
||||
val energyConfigButton: LargeRectangleButtonPanel<S>?
|
||||
val fluidConfigButton: LargeRectangleButtonPanel<S>?
|
||||
val redstoneControlsButton: LargeEnumRectangleButtonPanel<S, RedstoneSetting>?
|
||||
val balanceInputsButton: LargeBooleanRectangleButtonPanel<S>?
|
||||
private var nextY = 0f
|
||||
|
||||
fun <P : EditablePanel<@UnsafeVariance S>> addButton(button: P): P {
|
||||
@ -302,6 +304,18 @@ class DeviceControls<out S : MatteryScreen<*>>(
|
||||
redstoneControlsButton = null
|
||||
}
|
||||
|
||||
if (balanceInputs != null) {
|
||||
balanceInputsButton = addButton(LargeBooleanRectangleButtonPanel(
|
||||
screen, this,
|
||||
prop = balanceInputs,
|
||||
skinElementActive = Widgets18.BALANCING_ENABLED,
|
||||
skinElementInactive = Widgets18.BALANCING_DISABLED).also {
|
||||
it.tooltip = TranslatableComponent("otm.gui.balance_inputs")
|
||||
})
|
||||
} else {
|
||||
balanceInputsButton = null
|
||||
}
|
||||
|
||||
if (itemConfig != null) {
|
||||
itemConfigButton = addButton(object : LargeRectangleButtonPanel<S>(screen, this@DeviceControls, skinElement = Widgets18.ITEMS_CONFIGURATION) {
|
||||
init {
|
||||
@ -375,6 +389,9 @@ fun <S : MatteryScreen<*>> makeDeviceControls(
|
||||
itemConfig: ItemConfigPlayerInput? = null,
|
||||
energyConfig: EnergyConfigPlayerInput? = null,
|
||||
fluidConfig: FluidConfigPlayerInput? = null,
|
||||
balanceInputs: BooleanInputWithFeedback? = null,
|
||||
): DeviceControls<S> {
|
||||
return DeviceControls(screen, parent, extra = extra, redstoneConfig = redstoneConfig, itemConfig = itemConfig, energyConfig = energyConfig, fluidConfig = fluidConfig)
|
||||
return DeviceControls(screen, parent, extra = extra, redstoneConfig = redstoneConfig,
|
||||
itemConfig = itemConfig, energyConfig = energyConfig, fluidConfig = fluidConfig,
|
||||
balanceInputs = balanceInputs)
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class TwinPlatePressScreen(menu: TwinPlatePressMenu, inventory: Inventory, title
|
||||
ProgressGaugePanel(this, frame, menu.progressGauge1, 78f, PROGRESS_ARROW_TOP + 10f)
|
||||
SlotPanel(this, frame, menu.outputSlots[1], 104f, PROGRESS_SLOT_TOP + 10f)
|
||||
|
||||
makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig)
|
||||
makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig, balanceInputs = menu.balanceInputs)
|
||||
|
||||
return frame
|
||||
}
|
||||
|
@ -1,11 +1,19 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet
|
||||
import it.unimi.dsi.fastutil.ints.IntSet
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraftforge.common.capabilities.Capability
|
||||
import net.minecraftforge.fluids.capability.IFluidHandler
|
||||
import ru.dbotthepony.mc.otm.core.addAll
|
||||
import ru.dbotthepony.mc.otm.core.collect.iterator
|
||||
import ru.dbotthepony.mc.otm.core.collect.nonEmpty
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
operator fun Container.set(index: Int, value: ItemStack) = setItem(index, value)
|
||||
operator fun Container.get(index: Int): ItemStack = getItem(index)
|
||||
@ -109,3 +117,133 @@ inline fun Container.forEachNonEmpty(lambda: (ItemStack) -> Unit) {
|
||||
lambda(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun Container.balance(slots: IntSet) {
|
||||
if (slots.isEmpty()) return
|
||||
|
||||
val empty = IntArrayList()
|
||||
val itemTypes = Object2ObjectOpenCustomHashMap<ItemStack, IntAVLTreeSet>(ItemStackHashStrategy)
|
||||
|
||||
for (i in slots.intIterator()) {
|
||||
val item = getItem(i)
|
||||
|
||||
if (item.isEmpty) {
|
||||
empty.add(i)
|
||||
} else {
|
||||
itemTypes.computeIfAbsent(item, Object2ObjectFunction { IntAVLTreeSet() }).add(i)
|
||||
}
|
||||
}
|
||||
|
||||
if (itemTypes.isEmpty())
|
||||
return
|
||||
|
||||
// только один вид предмета, просто балансируем его во все слоты
|
||||
if (itemTypes.size == 1) {
|
||||
val (item, list) = itemTypes.entries.first()
|
||||
var count = list.stream().mapToInt { getItem(it).count }.sum()
|
||||
|
||||
// всего предметов меньше, чем слотов
|
||||
if (count < slots.size) {
|
||||
for (slot in list.intIterator()) {
|
||||
getItem(slot).count = 1
|
||||
}
|
||||
|
||||
count -= list.size
|
||||
|
||||
while (count > 0 && empty.isNotEmpty()) {
|
||||
setItem(empty.removeInt(0), item.copyWithCount(1))
|
||||
count--
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
getItem(list.firstInt()).count += count
|
||||
}
|
||||
} else {
|
||||
// всего предметов больше, чем слотов
|
||||
val perSlot = count / slots.size
|
||||
var leftover = count - perSlot * slots.size
|
||||
|
||||
for (i in slots.intIterator()) {
|
||||
setItem(i, item.copyWithCount(perSlot))
|
||||
}
|
||||
|
||||
for (i in slots.intIterator()) {
|
||||
if (leftover <= 0) break
|
||||
getItem(i).count++
|
||||
leftover--
|
||||
}
|
||||
}
|
||||
|
||||
setChanged()
|
||||
return
|
||||
}
|
||||
|
||||
// а вот тут уже проблемы
|
||||
// для упрощения задачи, выполним рекурсивное разбитие задачи на более простые,
|
||||
// где балансировка будет происходить только между пустыми слотами и предметами одного типа
|
||||
|
||||
// если у нас нет пустых слотов, просто балансируем между заполненными слотами
|
||||
if (empty.isEmpty) {
|
||||
for (set in itemTypes.values) {
|
||||
balance(set)
|
||||
}
|
||||
|
||||
return
|
||||
} else if (empty.size == 1) {
|
||||
// только один пустой слот, отдадим самому "жирному" предмету
|
||||
val type = itemTypes.entries.stream().max { a, b -> b.value.intStream().sum().compareTo(a.value.intStream().sum()) }.orElseThrow()
|
||||
type.value.add(empty.getInt(0))
|
||||
balance(type.value)
|
||||
|
||||
for ((a, set) in itemTypes) {
|
||||
if (a !== type.key) {
|
||||
balance(set)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// определяем общее количество предметов
|
||||
val totalCount = itemTypes.values.stream().mapToInt { it.stream().mapToInt { getItem(it).count }.sum() }.sum().toDouble()
|
||||
val totalEmpty = empty.size
|
||||
|
||||
// определяем доли предметов по их количеству к общему количеству,
|
||||
// что позволит нам выделить претендентов на пустые слоты
|
||||
for (list in itemTypes.values) {
|
||||
if (empty.isEmpty) break // ошибка округления
|
||||
|
||||
val perc = list.stream().mapToInt { getItem(it).count }.sum() / totalCount
|
||||
|
||||
for (i in 0 until (perc * totalEmpty).roundToInt()) {
|
||||
list.add(empty.removeInt(0))
|
||||
if (empty.isEmpty) break // ошибка округления
|
||||
}
|
||||
}
|
||||
|
||||
if (empty.isNotEmpty()) {
|
||||
// ошибка округления
|
||||
itemTypes.values.stream().max { a, b -> b.intStream().sum().compareTo(a.intStream().sum()) }.orElseThrow().add(empty.removeInt(0))
|
||||
}
|
||||
|
||||
for (list in itemTypes.values) {
|
||||
balance(list)
|
||||
}
|
||||
}
|
||||
|
||||
fun Container.balance(slots: Iterator<Int>) {
|
||||
balance(IntArraySet().also { it.addAll(slots) })
|
||||
}
|
||||
|
||||
fun Container.balance(slots: Iterable<Int>) {
|
||||
balance(IntArraySet().also { it.addAll(slots) })
|
||||
}
|
||||
|
||||
fun Container.balance(slots: IntRange) {
|
||||
balance(IntArraySet().also { it.addAll(slots) })
|
||||
}
|
||||
|
||||
fun Container.balance(startSlot: Int = 0, endSlot: Int = containerSize - 1) {
|
||||
require(startSlot <= endSlot) { "Invalid slot range: $startSlot .. $endSlot" }
|
||||
balance(IntArrayList(endSlot - startSlot + 1).also { for (i in startSlot .. endSlot) it.add(i) })
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import it.unimi.dsi.fastutil.Hash
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
object ItemStackHashStrategy : Hash.Strategy<ItemStack> {
|
||||
override fun equals(a: ItemStack?, b: ItemStack?): Boolean {
|
||||
return a === b || a != null && b != null && ItemStack.isSameItemSameTags(a, b)
|
||||
}
|
||||
|
||||
override fun hashCode(o: ItemStack?): Int {
|
||||
o ?: return 0
|
||||
return o.item.hashCode().xor(o.tag.hashCode())
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import kotlin.reflect.KMutableProperty0
|
||||
|
||||
class BooleanInputWithFeedback(menu: MatteryMenu) : AbstractPlayerInputWithFeedback<Boolean>() {
|
||||
override val input = menu.booleanInput { consumer?.invoke(it) }
|
||||
override val value by menu.mSynchronizer.computedBool(BooleanSupplier { supplier?.invoke() ?: false })
|
||||
override val value by menu.mSynchronizer.computedBool(BooleanSupplier { supplier?.invoke() ?: false }).property
|
||||
|
||||
constructor(menu: MatteryMenu, state: KMutableProperty0<Boolean>) : this(menu) {
|
||||
with(state)
|
||||
|
@ -7,6 +7,7 @@ import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity
|
||||
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
|
||||
import ru.dbotthepony.mc.otm.menu.MatterySlot
|
||||
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
|
||||
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
|
||||
import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput
|
||||
import ru.dbotthepony.mc.otm.menu.makeSlots
|
||||
@ -28,6 +29,14 @@ class TwinPlatePressMenu @JvmOverloads constructor(
|
||||
val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig, allowPull = true)
|
||||
val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget)
|
||||
|
||||
val balanceInputs = BooleanInputWithFeedback(this)
|
||||
|
||||
init {
|
||||
if (tile != null) {
|
||||
balanceInputs.with(tile::balanceInputs)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
addStorageSlot(inputSlots)
|
||||
addStorageSlot(outputSlots)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
Loading…
Reference in New Issue
Block a user