Fluid capsules test

This commit is contained in:
DBotThePony 2023-04-02 14:48:28 +07:00
parent 6c4c40073e
commit 516de507e4
Signed by: DBot
GPG Key ID: DCC23B5715498507
17 changed files with 577 additions and 149 deletions

View File

@ -261,39 +261,41 @@ private fun misc(provider: MatteryLanguageProvider) {
misc("pill.message", "Nothing happened, but you feel... exhausted?.. Maybe get rest.")
misc("pill.message_finish", "§kONE OF US ONE OF US ONE OF US")
misc("gui.power.percentage_level", "Energy level: %s%%")
misc("gui.level", "%s / %s")
misc("gui.power.name", "MtJ")
gui("power.percentage_level", "Energy level: %s%%")
gui("level", "%s / %s")
gui("power.name", "MtJ")
gui("fluid.name", "mB")
gui("fluid.level", "%s / %s of %s")
misc("gui.power.burn_time", "Burn time left: %s ticks")
gui("power.burn_time", "Burn time left: %s ticks")
misc("gui.progress_widget", "Progress: %s%%")
misc("gui.progress_widget_stuck", "The machine can not work, check configuration")
gui("progress_widget", "Progress: %s%%")
gui("progress_widget_stuck", "The machine can not work, check configuration")
misc("gui.total_raw", "Total:")
gui("total_raw", "Total:")
misc("gui.matter.percentage_level", "Matter level: %s%%")
misc("gui.matter.format", "Matter: %s")
misc("gui.matter.format_and_complexity", "%s / Complexity: %s")
misc("gui.matter.format_and_complexity2", "%s (%s) / Complexity: %s (%s)")
misc("gui.matter.name", "MtU")
gui("matter.percentage_level", "Matter level: %s%%")
gui("matter.format", "Matter: %s")
gui("matter.format_and_complexity", "%s / Complexity: %s")
gui("matter.format_and_complexity2", "%s (%s) / Complexity: %s (%s)")
gui("matter.name", "MtU")
misc("gui.filter.is_whitelist", "Is Whitelist")
misc("gui.filter.match_nbt", "Match NBT")
misc("gui.filter.match_tag", "Match Tag")
gui("filter.is_whitelist", "Is Whitelist")
gui("filter.match_nbt", "Match NBT")
gui("filter.match_tag", "Match Tag")
misc("gui.android_research", "Research Tree")
gui("android_research", "Research Tree")
misc("gui.pattern.percentage_level", "Fill level: %s%%")
misc("gui.pattern.format", "Stored patterns: %s / %s")
gui("pattern.percentage_level", "Fill level: %s%%")
gui("pattern.format", "Stored patterns: %s / %s")
misc("gui.redstone.ignored", "Redstone mode: Ignored")
misc("gui.redstone.low", "Redstone mode: Low")
misc("gui.redstone.high", "Redstone mode: High")
gui("redstone.ignored", "Redstone mode: Ignored")
gui("redstone.low", "Redstone mode: Low")
gui("redstone.high", "Redstone mode: High")
misc("gui.redstone.ignored.description", "Redstone signal does not affect machine's function")
misc("gui.redstone.low.description", "Machine work if no redstone signal is present")
misc("gui.redstone.high.description", "Machine work only if any redstone signal is present")
gui("redstone.ignored.description", "Redstone signal does not affect machine's function")
gui("redstone.low.description", "Machine work if no redstone signal is present")
gui("redstone.high.description", "Machine work only if any redstone signal is present")
misc("3d2d.gravitation_stabilizer.mass", "Singularity mass: %s")
misc("3d2d.gravitation_stabilizer.strength", "Gravitation strength: %s")
@ -458,6 +460,9 @@ private fun items(provider: MatteryLanguageProvider) {
add(MItems.NUTRIENT_PASTE, "Nutrient Paste")
add(MItems.FLUID_CAPSULE, "Fluid Capsule")
add(MItems.FLUID_CAPSULE, "named", "Fluid Capsule (%s)")
add(MItems.BLACK_HOLE_SCANNER, "Singularity Scanner")
add(MItems.BLACK_HOLE_SCANNER, "desc", "Scans singularities for their properties")
add(MItems.BLACK_HOLE_SCANNER, "desc2", "Hold in hand to determine mass of singularities")

View File

@ -268,39 +268,41 @@ private fun misc(provider: MatteryLanguageProvider) {
misc("pill.message", "Ничего не произошло, но вы чувствуете... себя уставшим?.. Возможно надо отдохнуть.")
misc("pill.message_finish", "§kОДИН ИЗ НАС ОДИН ИЗ НАС ОДИН ИЗ НАС ОДИН ИЗ НАС ОДИН ИЗ НАС")
misc("gui.power.percentage_level", "Уровень энергии: %s%%")
misc("gui.level", "%s / %s")
misc("gui.power.name", "МтДж")
gui("power.percentage_level", "Уровень энергии: %s%%")
gui("level", "%s / %s")
gui("power.name", "МтДж")
gui("fluid.name", "мВ")
gui("fluid.level", "%s / %s с %s")
misc("gui.power.burn_time", "Оставшееся время горения: %s тиков")
gui("power.burn_time", "Оставшееся время горения: %s тиков")
misc("gui.progress_widget", "Прогресс: %s%%")
misc("gui.progress_widget_stuck", "Это устройство не может продолжить работу, проверьте конфигурацию")
gui("progress_widget", "Прогресс: %s%%")
gui("progress_widget_stuck", "Это устройство не может продолжить работу, проверьте конфигурацию")
misc("gui.total_raw", "Всего:")
gui("total_raw", "Всего:")
misc("gui.matter.percentage_level", "Уровень материи: %s%%")
misc("gui.matter.format", "Материя: %s")
misc("gui.matter.format_and_complexity", "%s / Сложность: %s")
misc("gui.matter.format_and_complexity2", "%s (%s) / Сложность: %s (%s)")
misc("gui.matter.name", "МтЕд")
gui("matter.percentage_level", "Уровень материи: %s%%")
gui("matter.format", "Материя: %s")
gui("matter.format_and_complexity", "%s / Сложность: %s")
gui("matter.format_and_complexity2", "%s (%s) / Сложность: %s (%s)")
gui("matter.name", "МтЕд")
misc("gui.filter.is_whitelist", "Белый список")
misc("gui.filter.match_nbt", "Сравнивать NBT")
misc("gui.filter.match_tag", "Сравнить Теги")
gui("filter.is_whitelist", "Белый список")
gui("filter.match_nbt", "Сравнивать NBT")
gui("filter.match_tag", "Сравнить Теги")
misc("gui.android_research", "Дерево Исследований")
gui("android_research", "Дерево Исследований")
misc("gui.pattern.percentage_level", "Уровень заполнения: %s%%")
misc("gui.pattern.format", "Хранимые шаблоны: %s / %s")
gui("pattern.percentage_level", "Уровень заполнения: %s%%")
gui("pattern.format", "Хранимые шаблоны: %s / %s")
misc("gui.redstone.ignored", "Режим красного камня: Игнорирование")
misc("gui.redstone.low", "Режим красного камня: Нет сигнала")
misc("gui.redstone.high", "Режим красного камня: Есть сигнал")
gui("redstone.ignored", "Режим красного камня: Игнорирование")
gui("redstone.low", "Режим красного камня: Нет сигнала")
gui("redstone.high", "Режим красного камня: Есть сигнал")
misc("gui.redstone.ignored.description", "Сигнал красного камня не влияет на работу устройства")
misc("gui.redstone.low.description", "Устройство работает если нет сигнала красного камня")
misc("gui.redstone.high.description", "Устройство работает если есть сигнал красного камня")
gui("redstone.ignored.description", "Сигнал красного камня не влияет на работу устройства")
gui("redstone.low.description", "Устройство работает если нет сигнала красного камня")
gui("redstone.high.description", "Устройство работает если есть сигнал красного камня")
misc("3d2d.gravitation_stabilizer.mass", "Масса сингулярности: %s")
misc("3d2d.gravitation_stabilizer.strength", "Гравитационная сила: %s")
@ -465,6 +467,9 @@ private fun items(provider: MatteryLanguageProvider) {
add(MItems.NUTRIENT_PASTE, "Питательная паста")
add(MItems.FLUID_CAPSULE, "Жидкостная капсула")
add(MItems.FLUID_CAPSULE, "named", "Жидкостная капсула (%s)")
add(MItems.BLACK_HOLE_SCANNER, "Сканер сингулярностей")
add(MItems.BLACK_HOLE_SCANNER, "desc", "Сканирует сингулярности и считывает их свойства")
add(MItems.BLACK_HOLE_SCANNER, "desc2", "Держите в любой их рук для считывания массы сингулярностей")

View File

@ -15,6 +15,7 @@ import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import ru.dbotthepony.mc.otm.android.AndroidResearchManager;
import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature;
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity;
@ -46,6 +47,7 @@ import ru.dbotthepony.mc.otm.config.ServerConfig;
import ru.dbotthepony.mc.otm.config.ToolsConfig;
import ru.dbotthepony.mc.otm.core.math.Decimal;
import ru.dbotthepony.mc.otm.item.ItemTritaniumArmor;
import ru.dbotthepony.mc.otm.item.PriorityUseItemKt;
import ru.dbotthepony.mc.otm.item.QuantumBatteryItem;
import ru.dbotthepony.mc.otm.item.weapon.AbstractWeaponItem;
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem;
@ -59,6 +61,7 @@ import top.theillusivec4.curios.api.CuriosApi;
import static net.minecraftforge.common.MinecraftForge.EVENT_BUS;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.Objects;
// The value here should match an entry in the META-INF/mods.toml file
@Mod(OverdriveThatMatters.MOD_ID)
@ -72,8 +75,9 @@ public final class OverdriveThatMatters {
public static OverdriveThatMatters INSTANCE;
private StorageStackType<ItemStackWrapper> ITEM_STORAGE;
@NotNull
public StorageStackType<ItemStackWrapper> ITEM_STORAGE() {
return ITEM_STORAGE;
return Objects.requireNonNull(ITEM_STORAGE);
}
public static ResourceLocation loc(String path) {
@ -183,6 +187,8 @@ public final class OverdriveThatMatters {
EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::playerDisconnected);
EVENT_BUS.addListener(EventPriority.LOWEST, MatteryBlockEntity.Companion::postLevelTick);
EVENT_BUS.addListener(EventPriority.NORMAL, PriorityUseItemKt::onItemRightClick);
EVENT_BUS.addListener(EventPriority.LOWEST, KillAsAndroidTrigger.INSTANCE::onKill);
EVENT_BUS.addListener(EventPriority.NORMAL, EnderTeleporterFeature.Companion::onEntityDeath);

View File

@ -1,15 +1,23 @@
package ru.dbotthepony.mc.otm.capability
import com.google.common.collect.Streams
import earth.terrarium.botarium.common.registry.fluid.FluidSounds
import net.minecraft.ChatFormatting
import net.minecraft.core.Direction
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.capabilities.ICapabilityProvider
import net.minecraftforge.common.util.LazyOptional
import net.minecraftforge.energy.IEnergyStorage
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import net.minecraftforge.fml.ModList
import net.minecraftforge.items.IItemHandler
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.client.ShiftPressedCond
import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorAwareStream
import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorStream
import ru.dbotthepony.mc.otm.compat.cos.isCosmeticArmorLoaded
@ -24,10 +32,13 @@ import ru.dbotthepony.mc.otm.core.collect.AwareItemStack
import ru.dbotthepony.mc.otm.core.collect.ContainerItemStackEntry
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.orNull
import ru.dbotthepony.mc.otm.core.util.formatFluidLevel
import java.util.IdentityHashMap
import java.util.LinkedList
import java.util.stream.Stream
private val LOGGER = LogManager.getLogger()
val ICapabilityProvider.matteryPlayer: MatteryPlayerCapability? get() = getCapability(MatteryCapability.MATTERY_PLAYER).orNull()
/**
@ -297,3 +308,119 @@ fun Player.awareAllItemsStream(includeCosmetics: Boolean = false): Stream<out Aw
seen.stream())
}
/**
* Attempts to safely exchange/move item between slots of two handlers
*
* @return pair of new (advanced) [sourceSlot] and [destinationSlot]
*/
internal 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) {
return sourceSlot + 1 to destinationSlot
} else {
val leftover = destination.insertItem(destinationSlot, getItem, true)
if (leftover.count == getItem.count) {
return sourceSlot to destinationSlot + 1
} else {
val getItem2 = source.extractItem(sourceSlot, getItem.count - leftover.count, true)
if (getItem2.isEmpty) {
return sourceSlot + 1 to destinationSlot
} else {
val leftover2 = destination.insertItem(destinationSlot, getItem2, true)
if (leftover2.isEmpty) {
source.extractItem(sourceSlot, getItem2.count, false)
destination.insertItem(destinationSlot, getItem2, false)
if (getItem2.count == getItem.count) {
return sourceSlot + 1 to destinationSlot
} else {
return sourceSlot to destinationSlot
}
}
}
}
}
return sourceSlot to destinationSlot
}
@Suppress("name_shadowing")
internal 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) {
val received = destination.receiveEnergy(extracted, true)
if (received.isPositive) {
val extracted = if (ignoreFlowRestrictions && source is IMatteryEnergyStorage) source.extractEnergy(received, true) else source.extractEnergy(received, true)
if (extracted.isPositive) {
if (simulate) {
return extracted
}
val received = destination.receiveEnergy(extracted, false)
return if (ignoreFlowRestrictions && source is IMatteryEnergyStorage) source.extractEnergy(received, false) else source.extractEnergy(received, false)
}
}
}
return Decimal.ZERO
}
internal fun fluidLevel(it: IFluidHandler, tooltips: MutableList<Component>) {
val fluid = it.getFluidInTank(0)
if (fluid.isEmpty) {
tooltips.add(formatFluidLevel(0, it.getTankCapacity(0), formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY))
} else {
tooltips.add(formatFluidLevel(fluid.amount, it.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)
}
if (drained.isEmpty) return FluidStack.EMPTY
val filled = destination.fill(drained, IFluidHandler.FluidAction.SIMULATE)
if (filled == 0) return FluidStack.EMPTY
val drained2 = source.drain(FluidStack(drained, filled), IFluidHandler.FluidAction.SIMULATE)
if (drained2.amount != filled) return FluidStack.EMPTY
val filled2 = destination.fill(drained2, IFluidHandler.FluidAction.SIMULATE)
if (filled2 != drained2.amount) return FluidStack.EMPTY
if (simulate) return FluidStack(drained2, filled2)
val drained3: FluidStack
if (actuallyDrain) {
drained3 = source.drain(FluidStack(drained2, filled2), IFluidHandler.FluidAction.EXECUTE)
if (drained3.amount != filled2) {
LOGGER.warn("Inconsistency of fluid extraction from $source between simulate and execute modes (simulated $drained2; extracted $drained3); This can lead to duping!!!")
}
} else {
drained3 = drained2
}
val 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!!!")
}
return FluidStack(drained3, filled3)
}

View File

@ -1,70 +0,0 @@
package ru.dbotthepony.mc.otm.capability
import net.minecraftforge.energy.IEnergyStorage
import net.minecraftforge.items.IItemHandler
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.core.math.Decimal
/**
* Attempts to safely exchange/move item between slots of two handlers
*
* @return pair of new (advanced) [sourceSlot] and [destinationSlot]
*/
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) {
return sourceSlot + 1 to destinationSlot
} else {
val leftover = destination.insertItem(destinationSlot, getItem, true)
if (leftover.count == getItem.count) {
return sourceSlot to destinationSlot + 1
} else {
val getItem2 = source.extractItem(sourceSlot, getItem.count - leftover.count, true)
if (getItem2.isEmpty) {
return sourceSlot + 1 to destinationSlot
} else {
val leftover2 = destination.insertItem(destinationSlot, getItem2, true)
if (leftover2.isEmpty) {
source.extractItem(sourceSlot, getItem2.count, false)
destination.insertItem(destinationSlot, getItem2, false)
if (getItem2.count == getItem.count) {
return sourceSlot + 1 to destinationSlot
} else {
return sourceSlot to destinationSlot
}
}
}
}
}
return sourceSlot to destinationSlot
}
@Suppress("name_shadowing")
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) {
val received = destination.receiveEnergy(extracted, true)
if (received.isPositive) {
val extracted = if (ignoreFlowRestrictions && source is IMatteryEnergyStorage) source.extractEnergy(received, true) else source.extractEnergy(received, true)
if (extracted.isPositive) {
if (simulate) {
return extracted
}
val received = destination.receiveEnergy(extracted, false)
return if (ignoreFlowRestrictions && source is IMatteryEnergyStorage) source.extractEnergy(received, false) else source.extractEnergy(received, false)
}
}
}
return Decimal.ZERO
}

View File

@ -91,6 +91,10 @@ object ItemsConfig : AbstractConfig("items") {
}
init {
builder.push("PatternDrives")
PatternDrives
builder.pop()
}
val FLUID_CAPSULE_CAPACITY: Int by builder.defineInRange("LIQUID_CAPSULE_CAPACITY", 1000, 1, Int.MAX_VALUE)
}

View File

@ -47,7 +47,7 @@ class AwareContainerSpliterator(private val container: Container, offset: Int =
}
}
fun Container.spliterator() = ContainerSpliterator(this)
fun Container.awareSpliterator() = AwareContainerSpliterator(this)
fun Container.stream(): Stream<out ItemStack> = StreamSupport.stream(spliterator(), false)
fun Container.awareStream(): Stream<out AwareItemStack> = StreamSupport.stream(awareSpliterator(), false)
fun Container.spliterator(offset: Int = 0) = ContainerSpliterator(this, offset)
fun Container.awareSpliterator(offset: Int = 0) = AwareContainerSpliterator(this, offset)
fun Container.stream(offset: Int = 0): Stream<out ItemStack> = StreamSupport.stream(spliterator(offset), false)
fun Container.awareStream(offset: Int = 0): Stream<out AwareItemStack> = StreamSupport.stream(awareSpliterator(offset), false)

View File

@ -3,12 +3,15 @@ package ru.dbotthepony.mc.otm.container
import net.minecraft.world.Container
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.core.collect.iterator
import ru.dbotthepony.mc.otm.core.collect.nonEmpty
operator fun Container.set(index: Int, value: ItemStack) = setItem(index, value)
operator fun Container.get(index: Int): ItemStack = getItem(index)
operator fun IFluidHandler.get(index: Int) = getFluidInTank(index)
fun Container.addItem(stack: ItemStack, range: IntRange, simulate: Boolean = false): ItemStack {
if (this is MatteryContainer) {
return this.addItem(stack, range, simulate)

View File

@ -0,0 +1,25 @@
package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.objects.ObjectIterators.AbstractIndexBasedIterator
import net.minecraftforge.fluids.FluidStack
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" }
}
override fun remove(location: Int) {
throw UnsupportedOperationException()
}
override fun get(location: Int): FluidStack {
return handler[location]
}
override fun getMaxPos(): Int {
return handler.tanks
}
}
fun IFluidHandler.iterator(position: Int = 0) = FluidHandlerIterator(this, position)

View File

@ -0,0 +1,31 @@
package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.objects.ObjectSpliterator
import it.unimi.dsi.fastutil.objects.ObjectSpliterators.AbstractIndexBasedSpliterator
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import java.util.Spliterator
import java.util.stream.Stream
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}" }
}
override fun get(location: Int): FluidStack {
return handler[location]
}
override fun getMaxPos(): Int {
return maxPos
}
override fun makeForSplit(pos: Int, maxPos: Int): ObjectSpliterator<FluidStack> {
return FluidHandlerSpliterator(handler, pos, maxPos)
}
}
fun IFluidHandler.spliterator(offset: Int = 0): Spliterator<FluidStack> = FluidHandlerSpliterator(this, offset)
fun IFluidHandler.stream(offset: Int = 0): Stream<FluidStack> = StreamSupport.stream(spliterator(offset), false)

View File

@ -14,6 +14,7 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.item.Item
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.material.Fluid
import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.IForgeRegistry
@ -65,6 +66,7 @@ fun <T> IForgeRegistry<T>.getKeyNullable(value: T): ResourceLocation? {
}
val Item.registryName get() = ForgeRegistries.ITEMS.getKeyNullable(this)
val Fluid.registryName get() = ForgeRegistries.FLUIDS.getKeyNullable(this)
val Block.registryName get() = ForgeRegistries.BLOCKS.getKeyNullable(this)
fun FriendlyByteBuf.writeRegistryId(value: Item) = writeRegistryId(ForgeRegistries.ITEMS, value)

View File

@ -1,26 +0,0 @@
package ru.dbotthepony.mc.otm.core.collect
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
private class SlotIterator(private val parent: Iterator<Slot>) : MutableIterator<ItemStack> {
private var last: Slot? = null
override fun hasNext(): Boolean {
return parent.hasNext()
}
override fun next(): ItemStack {
return parent.next().also { last = it }.item
}
override fun remove() {
val last = last ?: throw IllegalStateException("Never called next()")
last.set(ItemStack.EMPTY)
}
}
fun AbstractContainerMenu.itemStackIterator() : MutableIterator<ItemStack> = SlotIterator(slots.iterator())
fun Iterable<Slot>.itemStackIterator() : MutableIterator<ItemStack> = SlotIterator(iterator())
fun Iterator<Slot>.asItemStackIterator() : MutableIterator<ItemStack> = SlotIterator(this)

View File

@ -209,6 +209,7 @@ fun Decimal.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2, formatAs
}
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 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))
@ -219,6 +220,8 @@ fun BigInteger.formatMatterFull(decimalPlaces: Int = 2, formatAsReadable: Boolea
fun formatPowerLevel(a: Decimal, b: Decimal, decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.level", a.formatPower(decimalPlaces, formatAsReadable = formatAsReadable), b.formatPower(decimalPlaces, formatAsReadable = formatAsReadable))
fun formatMatterLevel(a: Decimal, b: Decimal, decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.level", a.formatMatter(decimalPlaces, formatAsReadable = formatAsReadable), b.formatMatter(decimalPlaces, formatAsReadable = formatAsReadable))
fun formatFluidLevel(a: Int, b: Int, decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.level", a.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable), b.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable))
fun formatFluidLevel(a: Int, b: Int, name: Component, decimalPlaces: Int = 2, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.fluid.level", a.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable), b.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable), name)
private fun padded(num: Int): String {
if (num in 0 .. 9) {

View File

@ -0,0 +1,274 @@
package ru.dbotthepony.mc.otm.item
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
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.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.LayeredCauldronBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.material.Fluid
import net.minecraft.world.level.material.Fluids
import net.minecraftforge.common.SoundActions
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.capabilities.ICapabilityProvider
import net.minecraftforge.event.entity.player.PlayerInteractEvent
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import net.minecraftforge.fluids.capability.templates.FluidHandlerItemStack
import ru.dbotthepony.mc.otm.capability.fluidLevel
import ru.dbotthepony.mc.otm.capability.moveFluid
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.container.stream
import ru.dbotthepony.mc.otm.core.TranslatableComponent
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
class FluidCapsuleItem(val capacity: () -> Int) : Item(Properties().stacksTo(64)), PriorityUseItem {
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)
}
}
override fun isPriorityConsumer(event: PlayerInteractEvent.RightClickBlock): Boolean {
return canInteract(event.itemStack, event.entity, Context(event.hitVec.blockPos, event.entity.level.getBlockState(event.hitVec.blockPos), event.hitVec.direction))
}
override fun useOn(pContext: UseOnContext): InteractionResult {
val context = Context(pContext.clickedPos, pContext.level.getBlockState(pContext.clickedPos), pContext.clickedFace)
if (canInteract(pContext.itemInHand, pContext.player ?: return InteractionResult.FAIL, context))
return interact(pContext.itemInHand, pContext.player!!, context)
return InteractionResult.FAIL
}
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?, pTooltipComponents: MutableList<Component>, pIsAdvanced: TooltipFlag) {
super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced)
pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK {
fluidLevel(it, pTooltipComponents)
}
}
override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider {
return Cap(stack)
}
interface Interaction {
fun canInteract(item: ItemStack, player: Player, context: Context): Boolean
fun interact(item: ItemStack, player: Player, context: Context): InteractionResult
}
private object FillCauldron : Interaction {
private data class Mapping(val fluid: Fluid, val blockState: BlockState, val soundEvent: SoundEvent)
private val mapping = immutableList {
accept(Mapping(Fluids.WATER, Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY))
accept(Mapping(Fluids.LAVA, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA))
}
override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean {
return player.level.getBlockState(context.blockPos).`is`(Blocks.CAULDRON)
}
override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult {
if (item.isEmpty || !context.blockState.`is`(Blocks.CAULDRON)) return InteractionResult.FAIL
val targetItem = if (item.count == 1) item else item.copyWithCount(1)
val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL
for ((fluid, newState, soundEvent) in mapping) {
val toDrain = FluidStack(fluid, 1000)
val drained = cap.drain(toDrain, IFluidHandler.FluidAction.SIMULATE)
if (!drained.isEmpty && drained.amount == 1000) {
player.level.playSound(player, context.blockPos, soundEvent, SoundSource.BLOCKS)
val level = player.level as? ServerLevel ?: return InteractionResult.SUCCESS
level.setBlock(context.blockPos, newState, Block.UPDATE_ALL)
cap.drain(toDrain, IFluidHandler.FluidAction.EXECUTE)
if (item.count != 1 && !player.level.isClientSide) {
item.count--
if (!player.inventory.add(targetItem)) {
player.spawnAtLocation(targetItem)
}
}
return InteractionResult.sidedSuccess(player.level.isClientSide)
}
}
return InteractionResult.FAIL
}
}
private object EmptyCauldron : Interaction {
private val mapping = immutableMap {
put(Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), Fluids.WATER to SoundEvents.BUCKET_FILL)
put(Blocks.LAVA_CAULDRON.defaultBlockState(), Fluids.LAVA to SoundEvents.BUCKET_FILL_LAVA)
}
override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean {
return player.level.getBlockState(context.blockPos) in mapping
}
override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult {
if (item.isEmpty) return InteractionResult.FAIL
val targetItem = if (item.count == 1) item else item.copyWithCount(1)
val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL
val mapped = mapping[context.blockState] ?: return InteractionResult.FAIL
val toFill = FluidStack(mapped.first, 1000)
val fill = cap.fill(toFill, IFluidHandler.FluidAction.SIMULATE)
if (fill != 1000) return InteractionResult.FAIL
player.level.playSound(player, context.blockPos, mapped.second, SoundSource.BLOCKS)
val level = player.level as? ServerLevel ?: return InteractionResult.SUCCESS
level.setBlock(context.blockPos, Blocks.CAULDRON.defaultBlockState(), Block.UPDATE_ALL)
cap.fill(toFill, IFluidHandler.FluidAction.EXECUTE)
if (item.count != 1 && !player.level.isClientSide) {
item.count--
if (!player.inventory.add(targetItem)) {
player.spawnAtLocation(targetItem)
}
}
return InteractionResult.sidedSuccess(player.level.isClientSide)
}
}
private object FillEmptyCapability : Interaction {
override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean {
val target = player.level.getBlockEntity(context.blockPos)?.getCapability(ForgeCapabilities.FLUID_HANDLER, context.side)?.orNull() ?: return false
val cap = item.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return false
return target.stream().anyMatch { !it.isEmpty } || cap.stream().anyMatch { !it.isEmpty }
}
override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult {
if (item.isEmpty) return InteractionResult.FAIL
val targetItem = if (item.count == 1) item else item.copyWithCount(1)
val itemCap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL
val blockCap = player.level.getBlockEntity(context.blockPos)?.getCapability(ForgeCapabilities.FLUID_HANDLER, context.side)?.orNull() ?: return InteractionResult.FAIL
val fluid = itemCap[0]
if (fluid.isEmpty || player.isCrouching) {
// заполняем из блока
val moveResult = moveFluid(source = blockCap, destination = itemCap, simulate = player.level.isClientSide)
if (!moveResult.isEmpty) {
val sound = moveResult.fluid.fluidType.getSound(moveResult, SoundActions.BUCKET_FILL)
if (sound != null) {
player.level.playSound(player, context.blockPos, sound, SoundSource.BLOCKS)
}
if (item.count != 1 && !player.level.isClientSide) {
item.count--
if (!player.inventory.add(targetItem)) {
player.spawnAtLocation(targetItem)
}
}
return InteractionResult.sidedSuccess(player.level.isClientSide)
} else {
return InteractionResult.FAIL
}
} else {
val moveResult = moveFluid(source = itemCap, destination = blockCap, simulate = player.level.isClientSide)
if (!moveResult.isEmpty) {
val sound = moveResult.fluid.fluidType.getSound(moveResult, SoundActions.BUCKET_EMPTY)
if (sound != null) {
player.level.playSound(player, context.blockPos, sound, SoundSource.BLOCKS)
}
if (item.count != 1 && !player.level.isClientSide) {
item.count--
if (!player.inventory.add(targetItem)) {
player.spawnAtLocation(targetItem)
}
}
return InteractionResult.sidedSuccess(player.level.isClientSide)
} else {
return InteractionResult.FAIL
}
}
}
}
data class Context(val blockPos: BlockPos, val blockState: BlockState, val side: Direction)
companion object : Interaction {
val interactions = immutableList {
accept(FillCauldron)
accept(EmptyCauldron)
accept(FillEmptyCapability)
}
override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean {
return interactions.any { it.canInteract(item, player, context) }
}
override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult {
return interactions.firstOrNull { it.canInteract(item, player, context) }?.interact(item, player, context) ?: InteractionResult.PASS
}
}
}

View File

@ -0,0 +1,18 @@
package ru.dbotthepony.mc.otm.item
import net.minecraftforge.event.entity.player.PlayerInteractEvent
import net.minecraftforge.eventbus.api.Event
interface PriorityUseItem {
fun isPriorityConsumer(event: PlayerInteractEvent.RightClickBlock): Boolean
}
internal fun onItemRightClick(event: PlayerInteractEvent.RightClickBlock) {
val item = event.itemStack.item
if (!event.itemStack.isEmpty && item is PriorityUseItem) {
if (item.isPriorityConsumer(event)) {
event.useBlock = Event.Result.DENY
}
}
}

View File

@ -4,8 +4,15 @@ import net.minecraft.world.item.CreativeModeTab
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.material.FlowingFluid
import net.minecraft.world.level.material.Fluids
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import net.minecraftforge.registries.ForgeRegistries
import ru.dbotthepony.mc.otm.capability.matter.matter
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.registryName
private fun CreativeModeTab.Output.accept(values: Collection<Item>) {
@ -140,6 +147,18 @@ 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)
}
})
}
}
base(MItems.CARGO_CRATE_MINECARTS)
accept(MItems.NUTRIENT_PASTE)

View File

@ -155,6 +155,8 @@ object MItems {
val ESSENCE_CAPSULE: EssenceCapsuleItem by registry.register("essence_capsule") { EssenceCapsuleItem() }
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 TRITANIUM_COMPONENT: ForgeTier = ForgeTier(
Tiers.IRON.level,
3072,