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 a7225c4cc..771ba8869 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 @@ -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") 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 72cc33147..77ccaab54 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 @@ -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, "Голографическая табличка") diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTablesData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTablesData.kt index 4a5546942..e6217e755 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTablesData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTablesData.kt @@ -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) diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt index ef76205cf..795954f11 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt @@ -308,4 +308,20 @@ fun addCraftingTableRecipes(consumer: Consumer) { .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) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt index dce283293..6dcc91e5f 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt new file mode 100644 index 000000000..8586e54ab --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt @@ -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 getTicker(pLevel: Level, pState: BlockState, pBlockEntityType: BlockEntityType): BlockEntityTicker? { + if (pLevel.isClientSide) + return null + + return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is FluidTankBlockEntity) pBlockEntity.tick() } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt index bb7e717c7..0bf39c687 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt @@ -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( fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, 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)) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt new file mode 100644 index 000000000..1012264a9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt @@ -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() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt index 61a906c2f..1f2950ed1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt @@ -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 { +fun moveBetweenSlots(source: IItemHandler, sourceSlot: Int, destination: IItemHandler, destinationSlot: Int): Pair { 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) { - val fluid = it.getFluidInTank(0) +internal fun IFluidHandler.fluidLevel(tooltips: MutableList) { + 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 (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!!!") + 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 +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt index b26b06f06..4e72ceb3a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt @@ -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]) */ diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt new file mode 100644 index 000000000..5cb8ac463 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt @@ -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) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt new file mode 100644 index 000000000..6d836f859 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt @@ -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 { + 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()) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt new file mode 100644 index 000000000..6cddb0c21 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt @@ -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 getCapability(cap: Capability, side: Direction?): LazyOptional { + if (cap === ForgeCapabilities.FLUID_HANDLER_ITEM || cap === ForgeCapabilities.FLUID_HANDLER) { + return resolver.cast() + } + + return LazyOptional.empty() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MatterySprite.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MatterySprite.kt index 741654d4a..867b6be9e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MatterySprite.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MatterySprite.kt @@ -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) + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt new file mode 100644 index 000000000..4f9f9f00e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt @@ -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(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + 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 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/SpritePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/SpritePanel.kt new file mode 100644 index 000000000..c84b5072c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/SpritePanel.kt @@ -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( + 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(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) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt new file mode 100644 index 000000000..c6147db1e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt @@ -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( + screen: S, + parent: EditablePanel<*>? = null, + val widget: FluidGaugeWidget, + x: Float = 0f, + y: Float = 0f +) : EditablePanel(screen, parent, x, y, width = GAUGE.width, height = GAUGE.height) { + init { + scissor = true + } + + protected open fun makeTooltip(): MutableList { + 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) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt index 64072038c..b6477f102 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt @@ -22,7 +22,7 @@ import kotlin.math.cos import kotlin.math.pow import kotlin.math.sin -open class MatterGaugePanel @JvmOverloads constructor( +open class MatterGaugePanel @JvmOverloads constructor( screen: S, parent: EditablePanel<*>? = null, val widget: LevelGaugeWidget, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PatternGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PatternGaugePanel.kt index 1c7f8d339..1fde122eb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PatternGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PatternGaugePanel.kt @@ -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 @JvmOverloads constructor( +open class PatternGaugePanel @JvmOverloads constructor( screen: S, parent: EditablePanel<*>? = null, val widget: LevelGaugeWidget, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/ProgressGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/ProgressGaugePanel.kt index 31ffc38ed..1838bef86 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/ProgressGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/ProgressGaugePanel.kt @@ -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 @JvmOverloads constructor( +open class ProgressGaugePanel @JvmOverloads constructor( screen: S, parent: EditablePanel<*>? = null, val widget: ProgressGaugeWidget, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt index 884a5150b..02864211c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt @@ -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) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/FluidHandlerIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/FluidHandlerIterator.kt index f837c7265..65cfcc2d1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/FluidHandlerIterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/FluidHandlerIterator.kt @@ -6,7 +6,7 @@ import net.minecraftforge.fluids.capability.IFluidHandler class FluidHandlerIterator(private val handler: IFluidHandler, initialPosition: Int = 0) : AbstractIndexBasedIterator(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) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/FluidHandlerSpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/FluidHandlerSpliterator.kt index 9e8b3b672..89d2c15fa 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/FluidHandlerSpliterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/FluidHandlerSpliterator.kt @@ -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(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 { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt index 3dd2450ba..4d62d09c8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt @@ -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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt index 7f445dd50..6b44eee1f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index 181419a23..dfd5c2a12 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -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 LazyOptional.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) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/RGBAColor.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/RGBAColor.kt index 26a611b6b..3b61517be 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/RGBAColor.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/RGBAColor.kt @@ -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) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/DataStreams.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/DataStreams.kt index 4aeb78a21..8dcb64a3b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/DataStreams.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/DataStreams.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt index 60b8f775d..62e26f071 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt @@ -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)) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt index 05f07044d..ef31ac554 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt @@ -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).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).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() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt index 7c227a279..c044c136f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt @@ -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 = ImmutableList.builder() + val VALUES: ImmutableList = ImmutableList.copyOf(values()) + + @JvmField + val MULTIPLIES: ImmutableList = ImmutableList.builder() + .add(NONE) .add(KILO) .add(MEGA) .add(GIGA) @@ -69,9 +97,8 @@ enum class SiPrefix( .build() @JvmField - val DECIMALS: List = ImmutableList.builder() - //.add(DECI) - //.add(CENTI) + val DECIMALS: ImmutableList = ImmutableList.builder() + .add(NONE) .add(MILLI) .add(MICRO) .add(NANO) @@ -82,13 +109,46 @@ enum class SiPrefix( .add(YOCTO) .build() - @JvmField - val DECIMALS_IMPRECISE: List = ImmutableList.builder() - //.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) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/CopyTileNbtFunction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/CopyTileNbtFunction.kt index 8b84bc86e..d607e1ec4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/CopyTileNbtFunction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/CopyTileNbtFunction.kt @@ -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 = 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 = 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]!! diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt index 52fc46eab..755654e6e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt new file mode 100644 index 000000000..582c14b2a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt @@ -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, pFlag: TooltipFlag) { + super.appendHoverText(pStack, pLevel, pTooltip, pFlag) + + pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + it.fluidLevel(pTooltip) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt new file mode 100644 index 000000000..db985d43c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt @@ -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(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) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt new file mode 100644 index 000000000..ca7d3b7cd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt @@ -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 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt index 5b06f9c59..eddd1a7ba 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt @@ -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 } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt index 7ceb4aa32..d182f7971 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt @@ -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, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CreativeTabs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CreativeTabs.kt index d9ada7784..ea350bfa5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CreativeTabs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/CreativeTabs.kt @@ -104,6 +104,20 @@ private fun CreativeModeTab.Output.mattery(values: Iterable) { } } +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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt index d0f4c3e08..397b88194 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt @@ -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 by registry.register(MNames.COBBLESTONE_GENERATOR) { BlockEntityType.Builder.of(::CobblerBlockEntity, MBlocks.COBBLESTONE_GENERATOR).build(null) } val ESSENCE_STORAGE: BlockEntityType by registry.register(MNames.ESSENCE_STORAGE) { BlockEntityType.Builder.of(::EssenceStorageBlockEntity, MBlocks.ESSENCE_STORAGE).build(null) } val MATTER_RECONSTRUCTOR: BlockEntityType by registry.register(MNames.MATTER_RECONSTRUCTOR) { BlockEntityType.Builder.of(::MatterReconstructorBlockEntity, MBlocks.MATTER_RECONSTRUCTOR).build(null) } + val FLUID_TANK: BlockEntityType by registry.register(MNames.FLUID_TANK) { BlockEntityType.Builder.of(::FluidTankBlockEntity, MBlocks.FLUID_TANK).build(null) } val STORAGE_BUS: BlockEntityType by registry.register(MNames.STORAGE_BUS) { BlockEntityType.Builder.of(::StorageBusBlockEntity, MBlocks.STORAGE_BUS).build(null) } val STORAGE_IMPORTER: BlockEntityType by registry.register(MNames.STORAGE_IMPORTER) { BlockEntityType.Builder.of(::StorageImporterBlockEntity, MBlocks.STORAGE_IMPORTER).build(null) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt index 90de2a4e3..55ecb67b2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt index 19ab301f6..472f4078e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt @@ -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, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt index 3d5791c14..a5ab542cd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt @@ -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 by registry.register(MNames.COBBLESTONE_GENERATOR) { MenuType(::CobblerMenu) } val ESSENCE_STORAGE: MenuType by registry.register(MNames.ESSENCE_STORAGE) { MenuType(::EssenceStorageMenu) } val ITEM_REPAIER: MenuType by registry.register(MNames.MATTER_RECONSTRUCTOR) { MenuType(::MatterReconstructorMenu) } + val FLUID_TANK: MenuType 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) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt index 0347f06dd..e56fb83b6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt @@ -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" diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.png new file mode 100644 index 000000000..509d6d5f4 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.xcf new file mode 100644 index 000000000..46606d3f7 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.xcf differ