diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index a577a70d2..08a15b524 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -264,6 +264,7 @@ private fun misc(provider: MatteryLanguageProvider) { gui("power.percentage_level", "Energy level: %s%%") gui("level", "%s / %s") + gui("diff", "%s (%s / %s)") gui("power.name", "MtJ") gui("fluid.name", "B") gui("fluid.level", "%s / %s of %s") diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt index 1e8345a1e..5b98c30c2 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt @@ -270,7 +270,6 @@ private fun misc(provider: MatteryLanguageProvider) { misc("pill.message_finish", "§kОДИН ИЗ НАС ОДИН ИЗ НАС ОДИН ИЗ НАС ОДИН ИЗ НАС ОДИН ИЗ НАС") gui("power.percentage_level", "Уровень энергии: %s%%") - gui("level", "%s / %s") gui("power.name", "МтДж") gui("fluid.name", "В") gui("fluid.level", "%s / %s с %s") diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt index e1f8696a2..382295cce 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt @@ -15,6 +15,7 @@ import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.capability.* import ru.dbotthepony.mc.otm.capability.energy.GeneratorEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.core.* @@ -39,7 +40,7 @@ class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDe val residueItemHandler = residueContainer.handler(HandlerFilter.OnlyOut) val fuelItemHandler = fuelContainer.handler(HandlerFilter.ChemicalFuel) - val energy = GeneratorEnergyStorage(::setChangedLight, CAPACITY, THROUGHPUT) + val energy = ProfiledEnergyStorage(GeneratorEnergyStorage(::setChangedLight, CAPACITY, THROUGHPUT)) val itemConfig = ConfigurableItemHandler( input = fuelItemHandler, @@ -77,6 +78,7 @@ class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDe override fun tick() { super.tick() + energy.tick() if (workTicks > 0) { workTicks-- diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt new file mode 100644 index 000000000..7bb2efad4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt @@ -0,0 +1,169 @@ +package ru.dbotthepony.mc.otm.capability.energy + +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.NumericTag +import net.minecraft.nbt.Tag +import net.minecraftforge.common.util.INBTSerializable +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.set +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.ITickable +import java.util.* +import kotlin.collections.ArrayList + +class ProfiledEnergyStorage(val parent: E) : IMatteryEnergyStorage, ITickable, INBTSerializable { + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return recordTransfer(parent.extractEnergy(howMuch, simulate), simulate) + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return recordReceive(parent.receiveEnergy(howMuch, simulate), simulate) + } + + override fun extractEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return recordTransfer(parent.extractEnergyChecked(howMuch, simulate), simulate) + } + + override fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return recordReceive(parent.receiveEnergyChecked(howMuch, simulate), simulate) + } + + override val canSetBatteryLevel: Boolean get() = parent.canSetBatteryLevel + override val missingPower: Decimal get() = parent.missingPower + + override fun drainBattery(): Boolean { + return parent.drainBattery() + } + + override fun fillBattery(): Boolean { + return parent.fillBattery() + } + + override var batteryLevel: Decimal + get() = parent.batteryLevel + set(value) { parent.batteryLevel = value } + override val maxBatteryLevel: Decimal get() = parent.maxBatteryLevel + override val energyFlow: FlowDirection get() = parent.energyFlow + + var lastTickReceive = Decimal.ZERO + private set + + var lastTickTransfer = Decimal.ZERO + private set + + var tick = 0 + private set + + private val historyReceiveInternal = ArrayList() + private val historyTransferInternal = ArrayList() + + val historyReceive: List = Collections.unmodifiableList(historyReceiveInternal) + val historyTransfer: List = Collections.unmodifiableList(historyTransferInternal) + + init { + for (i in 0 until HISTORY_SIZE) { + historyReceiveInternal.add(Decimal.ZERO) + historyTransferInternal.add(Decimal.ZERO) + } + } + + private fun recordTransfer(value: Decimal, simulate: Boolean): Decimal { + if (!simulate) { + lastTickTransfer += value + } + + return value + } + + private fun recordReceive(value: Decimal, simulate: Boolean): Decimal { + if (!simulate) { + lastTickReceive += value + } + + return value + } + + override fun tick() { + tick = (tick + 1) % HISTORY_SIZE + historyReceiveInternal[tick] = lastTickReceive + historyTransferInternal[tick] = lastTickTransfer + + lastTickReceive = Decimal.ZERO + lastTickTransfer = Decimal.ZERO + } + + override fun serializeNBT(): CompoundTag { + val tag: CompoundTag + + if (parent is INBTSerializable<*>) { + tag = (parent as INBTSerializable).serializeNBT() ?: CompoundTag() + } else { + tag = CompoundTag() + } + + tag["historyReceive"] = ListTag().also { + for (value in historyReceiveInternal) { + it.add(value.serializeNBT()) + } + } + + tag["historyTransfer"] = ListTag().also { + for (value in historyTransferInternal) { + it.add(value.serializeNBT()) + } + } + + tag["historyTick"] = tick + tag["lastTickReceive"] = lastTickReceive + tag["lastTickTransfer"] = lastTickTransfer + + return tag + } + + override fun deserializeNBT(nbt: CompoundTag?) { + if (parent is INBTSerializable<*>) { + (parent as INBTSerializable).deserializeNBT(nbt) + } + + if (nbt != null) { + historyReceiveInternal.clear() + historyTransferInternal.clear() + + for (i in 0 until HISTORY_SIZE) { + historyReceiveInternal.add(Decimal.ZERO) + historyTransferInternal.add(Decimal.ZERO) + } + + nbt.map("historyTick") { it: NumericTag -> + tick = it.asInt + } + + nbt.map("lastTickReceive") { it: Tag -> + lastTickReceive = Decimal.deserializeNBT(it) + } + + nbt.map("lastTickTransfer") { it: Tag -> + lastTickReceive = Decimal.deserializeNBT(it) + } + + nbt.map("historyReceive") { it: ListTag -> + for (i in 0 until HISTORY_SIZE.coerceAtMost(it.size)) { + historyReceiveInternal[i] = Decimal.deserializeNBT(it[i]) + } + } + + nbt.map("historyTransfer") { it: ListTag -> + for (i in 0 until HISTORY_SIZE.coerceAtMost(it.size)) { + historyTransferInternal[i] = Decimal.deserializeNBT(it[i]) + } + } + } + } + + companion object { + const val HISTORY_SIZE = 20 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt index 8f9f7562a..2f84ddd7e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt @@ -9,18 +9,17 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu class ChemicalGeneratorScreen(menu: ChemicalGeneratorMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { override fun makeMainFrame(): FramePanel> { val frame = super.makeMainFrame()!! - WidePowerGaugePanel(this, frame, menu.energy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + WideProfiledPowerGaugePanel(this, frame, menu.energy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - val self = this - val progress = object : ProgressGaugePanel(self, frame, menu.progress, 78f, PROGRESS_ARROW_TOP) { + val progress = object : ProgressGaugePanel(this@ChemicalGeneratorScreen, frame, menu.progress, 78f, PROGRESS_ARROW_TOP) { override fun makeTooltip(): MutableList { val list = super.makeTooltip() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt index 1f955c3d7..2ed40ee44 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt @@ -1,16 +1,26 @@ package ru.dbotthepony.mc.otm.client.screen.widget import com.mojang.blaze3d.vertex.PoseStack +import it.unimi.dsi.fastutil.ints.IntArrayList +import net.minecraft.ChatFormatting import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.client.isShiftDown +import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.client.render.* import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.stream +import ru.dbotthepony.mc.otm.core.util.formatPower import ru.dbotthepony.mc.otm.core.util.formatPowerLevel import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProfiledEnergyGaugeWidget -open class PowerGaugePanel @JvmOverloads constructor( +open class PowerGaugePanel( screen: S, parent: EditablePanel<*>? = null, val widget: LevelGaugeWidget, @@ -73,6 +83,7 @@ open class PowerGaugePanel @JvmOverloads constructor( /** * Shortcut to [PowerGaugePanel] with doubled width */ +@Suppress("FunctionName") fun WidePowerGaugePanel( screen: S, parent: EditablePanel<*>? = null, @@ -82,3 +93,78 @@ fun WidePowerGaugePanel( width: Float = 18f, height: Float = 48f ) = PowerGaugePanel(screen, parent, widget, x, y, width, height) + +private fun formatLevel(a: Decimal, b: Decimal): Component { + val diff = a - b + + val fa = a.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN) + val fb = b.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_RED) + + if (diff.isZero) { + return TranslatableComponent("otm.gui.diff", diff.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.GRAY), fa, fb) + } else if (diff.isPositive) { + return TranslatableComponent("otm.gui.diff", diff.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN), fa, fb) + } else { + return TranslatableComponent("otm.gui.diff", (-diff).formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_RED), fa, fb) + } +} + +open class ProfiledPowerGaugePanel( + screen: S, + parent: EditablePanel<*>? = null, + val profiledWidget: ProfiledEnergyGaugeWidget, + x: Float = 0f, + y: Float = 0f, + width: Float = GAUGE_BACKGROUND.width, + height: Float = GAUGE_BACKGROUND.height +) : PowerGaugePanel(screen, parent, profiledWidget.gauge, x, y, width, height) { + override fun makeTooltip(): MutableList { + return super.makeTooltip().also { + it.add(TextComponent("")) + + if (minecraft.window.isShiftDown) { + it.add(formatLevel(profiledWidget.lastTickReceive, profiledWidget.lastTickTransfer)) + it.add(TextComponent("---")) + } + + it.add(formatLevel( + profiledWidget.historyReceive + .stream() + .map { it.value } + .reduce(Decimal.ZERO) { a, b -> a + b } + .div(profiledWidget.historyReceive.size), + + profiledWidget.historyTransfer.stream() + .map { it.value } + .reduce(Decimal.ZERO) { a, b -> a + b } + .div(profiledWidget.historyReceive.size), + )) + + if (minecraft.window.isShiftDown && minecraft.options.advancedItemTooltips) { + it.add(TextComponent("---")) + val values = IntArrayList() + + values.addAll(profiledWidget.tick downTo 0) + values.addAll(ProfiledEnergyStorage.HISTORY_SIZE - 1 downTo profiledWidget.tick + 1) + + for (i in values.intIterator()) { + it.add(formatLevel(profiledWidget.historyReceive[i].value, profiledWidget.historyTransfer[i].value)) + } + } + } + } +} + +/** + * Shortcut to [ProfiledPowerGaugePanel] with doubled width + */ +@Suppress("FunctionName") +fun WideProfiledPowerGaugePanel( + screen: S, + parent: EditablePanel<*>? = null, + widget: ProfiledEnergyGaugeWidget, + x: Float = 0f, + y: Float = 0f, + width: Float = 18f, + height: Float = 48f +) = ProfiledPowerGaugePanel(screen, parent, widget, x, y, width, height) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt index 1115da4fd..b4c125658 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt @@ -14,6 +14,7 @@ import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProfiledEnergyGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget import ru.dbotthepony.mc.otm.registry.MMenus @@ -53,7 +54,7 @@ class ChemicalGeneratorMenu @JvmOverloads constructor(id: Int, inv: Inventory, t } val progress = ProgressGaugeWidget(this) - val energy = LevelGaugeWidget(this, tile?.energy) + val energy = ProfiledEnergyGaugeWidget(this, tile?.energy) var burnTime by mSynchronizer.int().property init { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledEnergyGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledEnergyGaugeWidget.kt new file mode 100644 index 000000000..9bcae5438 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledEnergyGaugeWidget.kt @@ -0,0 +1,44 @@ +package ru.dbotthepony.mc.otm.menu.widget + +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.DecimalValueCodec +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer + +class ProfiledEnergyGaugeWidget(synchronizer: FieldSynchronizer, val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)) { + var parent: ProfiledEnergyStorage<*>? = null + private set + + val historyReceive = immutableList(ProfiledEnergyStorage.HISTORY_SIZE) { + synchronizer.ComputedField({ parent?.historyReceive?.get(it) ?: Decimal.ZERO }, DecimalValueCodec) + } + + val historyTransfer = immutableList(ProfiledEnergyStorage.HISTORY_SIZE) { + synchronizer.ComputedField({ parent?.historyTransfer?.get(it) ?: Decimal.ZERO }, DecimalValueCodec) + } + + val tick by synchronizer.ComputedIntField({ parent?.tick ?: 0 }).property + val lastTickReceive by synchronizer.ComputedField({ parent?.lastTickReceive ?: Decimal.ZERO }, DecimalValueCodec) + val lastTickTransfer by synchronizer.ComputedField({ parent?.lastTickTransfer ?: Decimal.ZERO }, DecimalValueCodec) + + constructor(synchronizer: FieldSynchronizer, storage: ProfiledEnergyStorage<*>?, gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)) : this(synchronizer, gauge = gauge) { + if (storage != null) { + with(storage) + } + } + + constructor(menu: MatteryMenu, storage: ProfiledEnergyStorage<*>?, gauge: LevelGaugeWidget = LevelGaugeWidget(menu)) : this(menu.mSynchronizer, storage, gauge = gauge) + constructor(menu: MatteryMenu, gauge: LevelGaugeWidget = LevelGaugeWidget(menu)) : this(menu.mSynchronizer, gauge = gauge) + + constructor(menu: MatteryPoweredMenu, storage: ProfiledEnergyStorage<*>?) : this(menu.mSynchronizer, storage, menu.powerWidget) + constructor(menu: MatteryPoweredMenu) : this(menu.mSynchronizer, menu.powerWidget) + + fun with(storage: ProfiledEnergyStorage<*>): ProfiledEnergyGaugeWidget { + gauge.with(storage) + parent = storage + return this + } +}