diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt index dc9a72811..2ded4aa6b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt @@ -12,6 +12,7 @@ import net.minecraft.world.item.ItemStack import net.minecraftforge.common.util.INBTSerializable import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.capability.awareItemsStream import ru.dbotthepony.mc.otm.capability.itemsStream import ru.dbotthepony.mc.otm.client.render.SkinElement import ru.dbotthepony.mc.otm.container.iterator @@ -136,20 +137,11 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay for ((tag, count) in type.items) { var required = count - val iterator = capability.ply.inventory.iterator().nonEmpty() - for (invItem in iterator) { - if (tag.test(invItem)) { - val toExtract = required.coerceAtMost(invItem.count) - required -= toExtract - - if (!simulate) { - if (toExtract == invItem.count) { - iterator.remove() - } else { - invItem.count -= toExtract - } - } + for (invItem in ply.awareItemsStream(false)) { + if (tag.test(invItem.itemStack)) { + val toExtract = required.coerceAtMost(invItem.itemStack.count) + required -= invItem.extract(toExtract, simulate).count if (required <= 0) { break @@ -336,11 +328,11 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay for ((tag, count) in type.items) { var required = count - val iterator = capability.ply.inventory.iterator().nonEmpty() - for (invItem in iterator) { - if (tag.test(invItem)) { - required -= required.coerceAtMost(invItem.count) + for (invItem in ply.awareItemsStream(false)) { + if (tag.test(invItem.itemStack)) { + val toExtract = required.coerceAtMost(invItem.itemStack.count) + required -= invItem.extract(toExtract, true).count if (required <= 0) { break 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 82b62e662..0f6e26bc9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt @@ -9,15 +9,19 @@ import net.minecraftforge.common.capabilities.ICapabilityProvider import net.minecraftforge.common.util.LazyOptional import net.minecraftforge.energy.IEnergyStorage import net.minecraftforge.fml.ModList +import ru.dbotthepony.mc.otm.compat.curios.curiosAwareStream import ru.dbotthepony.mc.otm.compat.curios.curiosStream 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.iterator +import ru.dbotthepony.mc.otm.container.awareStream import ru.dbotthepony.mc.otm.container.stream +import ru.dbotthepony.mc.otm.core.AwareItemStack +import ru.dbotthepony.mc.otm.core.ContainerItemStackEntry import ru.dbotthepony.mc.otm.core.ImpreciseFraction import ru.dbotthepony.mc.otm.core.orNull import java.util.IdentityHashMap +import java.util.LinkedList import java.util.stream.Stream val ICapabilityProvider.matteryPlayer: MatteryPlayerCapability? get() = getCapability(MatteryCapability.MATTERY_PLAYER).orNull() @@ -156,7 +160,7 @@ fun ICapabilityProvider.getMatteryEnergySided(side: Direction? = null): LazyOpti * Contains all items that player might carry */ fun Player.itemsStream(includeCosmetics: Boolean = true): Stream { - val streams = ArrayList>() + val streams = LinkedList>() streams.add(inventory.stream()) matteryPlayer?.let { @@ -178,8 +182,8 @@ fun Player.itemsStream(includeCosmetics: Boolean = true): Stream * Contains all items that player might see/access ([itemsStream] + open container's items) */ fun Player.allItemsStream(includeCosmetics: Boolean = true): Stream { - if (containerMenu == inventoryMenu) { - return itemsStream() + if (containerMenu == inventoryMenu || containerMenu == matteryPlayer?.exoSuitMenu) { + return itemsStream(includeCosmetics) } val seen = IdentityHashMap(containerMenu.slots.size) @@ -192,3 +196,47 @@ fun Player.allItemsStream(includeCosmetics: Boolean = true): Stream { + val streams = LinkedList>() + streams.add(inventory.awareStream()) + + matteryPlayer?.let { + if (it.hasExoSuit) { + streams.add(it.exoSuitContainer.awareStream()) + } + } + + if (isCuriosLoaded) { + streams.add(curiosAwareStream(includeCosmetics)) + } + + return Streams.concat(*streams.toTypedArray()) +} + +/** + * Modify returned [ItemStack]s through [AwareItemStack.extract] + * + * Contains all items that player might see/access ([itemsStream] + open container's items) + */ +fun Player.awareAllItemsStream(includeCosmetics: Boolean = true): Stream { + if (containerMenu == inventoryMenu || containerMenu == matteryPlayer?.exoSuitMenu) { + return awareItemsStream(includeCosmetics) + } + + val seen = HashSet(containerMenu.slots.size) + + for (slot in containerMenu.slots) { + seen.add(ContainerItemStackEntry(slot.slotIndex, slot.container)) + } + + return Streams.concat( + awareItemsStream(includeCosmetics).filter { it !in seen }, + seen.stream()) +} + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt index 9d4939b77..72187171e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.compat.curios +import com.google.common.collect.Iterators import com.google.common.collect.Streams import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap import net.minecraft.world.entity.player.Player @@ -7,11 +8,18 @@ import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack import net.minecraftforge.fml.ModList import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.container.awareIterator +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.AwareItemStack +import ru.dbotthepony.mc.otm.core.EmptyMutableIterator +import ru.dbotthepony.mc.otm.core.IAwareItemStackIterator import ru.dbotthepony.mc.otm.core.orNull import top.theillusivec4.curios.api.CuriosApi import top.theillusivec4.curios.common.inventory.CosmeticCurioSlot import top.theillusivec4.curios.common.inventory.CurioSlot +import java.util.LinkedList import java.util.stream.Stream val isCuriosLoaded by lazy { @@ -67,7 +75,7 @@ val Player.curiosSlots: Collection> get() { private fun Player.curiosStreamImpl(includeCosmetics: Boolean): Stream { val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return Stream.empty() - val result = ArrayList>() + val result = LinkedList>() for ((identifier, curio) in handler.curios) { result.add(curio.stacks.stream()) @@ -87,3 +95,27 @@ fun Player.curiosStream(includeCosmetics: Boolean = true): Stream return curiosStreamImpl(includeCosmetics) } + +private fun Player.curiosAwareStreamImpl(includeCosmetics: Boolean): Stream { + val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return Stream.empty() + + val result = LinkedList>() + + for ((identifier, curio) in handler.curios) { + result.add(curio.stacks.awareStream()) + + if (includeCosmetics && curio.hasCosmetic()) { + result.add(curio.cosmeticStacks.awareStream()) + } + } + + return Streams.concat(*result.toTypedArray()) +} + +fun Player.curiosAwareStream(includeCosmetics: Boolean = true): Stream { + if (!isCuriosLoaded) { + return Stream.empty() + } + + return curiosAwareStreamImpl(includeCosmetics) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt index 01dcca7c0..7a1c637f9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt @@ -1,30 +1,58 @@ package ru.dbotthepony.mc.otm.container +import it.unimi.dsi.fastutil.objects.ObjectIterators import net.minecraft.world.Container import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.ContainerItemStackEntry +import ru.dbotthepony.mc.otm.core.IAwareItemStackIterator +import ru.dbotthepony.mc.otm.core.AwareItemStack -class ContainerIterator(private val container: Container) : MutableIterator { - private var index = 0 - - override fun hasNext(): Boolean { - return index < container.containerSize +class ContainerIterator(private val container: Container, initialPosition: Int = 0) : ObjectIterators.AbstractIndexBasedListIterator(0, initialPosition), MutableListIterator { + init { + require(initialPosition in 0 .. container.containerSize) { "Invalid initial position: $initialPosition" } } - override fun next(): ItemStack { - if (index >= container.containerSize) { - throw IllegalStateException("Already finished iterating") - } - - return container[index++] + override fun add(location: Int, k: ItemStack) { + throw UnsupportedOperationException() } - override fun remove() { - if (index == 0) { - throw IllegalStateException("Never called next()") - } + override fun set(location: Int, k: ItemStack) { + container[location] = k + } - container[index - 1] = ItemStack.EMPTY + override fun remove(location: Int) { + container[location] = ItemStack.EMPTY + } + + override fun get(location: Int): ItemStack { + return container[location] + } + + override fun getMaxPos(): Int { + return container.containerSize } } -fun Container.iterator() = ContainerIterator(this) +class AwareContainerIterator(private val container: Container, initialPosition: Int = 0) : ObjectIterators.AbstractIndexBasedIterator(0, initialPosition), IAwareItemStackIterator { + init { + require(initialPosition in 0 .. container.containerSize) { "Invalid initial position: $initialPosition" } + } + + override fun remove(location: Int) { + container[location] = ItemStack.EMPTY + } + + override fun get(location: Int): AwareItemStack { + return ContainerItemStackEntry(location, container) + } + + override fun getMaxPos(): Int { + return container.containerSize + } +} + +@JvmOverloads +fun Container.iterator(initialPosition: Int = 0) = ContainerIterator(this, initialPosition) + +@JvmOverloads +fun Container.awareIterator(initialPosition: Int = 0) = AwareContainerIterator(this, initialPosition) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSpliterator.kt index 180ee8954..305792410 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSpliterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSpliterator.kt @@ -4,6 +4,8 @@ import it.unimi.dsi.fastutil.objects.ObjectSpliterator import it.unimi.dsi.fastutil.objects.ObjectSpliterators import net.minecraft.world.Container import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.AwareItemStack +import ru.dbotthepony.mc.otm.core.ContainerItemStackEntry import java.util.stream.Stream import java.util.stream.StreamSupport @@ -26,5 +28,26 @@ class ContainerSpliterator(private val container: Container, offset: Int = 0, pr } } +class AwareContainerSpliterator(private val container: Container, offset: Int = 0, private val maxPos: Int = container.containerSize) : ObjectSpliterators.AbstractIndexBasedSpliterator(offset) { + init { + require(offset >= 0) { "Invalid offset $offset" } + require(offset + maxPos <= container.containerSize) { "$offset -> $maxPos while having only size of ${container.containerSize}!" } + } + + override fun get(location: Int): AwareItemStack { + return ContainerItemStackEntry(location, container) + } + + override fun getMaxPos(): Int { + return maxPos + } + + override fun makeForSplit(pos: Int, maxPos: Int): ObjectSpliterator { + return AwareContainerSpliterator(container, pos, maxPos) + } +} + fun Container.spliterator() = ContainerSpliterator(this) +fun Container.awareSpliterator() = AwareContainerSpliterator(this) fun Container.stream(): Stream = StreamSupport.stream(spliterator(), false) +fun Container.awareStream(): Stream = StreamSupport.stream(awareSpliterator(), false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt index 6a9b11aab..bd8cffb9d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt @@ -4,6 +4,8 @@ import it.unimi.dsi.fastutil.objects.ObjectSpliterator import it.unimi.dsi.fastutil.objects.ObjectSpliterators import net.minecraft.world.item.ItemStack import net.minecraftforge.items.IItemHandler +import ru.dbotthepony.mc.otm.core.AwareItemStack +import ru.dbotthepony.mc.otm.core.ItemHandlerItemStackEntry import java.util.Spliterator import java.util.stream.Stream import java.util.stream.StreamSupport @@ -27,5 +29,26 @@ class ItemHandlerSpliterator(private val handler: IItemHandler, offset: Int = 0, } } +class ItemHandlerAwareSpliterator(private val handler: IItemHandler, offset: Int = 0, private val maxPos: Int = handler.slots) : ObjectSpliterators.AbstractIndexBasedSpliterator(offset) { + init { + require(offset >= 0) { "Invalid offset $offset" } + require(offset + maxPos <= handler.slots) { "$offset -> $maxPos while having only size of ${handler.slots}!" } + } + + override fun get(location: Int): AwareItemStack { + return ItemHandlerItemStackEntry(location, handler) + } + + override fun getMaxPos(): Int { + return maxPos + } + + override fun makeForSplit(pos: Int, maxPos: Int): ObjectSpliterator { + return ItemHandlerAwareSpliterator(handler, pos, maxPos) + } +} + fun IItemHandler.spliterator(): Spliterator = ItemHandlerSpliterator(this) +fun IItemHandler.awareSpliterator(): Spliterator = ItemHandlerAwareSpliterator(this) fun IItemHandler.stream(): Stream = StreamSupport.stream(spliterator(), false) +fun IItemHandler.awareStream(): Stream = StreamSupport.stream(awareSpliterator(), false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/AwareItemStack.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/AwareItemStack.kt new file mode 100644 index 000000000..0fb0d5f92 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/AwareItemStack.kt @@ -0,0 +1,56 @@ +package ru.dbotthepony.mc.otm.core + +import net.minecraft.world.Container +import net.minecraft.world.item.ItemStack +import net.minecraftforge.items.IItemHandler +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.set + +/** + * Allows to see the contents of container and extract the item from it + */ +interface AwareItemStack { + operator fun component0(): ItemStack + val itemStack: ItemStack get() = component0() + + /** + * Extracts item from underlying container + */ + fun extract(amount: Int, simulate: Boolean = true): ItemStack +} + +typealias IAwareItemStackIterator = Iterator + +data class ContainerItemStackEntry(val index: Int, val container: Container) : AwareItemStack { + override fun component0(): ItemStack { + return container[index] + } + + override fun extract(amount: Int, simulate: Boolean): ItemStack { + if (simulate) { + return container[index].let { if (it.isEmpty) it else it.copy().also { it.count = it.count.coerceAtMost(amount) } } + } + + val item = container[index] + val newCount = (item.count - amount).coerceAtLeast(0) + + if (newCount == 0) { + container[index] = ItemStack.EMPTY + return item + } + + val copy = item.copy().also { it.count = amount } + container[index] = item.copy().also { it.count -= amount } + return copy + } +} + +data class ItemHandlerItemStackEntry(val index: Int, val handler: IItemHandler) : AwareItemStack { + override fun component0(): ItemStack { + return handler[index] + } + + override fun extract(amount: Int, simulate: Boolean): ItemStack { + return handler.extractItem(index, amount, simulate) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/EmptyMutableIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/EmptyMutableIterator.kt new file mode 100644 index 000000000..b673ac9c3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/EmptyMutableIterator.kt @@ -0,0 +1,44 @@ +package ru.dbotthepony.mc.otm.core + +object EmptyMutableIterator : MutableIterator<@UnsafeVariance Nothing>, MutableListIterator<@UnsafeVariance Nothing> { + override fun hasPrevious(): Boolean { + return false + } + + override fun nextIndex(): Int { + return 0 + } + + override fun previous(): Nothing { + throw NoSuchElementException() + } + + override fun previousIndex(): Int { + return -1 + } + + override fun add(element: Nothing) { + throw UnsupportedOperationException() + } + + override fun hasNext(): Boolean { + return false + } + + override fun next(): Nothing { + throw NoSuchElementException() + } + + override fun remove() { + throw NoSuchElementException() + } + + override fun set(element: Nothing) { + throw UnsupportedOperationException() + } + + @Suppress("unchecked_cast") + fun cast(): MutableListIterator { + return this as MutableListIterator + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ItemStackIterators.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ItemStackIterators.kt index 01bbe5fbc..e64500e50 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ItemStackIterators.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ItemStackIterators.kt @@ -2,50 +2,5 @@ package ru.dbotthepony.mc.otm.core import net.minecraft.world.item.ItemStack -open class NonEmptyItemStackIterator(protected open val parent: Iterator) : Iterator { - private var itemStack: ItemStack? = null - private var searched = false - - private fun search() { - searched = true - - if (itemStack != null) { - return - } - - for (stack in parent) { - if (!stack.isEmpty) { - itemStack = stack - return - } - } - } - - override fun hasNext(): Boolean { - if (!searched) { - search() - } - - return itemStack != null - } - - override fun next(): ItemStack { - if (!searched) { - search() - } - - val itemStack = itemStack ?: throw IllegalStateException("No next element") - this.itemStack = null - this.searched = false - return itemStack - } -} - -class NonEmptyMutableItemStackIterator(override val parent: MutableIterator) : NonEmptyItemStackIterator(parent), MutableIterator { - override fun remove() { - parent.remove() - } -} - -fun Iterator.nonEmpty() = NonEmptyItemStackIterator(this) -fun MutableIterator.nonEmpty() = NonEmptyMutableItemStackIterator(this) \ No newline at end of file +fun Iterator.nonEmpty() = PredicateIterator(this) { !it.isEmpty } +fun MutableIterator.nonEmpty() = MutablePredicateIterator(this) { !it.isEmpty } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/PredicateIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/PredicateIterator.kt new file mode 100644 index 000000000..92ba48e04 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/PredicateIterator.kt @@ -0,0 +1,91 @@ +package ru.dbotthepony.mc.otm.core + +import java.util.function.Consumer +import java.util.function.Predicate + +class PredicateIterator : Iterator { + private val parent: Iterator + private val predicate: Predicate + private val consumer: Consumer? + + constructor(parent: Iterator, predicate: Predicate) { + this.parent = parent + this.predicate = predicate + this.consumer = null + } + + constructor(parent: Iterator, predicate: Predicate, consumer: Consumer) { + this.parent = parent + this.predicate = predicate + this.consumer = consumer + } + + private var foundValue: Any? = Companion + + override fun hasNext(): Boolean { + if (foundValue === Companion) { + while (parent.hasNext()) { + val next = parent.next() + + if (predicate.test(next)) { + foundValue = next + return true + } + } + + return false + } + + return true + } + + @Suppress("unchecked_cast") + override fun next(): T { + if (!hasNext()) { + throw NoSuchElementException() + } + + val foundValue = foundValue + + if (foundValue === Companion) { + throw ConcurrentModificationException() + } + + this.foundValue = Companion + consumer?.accept(foundValue as T) + return foundValue as T + } + + private companion object +} + +fun Iterator.filter(condition: Predicate) = PredicateIterator(this, condition) + +class MutablePredicateIterator : MutableIterator { + private val parent: MutableIterator + private val predicateParent: PredicateIterator + + constructor(parent: MutableIterator, predicate: Predicate) { + this.parent = parent + this.predicateParent = PredicateIterator(parent, predicate) + } + + constructor(parent: MutableIterator, predicate: Predicate, consumer: Consumer) { + this.parent = parent + this.predicateParent = PredicateIterator(parent, predicate, consumer) + } + + override fun hasNext(): Boolean { + return predicateParent.hasNext() + } + + override fun next(): T { + return predicateParent.next() + } + + override fun remove() { + return parent.remove() + } +} + +fun MutableIterator.filter(condition: Predicate) = MutablePredicateIterator(this, condition)