Fluid tanks

general improvements to SI prefix formatter
networking regarding fluid stacks
Own fluid handler implementations
This commit is contained in:
DBotThePony 2023-04-06 15:07:11 +07:00
parent 82e410ad33
commit 90f345a2d9
Signed by: DBot
GPG Key ID: DCC23B5715498507
46 changed files with 1211 additions and 191 deletions

View File

@ -177,6 +177,7 @@ private fun misc(provider: MatteryLanguageProvider) {
misc("suffix.merge", "%s %s")
misc("suffix.none", "%s %s")
misc("suffix.kilo", "%s k%s")
misc("suffix.mega", "%s M%s")
misc("suffix.giga", "%s G%s")
@ -264,9 +265,11 @@ private fun misc(provider: MatteryLanguageProvider) {
gui("power.percentage_level", "Energy level: %s%%")
gui("level", "%s / %s")
gui("power.name", "MtJ")
gui("fluid.name", "mB")
gui("fluid.name", "B")
gui("fluid.level", "%s / %s of %s")
gui("empty", "Empty")
gui("power.burn_time", "Burn time left: %s ticks")
gui("progress_widget", "Progress: %s%%")
@ -370,6 +373,9 @@ private fun blocks(provider: MatteryLanguageProvider) {
add(MBlocks.MATTER_RECONSTRUCTOR, "Matter Reconstructor")
add(MBlocks.MATTER_RECONSTRUCTOR, "desc", "Repairs tools using matter")
add(MBlocks.FLUID_TANK, "Fluid Tank")
add(MBlocks.FLUID_TANK, "named", "Fluid Tank (%s)")
add(MBlocks.ENGINE, "Ship Engine")
add(MBlocks.HOLO_SIGN, "Holo Sign")

View File

@ -185,6 +185,7 @@ private fun misc(provider: MatteryLanguageProvider) {
misc("suffix.merge", "%s %s")
misc("suffix.none", "%s %s")
misc("suffix.kilo", "%s к%s")
misc("suffix.mega", "%s М%s")
misc("suffix.giga", "%s Г%s")
@ -271,9 +272,11 @@ private fun misc(provider: MatteryLanguageProvider) {
gui("power.percentage_level", "Уровень энергии: %s%%")
gui("level", "%s / %s")
gui("power.name", "МтДж")
gui("fluid.name", "мВ")
gui("fluid.name", "В")
gui("fluid.level", "%s / %s с %s")
gui("empty", "Пусто")
gui("power.burn_time", "Оставшееся время горения: %s тиков")
gui("progress_widget", "Прогресс: %s%%")
@ -377,6 +380,9 @@ private fun blocks(provider: MatteryLanguageProvider) {
add(MBlocks.MATTER_RECONSTRUCTOR, "Материальный реконструктор")
add(MBlocks.MATTER_RECONSTRUCTOR, "desc", "Чинит инструменты используя материю")
add(MBlocks.FLUID_TANK, "Жидкостный бак")
add(MBlocks.FLUID_TANK, "named", "Жидкостный бак (%s)")
add(MBlocks.ENGINE, "Двигатель корабля")
add(MBlocks.HOLO_SIGN, "Голографическая табличка")

View File

@ -129,6 +129,7 @@ fun addLootTables(lootTables: LootTables) {
lootTables.tile(MBlocks.COBBLESTONE_GENERATOR)
lootTables.tile(MBlocks.ESSENCE_STORAGE)
lootTables.tile(MBlocks.MATTER_RECONSTRUCTOR)
lootTables.tile(MBlocks.FLUID_TANK)
lootTables.tile(MBlocks.ENERGY_SERVO)
lootTables.tile(MBlocks.ENERGY_COUNTER)

View File

@ -308,4 +308,20 @@ fun addCraftingTableRecipes(consumer: Consumer<FinishedRecipe>) {
.row(MItems.ELECTRIC_PARTS, MItems.MATTER_REPLICATOR, MItems.ELECTRIC_PARTS)
.row(MItems.ELECTROMAGNET, MItems.ELECTROMAGNET, MItems.ELECTROMAGNET)
.build(consumer)
MatteryRecipe(MItems.FLUID_CAPSULE, category = RecipeCategory.TOOLS, count = 8)
.row(MItemTags.TRITANIUM_NUGGETS, MItemTags.TRITANIUM_NUGGETS, MItemTags.TRITANIUM_NUGGETS)
.rowB(MItemTags.HARDENED_GLASS_PANES)
.row(MItemTags.TRITANIUM_NUGGETS, MItemTags.TRITANIUM_NUGGETS, MItemTags.TRITANIUM_NUGGETS)
.unlockedBy(MItemTags.HARDENED_GLASS_PANES)
.unlockedBy(MItemTags.TRITANIUM_NUGGETS)
.build(consumer)
MatteryRecipe(MItems.FLUID_TANK, category = RecipeCategory.DECORATIONS)
.row(MItemTags.TRITANIUM_INGOTS, MItemTags.HARDENED_GLASS_PANES, MItemTags.TRITANIUM_INGOTS)
.rowAC(MItemTags.HARDENED_GLASS_PANES, MItemTags.HARDENED_GLASS_PANES)
.row(MItemTags.TRITANIUM_INGOTS, MItemTags.HARDENED_GLASS_PANES, MItemTags.TRITANIUM_INGOTS)
.unlockedBy(MItemTags.HARDENED_GLASS_PANES)
.unlockedBy(MItemTags.TRITANIUM_INGOTS)
.build(consumer)
}

View File

@ -171,6 +171,7 @@ fun addTags(tagsProvider: TagsProvider) {
MBlocks.COBBLESTONE_GENERATOR,
MBlocks.ESSENCE_STORAGE,
MBlocks.MATTER_RECONSTRUCTOR,
MBlocks.FLUID_TANK,
), Tiers.IRON)
tagsProvider.requiresPickaxe(MBlocks.TRITANIUM_ANVIL, Tiers.IRON)

View File

@ -0,0 +1,24 @@
package ru.dbotthepony.mc.otm.block.decorative
import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
class FluidTankBlock : RotatableMatteryBlock(), EntityBlock {
override fun newBlockEntity(pPos: BlockPos, pState: BlockState): BlockEntity {
return FluidTankBlockEntity(pPos, pState)
}
override fun <T : BlockEntity?> getTicker(pLevel: Level, pState: BlockState, pBlockEntityType: BlockEntityType<T>): BlockEntityTicker<T>? {
if (pLevel.isClientSide)
return null
return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is FluidTankBlockEntity) pBlockEntity.tick() }
}
}

View File

@ -6,6 +6,7 @@ import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.BlockGetter
@ -388,7 +389,7 @@ abstract class MatteryWorkerBlockEntity<JobType : MatteryWorkerBlockEntity.Job>(
fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList<Component>, flag: TooltipFlag) {
val tag = itemStack.tag ?: return
val subtag = tag.get("BlockEntityTag") as? CompoundTag
val subtag = tag.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag
if (subtag != null) {
if (subtag.contains(WORK_TICKS_KEY) && !subtag.contains(JOB_KEY)) {

View File

@ -0,0 +1,204 @@
package ru.dbotthepony.mc.otm.block.entity.decorative
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.capability.CombinedItemHandler
import ru.dbotthepony.mc.otm.capability.fluid.BlockMatteryFluidHandler
import ru.dbotthepony.mc.otm.capability.moveFluid
import ru.dbotthepony.mc.otm.config.ItemsConfig
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.orNull
import ru.dbotthepony.mc.otm.core.util.FluidStackValueCodec
import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.FLUID_TANK, blockPos, blockState) {
val fluid = BlockMatteryFluidHandler(::onChanged, ItemsConfig::FLUID_TANK_CAPACITY)
var synchronizedFluid by synchronizer.Field(FluidStack.EMPTY!!, FluidStackValueCodec)
private set
val fillInput = MatteryContainer(::setChangedLight, 1)
val drainInput = MatteryContainer(::setChangedLight, 1)
val output = MatteryContainer(::setChangedLight, 1)
val itemConfig = ConfigurableItemHandler(
input = CombinedItemHandler(
drainInput.handler(HandlerFilter.DrainableFluidContainers),
fillInput.handler(object : HandlerFilter {
override fun canInsert(slot: Int, stack: ItemStack): Boolean {
if (fluid.isEmpty)
return false
stack.getCapability(ForgeCapabilities.FLUID_HANDLER).ifPresentK {
if (it.fill(fluid[0], IFluidHandler.FluidAction.SIMULATE) > 0)
return true
}
stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
if (it.fill(fluid[0], IFluidHandler.FluidAction.SIMULATE) > 0)
return true
}
return false
}
override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
return !canInsert(slot, stack)
}
})
),
output = output.handler(HandlerFilter.OnlyOut)
)
init {
exposeGlobally(ForgeCapabilities.FLUID_HANDLER, fluid)
savetables.stateful(::fluid, FLUID_KEY)
savetables.stateful(::fillInput)
savetables.stateful(::drainInput)
savetables.stateful(::output)
}
private fun onChanged(new: FluidStack, old: FluidStack) {
synchronizedFluid = new.copy()
setChangedLight()
}
private fun drainItem() {
val item = drainInput[0]
if (item.isNotEmpty) {
val target = if (item.count == 1) item else item.copyWithCount(1)
val cap = target.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: target.getCapability(ForgeCapabilities.FLUID_HANDLER).orNull()
if (cap == null) {
if (output.consumeItem(item, simulate = false)) {
drainInput.setChanged(0)
}
return
}
if (fluid.isNotFull) {
if (item.count == 1) {
val moved0 = moveFluid(source = cap, destination = fluid)
if (moved0.isNotEmpty) {
drainInput.setChanged(0)
if (output.consumeItem(item, simulate = false)) {
drainInput.setChanged(0)
}
}
} else {
val moved0 = moveFluid(source = cap, destination = fluid, actuallyFill = false)
if (moved0.isNotEmpty) {
if (output.consumeItem(target, simulate = true)) {
val target1 = item.copyWithCount(1)
val cap1 = target1.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: target1.getCapability(ForgeCapabilities.FLUID_HANDLER).orNull() ?: throw ConcurrentModificationException()
val moved1 = moveFluid(source = cap1, destination = fluid)
if (moved1 != moved0 || moved1.amount != moved0.amount) {
LOGGER.error("Error moving fluids in Fluid tank at $blockPos: moved $moved0 during simulation from $target, moved $moved1 from $target1 during execution. This is likely a bug in OTM or other mod!")
} else {
item.count--
drainInput.setChanged(0)
if (!output.consumeItem(target1, simulate = false)) {
LOGGER.error("Unable to insert $target1 into output slot of Fluid tank at $blockPos, popping item in world instead to avoid item loss! This is likely a bug in OTM or other mod!")
(level as? ServerLevel)?.addFreshEntity(ItemEntity(level!!, blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble(), target1))
}
}
}
}
}
}
}
}
private fun fillItem() {
val item = fillInput[0]
if (item.isNotEmpty) {
val target = if (item.count == 1) item else item.copyWithCount(1)
val cap = target.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: target.getCapability(ForgeCapabilities.FLUID_HANDLER).orNull()
if (cap == null) {
if (output.consumeItem(item, simulate = false)) {
fillInput.setChanged(0)
}
return
}
if (fluid.isNotEmpty) {
if (item.count == 1) {
val moved0 = moveFluid(source = fluid, destination = cap)
if (moved0.isNotEmpty) {
fillInput.setChanged(0)
if (output.consumeItem(item, simulate = false)) {
fillInput.setChanged(0)
}
}
} else {
val moved0 = moveFluid(source = fluid, destination = cap, actuallyDrain = false)
if (moved0.isNotEmpty) {
if (output.consumeItem(target, simulate = true)) {
val target1 = item.copyWithCount(1)
val cap1 = target1.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: target1.getCapability(ForgeCapabilities.FLUID_HANDLER).orNull() ?: throw ConcurrentModificationException()
val moved1 = moveFluid(source = fluid, destination = cap1)
if (moved1 != moved0 || moved1.amount != moved0.amount) {
LOGGER.error("Error moving fluids in Fluid tank at $blockPos: moved $moved0 during simulation from $target, moved $moved1 from $target1 during execution. This is likely a bug in OTM or other mod!")
} else {
item.count--
fillInput.setChanged(0)
if (!output.consumeItem(target1, simulate = false)) {
LOGGER.error("Unable to insert $target1 into output slot of Fluid tank at $blockPos, popping item in world instead to avoid item loss! This is likely a bug in OTM or other mod!")
(level as? ServerLevel)?.addFreshEntity(ItemEntity(level!!, blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble(), target1))
}
}
}
}
}
}
}
}
override fun tick() {
super.tick()
drainItem()
fillItem()
}
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
return FluidTankMenu(containerID, inventory, this)
}
companion object {
const val FLUID_KEY = "fluid"
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -27,9 +27,11 @@ import ru.dbotthepony.mc.otm.compat.curios.isCuriosLoaded
import ru.dbotthepony.mc.otm.compat.mekanism.getMekanismEnergySided
import ru.dbotthepony.mc.otm.compat.mekanism.mekanismEnergy
import ru.dbotthepony.mc.otm.container.awareStream
import ru.dbotthepony.mc.otm.container.iterator
import ru.dbotthepony.mc.otm.container.stream
import ru.dbotthepony.mc.otm.core.collect.AwareItemStack
import ru.dbotthepony.mc.otm.core.collect.ContainerItemStackEntry
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.orNull
import ru.dbotthepony.mc.otm.core.util.formatFluidLevel
@ -44,7 +46,7 @@ val ICapabilityProvider.matteryPlayer: MatteryPlayerCapability? get() = getCapab
/**
* Does a checked energy receive, calls [IMatteryEnergyStorage.receiveEnergyChecked] if possible
*/
internal fun IEnergyStorage.receiveEnergy(amount: Decimal, simulate: Boolean): Decimal {
fun IEnergyStorage.receiveEnergy(amount: Decimal, simulate: Boolean): Decimal {
if (this is IMatteryEnergyStorage)
return receiveEnergyChecked(amount, simulate)
@ -60,7 +62,7 @@ internal fun IEnergyStorage.receiveEnergy(amount: Decimal, simulate: Boolean): D
return Decimal.valueOf(receiveEnergy(amount.toInt(), simulate))
}
internal fun IEnergyStorage.transcieveEnergy(amount: Decimal, isReceiving: Boolean, simulate: Boolean): Decimal {
fun IEnergyStorage.transcieveEnergy(amount: Decimal, isReceiving: Boolean, simulate: Boolean): Decimal {
if (isReceiving)
return receiveEnergy(amount, simulate)
else
@ -70,7 +72,7 @@ internal fun IEnergyStorage.transcieveEnergy(amount: Decimal, isReceiving: Boole
/**
* Does a checked energy extraction, calls [IMatteryEnergyStorage.extractEnergyChecked] if possible
*/
internal fun IEnergyStorage.extractEnergy(amount: Decimal, simulate: Boolean): Decimal {
fun IEnergyStorage.extractEnergy(amount: Decimal, simulate: Boolean): Decimal {
if (this is IMatteryEnergyStorage)
return extractEnergyChecked(amount, simulate)
@ -313,7 +315,7 @@ fun Player.awareAllItemsStream(includeCosmetics: Boolean = false): Stream<out Aw
*
* @return pair of new (advanced) [sourceSlot] and [destinationSlot]
*/
internal fun moveBetweenSlots(source: IItemHandler, sourceSlot: Int, destination: IItemHandler, destinationSlot: Int): Pair<Int, Int> {
fun moveBetweenSlots(source: IItemHandler, sourceSlot: Int, destination: IItemHandler, destinationSlot: Int): Pair<Int, Int> {
val getItem = source.extractItem(sourceSlot, Int.MAX_VALUE, true)
if (getItem.isEmpty) {
@ -349,7 +351,7 @@ internal fun moveBetweenSlots(source: IItemHandler, sourceSlot: Int, destination
}
@Suppress("name_shadowing")
internal fun moveEnergy(source: IEnergyStorage, destination: IEnergyStorage, amount: Decimal = Decimal.LONG_MAX_VALUE, simulate: Boolean, ignoreFlowRestrictions: Boolean = false): Decimal {
fun moveEnergy(source: IEnergyStorage, destination: IEnergyStorage, amount: Decimal = Decimal.LONG_MAX_VALUE, simulate: Boolean, ignoreFlowRestrictions: Boolean = false): Decimal {
val extracted = if (ignoreFlowRestrictions && source is IMatteryEnergyStorage) source.extractEnergy(amount, true) else source.extractEnergy(amount, true)
if (extracted.isPositive) {
@ -372,25 +374,17 @@ internal fun moveEnergy(source: IEnergyStorage, destination: IEnergyStorage, amo
return Decimal.ZERO
}
internal fun fluidLevel(it: IFluidHandler, tooltips: MutableList<Component>) {
val fluid = it.getFluidInTank(0)
internal fun IFluidHandler.fluidLevel(tooltips: MutableList<Component>) {
val fluid = getFluidInTank(0)
if (fluid.isEmpty) {
tooltips.add(formatFluidLevel(0, it.getTankCapacity(0), formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY))
tooltips.add(formatFluidLevel(0, getTankCapacity(0), formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY))
} else {
tooltips.add(formatFluidLevel(fluid.amount, it.getTankCapacity(0), fluid.displayName, formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY))
tooltips.add(formatFluidLevel(fluid.amount, getTankCapacity(0), fluid.displayName, formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY))
}
}
internal fun moveFluid(source: IFluidHandler, sourceTank: Int? = null, destination: IFluidHandler, limit: Int = Int.MAX_VALUE, simulate: Boolean = false, actuallyDrain: Boolean = true): FluidStack {
val drained: FluidStack
if (sourceTank == null) {
drained = source.drain(limit, IFluidHandler.FluidAction.SIMULATE)
} else {
drained = source.drain(source.getFluidInTank(sourceTank), IFluidHandler.FluidAction.SIMULATE)
}
private fun actuallyMoveFluid(drained: FluidStack, source: IFluidHandler, destination: IFluidHandler, limit: Int, actuallyDrain: Boolean, actuallyFill: Boolean): FluidStack {
if (drained.isEmpty) return FluidStack.EMPTY
val filled = destination.fill(drained, IFluidHandler.FluidAction.SIMULATE)
@ -402,7 +396,7 @@ internal fun moveFluid(source: IFluidHandler, sourceTank: Int? = null, destinati
val filled2 = destination.fill(drained2, IFluidHandler.FluidAction.SIMULATE)
if (filled2 != drained2.amount) return FluidStack.EMPTY
if (simulate) return FluidStack(drained2, filled2)
if (!actuallyDrain && !actuallyFill) return FluidStack(drained2, filled2)
val drained3: FluidStack
@ -416,11 +410,49 @@ internal fun moveFluid(source: IFluidHandler, sourceTank: Int? = null, destinati
drained3 = drained2
}
val filled3 = destination.fill(drained3, IFluidHandler.FluidAction.EXECUTE)
val filled3: Int
if (actuallyFill) {
filled3 = destination.fill(drained3, IFluidHandler.FluidAction.EXECUTE)
if (filled3 != drained3.amount) {
LOGGER.warn("Inconsistency of fluid insertion to $destination between simulate and execute modes (simulated $filled2; inserted $filled3); This can lead to duping!!!")
}
} else {
filled3 = filled2
}
return FluidStack(drained3, filled3)
}
fun moveFluid(source: IFluidHandler, sourceTank: Int? = null, destination: IFluidHandler, limit: Int = Int.MAX_VALUE, actuallyDrain: Boolean = true, actuallyFill: Boolean = true): FluidStack {
if (sourceTank == null) {
for (drained in destination.iterator()) {
if (drained.isNotEmpty) {
val moved = actuallyMoveFluid(drained, source, destination, limit, actuallyDrain, actuallyFill)
if (moved.isNotEmpty) return moved
}
}
for (drained in source.iterator()) {
if (drained.isNotEmpty) {
val moved = actuallyMoveFluid(drained, source, destination, limit, actuallyDrain, actuallyFill)
if (moved.isNotEmpty) return moved
}
}
return FluidStack.EMPTY
} else {
return actuallyMoveFluid(source.drain(source.getFluidInTank(sourceTank), IFluidHandler.FluidAction.SIMULATE), source, destination, limit, actuallyDrain, actuallyFill)
}
}
val IFluidHandler.isEmpty: Boolean get() = stream().allMatch { it.isEmpty }
val IFluidHandler.isNotEmpty: Boolean get() = stream().anyMatch { it.isNotEmpty }
val IFluidHandler.isNotFull: Boolean get() {
for ((i, fluid) in iterator().withIndex())
if (fluid.isEmpty || fluid.amount < getTankCapacity(i))
return true
return false
}

View File

@ -1,10 +1,12 @@
package ru.dbotthepony.mc.otm.capability
import com.google.common.collect.ImmutableSet
import net.minecraft.network.chat.Component
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import java.util.function.Predicate
/**
* Represents possible flow direction, both for matter and for energy
* Represents possible flow direction, be it matter, energy, fluids, etc
*
* To dynamically get this enum by two booleans (by input and output states), use [FlowDirection.of]
*
@ -38,7 +40,7 @@ enum class FlowDirection(val input: Boolean, val output: Boolean, val translatio
BI_DIRECTIONAL(true, true, "otm.gui.side_mode.input_output"),
/**
* Why would you want to use this
* No flow possible
*/
NONE(false, false, "otm.gui.side_mode.disabled");
@ -62,6 +64,9 @@ enum class FlowDirection(val input: Boolean, val output: Boolean, val translatio
}.build()
}
val translation: Component
get() = TranslatableComponent(translationKey)
/**
* Subtype test (returns true if we can assign [t] to this, e.g. we can assign [BI_DIRECTIONAL] to [INPUT])
*/

View File

@ -0,0 +1,102 @@
package ru.dbotthepony.mc.otm.capability.fluid
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.core.isNotEmpty
abstract class AbstractMatteryFluidHandler : IFluidHandler {
abstract var fluid: FluidStack
abstract val capacity: Int
val isEmpty: Boolean
get() = fluid.isEmpty
val isNotEmpty: Boolean
get() = fluid.isNotEmpty
val isNotFull: Boolean
get() = fluid.isEmpty || fluid.amount < capacity
open val direction: FlowDirection
get() = FlowDirection.BI_DIRECTIONAL
final override fun getTanks() = 1
final override fun getFluidInTank(tank: Int): FluidStack {
require(tank == 0) { "Invalid tank: $tank" }
return fluid.copy()
}
final override fun getTankCapacity(tank: Int): Int {
require(tank == 0) { "Invalid tank: $tank" }
return capacity
}
protected open fun isFluidValid(stack: FluidStack): Boolean {
return true
}
final override fun isFluidValid(tank: Int, stack: FluidStack): Boolean {
require(tank == 0) { "Invalid tank: $tank" }
return isFluidValid(stack)
}
final override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int {
if (resource.isEmpty || !isFluidValid(resource) || !direction.input) {
return 0
}
val fluid = fluid
if (fluid.isEmpty || fluid.isFluidEqual(resource)) {
val new = (fluid.amount + resource.amount).coerceAtMost(capacity)
if (new <= fluid.amount) return 0
if (action.execute()) {
this.fluid = FluidStack(resource, new)
}
return new - fluid.amount
} else {
return 0
}
}
final override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack {
if (resource.isEmpty || !direction.output) {
return FluidStack.EMPTY
}
val fluid = fluid
if (!fluid.isEmpty && fluid.isFluidEqual(resource)) {
return drain(resource.amount, action)
} else {
return FluidStack.EMPTY
}
}
final override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack {
require(maxDrain >= 0) { "Invalid amount to drain: $maxDrain" }
if (maxDrain == 0 || !direction.output) return FluidStack.EMPTY
val fluid = fluid
if (fluid.isEmpty) {
return FluidStack.EMPTY
} else {
val new = (fluid.amount - maxDrain).coerceAtLeast(0)
if (action.execute()) {
if (new == 0) {
this.fluid = FluidStack.EMPTY
} else {
this.fluid = FluidStack(fluid, new)
}
}
return FluidStack(fluid, fluid.amount - new)
}
}
}

View File

@ -0,0 +1,54 @@
package ru.dbotthepony.mc.otm.capability.fluid
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.fluids.FluidStack
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.tagNotNull
import java.util.function.IntSupplier
/**
* Fluid handler for blocks
*/
class BlockMatteryFluidHandler(val onChanged: (new: FluidStack, old: FluidStack) -> Unit, private val _capacity: IntSupplier) : AbstractMatteryFluidHandler(), INBTSerializable<CompoundTag?> {
override var fluid: FluidStack = FluidStack.EMPTY
set(value) {
val old = field
field = value
onChanged(value, old)
}
override val capacity: Int
get() = _capacity.asInt
override fun serializeNBT(): CompoundTag {
return fluid.writeToNBT(CompoundTag())
}
override fun deserializeNBT(nbt: CompoundTag?) {
fluid = FluidStack.loadFluidStackFromNBT(nbt)
}
/**
* Fluid handler for items representing block with [BlockMatteryFluidHandler]
*/
open class Item(itemStack: ItemStack, capacity: IntSupplier, private val nbtName: String) : ItemMatteryFluidHandler(itemStack, capacity) {
override var fluid: FluidStack
get() {
val sub = itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag ?: return FluidStack.EMPTY
return FluidStack.loadFluidStackFromNBT(sub[nbtName] as? CompoundTag ?: return FluidStack.EMPTY)
}
set(value) {
var sub = itemStack.tagNotNull.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag
if (sub == null) {
sub = CompoundTag()
itemStack.tagNotNull[BlockItem.BLOCK_ENTITY_TAG] = sub
}
sub[nbtName] = value.writeToNBT(CompoundTag())
}
}
}

View File

@ -0,0 +1,41 @@
package ru.dbotthepony.mc.otm.capability.fluid
import net.minecraft.core.Direction
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.capabilities.ICapabilityProvider
import net.minecraftforge.common.util.LazyOptional
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandlerItem
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.tagNotNull
import java.util.function.IntSupplier
/**
* Fluid handler for standalone items
*/
open class ItemMatteryFluidHandler(val itemStack: ItemStack, private val _capacity: IntSupplier) : AbstractMatteryFluidHandler(), IFluidHandlerItem, ICapabilityProvider {
private val resolver = LazyOptional.of { this }
override var fluid: FluidStack
get() { return FluidStack.loadFluidStackFromNBT(itemStack.tag?.get("fluid") as? CompoundTag ?: return FluidStack.EMPTY) }
set(value) { itemStack.tagNotNull["fluid"] = value.writeToNBT(CompoundTag()) }
final override val capacity: Int
get() = _capacity.asInt
final override fun getContainer(): ItemStack {
return itemStack
}
override fun <T : Any?> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
if (cap === ForgeCapabilities.FLUID_HANDLER_ITEM || cap === ForgeCapabilities.FLUID_HANDLER) {
return resolver.cast()
}
return LazyOptional.empty()
}
}

View File

@ -64,5 +64,11 @@ data class MatterySprite @JvmOverloads constructor(
override val type: SpriteType
get() = SpriteType.SINGLE
companion object {
fun single(texture: ResourceLocation, width: Float, height: Float, winding: UVWindingOrder = UVWindingOrder.NORMAL): MatterySprite {
return MatterySprite(texture, 0f, 0f, width, height, width, height, winding)
}
}
}

View File

@ -0,0 +1,44 @@
package ru.dbotthepony.mc.otm.client.screen.decorative
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
import ru.dbotthepony.mc.otm.client.screen.panels.PlayerEquipmentPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.makeCuriosPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.SpritePanel
import ru.dbotthepony.mc.otm.client.screen.widget.FluidGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu
class FluidTankScreen(menu: FluidTankMenu, inventory: Inventory, title: Component) : MatteryScreen<FluidTankMenu>(menu, inventory, title) {
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
val frame = super.makeMainFrame()!!
FluidGaugePanel(this, frame, menu.fluid, x = LEFT_MARGIN, y = GAUGE_TOP_WITHOUT_SLOT - 6f)
val s = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND, x = 30f, y = 30f)
SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND, x = 30f, y = 55f, winding = UVWindingOrder.FLOP)
SlotPanel(this, frame, menu.fillInput, x = 30f + s.width + 4f, y = 28f)
SlotPanel(this, frame, menu.drainInput, x = 30f + s.width + 4f, y = 53f)
SlotPanel(this, frame, menu.output, x = 30f + s.width + 4f + 20f, y = 53f)
makeDeviceControls(this, frame, itemConfig = menu.itemConfig, redstoneConfig = menu.redstoneConfig)
makeCuriosPanel(this, frame, menu.equipment.curiosSlots, autoAlign = true)
PlayerEquipmentPanel(this, frame, armorSlots = menu.equipment.armorSlots).also {
it.leftSided = false
it.dock = Dock.RIGHT
it.dockResize = DockResizeMode.NONE
}
return frame
}
}

View File

@ -0,0 +1,22 @@
package ru.dbotthepony.mc.otm.client.screen.panels.util
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.gui.screens.Screen
import ru.dbotthepony.mc.otm.client.render.AbstractMatterySprite
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
class SpritePanel<out S : Screen>(
screen: S,
parent: EditablePanel<*>? = null,
val sprite: AbstractMatterySprite,
x: Float = 0f,
y: Float = 0f,
width: Float = sprite.width,
height: Float = sprite.height,
val winding: UVWindingOrder = sprite.winding
) : EditablePanel<S>(screen, parent, x, y, width, height) {
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
sprite.render(stack, 0f, 0f, width, height, winding)
}
}

View File

@ -0,0 +1,106 @@
package ru.dbotthepony.mc.otm.client.screen.widget
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.inventory.InventoryMenu
import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions
import org.lwjgl.opengl.GL11
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.client.ShiftPressedCond
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.MatterySprite
import ru.dbotthepony.mc.otm.client.render.tesselator
import ru.dbotthepony.mc.otm.client.render.vertex
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import ru.dbotthepony.mc.otm.core.math.linearInterpolation
import ru.dbotthepony.mc.otm.core.util.formatFluidLevel
import ru.dbotthepony.mc.otm.menu.widget.FluidGaugeWidget
open class FluidGaugePanel<out S : Screen>(
screen: S,
parent: EditablePanel<*>? = null,
val widget: FluidGaugeWidget,
x: Float = 0f,
y: Float = 0f
) : EditablePanel<S>(screen, parent, x, y, width = GAUGE.width, height = GAUGE.height) {
init {
scissor = true
}
protected open fun makeTooltip(): MutableList<Component> {
return mutableListOf(
if (widget.fluid.isEmpty) TranslatableComponent("otm.gui.empty") else TextComponent(String.format("%s: %.2f%%", widget.fluid.displayName.string, widget.percentage * 100.0)),
formatFluidLevel(if (widget.fluid.isEmpty) 0 else widget.fluid.amount, widget.maxCapacity, formatAsReadable = ShiftPressedCond)
)
}
override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean {
if (isHovered) {
screen.renderComponentTooltip(stack, makeTooltip(), mouseX.toInt(), mouseY.toInt())
return true
}
return false
}
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
if (widget.percentage > 0.01f && widget.fluid.isNotEmpty) {
val data = IClientFluidTypeExtensions.of(widget.fluid.fluid)
val texture = data.stillTexture!!
val sprite = minecraft.getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(texture)!!
val tint = RGBAColor.argb(data.getTintColor(widget.fluid))
var height = (height * widget.percentage) / 16f
var bottom = this.height
val matrix = stack.last().pose()
val builder = tesselator.builder
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)
while (height > 0.01f) {
val thisHeight = height.coerceAtMost(1f)
height -= thisHeight
val actualHeight = thisHeight * 16f
val interp = linearInterpolation(thisHeight, sprite.v1, sprite.v0)
builder.vertex(matrix, 0f, bottom, 0f).uv(sprite.u0, sprite.v1).endVertex()
builder.vertex(matrix, width, bottom, 0f).uv(sprite.u1, sprite.v1).endVertex()
builder.vertex(matrix, width, bottom - actualHeight, 0f).uv(sprite.u1, interp).endVertex()
builder.vertex(matrix, 0f, bottom - actualHeight, 0f).uv(sprite.u0, interp).endVertex()
bottom -= actualHeight
}
RenderSystem.setShader(GameRenderer::getPositionTexShader)
RenderSystem.enableTexture()
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
RenderSystem.depthFunc(GL11.GL_ALWAYS)
RenderSystem.setShaderColor(tint.red, tint.green, tint.blue, tint.alpha)
RenderSystem.setShaderTexture(0, sprite.atlasLocation())
tesselator.end()
RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
RenderSystem.depthFunc(GL11.GL_LESS)
}
GAUGE.render(stack, 0f, 0f, width = width, height = height)
}
companion object {
val GAUGE = MatterySprite.single(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/fluid_level.png"), 18f, 61f)
}
}

View File

@ -22,7 +22,7 @@ import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
open class MatterGaugePanel<S : Screen> @JvmOverloads constructor(
open class MatterGaugePanel<out S : Screen> @JvmOverloads constructor(
screen: S,
parent: EditablePanel<*>? = null,
val widget: LevelGaugeWidget,

View File

@ -8,7 +8,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget
open class PatternGaugePanel<S : Screen> @JvmOverloads constructor(
open class PatternGaugePanel<out S : Screen> @JvmOverloads constructor(
screen: S,
parent: EditablePanel<*>? = null,
val widget: LevelGaugeWidget,

View File

@ -12,7 +12,7 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget
import kotlin.math.roundToInt
open class ProgressGaugePanel<S : Screen> @JvmOverloads constructor(
open class ProgressGaugePanel<out S : Screen> @JvmOverloads constructor(
screen: S,
parent: EditablePanel<*>? = null,
val widget: ProgressGaugeWidget,

View File

@ -96,5 +96,6 @@ object ItemsConfig : AbstractConfig("items") {
builder.pop()
}
val FLUID_CAPSULE_CAPACITY: Int by builder.defineInRange("LIQUID_CAPSULE_CAPACITY", 1000, 1, Int.MAX_VALUE)
val FLUID_CAPSULE_CAPACITY: Int by builder.defineInRange("FLUID_CAPSULE_CAPACITY", 1000, 1, Int.MAX_VALUE)
val FLUID_TANK_CAPACITY: Int by builder.defineInRange("FLUID_TANK_CAPACITY", 32_000, 1, Int.MAX_VALUE)
}

View File

@ -6,7 +6,7 @@ import net.minecraftforge.fluids.capability.IFluidHandler
class FluidHandlerIterator(private val handler: IFluidHandler, initialPosition: Int = 0) : AbstractIndexBasedIterator<FluidStack>(0, initialPosition) {
init {
require(initialPosition in 0 .. handler.tanks) { "Invalid initial position: $initialPosition" }
require(initialPosition in 0 until handler.tanks) { "Invalid initial position: $initialPosition" }
}
override fun remove(location: Int) {

View File

@ -11,7 +11,8 @@ import java.util.stream.StreamSupport
class FluidHandlerSpliterator(private val handler: IFluidHandler, offset: Int = 0, private val maxPos: Int = handler.tanks) : AbstractIndexBasedSpliterator<FluidStack>(offset) {
init {
require(offset in 0 until handler.tanks) { "Invalid offset $offset" }
require(maxPos >= offset && maxPos in 0 until handler.tanks) { "Invalid spliterator configuration: maxPos $maxPos offset $offset max tanks ${handler.tanks}" }
require(offset <= maxPos) { "offset <= maxPos: $offset > $maxPos!" }
require(maxPos >= offset && maxPos in 0 .. handler.tanks) { "Invalid spliterator configuration: maxPos $maxPos offset $offset max tanks ${handler.tanks}" }
}
override fun get(location: Int): FluidStack {

View File

@ -3,6 +3,8 @@ package ru.dbotthepony.mc.otm.container
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.ForgeHooks
import net.minecraftforge.common.capabilities.ForgeCapabilities
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.isNotEmpty
interface HandlerFilter {
fun canInsert(slot: Int, stack: ItemStack): Boolean {
@ -38,6 +40,38 @@ interface HandlerFilter {
}
}
object FluidContainers : HandlerFilter {
override fun canInsert(slot: Int, stack: ItemStack): Boolean {
stack.getCapability(ForgeCapabilities.FLUID_HANDLER).ifPresentK {
return it.tanks > 0
}
stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
return it.tanks > 0
}
return false
}
}
object DrainableFluidContainers : HandlerFilter {
override fun canInsert(slot: Int, stack: ItemStack): Boolean {
stack.getCapability(ForgeCapabilities.FLUID_HANDLER).ifPresentK {
return it.stream().anyMatch { it.isNotEmpty }
}
stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
return it.stream().anyMatch { it.isNotEmpty }
}
return false
}
override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
return !canInsert(slot, stack)
}
}
object OnlyIn : HandlerFilter {
override fun canInsert(slot: Int, stack: ItemStack): Boolean {
return true

View File

@ -250,6 +250,9 @@ open class MatteryContainer(protected val watcher: Runnable, private val size: I
return getMaxStackSize(slot)
}
/**
* @return Leftover [ItemStack]
*/
@JvmOverloads
fun addItem(stack: ItemStack, range: IntRange, simulate: Boolean = false, onlyIntoExisting: Boolean = false, popTime: Int? = null): ItemStack {
if (range.last >= size || range.first < 0)
@ -322,6 +325,25 @@ open class MatteryContainer(protected val watcher: Runnable, private val size: I
return addItem(stack, 0 until size, simulate, onlyIntoExisting = onlyIntoExisting, popTime = popTime)
}
/**
* Unlike [addItem], modifies original [stack]
*
* @return Whenever [stack] was modified
*/
fun consumeItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean = false, popTime: Int? = null): Boolean {
val result = addItem(stack, 0 until size, simulate, onlyIntoExisting = onlyIntoExisting, popTime = popTime)
if (result.count != stack.count) {
if (!simulate) {
stack.count = result.count
}
return true
}
return false
}
@JvmOverloads
fun fullyAddItem(stack: ItemStack, start: Int = 0, end: Int = size - 1): Boolean {
return fullyAddItem(stack, start .. end)

View File

@ -28,6 +28,7 @@ import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.phys.Vec3
import net.minecraftforge.common.ForgeHooks
import net.minecraftforge.common.util.LazyOptional
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.items.IItemHandler
import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.ForgeRegistry
@ -87,6 +88,9 @@ inline fun <T> LazyOptional<T>.ifPresentK(lambda: (T) -> Unit) {
val ItemStack.tagNotNull: CompoundTag get() = orCreateTag
val FluidStack.isNotEmpty get() = !isEmpty
val ItemStack.isNotEmpty get() = !isEmpty
inline var Entity.position: Vec3
get() = position()
set(value) { setPos(value) }

View File

@ -123,6 +123,14 @@ data class RGBAColor(val red: Float, val green: Float, val blue: Float, val alph
val b = (color and 0xFF) / 255f
return RGBAColor(r, g, b)
}
fun argb(color: Int): RGBAColor {
val a = (color and -0x1000000 ushr 24) / 255f
val r = (color and 0xFF0000 ushr 16) / 255f
val g = (color and 0xFF00 ushr 8) / 255f
val b = (color and 0xFF) / 255f
return RGBAColor(r, g, b, a)
}
}
}

View File

@ -7,6 +7,7 @@ import com.mojang.serialization.DataResult
import com.mojang.serialization.DynamicOps
import net.minecraft.nbt.NbtAccounter
import net.minecraft.world.item.ItemStack
import net.minecraftforge.fluids.FluidStack
import ru.dbotthepony.mc.otm.core.immutableMap
import ru.dbotthepony.mc.otm.core.math.readDecimal
import ru.dbotthepony.mc.otm.core.math.writeDecimal
@ -112,6 +113,7 @@ val LongValueCodec = StreamCodec(DataInputStream::readLong, 8L, DataOutputStream
val FloatValueCodec = StreamCodec(DataInputStream::readFloat, 4L, DataOutputStream::writeFloat) { a, b -> a == b }
val DoubleValueCodec = StreamCodec(DataInputStream::readDouble, 8L, DataOutputStream::writeDouble) { a, b -> a == b }
val ItemStackValueCodec = StreamCodec(DataInputStream::readItem, DataOutputStream::writeItem, ItemStack::copy) { a, b -> a.equals(b, true) }
val FluidStackValueCodec = StreamCodec(DataInputStream::readFluidStack, DataOutputStream::writeFluidStack, FluidStack::copy) { a, b -> a == b && a.amount == b.amount }
val ItemValueCodec = StreamCodec(DataInputStream::readItemType, DataOutputStream::writeItemType) { a, b -> a === b }
val DecimalValueCodec = StreamCodec(DataInputStream::readDecimal, DataOutputStream::writeDecimal)
val BigDecimalValueCodec = StreamCodec(DataInputStream::readBigDecimal, DataOutputStream::writeBigDecimal)

View File

@ -7,7 +7,6 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.isNegative
import ru.dbotthepony.mc.otm.core.math.isZero
import java.math.BigDecimal
import java.math.BigInteger
import java.util.function.BooleanSupplier
import kotlin.math.absoluteValue
@ -61,28 +60,12 @@ fun BigInteger.formatReadableNumber(): String {
return String(buffer)
}
fun BigDecimal.determineSiPrefix(): SiPrefix? {
if (isZero) return null
val num = this.abs()
if (num >= BigDecimal.ONE) {
return SiPrefix.MULTIPLIES.lastOrNull { it.decimal <= num }
} else {
return SiPrefix.DECIMALS.lastOrNull { it.decimal >= num }
}
}
fun BigInteger.determineSiPrefix(): SiPrefix? {
if (isZero) return null
val num = this.abs()
return SiPrefix.MULTIPLIES.lastOrNull { it.integer!! <= num }
}
fun BigInteger.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never): Component {
require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
val prefix = determineSiPrefix() ?: return concat(toString(decimalPlaces), suffix)
val prefix = SiPrefix.determine(this)
if (prefix.isEmpty) return concat(toString(decimalPlaces), suffix)
val isNegative = isNegative
val arr = (if (isNegative) -this else this).divideAndRemainder(prefix.integer)
val arr = (if (isNegative) -this else this).divideAndRemainder(prefix.bigInteger)
val divided = arr[0].toString()
val remainder = arr[1].toString()
@ -116,41 +99,6 @@ fun BigInteger.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2, forma
return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix)
}
fun Decimal.determineSiPrefix(): SiPrefix? {
if (isZero) {
return null
}
val num = this.absoluteValue
if (num >= Decimal.ONE) {
return SiPrefix.MULTIPLIES.lastOrNull { it.impreciseFraction <= num }
} else {
return SiPrefix.DECIMALS.lastOrNull { it.impreciseFraction >= num }
}
}
fun Int.determineSiPrefix(): SiPrefix? {
if (this == 0) {
return null
}
val num = this.absoluteValue
if (num <= 1) return null
return SiPrefix.MULTIPLIES.lastOrNull { it.int != null && it.int <= num }
}
fun Double.determineSiPrefix(): SiPrefix? {
if (this == 0.0) return null
val num = this.absoluteValue
if (num >= 1.0) {
return SiPrefix.MULTIPLIES.lastOrNull { it.double <= num }
} else {
return SiPrefix.DECIMALS.lastOrNull { it.double >= num }
}
}
private val never = BooleanSupplier { false }
private fun reformat(numbers: String): String {
@ -187,29 +135,46 @@ private fun reformat(numbers: String): String {
})
}
fun Int.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never): Component {
fun Long.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never, bias: Int = 0): Component {
require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
if (formatAsReadable.asBoolean) return concat(reformat(toString()), suffix)
val prefix = determineSiPrefix() ?: return concat(toString(), suffix)
return TranslatableComponent(prefix.formatLocaleKey, "%.${decimalPlaces}f".format(this.toFloat() / prefix.int!!.toFloat()), suffix)
if (formatAsReadable.asBoolean) {
if (bias == 0) {
return concat(reformat(toString()), suffix)
} else {
val prefix = SiPrefix.NONE.neighbour(bias)
return TranslatableComponent(prefix.formatLocaleKey, reformat(toString()), suffix)
}
}
val prefix = SiPrefix.determine(this)
if (bias == 0) {
return TranslatableComponent(prefix.formatLocaleKey, "%.${decimalPlaces}f".format(this.toDouble() / prefix.long!!.toDouble()), suffix)
} else {
return TranslatableComponent(SiPrefix.determine(this, bias).formatLocaleKey, "%.${decimalPlaces}f".format(this.toDouble() / prefix.long!!.toDouble()), suffix)
}
}
fun Int.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never, bias: Int = 0): Component {
return toLong().formatSiComponent(suffix, decimalPlaces, formatAsReadable, bias)
}
fun Double.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never): Component {
require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
if (formatAsReadable.asBoolean) return concat(reformat("%.${decimalPlaces}f".format(this)), suffix)
val prefix = determineSiPrefix() ?: return concat("%.${decimalPlaces}f".format(this), suffix)
val prefix = SiPrefix.determine(this)
return TranslatableComponent(prefix.formatLocaleKey, "%.${decimalPlaces}f".format(this / prefix.double), suffix)
}
fun Decimal.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never): Component {
require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
if (formatAsReadable.asBoolean) return concat(reformat(toString(decimalPlaces)), suffix)
val prefix = determineSiPrefix() ?: return concat(toString(decimalPlaces), suffix)
return TranslatableComponent(prefix.formatLocaleKey, (this / prefix.impreciseFraction).toString(decimalPlaces), suffix)
val prefix = SiPrefix.determine(this)
return TranslatableComponent(prefix.formatLocaleKey, (this / prefix.decimal).toString(decimalPlaces), suffix)
}
fun Int.formatPower(decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.power.name"), decimalPlaces, formatAsReadable = formatAsReadable)
fun Int.formatFluid(decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.fluid.name"), decimalPlaces, formatAsReadable = formatAsReadable)
fun Int.formatFluid(decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.fluid.name"), decimalPlaces, formatAsReadable = formatAsReadable, bias = -1)
fun Decimal.formatPower(decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.power.name"), decimalPlaces, formatAsReadable = formatAsReadable)
fun Decimal.formatMatter(decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable)
fun Decimal.formatMatterFull(decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.matter.format", formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable))

View File

@ -17,6 +17,8 @@ import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.material.Fluid
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.ForgeRegistry
import java.io.*
@ -87,6 +89,41 @@ fun InputStream.readItem(sizeLimit: NbtAccounter = NbtAccounter(1L shl 18 /* 256
return itemStack
}
fun OutputStream.writeFluidStack(value: FluidStack) {
if (value.isEmpty) {
write(0)
} else {
write(1)
val id = (ForgeRegistries.FLUIDS as ForgeRegistry<Fluid>).getID(value.fluid)
writeVarIntLE(id)
writeInt(value.amount)
if (value.hasTag()) {
write(1)
writeNbt(value.tag!!)
} else {
write(0)
}
}
}
fun InputStream.readFluidStack(sizeLimit: NbtAccounter = NbtAccounter(1L shl 14 /* 16 KiB */)): FluidStack {
if (read() == 0) {
return FluidStack.EMPTY
} else {
val id = readVarIntLE()
val fluid = (ForgeRegistries.FLUIDS as ForgeRegistry<Fluid>).getValue(id) ?: return FluidStack.EMPTY
val amount = readInt()
if (read() > 0) {
return FluidStack(fluid, amount, readNbt(sizeLimit))
} else {
return FluidStack(fluid, amount)
}
}
}
fun OutputStream.writeBigDecimal(value: BigDecimal) {
writeInt(value.scale())
val bytes = value.unscaledValue().toByteArray()

View File

@ -2,43 +2,53 @@ package ru.dbotthepony.mc.otm.core.util
import com.google.common.collect.ImmutableList
import ru.dbotthepony.mc.otm.core.math.Decimal
import java.math.BigDecimal
import ru.dbotthepony.mc.otm.core.math.isZero
import java.math.BigInteger
import kotlin.math.absoluteValue
enum class SiPrefix(
val power: Int,
fractional: Boolean,
val symbol: Char,
) {
// multiplies
KILO (3, false, 'k'),
MEGA (6, false, 'M'),
GIGA (9, false, 'G'),
TERA (12, false, 'T'),
PETA (15, false, 'P'),
EXA (18, false, 'E'),
ZETTA(21, false, 'Z'),
YOTTA(24, false, 'Y'),
enum class SiPrefix(val power: Int, val symbol: String) {
YOCTO(-8, "y"),
ZEPTO(-7, "z"),
ATTO (-6, "a"),
FEMTO(-5, "f"),
PICO (-4, "p"),
NANO (-3, "n"),
MICRO(-2, "μ"),
MILLI(-1, "m"),
// decimals
// DECI (1, true, 'd'),
// CENTI(2, true, 'c'),
MILLI(3, true, 'm'),
MICRO(6, true, 'μ'),
NANO (9, true, 'n'),
PICO (12, true, 'p'),
FEMTO(15, true, 'f'),
ATTO (18, true, 'a'),
ZEPTO(21, true, 'z'),
YOCTO(24, true, 'y');
NONE(0, "") {
override val isEmpty: Boolean
get() = true
},
KILO (1, "k"),
MEGA (2, "M"),
GIGA (3, "G"),
TERA (4, "T"),
PETA (5, "P"),
EXA (6, "E"),
ZETTA(7, "Z"),
YOTTA(8, "Y");
open val isEmpty: Boolean get() = false
val formatLocaleKey = "otm.suffix.${name.lowercase()}".intern()
val rawLocaleKey = "otm.suffix_raw.${name.lowercase()}".intern()
val string = if (fractional) "0." + "0".repeat(power - 1) + "1" else "1" + "0".repeat(power)
val string: String
init {
if (power == 0) {
string = "1"
} else if (power < 0) {
string = "0." + "0".repeat(power.absoluteValue * 3 - 1) + "1"
} else {
string = "1" + "0".repeat(power.absoluteValue * 3)
}
}
fun paddedIndex(input: String, index: Int): Char {
val finalIndex = input.length - power + index
val finalIndex = input.length - power.absoluteValue * 3 + index
if (finalIndex >= 0) {
return input[finalIndex]
@ -47,17 +57,35 @@ enum class SiPrefix(
return '0'
}
val decimal = BigDecimal(string)
val impreciseFraction = Decimal(string)
val integer = if (!fractional) BigInteger(string) else null
val decimal = Decimal(string)
val bigInteger = if (power >= 0) BigInteger(string) else null
val long = if (!fractional) string.toLongOrNull() else null
val int = if (!fractional) string.toIntOrNull() else null
val long = if (power >= 0) string.toLongOrNull() else null
val double = string.toDouble()
fun neighbour(bias: Int): SiPrefix {
if (bias == 0) {
return this
} else {
val new = ordinal + bias
if (new < 0) {
return YOCTO
} else if (new >= VALUES.size) {
return YOTTA
} else {
return VALUES[new]
}
}
}
companion object {
@JvmField
val MULTIPLIES: List<SiPrefix> = ImmutableList.builder<SiPrefix>()
val VALUES: ImmutableList<SiPrefix> = ImmutableList.copyOf(values())
@JvmField
val MULTIPLIES: ImmutableList<SiPrefix> = ImmutableList.builder<SiPrefix>()
.add(NONE)
.add(KILO)
.add(MEGA)
.add(GIGA)
@ -69,9 +97,8 @@ enum class SiPrefix(
.build()
@JvmField
val DECIMALS: List<SiPrefix> = ImmutableList.builder<SiPrefix>()
//.add(DECI)
//.add(CENTI)
val DECIMALS: ImmutableList<SiPrefix> = ImmutableList.builder<SiPrefix>()
.add(NONE)
.add(MILLI)
.add(MICRO)
.add(NANO)
@ -82,13 +109,46 @@ enum class SiPrefix(
.add(YOCTO)
.build()
@JvmField
val DECIMALS_IMPRECISE: List<SiPrefix> = ImmutableList.builder<SiPrefix>()
//.add(DECI)
//.add(CENTI)
.add(MILLI)
.add(MICRO)
.build()
fun determine(value: Int, bias: Int = 0): SiPrefix {
return determine(value.toLong(), bias)
}
fun determine(value: Long, bias: Int = 0): SiPrefix {
val num = value.absoluteValue
if (num <= 1L) return NONE
return MULTIPLIES.last { it.long != null && it.long <= num }.neighbour(bias)
}
fun determine(value: Decimal, bias: Int = 0): SiPrefix {
if (value.isZero) {
return NONE
}
val num = value.absoluteValue
if (num >= Decimal.ONE) {
return (MULTIPLIES.lastOrNull { it.decimal <= num } ?: NONE).neighbour(bias)
} else {
return (DECIMALS.lastOrNull { it.decimal >= num } ?: NONE).neighbour(bias)
}
}
fun determine(value: Double, bias: Int = 0): SiPrefix {
if (value == 0.0) return NONE
val num = value.absoluteValue
if (num >= 1.0) {
return (MULTIPLIES.lastOrNull { it.double <= num } ?: NONE).neighbour(bias)
} else {
return (DECIMALS.lastOrNull { it.double >= num } ?: NONE).neighbour(bias)
}
}
fun determine(value: BigInteger, bias: Int = 0): SiPrefix {
if (value.isZero) return NONE
val num = value.abs()
return (MULTIPLIES.lastOrNull { it.bigInteger!! <= num } ?: NONE).neighbour(bias)
}
}
}

View File

@ -7,6 +7,7 @@ import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.storage.loot.LootContext
import net.minecraft.world.level.storage.loot.Serializer
@ -31,7 +32,7 @@ class CopyTileNbtFunction(filter: Stream<out String> = Stream.empty()) : LootIte
override fun apply(t: ItemStack, u: LootContext): ItemStack {
val blockEntity = u.getParamOrNull(LootContextParams.BLOCK_ENTITY) ?: return t
val result = t.tagNotNull["BlockEntityTag"] as? CompoundTag
val result = t.tagNotNull[BlockItem.BLOCK_ENTITY_TAG] as? CompoundTag
val data = blockEntity.saveWithoutMetadata()
@ -40,7 +41,7 @@ class CopyTileNbtFunction(filter: Stream<out String> = Stream.empty()) : LootIte
}
if (result == null) {
t.tagNotNull["BlockEntityTag"] = data
t.tagNotNull[BlockItem.BLOCK_ENTITY_TAG] = data
} else {
for (k in data.allKeys) {
result[k] = data[k]!!

View File

@ -26,7 +26,7 @@ import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.capabilities.ICapabilityProvider
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import net.minecraftforge.fluids.capability.templates.FluidHandlerItemStack
import ru.dbotthepony.mc.otm.capability.fluid.ItemMatteryFluidHandler
import ru.dbotthepony.mc.otm.capability.fluidLevel
import ru.dbotthepony.mc.otm.capability.moveFluid
import ru.dbotthepony.mc.otm.container.get
@ -36,30 +36,9 @@ import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.immutableMap
import ru.dbotthepony.mc.otm.core.orNull
import java.util.function.IntSupplier
class FluidCapsuleItem(val capacity: () -> Int) : Item(Properties().stacksTo(64)) {
private inner class Cap(itemStack: ItemStack) : FluidHandlerItemStack(itemStack, capacity.invoke()) {
override fun fill(resource: FluidStack, doFill: IFluidHandler.FluidAction): Int {
this.capacity = this@FluidCapsuleItem.capacity.invoke()
return super.fill(resource, doFill)
}
override fun getTankCapacity(tank: Int): Int {
this.capacity = this@FluidCapsuleItem.capacity.invoke()
return super.getTankCapacity(tank)
}
override fun drain(resource: FluidStack?, action: IFluidHandler.FluidAction?): FluidStack {
this.capacity = this@FluidCapsuleItem.capacity.invoke()
return super.drain(resource, action)
}
override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction?): FluidStack {
this.capacity = this@FluidCapsuleItem.capacity.invoke()
return super.drain(maxDrain, action)
}
}
class FluidCapsuleItem(val capacity: IntSupplier) : Item(Properties().stacksTo(64)) {
// TODO: Так как использование предмета заблокировано за player.abilities.canBuild
// капсулу нельзя использовать в режиме приключения
// почему же можно использовать вёдра на котлах?
@ -89,12 +68,12 @@ class FluidCapsuleItem(val capacity: () -> Int) : Item(Properties().stacksTo(64)
super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced)
pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
fluidLevel(it, pTooltipComponents)
it.fluidLevel(pTooltipComponents)
}
}
override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
return Cap(stack)
return ItemMatteryFluidHandler(stack, capacity)
}
interface Interaction {
@ -206,7 +185,7 @@ class FluidCapsuleItem(val capacity: () -> Int) : Item(Properties().stacksTo(64)
if (fluid.isEmpty || player.isCrouching) {
// заполняем из блока
val moveResult = moveFluid(source = blockCap, destination = itemCap, simulate = player.level.isClientSide)
val moveResult = moveFluid(source = blockCap, destination = itemCap, actuallyDrain = !player.level.isClientSide, actuallyFill = !player.level.isClientSide)
if (!moveResult.isEmpty) {
val sound = moveResult.fluid.fluidType.getSound(moveResult, SoundActions.BUCKET_FILL)
@ -228,7 +207,7 @@ class FluidCapsuleItem(val capacity: () -> Int) : Item(Properties().stacksTo(64)
return InteractionResult.FAIL
}
} else {
val moveResult = moveFluid(source = itemCap, destination = blockCap, simulate = player.level.isClientSide)
val moveResult = moveFluid(source = itemCap, destination = blockCap, actuallyDrain = !player.level.isClientSide, actuallyFill = !player.level.isClientSide)
if (!moveResult.isEmpty) {
val sound = moveResult.fluid.fluidType.getSound(moveResult, SoundActions.BUCKET_EMPTY)

View File

@ -0,0 +1,57 @@
package ru.dbotthepony.mc.otm.item
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.world.InteractionResult
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.Level
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.capabilities.ICapabilityProvider
import ru.dbotthepony.mc.otm.block.decorative.FluidTankBlock
import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
import ru.dbotthepony.mc.otm.capability.fluid.BlockMatteryFluidHandler
import ru.dbotthepony.mc.otm.capability.fluidLevel
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.ifPresentK
import java.util.function.IntSupplier
class FluidTankItem(block: FluidTankBlock, properties: Properties, val capacity: IntSupplier) : BlockItem(block, properties) {
override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
return BlockMatteryFluidHandler.Item(stack, capacity, FluidTankBlockEntity.FLUID_KEY)
}
override fun onItemUseFirst(stack: ItemStack, pContext: UseOnContext): InteractionResult {
if (pContext.player?.isCrouching == true)
return InteractionResult.PASS
val context = FluidCapsuleItem.Context(pContext.clickedPos, pContext.level.getBlockState(pContext.clickedPos), pContext.clickedFace)
if (FluidCapsuleItem.canInteract(pContext.itemInHand, pContext.player ?: return InteractionResult.FAIL, context))
return FluidCapsuleItem.interact(pContext.itemInHand, pContext.player!!, context)
return super.onItemUseFirst(stack, pContext)
}
override fun getName(pStack: ItemStack): Component {
pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
it.getFluidInTank(0).also {
if (!it.isEmpty) {
return TranslatableComponent("$descriptionId.named", it.displayName)
}
}
}
return super.getName(pStack)
}
override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltip: MutableList<Component>, pFlag: TooltipFlag) {
super.appendHoverText(pStack, pLevel, pTooltip, pFlag)
pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
it.fluidLevel(pTooltip)
}
}
}

View File

@ -0,0 +1,59 @@
package ru.dbotthepony.mc.otm.menu.decorative
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting
import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
import ru.dbotthepony.mc.otm.capability.isNotEmpty
import ru.dbotthepony.mc.otm.capability.isNotFull
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.widget.FluidGaugeWidget
import ru.dbotthepony.mc.otm.registry.MMenus
class FluidTankMenu(containerId: Int, inventory: Inventory, tile: FluidTankBlockEntity? = null) : MatteryMenu(MMenus.FLUID_TANK, containerId, inventory, tile) {
val fluid = FluidGaugeWidget(mSynchronizer, tile?.fluid)
val equipment = makeEquipmentSlots(true)
val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig)
val redstoneConfig = EnumInputWithFeedback<RedstoneSetting>(this)
val drainInput = object : MatterySlot(tile?.drainInput ?: SimpleContainer(1), 0) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return super.mayPlace(itemStack) && itemStack.getCapability(ForgeCapabilities.FLUID_HANDLER)
.map { it.isNotEmpty }
.orElse(itemStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM)
.map { it.isNotEmpty }
.orElse(false))
}
}
val fillInput = object : MatterySlot(tile?.fillInput ?: SimpleContainer(1), 0) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return super.mayPlace(itemStack) && itemStack.getCapability(ForgeCapabilities.FLUID_HANDLER)
.map { it.fill(fluid.fluid, IFluidHandler.FluidAction.SIMULATE) > 0 }
.orElse(itemStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM)
.map { it.fill(fluid.fluid, IFluidHandler.FluidAction.SIMULATE) > 0 }
.orElse(false))
}
}
val output = MachineOutputSlot(tile?.output ?: SimpleContainer(1), 0)
init {
// сначала слот на заполнение из бака
addStorageSlot(fillInput)
addStorageSlot(drainInput)
addStorageSlot(output)
addInventorySlots()
if (tile != null) {
redstoneConfig.with(tile.redstoneControl::redstoneSetting)
}
}
}

View File

@ -0,0 +1,46 @@
package ru.dbotthepony.mc.otm.menu.widget
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.util.FluidStackValueCodec
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer
import java.util.function.IntSupplier
import java.util.function.Supplier
class FluidGaugeWidget(synchronizer: FieldSynchronizer) {
constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
var maxCapacitySupplier = IntSupplier { 0 }
var fluidSupplier = Supplier { FluidStack.EMPTY!! }
val maxCapacity by synchronizer.ComputedIntField({ maxCapacitySupplier.asInt })
val fluid by synchronizer.ComputedField({ fluidSupplier.get() }, FluidStackValueCodec)
val percentage: Float get() {
if (maxCapacity <= 0 || fluid.isEmpty) {
return 0f
}
return (fluid.amount.toFloat() / maxCapacity.toFloat()).coerceIn(0f, 1f)
}
constructor(synchronizer: FieldSynchronizer, fluid: IFluidHandler?, tank: Int = 0) : this(synchronizer) {
if (fluid != null) {
with(fluid, tank)
}
}
constructor(menu: MatteryMenu, fluid: IFluidHandler?, tank: Int = 0) : this(menu) {
if (fluid != null) {
with(fluid, tank)
}
}
fun with(fluid: IFluidHandler, tank: Int = 0): FluidGaugeWidget {
maxCapacitySupplier = IntSupplier { fluid.getTankCapacity(tank) }
fluidSupplier = Supplier { fluid[tank] }
return this
}
}

View File

@ -6,19 +6,22 @@ import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage
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.network.synchronizer.FieldSynchronizer
@Suppress("unused")
class LevelGaugeWidget(menu: MatteryMenu) {
class LevelGaugeWidget(synchronizer: FieldSynchronizer) {
constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
var levelProvider = { Decimal.ONE }
var maxLevelProvider = { Decimal.ONE }
val level by menu.mSynchronizer.ComputedField(getter = { levelProvider.invoke() }, codec = DecimalValueCodec)
val maxLevel by menu.mSynchronizer.ComputedField(getter = { maxLevelProvider.invoke() }, codec = DecimalValueCodec)
val level by synchronizer.ComputedField(getter = { levelProvider.invoke() }, codec = DecimalValueCodec)
val maxLevel by synchronizer.ComputedField(getter = { maxLevelProvider.invoke() }, codec = DecimalValueCodec)
constructor(
menu: MatteryMenu,
power: IMatteryEnergyStorage?
) : this(menu) {
) : this(menu.mSynchronizer) {
if (power != null) {
with(power)
}
@ -27,7 +30,7 @@ class LevelGaugeWidget(menu: MatteryMenu) {
constructor(
menu: MatteryMenu,
matter: IMatterStorage?
) : this(menu) {
) : this(menu.mSynchronizer) {
if (matter != null) {
with(matter)
}
@ -36,7 +39,7 @@ class LevelGaugeWidget(menu: MatteryMenu) {
constructor(
menu: MatteryMenu,
patterns: IPatternStorage?
) : this(menu) {
) : this(menu.mSynchronizer) {
if (patterns != null) {
with(patterns)
}
@ -46,7 +49,43 @@ class LevelGaugeWidget(menu: MatteryMenu) {
menu: MatteryMenu,
level: () -> Decimal,
maxLevel: () -> Decimal,
) : this(menu) {
) : this(menu.mSynchronizer) {
this.levelProvider = level
this.maxLevelProvider = maxLevel
}
constructor(
synchronizer: FieldSynchronizer,
power: IMatteryEnergyStorage?
) : this(synchronizer) {
if (power != null) {
with(power)
}
}
constructor(
synchronizer: FieldSynchronizer,
matter: IMatterStorage?
) : this(synchronizer) {
if (matter != null) {
with(matter)
}
}
constructor(
synchronizer: FieldSynchronizer,
patterns: IPatternStorage?
) : this(synchronizer) {
if (patterns != null) {
with(patterns)
}
}
constructor(
synchronizer: FieldSynchronizer,
level: () -> Decimal,
maxLevel: () -> Decimal,
) : this(synchronizer) {
this.levelProvider = level
this.maxLevelProvider = maxLevel
}

View File

@ -3,15 +3,18 @@ package ru.dbotthepony.mc.otm.menu.widget
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
import ru.dbotthepony.mc.otm.core.FloatSupplier
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer
import java.util.function.BooleanSupplier
@Suppress("unused")
class ProgressGaugeWidget(menu: MatteryMenu) {
class ProgressGaugeWidget(synchronizer: FieldSynchronizer) {
constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
var progressSupplier: FloatSupplier = FloatSupplier { 0f }
var stuckSupplier: BooleanSupplier = BooleanSupplier { false }
val percentage by menu.mSynchronizer.ComputedFloatField(getter = { progressSupplier.getAsFloat() })
val isStuck by menu.mSynchronizer.ComputedBooleanField(getter = { stuckSupplier.asBoolean })
val percentage by synchronizer.ComputedFloatField(getter = { progressSupplier.getAsFloat() })
val isStuck by synchronizer.ComputedBooleanField(getter = { stuckSupplier.asBoolean })
constructor(
menu: MatteryMenu,
@ -29,6 +32,22 @@ class ProgressGaugeWidget(menu: MatteryMenu) {
}
}
constructor(
synchronizer: FieldSynchronizer,
progress: FloatSupplier
) : this(synchronizer) {
this.progressSupplier = progress
}
constructor(
synchronizer: FieldSynchronizer,
blockEntity: MatteryWorkerBlockEntity<*>?
) : this(synchronizer) {
if (blockEntity != null) {
with(blockEntity)
}
}
constructor(
menu: MatteryMenu,
progress: FloatSupplier,

View File

@ -104,6 +104,20 @@ private fun CreativeModeTab.Output.mattery(values: Iterable<Item>) {
}
}
private fun CreativeModeTab.Output.fluids(value: Item) {
accept(value)
for (fluid in ForgeRegistries.FLUIDS.values) {
if (fluid != Fluids.EMPTY && fluid.isSource(fluid.defaultFluidState())) {
accept(ItemStack(value, 1).also {
it.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
it.fill(FluidStack(fluid, it.getTankCapacity(0)), IFluidHandler.FluidAction.EXECUTE)
}
})
}
}
}
internal fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) {
with(consumer) {
accept(MItems.MACHINES)
@ -147,17 +161,8 @@ internal fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) {
accept(MItems.PATTERN_DRIVE_CREATIVE)
accept(MItems.PATTERN_DRIVE_CREATIVE2)
accept(MItems.FLUID_CAPSULE)
for (fluid in ForgeRegistries.FLUIDS.values) {
if (fluid != Fluids.EMPTY && fluid.isSource(fluid.defaultFluidState())) {
accept(ItemStack(MItems.FLUID_CAPSULE, 1).also {
it.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
it.fill(FluidStack(fluid, it.getTankCapacity(0)), IFluidHandler.FluidAction.EXECUTE)
}
})
}
}
fluids(MItems.FLUID_CAPSULE)
fluids(MItems.FLUID_TANK)
base(MItems.CARGO_CRATE_MINECARTS)

View File

@ -13,6 +13,7 @@ import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntityExplosionDebugger
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntitySphereDebugger
import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity
import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
import ru.dbotthepony.mc.otm.block.entity.decorative.HoloSignBlockEntity
import ru.dbotthepony.mc.otm.block.entity.matter.*
import ru.dbotthepony.mc.otm.block.entity.storage.*
@ -58,6 +59,7 @@ object MBlockEntities {
val COBBLESTONE_GENERATOR: BlockEntityType<CobblerBlockEntity> by registry.register(MNames.COBBLESTONE_GENERATOR) { BlockEntityType.Builder.of(::CobblerBlockEntity, MBlocks.COBBLESTONE_GENERATOR).build(null) }
val ESSENCE_STORAGE: BlockEntityType<EssenceStorageBlockEntity> by registry.register(MNames.ESSENCE_STORAGE) { BlockEntityType.Builder.of(::EssenceStorageBlockEntity, MBlocks.ESSENCE_STORAGE).build(null) }
val MATTER_RECONSTRUCTOR: BlockEntityType<MatterReconstructorBlockEntity> by registry.register(MNames.MATTER_RECONSTRUCTOR) { BlockEntityType.Builder.of(::MatterReconstructorBlockEntity, MBlocks.MATTER_RECONSTRUCTOR).build(null) }
val FLUID_TANK: BlockEntityType<FluidTankBlockEntity> by registry.register(MNames.FLUID_TANK) { BlockEntityType.Builder.of(::FluidTankBlockEntity, MBlocks.FLUID_TANK).build(null) }
val STORAGE_BUS: BlockEntityType<StorageBusBlockEntity> by registry.register(MNames.STORAGE_BUS) { BlockEntityType.Builder.of(::StorageBusBlockEntity, MBlocks.STORAGE_BUS).build(null) }
val STORAGE_IMPORTER: BlockEntityType<StorageImporterBlockEntity> by registry.register(MNames.STORAGE_IMPORTER) { BlockEntityType.Builder.of(::StorageImporterBlockEntity, MBlocks.STORAGE_IMPORTER).build(null) }

View File

@ -46,6 +46,7 @@ import ru.dbotthepony.mc.otm.block.tech.PhantomAttractorBlock
import ru.dbotthepony.mc.otm.block.tech.PlatePressBlock
import ru.dbotthepony.mc.otm.block.StorageCableBlock
import ru.dbotthepony.mc.otm.block.decorative.EngineBlock
import ru.dbotthepony.mc.otm.block.decorative.FluidTankBlock
import ru.dbotthepony.mc.otm.block.decorative.HoloSignBlock
import ru.dbotthepony.mc.otm.block.matter.MatterReconstructorBlock
import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock
@ -112,6 +113,7 @@ object MBlocks {
val GRAVITATION_STABILIZER_LENS: Block by registry.register(MNames.GRAVITATION_STABILIZER_LENS) { BlockGravitationStabilizerLens() }
val PHANTOM_ATTRACTOR: Block by registry.register(MNames.PHANTOM_ATTRACTOR) { PhantomAttractorBlock() }
val FLUID_TANK: FluidTankBlock by registry.register(MNames.FLUID_TANK) { FluidTankBlock() }
val TRITANIUM_ORE: Block by registry.register(MNames.TRITANIUM_ORE) { DropExperienceBlock(
BlockBehaviour.Properties.of(Material.STONE)

View File

@ -156,6 +156,7 @@ object MItems {
val ESSENCE_DRIVE: EssenceCapsuleItem by registry.register("essence_drive") { EssenceCapsuleItem() }
val FLUID_CAPSULE: FluidCapsuleItem by registry.register("fluid_capsule") { FluidCapsuleItem(ItemsConfig::FLUID_CAPSULE_CAPACITY) }
val FLUID_TANK: FluidTankItem by registry.register(MNames.FLUID_TANK) { FluidTankItem(MBlocks.FLUID_TANK, Item.Properties().stacksTo(1), ItemsConfig::FLUID_TANK_CAPACITY) }
val TRITANIUM_COMPONENT: ForgeTier = ForgeTier(
Tiers.IRON.level,

View File

@ -8,6 +8,7 @@ import net.minecraftforge.registries.DeferredRegister
import net.minecraftforge.registries.ForgeRegistries
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.client.screen.decorative.CargoCrateScreen
import ru.dbotthepony.mc.otm.client.screen.decorative.FluidTankScreen
import ru.dbotthepony.mc.otm.client.screen.decorative.HoloSignScreen
import ru.dbotthepony.mc.otm.client.screen.decorative.MinecartCargoCrateScreen
import ru.dbotthepony.mc.otm.client.screen.matter.MatterReconstructorScreen
@ -35,6 +36,7 @@ import ru.dbotthepony.mc.otm.client.screen.tech.EnergyServoScreen
import ru.dbotthepony.mc.otm.client.screen.tech.EssenceStorageScreen
import ru.dbotthepony.mc.otm.client.screen.tech.PlatePressScreen
import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu
import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu
import ru.dbotthepony.mc.otm.menu.decorative.HoloSignMenu
import ru.dbotthepony.mc.otm.menu.decorative.MinecartCargoCrateMenu
import ru.dbotthepony.mc.otm.menu.matter.MatterReconstructorMenu
@ -88,6 +90,7 @@ object MMenus {
val COBBLESTONE_GENERATOR: MenuType<CobblerMenu> by registry.register(MNames.COBBLESTONE_GENERATOR) { MenuType(::CobblerMenu) }
val ESSENCE_STORAGE: MenuType<EssenceStorageMenu> by registry.register(MNames.ESSENCE_STORAGE) { MenuType(::EssenceStorageMenu) }
val ITEM_REPAIER: MenuType<MatterReconstructorMenu> by registry.register(MNames.MATTER_RECONSTRUCTOR) { MenuType(::MatterReconstructorMenu) }
val FLUID_TANK: MenuType<FluidTankMenu> by registry.register(MNames.FLUID_TANK) { MenuType(::FluidTankMenu) }
val STORAGE_BUS: MenuType<*> by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu) }
val STORAGE_EXPORTER: MenuType<*> by registry.register(MNames.STORAGE_EXPORTER) { MenuType(::StorageExporterMenu) }
@ -129,6 +132,7 @@ object MMenus {
MenuScreens.register(COBBLESTONE_GENERATOR, ::CobblerScreen)
MenuScreens.register(ESSENCE_STORAGE, ::EssenceStorageScreen)
MenuScreens.register(ITEM_REPAIER, ::MatterReconstructorScreen)
MenuScreens.register(FLUID_TANK, ::FluidTankScreen)
}
}
}

View File

@ -11,6 +11,7 @@ object MNames {
const val METAL_BEAM = "metal_beam"
const val ENGINE = "engine"
const val HOLO_SIGN = "holo_sign"
const val FLUID_TANK = "fluid_tank"
// blocks
const val ANDROID_STATION = "android_station"

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B