Initial code for new containers
This commit is contained in:
parent
dfc35f393f
commit
ca6ff30414
@ -9,6 +9,7 @@ import it.unimi.dsi.fastutil.ints.IntIterator
|
||||
import it.unimi.dsi.fastutil.ints.IntList
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet
|
||||
import it.unimi.dsi.fastutil.ints.IntSet
|
||||
import it.unimi.dsi.fastutil.ints.IntSortedSet
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
@ -22,6 +23,7 @@ import ru.dbotthepony.mc.otm.container.util.ItemStackHashStrategy
|
||||
import ru.dbotthepony.mc.otm.container.util.containerSlot
|
||||
import ru.dbotthepony.mc.otm.container.util.slotIterator
|
||||
import ru.dbotthepony.mc.otm.core.addAll
|
||||
import ru.dbotthepony.mc.otm.core.collect.IntRange2Set
|
||||
import ru.dbotthepony.mc.otm.core.collect.filter
|
||||
import ru.dbotthepony.mc.otm.core.collect.toList
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
@ -38,89 +40,12 @@ inline operator fun Container.get(index: Int): ItemStack = getItem(index)
|
||||
@Suppress("nothing_to_inline")
|
||||
inline operator fun IFluidHandler.get(index: Int) = getFluidInTank(index)
|
||||
|
||||
val Container.slotRange: IntIterable get() {
|
||||
return IntIterable {
|
||||
val i = (0 until containerSize).iterator()
|
||||
|
||||
object : IntIterator {
|
||||
override fun hasNext(): Boolean {
|
||||
return i.hasNext()
|
||||
}
|
||||
|
||||
override fun remove() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun nextInt(): Int {
|
||||
return i.nextInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
val Container.slotRange: IntRange2Set get() {
|
||||
return IntRange2Set.openEnded(0, containerSize)
|
||||
}
|
||||
|
||||
fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange): ItemStack {
|
||||
if (this is IMatteryContainer) {
|
||||
return this.addItem(stack, simulate, slots)
|
||||
}
|
||||
|
||||
if (stack.isEmpty)
|
||||
return stack
|
||||
|
||||
val copy = stack.copy()
|
||||
|
||||
// двигаем в одинаковые слоты
|
||||
var i = slots.intIterator()
|
||||
|
||||
while (i.hasNext()) {
|
||||
val slot = i.nextInt()
|
||||
|
||||
if (ItemStack.isSameItemSameComponents(this[slot], copy)) {
|
||||
val slotStack = this[slot]
|
||||
val slotLimit = maxStackSize.coerceAtMost(slotStack.maxStackSize)
|
||||
|
||||
if (slotStack.count < slotLimit) {
|
||||
val newCount = (slotStack.count + copy.count).coerceAtMost(slotLimit)
|
||||
val diff = newCount - slotStack.count
|
||||
|
||||
if (!simulate) {
|
||||
slotStack.count = newCount
|
||||
setChanged()
|
||||
}
|
||||
|
||||
copy.shrink(diff)
|
||||
|
||||
if (copy.isEmpty) {
|
||||
return copy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// двигаем в пустые слоты
|
||||
i = slots.intIterator()
|
||||
|
||||
while (i.hasNext()) {
|
||||
val slot = i.nextInt()
|
||||
|
||||
if (this[slot].isEmpty) {
|
||||
val diff = copy.count.coerceAtMost(maxStackSize.coerceAtMost(copy.maxStackSize))
|
||||
|
||||
if (!simulate) {
|
||||
val copyToPut = copy.copy()
|
||||
copyToPut.count = diff
|
||||
this[slot] = copyToPut
|
||||
setChanged()
|
||||
}
|
||||
|
||||
copy.shrink(diff)
|
||||
|
||||
if (copy.isEmpty) {
|
||||
return copy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return copy
|
||||
fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntSortedSet = slotRange): ItemStack {
|
||||
return IEnhancedContainer.wrap(this).addItem(stack, simulate, slots)
|
||||
}
|
||||
|
||||
fun Container.vanishCursedItems() {
|
||||
@ -312,12 +237,18 @@ fun Container.sortWithIndices(sortedSlots: IntCollection) {
|
||||
if (value in 0 until containerSize && seen.add(value)) {
|
||||
val slot = containerSlot(value)
|
||||
|
||||
if (
|
||||
slot.isNotEmpty &&
|
||||
!slot.isForbiddenForAutomation &&
|
||||
slot.item.count <= slot.getMaxStackSize() &&
|
||||
(!slot.hasFilter || slot.getFilter() != slot.item.item || slot.getMaxStackSize() > 1)
|
||||
) {
|
||||
val condition: Boolean
|
||||
|
||||
if (slot is IFilteredContainerSlot) {
|
||||
condition = slot.isNotEmpty &&
|
||||
!slot.isForbiddenForAutomation &&
|
||||
slot.item.count <= slot.maxStackSize(slot.item) &&
|
||||
(!slot.hasFilter || slot.filter != slot.item.item || slot.maxStackSize(slot.item) > 1)
|
||||
} else {
|
||||
condition = slot.isNotEmpty && slot.item.count <= slot.maxStackSize(slot.item)
|
||||
}
|
||||
|
||||
if (condition) {
|
||||
valid.add(slot)
|
||||
}
|
||||
}
|
||||
@ -335,7 +266,7 @@ fun Container.computeSortedIndices(comparator: Comparator<ItemStack> = ItemStack
|
||||
if (isEmpty)
|
||||
return IntList.of()
|
||||
|
||||
val slots = slotIterator().filter { !it.isForbiddenForAutomation && it.getMaxStackSize() >= it.item.count }.toList()
|
||||
val slots = slotIterator().filter { (it !is IFilteredContainerSlot || !it.isForbiddenForAutomation) && it.maxStackSize(it.item) >= it.item.count }.toList()
|
||||
|
||||
if (slots.isEmpty())
|
||||
return IntList.of()
|
||||
|
171
src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSlot.kt
Normal file
171
src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSlot.kt
Normal file
@ -0,0 +1,171 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.NbtOps
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
import ru.dbotthepony.mc.otm.core.registryName
|
||||
import ru.dbotthepony.mc.otm.data.getOrNull
|
||||
|
||||
open class ContainerSlot(
|
||||
final override val container: SlottedContainer,
|
||||
final override val slot: Int
|
||||
) : ISlottedContainerSlot, INBTSerializable<CompoundTag> {
|
||||
private var _item: ItemStack = ItemStack.EMPTY
|
||||
|
||||
final override var item: ItemStack
|
||||
get() = _item
|
||||
set(value) {
|
||||
_item = value
|
||||
setChanged()
|
||||
}
|
||||
|
||||
private var observedItem = ItemStack.EMPTY
|
||||
|
||||
// called from inside setChanged
|
||||
protected open fun notifyChanged(old: ItemStack) {}
|
||||
|
||||
final override fun setChanged() {
|
||||
observeChanges()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when slot needs to be cleared of any data present
|
||||
*/
|
||||
open fun clear() {
|
||||
_item = ItemStack.EMPTY
|
||||
|
||||
if (observedItem.isNotEmpty) {
|
||||
notifyChanged(observedItem)
|
||||
}
|
||||
|
||||
observedItem = ItemStack.EMPTY
|
||||
}
|
||||
|
||||
fun observeChanges(): Boolean {
|
||||
if (observedItem.count != item.count || !ItemStack.isSameItemSameComponents(item, observedItem)) {
|
||||
notifyChanged(observedItem)
|
||||
observedItem = item.copy()
|
||||
container.notifyChanged()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun remove(): ItemStack {
|
||||
val item = item
|
||||
|
||||
if (item.isEmpty) {
|
||||
return ItemStack.EMPTY
|
||||
} else {
|
||||
this.item = ItemStack.EMPTY
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
override val maxStackSize: Int
|
||||
get() = Item.DEFAULT_MAX_STACK_SIZE
|
||||
|
||||
override fun remove(count: Int): ItemStack {
|
||||
val item = item
|
||||
|
||||
if (item.isEmpty) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
|
||||
if (item.count >= count) {
|
||||
this.item = ItemStack.EMPTY
|
||||
return item
|
||||
} else {
|
||||
val split = item.split(count)
|
||||
setChanged()
|
||||
return split
|
||||
}
|
||||
}
|
||||
|
||||
override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag {
|
||||
return CompoundTag().also {
|
||||
it["item"] = ItemStack.OPTIONAL_CODEC.encodeStart(provider.createSerializationContext(NbtOps.INSTANCE), item)
|
||||
.getOrThrow { RuntimeException("Unable to serialize $item in slot $slot: $it") }
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) {
|
||||
_item = ItemStack.OPTIONAL_CODEC.decode(provider.createSerializationContext(NbtOps.INSTANCE), nbt["item"])
|
||||
.ifError { LOGGER.error("Unable to deserialize item at slot $slot: ${it.message()}") }
|
||||
.getOrNull()?.first ?: ItemStack.EMPTY
|
||||
|
||||
observedItem = item.copy()
|
||||
notifyChanged(ItemStack.EMPTY)
|
||||
}
|
||||
|
||||
open class Simple(
|
||||
protected val listener: (new: ItemStack, old: ItemStack) -> Unit,
|
||||
protected val maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE,
|
||||
) : SlottedContainerBuilder.SlotProvider {
|
||||
protected open inner class Instance(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) {
|
||||
override val maxStackSize: Int
|
||||
get() = this@Simple.maxStackSize
|
||||
|
||||
override fun notifyChanged(old: ItemStack) {
|
||||
super.notifyChanged(old)
|
||||
listener(item, old)
|
||||
}
|
||||
}
|
||||
|
||||
override fun create(container: SlottedContainer, index: Int): ContainerSlot {
|
||||
return Instance(container, index)
|
||||
}
|
||||
}
|
||||
|
||||
open class Filtered(
|
||||
listener: (new: ItemStack, old: ItemStack) -> Unit,
|
||||
maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE,
|
||||
) : Simple(listener, maxStackSize) {
|
||||
protected open inner class Instance(container: SlottedContainer, slot: Int) : Simple.Instance(container, slot), IFilteredSlottedContainerSlot {
|
||||
override var filter: Item? = null
|
||||
set(value) {
|
||||
if (field !== value) {
|
||||
field = value
|
||||
container.notifyChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
super.clear()
|
||||
filter = null
|
||||
}
|
||||
|
||||
override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag {
|
||||
return super.serializeNBT(provider).also {
|
||||
if (filter != null)
|
||||
it["filter"] = filter!!.registryName!!.toString()
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) {
|
||||
super.deserializeNBT(provider, nbt)
|
||||
|
||||
if ("filter" in nbt) {
|
||||
filter = BuiltInRegistries.ITEM.get(ResourceLocation.parse(nbt.getString("filter")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun create(container: SlottedContainer, index: Int): ContainerSlot {
|
||||
return Instance(container, index)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import ru.dbotthepony.kommons.util.Delegate
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
|
||||
@ -15,54 +13,67 @@ interface IContainerSlot : Delegate<ItemStack> {
|
||||
val slot: Int
|
||||
val container: Container
|
||||
|
||||
fun setChanged()
|
||||
var item: ItemStack
|
||||
|
||||
operator fun component1() = slot
|
||||
operator fun component2() = item
|
||||
|
||||
fun getMaxStackSize(item: ItemStack = this.item): Int {
|
||||
return container.maxStackSize
|
||||
}
|
||||
|
||||
val isForbiddenForAutomation: Boolean get() {
|
||||
return getFilter() === Items.AIR
|
||||
}
|
||||
|
||||
fun getFilter(): Item?
|
||||
/**
|
||||
* Max stack size regardless of item
|
||||
*
|
||||
* Prefer to use ItemStack version instead
|
||||
*/
|
||||
val maxStackSize: Int
|
||||
|
||||
/**
|
||||
* @return whenever the filter was set. Returns false only if container can't be filtered.
|
||||
* Max amount of [item] that can be stored in this slot.
|
||||
*
|
||||
* This may be larger or smaller than value returned [ItemStack.getMaxStackSize],
|
||||
* and as such returned value by this method should be ground truth
|
||||
*/
|
||||
fun setFilter(filter: Item? = null): Boolean
|
||||
|
||||
val hasFilter: Boolean
|
||||
get() = getFilter() != null
|
||||
|
||||
fun remove() {
|
||||
container[slot] = ItemStack.EMPTY
|
||||
fun maxStackSize(item: ItemStack): Int {
|
||||
return maxStackSize.coerceAtMost(item.maxStackSize)
|
||||
}
|
||||
|
||||
fun remove(count: Int): ItemStack {
|
||||
return container.removeItem(slot, count)
|
||||
}
|
||||
fun remove(): ItemStack
|
||||
fun remove(count: Int): ItemStack
|
||||
|
||||
override fun get(): ItemStack {
|
||||
return container[slot]
|
||||
return item
|
||||
}
|
||||
|
||||
override fun accept(t: ItemStack) {
|
||||
container[slot] = t
|
||||
item = t
|
||||
}
|
||||
|
||||
fun setChanged() {
|
||||
container.setChanged()
|
||||
}
|
||||
|
||||
var item: ItemStack
|
||||
get() = container[slot]
|
||||
set(value) { container[slot] = value }
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = container[slot].isEmpty
|
||||
get() = item.isEmpty
|
||||
|
||||
val isNotEmpty: Boolean
|
||||
get() = container[slot].isNotEmpty
|
||||
get() = item.isNotEmpty
|
||||
|
||||
class Simple(override val slot: Int, override val container: Container) : IContainerSlot {
|
||||
override fun setChanged() {
|
||||
container.setChanged()
|
||||
}
|
||||
|
||||
override var item: ItemStack
|
||||
get() = container[slot]
|
||||
set(value) { container[slot] = value }
|
||||
override val maxStackSize: Int
|
||||
get() = container.maxStackSize
|
||||
|
||||
override fun remove(count: Int): ItemStack {
|
||||
return container.removeItem(slot, count)
|
||||
}
|
||||
|
||||
override fun remove(): ItemStack {
|
||||
return container.removeItemNoUpdate(slot)
|
||||
}
|
||||
|
||||
override fun maxStackSize(item: ItemStack): Int {
|
||||
return maxStackSize.coerceAtMost(item.maxStackSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,338 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntSet
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.item.crafting.RecipeInput
|
||||
import ru.dbotthepony.mc.otm.core.collect.any
|
||||
import ru.dbotthepony.mc.otm.core.collect.filter
|
||||
import ru.dbotthepony.mc.otm.core.collect.map
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
import java.util.function.Predicate
|
||||
|
||||
/**
|
||||
* "Backward-compatible" enhanced container interface, where all methods can be derived/emulated from existing [IContainer] ([Container]) code
|
||||
*
|
||||
* This is useful because it allows to interact with actually enhanced and regular containers through unified interface,
|
||||
* and actual implementations of this interface are likely to provide efficient method implementations in place of derived/emulated ones.
|
||||
*/
|
||||
interface IEnhancedContainer : IContainer, RecipeInput, Iterable<ItemStack> {
|
||||
fun containerSlot(slot: Int): IContainerSlot {
|
||||
return IContainerSlot.Simple(slot, this)
|
||||
}
|
||||
|
||||
fun slotIterator(): Iterator<IContainerSlot> {
|
||||
return (0 until containerSize).iterator().map { containerSlot(it) }
|
||||
}
|
||||
|
||||
fun nonEmptySlotIterator(): Iterator<IContainerSlot> {
|
||||
return (0 until containerSize).iterator().map { containerSlot(it) }.filter { it.isNotEmpty }
|
||||
}
|
||||
|
||||
private fun slotIterator(allowedSlots: IntSet, predicate: Predicate<ItemStack>): IntIterator {
|
||||
return object : IntIterator() {
|
||||
private val parent = allowedSlots.intIterator()
|
||||
private var foundNext = false
|
||||
private var next = -1
|
||||
|
||||
private fun findNext() {
|
||||
if (!foundNext) {
|
||||
foundNext = true
|
||||
next = -1
|
||||
|
||||
while (parent.hasNext()) {
|
||||
val i = parent.nextInt()
|
||||
|
||||
if (predicate.test(this@IEnhancedContainer[i])) {
|
||||
next = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun nextInt(): Int {
|
||||
findNext()
|
||||
|
||||
if (next == -1)
|
||||
throw NoSuchElementException()
|
||||
|
||||
foundNext = false
|
||||
val next = next
|
||||
this.next = -1
|
||||
return next
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
findNext()
|
||||
return next != -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun emptySlotIterator(allowedSlots: IntSet = slotRange): IntIterator {
|
||||
return slotIterator(allowedSlots) { it.isEmpty }
|
||||
}
|
||||
|
||||
fun nonEmptySlotIterator(allowedSlots: IntSet = slotRange): IntIterator {
|
||||
return slotIterator(allowedSlots) { it.isNotEmpty }
|
||||
}
|
||||
|
||||
fun slotWithItemIterator(item: Item, allowedSlots: IntSet = slotRange): IntIterator {
|
||||
return slotIterator(allowedSlots) { it.isNotEmpty && it.item === item }
|
||||
}
|
||||
|
||||
fun nextEmptySlot(startIndex: Int): Int {
|
||||
for (i in startIndex until containerSize) {
|
||||
if (this[i].isEmpty) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
fun nextNonEmptySlot(startIndex: Int): Int {
|
||||
for (i in startIndex until containerSize) {
|
||||
if (this[i].isNotEmpty) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
fun nextSlotWithItem(startIndex: Int, item: Item): Int {
|
||||
var find = nextNonEmptySlot(startIndex)
|
||||
|
||||
while (find != -1 && this[find].item !== item)
|
||||
find = nextNonEmptySlot(find + 1)
|
||||
|
||||
return find
|
||||
}
|
||||
|
||||
fun setChanged(slot: Int) {
|
||||
return setChanged()
|
||||
}
|
||||
|
||||
fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int {
|
||||
return maxStackSize
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<ItemStack> {
|
||||
return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty }
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean {
|
||||
return super.isEmpty()
|
||||
}
|
||||
|
||||
val hasEmptySlots: Boolean get() {
|
||||
for (i in 0 until containerSize) {
|
||||
if (this[i].isEmpty) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun size(): Int {
|
||||
return containerSize
|
||||
}
|
||||
|
||||
override fun countItem(item: Item): Int {
|
||||
var count = 0
|
||||
|
||||
for (stack in this) {
|
||||
if (stack.item === item) {
|
||||
count += stack.count
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
override fun hasAnyOf(items: Set<Item>): Boolean {
|
||||
if (Items.AIR in items && hasEmptySlots)
|
||||
return true
|
||||
|
||||
return iterator().any { it.item in items }
|
||||
}
|
||||
|
||||
override fun hasAnyMatching(predicate: Predicate<ItemStack>): Boolean {
|
||||
if (predicate.test(ItemStack.EMPTY) && hasEmptySlots)
|
||||
return true
|
||||
|
||||
return iterator().any(predicate)
|
||||
}
|
||||
|
||||
fun toList(): MutableList<ItemStack> {
|
||||
val list = ArrayList<ItemStack>(containerSize)
|
||||
|
||||
for (i in 0 until containerSize) {
|
||||
list.add(this[i])
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
fun addItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null): ItemStack {
|
||||
if (stack.isEmpty || slots.isEmpty())
|
||||
return stack
|
||||
|
||||
val copy = stack.copy()
|
||||
|
||||
// двигаем в одинаковые слоты
|
||||
for (slot in slotWithItemIterator(stack.item, slots)) {
|
||||
if (ItemStack.isSameItemSameComponents(this[slot], copy)) {
|
||||
val slotStack = this[slot]
|
||||
val slotLimit = getMaxStackSize(slot, slotStack)
|
||||
|
||||
if (slotStack.count < slotLimit) {
|
||||
val newCount = (slotStack.count + copy.count).coerceAtMost(slotLimit)
|
||||
val diff = newCount - slotStack.count
|
||||
|
||||
if (!simulate) {
|
||||
slotStack.count = newCount
|
||||
setChanged(slot)
|
||||
|
||||
if (popTime != null) {
|
||||
slotStack.popTime = popTime
|
||||
}
|
||||
}
|
||||
|
||||
copy.shrink(diff)
|
||||
|
||||
if (copy.isEmpty) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!onlyIntoExisting) {
|
||||
// двигаем в пустые слоты
|
||||
for (slot in emptySlotIterator(slots)) {
|
||||
val diff = copy.count.coerceAtMost(getMaxStackSize(slot, stack))
|
||||
|
||||
if (!simulate) {
|
||||
val copyToPut = copy.copy()
|
||||
copyToPut.count = diff
|
||||
this[slot] = copyToPut
|
||||
setChanged()
|
||||
}
|
||||
|
||||
copy.shrink(diff)
|
||||
|
||||
if (copy.isEmpty)
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
return copy
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlike [addItem], modifies original [stack]
|
||||
*
|
||||
* @return Whenever [stack] was modified
|
||||
*/
|
||||
fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null): Boolean {
|
||||
if (stack.isEmpty || slots.isEmpty())
|
||||
return false
|
||||
|
||||
val result = addItem(stack, simulate, slots, onlyIntoExisting, popTime)
|
||||
if (!simulate) stack.count = result.count
|
||||
return result.count != stack.count
|
||||
}
|
||||
|
||||
fun fullyAddItem(stack: ItemStack, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null): Boolean {
|
||||
if (!addItem(stack, true, slots, onlyIntoExisting, popTime).isEmpty)
|
||||
return false
|
||||
|
||||
return addItem(stack, false, slots, onlyIntoExisting, popTime).isEmpty
|
||||
}
|
||||
|
||||
private class Wrapper(private val parent: Container) : IEnhancedContainer {
|
||||
override fun clearContent() {
|
||||
return parent.clearContent()
|
||||
}
|
||||
|
||||
override fun setChanged() {
|
||||
return parent.setChanged()
|
||||
}
|
||||
|
||||
override fun getContainerSize(): Int {
|
||||
return parent.containerSize
|
||||
}
|
||||
|
||||
override fun getItem(slot: Int): ItemStack {
|
||||
return parent.getItem(slot)
|
||||
}
|
||||
|
||||
override fun removeItem(slot: Int, amount: Int): ItemStack {
|
||||
return parent.removeItem(slot, amount)
|
||||
}
|
||||
|
||||
override fun removeItemNoUpdate(slot: Int): ItemStack {
|
||||
return parent.removeItemNoUpdate(slot)
|
||||
}
|
||||
|
||||
override fun setItem(slot: Int, itemStack: ItemStack) {
|
||||
return parent.setItem(slot, itemStack)
|
||||
}
|
||||
|
||||
override fun stillValid(player: Player): Boolean {
|
||||
return parent.stillValid(player)
|
||||
}
|
||||
|
||||
override fun getMaxStackSize(): Int {
|
||||
return parent.maxStackSize
|
||||
}
|
||||
|
||||
override fun startOpen(player: Player) {
|
||||
parent.startOpen(player)
|
||||
}
|
||||
|
||||
override fun stopOpen(player: Player) {
|
||||
parent.stopOpen(player)
|
||||
}
|
||||
|
||||
override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean {
|
||||
return parent.canPlaceItem(slot, itemStack)
|
||||
}
|
||||
|
||||
override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean {
|
||||
return parent.canTakeItem(container, slot, itemStack)
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean {
|
||||
return parent.isEmpty
|
||||
}
|
||||
|
||||
override fun hasAnyMatching(predicate: Predicate<ItemStack>): Boolean {
|
||||
return parent.hasAnyMatching(predicate)
|
||||
}
|
||||
|
||||
override fun hasAnyOf(items: Set<Item>): Boolean {
|
||||
return parent.hasAnyOf(items)
|
||||
}
|
||||
|
||||
override fun countItem(item: Item): Int {
|
||||
return parent.countItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun wrap(other: Container): IEnhancedContainer {
|
||||
if (other is IEnhancedContainer)
|
||||
return other
|
||||
|
||||
return Wrapper(other)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
|
||||
interface IFilteredContainerSlot : IContainerSlot {
|
||||
var filter: Item?
|
||||
|
||||
val isForbiddenForAutomation: Boolean get() {
|
||||
return filter === Items.AIR
|
||||
}
|
||||
|
||||
val hasFilter: Boolean
|
||||
get() = filter != null
|
||||
|
||||
|
||||
fun testSlotFilter(itemStack: ItemStack): Boolean {
|
||||
return testSlotFilter(itemStack.item)
|
||||
}
|
||||
|
||||
fun testSlotFilter(item: Item): Boolean {
|
||||
if (filter == null) {
|
||||
return true
|
||||
} else {
|
||||
return filter === item
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
interface IFilteredSlottedContainerSlot : IFilteredContainerSlot, ISlottedContainerSlot {
|
||||
override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean {
|
||||
return super.canAutomationPlaceItem(itemStack) && testSlotFilter(itemStack)
|
||||
}
|
||||
}
|
@ -50,27 +50,6 @@ interface IMatteryContainer : IContainer, RecipeInput, Iterable<ItemStack> {
|
||||
}
|
||||
}
|
||||
|
||||
open class ContainerSlot(override val slot: Int, override val container: IMatteryContainer) : IContainerSlot {
|
||||
override val isForbiddenForAutomation: Boolean
|
||||
get() = container.isSlotForbiddenForAutomation(slot)
|
||||
|
||||
override fun getFilter(): Item? {
|
||||
return container.getSlotFilter(slot)
|
||||
}
|
||||
|
||||
override fun setFilter(filter: Item?): Boolean {
|
||||
return container.setSlotFilter(slot, filter)
|
||||
}
|
||||
|
||||
override fun getMaxStackSize(item: ItemStack): Int {
|
||||
return container.getMaxStackSize(slot, item)
|
||||
}
|
||||
|
||||
override fun setChanged() {
|
||||
container.setChanged(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates either non-empty slots of container or all slots of container
|
||||
*/
|
||||
@ -83,7 +62,7 @@ interface IMatteryContainer : IContainer, RecipeInput, Iterable<ItemStack> {
|
||||
}
|
||||
|
||||
fun containerSlot(slot: Int): IContainerSlot {
|
||||
return ContainerSlot(slot, this)
|
||||
return IContainerSlot.Simple(slot, this)
|
||||
}
|
||||
|
||||
fun hasSlotFilter(slot: Int) = getSlotFilter(slot) !== null
|
||||
@ -234,22 +213,4 @@ interface IMatteryContainer : IContainer, RecipeInput, Iterable<ItemStack> {
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
fun shrink(slot: Int, amount: Int): Boolean {
|
||||
if (slot < 0 || slot > size())
|
||||
return false
|
||||
|
||||
val item = this[slot]
|
||||
if (item.isEmpty)
|
||||
return false
|
||||
|
||||
if (item.count <= amount) {
|
||||
this[slot] = ItemStack.EMPTY
|
||||
} else {
|
||||
item.shrink(amount)
|
||||
setChanged(slot)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,220 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntSet
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.neoforged.neoforge.items.IItemHandler
|
||||
import ru.dbotthepony.kommons.collect.any
|
||||
import ru.dbotthepony.mc.otm.core.collect.filter
|
||||
import ru.dbotthepony.mc.otm.core.collect.map
|
||||
|
||||
/**
|
||||
* Container which revolve around embedding slot objects rather than providing direct item access,
|
||||
* and subsequently fully implement [IItemHandler]
|
||||
*/
|
||||
interface ISlottedContainer : IEnhancedContainer, IItemHandler {
|
||||
override fun containerSlot(slot: Int): ISlottedContainerSlot
|
||||
|
||||
override fun slotIterator(): Iterator<ISlottedContainerSlot> {
|
||||
return (0 until containerSize).iterator().map { containerSlot(it) }
|
||||
}
|
||||
|
||||
override fun nonEmptySlotIterator(): Iterator<ISlottedContainerSlot> {
|
||||
return (0 until containerSize).iterator().map { containerSlot(it) }.filter { it.isNotEmpty }
|
||||
}
|
||||
|
||||
override fun setChanged(slot: Int) {
|
||||
containerSlot(slot).setChanged()
|
||||
}
|
||||
|
||||
override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int {
|
||||
return containerSlot(slot).maxStackSize(itemStack)
|
||||
}
|
||||
|
||||
override fun getItem(slot: Int): ItemStack {
|
||||
return containerSlot(slot).item
|
||||
}
|
||||
|
||||
override fun removeItem(slot: Int, amount: Int): ItemStack {
|
||||
return containerSlot(slot).remove(amount)
|
||||
}
|
||||
|
||||
override fun removeItemNoUpdate(slot: Int): ItemStack {
|
||||
return containerSlot(slot).remove()
|
||||
}
|
||||
|
||||
override fun setItem(slot: Int, itemStack: ItemStack) {
|
||||
containerSlot(slot).item = itemStack
|
||||
}
|
||||
|
||||
override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean {
|
||||
return containerSlot(slot).canAutomationPlaceItem(itemStack)
|
||||
}
|
||||
|
||||
override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean {
|
||||
return containerSlot(slot).canAutomationTakeItem()
|
||||
}
|
||||
|
||||
override fun getSlots() = containerSize
|
||||
override fun getStackInSlot(slot: Int) = containerSlot(slot).item
|
||||
|
||||
override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack {
|
||||
return containerSlot(slot).insertItem(stack, simulate)
|
||||
}
|
||||
|
||||
override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack {
|
||||
return containerSlot(slot).extractItem(amount, simulate)
|
||||
}
|
||||
|
||||
override fun getSlotLimit(slot: Int): Int {
|
||||
return containerSlot(slot).maxStackSize
|
||||
}
|
||||
|
||||
override fun isItemValid(slot: Int, stack: ItemStack): Boolean {
|
||||
return canPlaceItem(slot, stack)
|
||||
}
|
||||
|
||||
private fun addItem(stack: ItemStack, simulate: Boolean, filterPass: Boolean, slots: IntSet, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack {
|
||||
if (stack.isEmpty || slots.isEmpty())
|
||||
return stack
|
||||
|
||||
// двигаем в одинаковые слоты
|
||||
for (i in slotWithItemIterator(stack.item, slots)) {
|
||||
val slot = containerSlot(i)
|
||||
|
||||
val condition: Boolean
|
||||
|
||||
if (slot is IFilteredContainerSlot) {
|
||||
condition = (ignoreFilters || !slot.isForbiddenForAutomation) &&
|
||||
ItemStack.isSameItemSameComponents(slot.item, stack) &&
|
||||
(ignoreFilters || !filterPass && !slot.hasFilter || filterPass && slot.hasFilter && slot.testSlotFilter(stack))
|
||||
} else {
|
||||
condition = (ignoreFilters || !filterPass) && ItemStack.isSameItemSameComponents(slot.item, stack)
|
||||
}
|
||||
|
||||
if (condition) {
|
||||
val slotLimit = slot.maxStackSize(slot.item)
|
||||
|
||||
if (slot.item.count < slotLimit) {
|
||||
val newCount = (slot.item.count + stack.count).coerceAtMost(slotLimit)
|
||||
val diff = newCount - slot.item.count
|
||||
|
||||
if (!simulate) {
|
||||
slot.item.count = newCount
|
||||
slot.setChanged()
|
||||
|
||||
if (popTime != null) {
|
||||
slot.item.popTime = popTime
|
||||
}
|
||||
}
|
||||
|
||||
stack.shrink(diff)
|
||||
|
||||
if (stack.isEmpty)
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!onlyIntoExisting) {
|
||||
for (i in emptySlotIterator(slots)) {
|
||||
val slot = containerSlot(i)
|
||||
|
||||
val condition: Boolean
|
||||
|
||||
if (slot is IFilteredContainerSlot) {
|
||||
condition = (ignoreFilters || !slot.isForbiddenForAutomation) &&
|
||||
(ignoreFilters || !filterPass && !slot.hasFilter || filterPass && slot.hasFilter && slot.testSlotFilter(stack))
|
||||
} else {
|
||||
condition = ignoreFilters || !filterPass
|
||||
}
|
||||
|
||||
if (condition) {
|
||||
val diff = stack.count.coerceAtMost(slot.maxStackSize(stack))
|
||||
|
||||
if (!simulate) {
|
||||
val copyToPut = stack.copy()
|
||||
copyToPut.count = diff
|
||||
slot.item = copyToPut
|
||||
|
||||
if (popTime != null) {
|
||||
copyToPut.popTime = popTime
|
||||
}
|
||||
}
|
||||
|
||||
stack.shrink(diff)
|
||||
|
||||
if (stack.isEmpty)
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
/**
|
||||
* Hint used internally by [ISlottedContainer] to potentially speed up default method implementations
|
||||
*/
|
||||
val hasFilterableSlots: Boolean
|
||||
get() = slotIterator().any { it is IFilteredContainerSlot }
|
||||
|
||||
fun addItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): ItemStack {
|
||||
if (stack.isEmpty || slots.isEmpty())
|
||||
return stack
|
||||
|
||||
if (ignoreFilters || !hasFilterableSlots) {
|
||||
return addItem(stack.copy(), simulate, filterPass = true, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = true)
|
||||
} else {
|
||||
var copy = addItem(stack.copy(), simulate, filterPass = true, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = false)
|
||||
copy = addItem(copy, simulate, filterPass = false, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = false)
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unlike [addItem], modifies original [stack]
|
||||
*
|
||||
* @return Whenever [stack] was modified
|
||||
*/
|
||||
fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): Boolean {
|
||||
if (stack.isEmpty)
|
||||
return false
|
||||
|
||||
val result = addItem(stack, simulate, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = ignoreFilters)
|
||||
if (!simulate) stack.count = result.count
|
||||
return result.count != stack.count
|
||||
}
|
||||
|
||||
fun fullyAddItem(stack: ItemStack, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): Boolean {
|
||||
if (!addItem(stack, true, slots, popTime = popTime, onlyIntoExisting = onlyIntoExisting, ignoreFilters = ignoreFilters).isEmpty)
|
||||
return false
|
||||
|
||||
return addItem(stack, false, slots, popTime = popTime, onlyIntoExisting = onlyIntoExisting, ignoreFilters = ignoreFilters).isEmpty
|
||||
}
|
||||
|
||||
override fun addItem(
|
||||
stack: ItemStack,
|
||||
simulate: Boolean,
|
||||
slots: IntSet,
|
||||
onlyIntoExisting: Boolean,
|
||||
popTime: Int?
|
||||
): ItemStack {
|
||||
return addItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters = false)
|
||||
}
|
||||
|
||||
override fun consumeItem(
|
||||
stack: ItemStack,
|
||||
simulate: Boolean,
|
||||
slots: IntSet,
|
||||
onlyIntoExisting: Boolean,
|
||||
popTime: Int?
|
||||
): Boolean {
|
||||
return consumeItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters = false)
|
||||
}
|
||||
|
||||
override fun fullyAddItem(stack: ItemStack, slots: IntSet, onlyIntoExisting: Boolean, popTime: Int?): Boolean {
|
||||
return fullyAddItem(stack, slots, onlyIntoExisting, popTime, ignoreFilters = false)
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.neoforged.neoforge.items.IItemHandler
|
||||
|
||||
/**
|
||||
* Slot of [ISlottedContainer], with additional methods to implement interaction behavior for both for players and mechanisms
|
||||
*/
|
||||
interface ISlottedContainerSlot : IContainerSlot {
|
||||
override val container: ISlottedContainer
|
||||
|
||||
fun canAutomationPlaceItem(itemStack: ItemStack): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun canAutomationTakeItem(desired: Int = item.count): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun modifyAutomationPlaceCount(itemStack: ItemStack): Int {
|
||||
return itemStack.count
|
||||
}
|
||||
|
||||
fun modifyAutomationExtractionCount(desired: Int): Int {
|
||||
return desired
|
||||
}
|
||||
|
||||
/**
|
||||
* Slot-specific implementation for [IItemHandler.insertItem]
|
||||
*/
|
||||
fun insertItem(stack: ItemStack, simulate: Boolean): ItemStack {
|
||||
if (!canAutomationPlaceItem(stack))
|
||||
return stack
|
||||
|
||||
var amount = modifyAutomationPlaceCount(stack)
|
||||
|
||||
if (amount <= 0)
|
||||
return stack
|
||||
|
||||
if (item.isEmpty) {
|
||||
amount = stack.count.coerceAtMost(maxStackSize(stack)).coerceAtMost(amount)
|
||||
|
||||
if (!simulate) {
|
||||
item = stack.copyWithCount(amount)
|
||||
}
|
||||
|
||||
if (stack.count <= amount) {
|
||||
return ItemStack.EMPTY
|
||||
} else {
|
||||
return stack.copyWithCount(stack.count - amount)
|
||||
}
|
||||
} else if (item.isStackable && maxStackSize(item) > item.count && ItemStack.isSameItemSameComponents(item, stack)) {
|
||||
val newCount = maxStackSize(item).coerceAtMost(item.count + stack.count.coerceAtMost(amount))
|
||||
val diff = newCount - item.count
|
||||
|
||||
if (diff != 0) {
|
||||
if (!simulate) {
|
||||
item.grow(diff)
|
||||
setChanged()
|
||||
}
|
||||
|
||||
val copy = stack.copy()
|
||||
copy.shrink(diff)
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
fun extractItem(amount: Int, simulate: Boolean): ItemStack {
|
||||
if (amount <= 0 || !canAutomationTakeItem(amount) || item.isEmpty)
|
||||
return ItemStack.EMPTY
|
||||
|
||||
@Suppress("name_shadowing")
|
||||
val amount = modifyAutomationExtractionCount(amount)
|
||||
if (amount <= 0 || !canAutomationTakeItem(amount)) return ItemStack.EMPTY
|
||||
|
||||
val minimal = amount.coerceAtMost(item.count)
|
||||
val copy = item.copy()
|
||||
copy.count = minimal
|
||||
|
||||
if (!simulate) {
|
||||
if (item.count == minimal) {
|
||||
item = ItemStack.EMPTY
|
||||
} else {
|
||||
item.shrink(minimal)
|
||||
}
|
||||
|
||||
setChanged()
|
||||
}
|
||||
|
||||
return copy
|
||||
}
|
||||
}
|
@ -426,22 +426,32 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Slot(override val slot: Int) : IContainerSlot {
|
||||
private inner class Slot(override val slot: Int) : IFilteredContainerSlot {
|
||||
override val container: Container
|
||||
get() = this@MatteryContainer
|
||||
|
||||
override var item: ItemStack
|
||||
get() = this@MatteryContainer[slot]
|
||||
set(value) { this@MatteryContainer[slot] = value }
|
||||
override val maxStackSize: Int
|
||||
get() = this@MatteryContainer.maxStackSize
|
||||
|
||||
override fun remove(): ItemStack {
|
||||
return removeItemNoUpdate(slot)
|
||||
}
|
||||
|
||||
override fun remove(count: Int): ItemStack {
|
||||
return removeItem(slot, count)
|
||||
}
|
||||
|
||||
override var filter: Item?
|
||||
get() = getSlotFilter(slot)
|
||||
set(value) { setSlotFilter(slot, value) }
|
||||
|
||||
override val isForbiddenForAutomation: Boolean
|
||||
get() = isSlotForbiddenForAutomation(slot)
|
||||
|
||||
override fun getFilter(): Item? {
|
||||
return getSlotFilter(slot)
|
||||
}
|
||||
|
||||
override fun setFilter(filter: Item?): Boolean {
|
||||
return setSlotFilter(slot, filter)
|
||||
}
|
||||
|
||||
override fun getMaxStackSize(item: ItemStack): Int {
|
||||
override fun maxStackSize(item: ItemStack): Int {
|
||||
return getMaxStackSize(slot, item)
|
||||
}
|
||||
|
||||
@ -450,12 +460,12 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I
|
||||
}
|
||||
}
|
||||
|
||||
final override fun slotIterator(): kotlin.collections.Iterator<IContainerSlot> {
|
||||
final override fun slotIterator(): kotlin.collections.Iterator<IFilteredContainerSlot> {
|
||||
indicesReferenced = true
|
||||
return nonEmptyIndices.iterator().map { Slot(it) }
|
||||
}
|
||||
|
||||
final override fun slotIterator(nonEmpty: Boolean): kotlin.collections.Iterator<IContainerSlot> {
|
||||
final override fun slotIterator(nonEmpty: Boolean): kotlin.collections.Iterator<IFilteredContainerSlot> {
|
||||
if (!nonEmpty) {
|
||||
return (0 until size).iterator().map { Slot(it) }
|
||||
} else if (isEmpty) {
|
||||
@ -466,7 +476,7 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I
|
||||
}
|
||||
}
|
||||
|
||||
final override fun containerSlot(slot: Int): IContainerSlot {
|
||||
final override fun containerSlot(slot: Int): IFilteredContainerSlot {
|
||||
return Slot(slot)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,177 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.nbt.NbtOps
|
||||
import net.minecraft.nbt.Tag
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.mc.otm.data.codec.minRange
|
||||
import java.util.function.Predicate
|
||||
|
||||
class SlottedContainer(
|
||||
slots: Collection<SlottedContainerBuilder.SlotProvider>,
|
||||
private val stillValid: Predicate<Player>,
|
||||
private val globalChangeListeners: Array<Runnable>
|
||||
) : ISlottedContainer, INBTSerializable<Tag> {
|
||||
private val slots: Array<ContainerSlot>
|
||||
|
||||
init {
|
||||
val itr = slots.iterator()
|
||||
this.slots = Array(slots.size) { itr.next().create(this, it) }
|
||||
}
|
||||
|
||||
override val hasFilterableSlots: Boolean = this.slots.any { it is IFilteredContainerSlot }
|
||||
private var suppressListeners = false
|
||||
|
||||
override fun clearContent() {
|
||||
suppressListeners = true
|
||||
|
||||
try {
|
||||
slots.forEach { it.remove() }
|
||||
notifyChanged()
|
||||
} finally {
|
||||
suppressListeners = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun containerSlot(slot: Int): ISlottedContainerSlot {
|
||||
return slots[slot]
|
||||
}
|
||||
|
||||
fun notifyChanged() {
|
||||
if (suppressListeners) return
|
||||
globalChangeListeners.forEach { it.run() }
|
||||
}
|
||||
|
||||
// called by outside code (vanilla and other unaware mods)
|
||||
override fun setChanged() {
|
||||
suppressListeners = true
|
||||
var hasChanges = false
|
||||
|
||||
try {
|
||||
slots.forEach { hasChanges = it.observeChanges() || hasChanges }
|
||||
} finally {
|
||||
suppressListeners = false
|
||||
|
||||
if (hasChanges)
|
||||
notifyChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getContainerSize(): Int {
|
||||
return slots.size
|
||||
}
|
||||
|
||||
override fun stillValid(player: Player): Boolean {
|
||||
return stillValid.test(player)
|
||||
}
|
||||
|
||||
private data class LegacySerializedItem(val item: ItemStack, val slot: Int) {
|
||||
companion object {
|
||||
val CODEC: Codec<LegacySerializedItem> = RecordCodecBuilder.create {
|
||||
it.group(
|
||||
ItemStack.OPTIONAL_CODEC.fieldOf("item").forGetter { it.item },
|
||||
Codec.INT.minRange(0).fieldOf("slot").forGetter { it.slot },
|
||||
).apply(it, ::LegacySerializedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class LegacySerializedFilter(val item: Item, val slot: Int) {
|
||||
companion object {
|
||||
val CODEC: Codec<LegacySerializedFilter> = RecordCodecBuilder.create {
|
||||
it.group(
|
||||
BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter { it.item },
|
||||
Codec.INT.minRange(0).fieldOf("slot").forGetter { it.slot },
|
||||
).apply(it, ::LegacySerializedFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class LegacySerializedState(
|
||||
val items: List<LegacySerializedItem>,
|
||||
val filters: List<LegacySerializedFilter>
|
||||
) {
|
||||
companion object {
|
||||
val CODEC: Codec<LegacySerializedState> = RecordCodecBuilder.create {
|
||||
it.group(
|
||||
Codec.list(LegacySerializedItem.CODEC).fieldOf("items").forGetter { it.items },
|
||||
Codec.list(LegacySerializedFilter.CODEC).fieldOf("filters").forGetter { it.filters },
|
||||
).apply(it, ::LegacySerializedState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val lostItems = ArrayList<CompoundTag>()
|
||||
|
||||
override fun serializeNBT(provider: HolderLookup.Provider): ListTag {
|
||||
return ListTag().also {
|
||||
for (slot in slots) {
|
||||
it.add(slot.serializeNBT(provider))
|
||||
}
|
||||
|
||||
it.addAll(lostItems)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserializeNBT(provider: HolderLookup.Provider, nbt: Tag) {
|
||||
lostItems.clear()
|
||||
slots.forEach { it.clear() }
|
||||
|
||||
if (nbt is CompoundTag) {
|
||||
// legacy container
|
||||
LegacySerializedState.CODEC.decode(provider.createSerializationContext(NbtOps.INSTANCE), nbt)
|
||||
.resultOrPartial { LOGGER.error("Error deserializing container: $it") }
|
||||
.ifPresent {
|
||||
val (items, filters) = it.first
|
||||
|
||||
// excessive items will be lost
|
||||
for ((item, slot) in items) {
|
||||
if (slot in 0 until containerSize) {
|
||||
slots[slot].item = item
|
||||
}
|
||||
}
|
||||
|
||||
for ((filter, slot) in filters) {
|
||||
if (slot in 0 until containerSize) {
|
||||
val getSlot = slots[slot]
|
||||
|
||||
if (getSlot is IFilteredContainerSlot) {
|
||||
getSlot.filter = filter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (nbt is ListTag) {
|
||||
// normal container
|
||||
for ((i, element) in nbt.withIndex()) {
|
||||
if (element !is CompoundTag) {
|
||||
LOGGER.error("Deserializing mattery container: Expected compound tag at $i, got $element")
|
||||
continue
|
||||
}
|
||||
|
||||
if (i in 0 until containerSize) {
|
||||
slots[i].deserializeNBT(provider, element)
|
||||
} else {
|
||||
lostItems.add(element)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGGER.error("Unable to deserialize mattery container, expected CompoundTag or ListTag, got $nbt")
|
||||
}
|
||||
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import java.util.function.Predicate
|
||||
|
||||
class SlottedContainerBuilder {
|
||||
fun interface SlotProvider {
|
||||
fun create(container: SlottedContainer, index: Int): ContainerSlot
|
||||
}
|
||||
|
||||
private val slots = ArrayList<SlotProvider>()
|
||||
private var stillValid = Predicate<Player> { true }
|
||||
private val globalChangeListeners = ArrayList<Runnable>()
|
||||
|
||||
fun add(slot: SlotProvider): SlottedContainerBuilder {
|
||||
slots.add(slot)
|
||||
return this
|
||||
}
|
||||
|
||||
fun add(amount: Int, provider: SlotProvider): SlottedContainerBuilder {
|
||||
for (i in 0 until amount)
|
||||
slots.add(provider)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun stillValid(predicate: Predicate<Player>): SlottedContainerBuilder {
|
||||
this.stillValid = predicate
|
||||
return this
|
||||
}
|
||||
|
||||
fun onChanged(listener: Runnable): SlottedContainerBuilder {
|
||||
globalChangeListeners.add(listener)
|
||||
return this
|
||||
}
|
||||
|
||||
fun copy(): SlottedContainerBuilder {
|
||||
val copy = SlottedContainerBuilder()
|
||||
copy.slots.addAll(slots)
|
||||
copy.globalChangeListeners.addAll(globalChangeListeners)
|
||||
copy.stillValid = stillValid
|
||||
return copy
|
||||
}
|
||||
|
||||
fun build(): SlottedContainer {
|
||||
return SlottedContainer(slots, stillValid, globalChangeListeners.toTypedArray())
|
||||
}
|
||||
}
|
@ -4,52 +4,41 @@ import net.minecraft.world.Container
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import ru.dbotthepony.mc.otm.container.IContainerSlot
|
||||
import ru.dbotthepony.mc.otm.container.IEnhancedContainer
|
||||
import ru.dbotthepony.mc.otm.container.IMatteryContainer
|
||||
import ru.dbotthepony.mc.otm.container.get
|
||||
import ru.dbotthepony.mc.otm.core.collect.filter
|
||||
import ru.dbotthepony.mc.otm.core.collect.map
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
|
||||
class SimpleContainerSlot(override val slot: Int, override val container: Container) : IContainerSlot {
|
||||
init {
|
||||
require(slot in 0 until container.containerSize) { "Slot out of bounds: $slot (container: $container with size ${container.containerSize})" }
|
||||
}
|
||||
|
||||
override fun getFilter(): Item? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun setFilter(filter: Item?): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun Container.containerSlot(slot: Int): IContainerSlot {
|
||||
if (this is IMatteryContainer) {
|
||||
if (this is IEnhancedContainer) {
|
||||
return containerSlot(slot)
|
||||
} else {
|
||||
return SimpleContainerSlot(slot, this)
|
||||
return IContainerSlot.Simple(slot, this)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun Container.iterator() = iterator(true)
|
||||
|
||||
fun Container.iterator(nonEmpty: Boolean): Iterator<ItemStack> {
|
||||
if (this is IMatteryContainer) {
|
||||
return iterator(nonEmpty)
|
||||
} else if (nonEmpty) {
|
||||
operator fun Container.iterator(): Iterator<ItemStack> {
|
||||
if (this is IEnhancedContainer) {
|
||||
return iterator()
|
||||
} else {
|
||||
return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty }
|
||||
} else {
|
||||
return (0 until containerSize).iterator().map { this[it] }
|
||||
}
|
||||
}
|
||||
|
||||
fun Container.slotIterator(nonEmpty: Boolean = true): Iterator<IContainerSlot> {
|
||||
if (this is IMatteryContainer) {
|
||||
return slotIterator(nonEmpty)
|
||||
} else if (nonEmpty) {
|
||||
return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { SimpleContainerSlot(it, this) }
|
||||
fun Container.slotIterator(): Iterator<IContainerSlot> {
|
||||
if (this is IEnhancedContainer) {
|
||||
return slotIterator()
|
||||
} else {
|
||||
return (0 until containerSize).iterator().map { SimpleContainerSlot(it, this) }
|
||||
return (0 until containerSize).iterator().map { IContainerSlot.Simple(it, this) }
|
||||
}
|
||||
}
|
||||
|
||||
fun Container.nonEmptySlotIterator(): Iterator<IContainerSlot> {
|
||||
if (this is IEnhancedContainer) {
|
||||
return nonEmptySlotIterator()
|
||||
} else {
|
||||
return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { IContainerSlot.Simple(it, this) }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,206 @@
|
||||
package ru.dbotthepony.mc.otm.core.collect
|
||||
|
||||
import it.unimi.dsi.fastutil.HashCommon
|
||||
import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator
|
||||
import it.unimi.dsi.fastutil.ints.IntCollection
|
||||
import it.unimi.dsi.fastutil.ints.IntComparator
|
||||
import it.unimi.dsi.fastutil.ints.IntIterators
|
||||
import it.unimi.dsi.fastutil.ints.IntSet
|
||||
import it.unimi.dsi.fastutil.ints.IntSortedSet
|
||||
|
||||
class IntRange2Set private constructor(private val first: Int, private val last: Int) : IntSortedSet {
|
||||
constructor(range: IntRange) : this(range.first, range.last) {
|
||||
require(range.step == 1) { "Provided range has non-standard step of ${range.step}" }
|
||||
}
|
||||
|
||||
@Deprecated("Not supported", level = DeprecationLevel.ERROR)
|
||||
override fun add(element: Int): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@Deprecated("Not supported", level = DeprecationLevel.ERROR)
|
||||
override fun addAll(c: IntCollection?): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@Deprecated("Not supported", level = DeprecationLevel.ERROR)
|
||||
override fun addAll(elements: Collection<Int>): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@Deprecated("Not supported", level = DeprecationLevel.ERROR)
|
||||
override fun clear() {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun iterator(fromElement: Int): IntBidirectionalIterator {
|
||||
if (isEmpty() || fromElement > last)
|
||||
return IntIterators.EMPTY_ITERATOR
|
||||
else if (fromElement <= first)
|
||||
return IntIterators.fromTo(first, last + 1)
|
||||
else
|
||||
return IntIterators.fromTo(fromElement, last + 1)
|
||||
}
|
||||
|
||||
override fun iterator(): IntBidirectionalIterator {
|
||||
if (isEmpty())
|
||||
return IntIterators.EMPTY_ITERATOR
|
||||
|
||||
return IntIterators.fromTo(first, last + 1)
|
||||
}
|
||||
|
||||
@Deprecated("Not supported", level = DeprecationLevel.ERROR)
|
||||
override fun remove(k: Int): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@Deprecated("Not supported", level = DeprecationLevel.ERROR)
|
||||
override fun removeAll(c: IntCollection?): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@Deprecated("Not supported", level = DeprecationLevel.ERROR)
|
||||
override fun removeAll(elements: Collection<Int>): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@Deprecated("Not supported", level = DeprecationLevel.ERROR)
|
||||
override fun retainAll(c: IntCollection?): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@Deprecated("Not supported", level = DeprecationLevel.ERROR)
|
||||
override fun retainAll(elements: Collection<Int>): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun contains(key: Int): Boolean {
|
||||
return key in first .. last
|
||||
}
|
||||
|
||||
override fun containsAll(c: IntCollection): Boolean {
|
||||
return c.ktIterator().all { it in first .. last }
|
||||
}
|
||||
|
||||
override fun containsAll(elements: Collection<Int>): Boolean {
|
||||
return elements.all { it in first .. last }
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean {
|
||||
return last > first
|
||||
}
|
||||
|
||||
override fun toArray(a: IntArray?): IntArray {
|
||||
if (a == null || a.size < size)
|
||||
return toIntArray()
|
||||
|
||||
var index = 0
|
||||
|
||||
for (i in first .. last) {
|
||||
a[index++] = i
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
override fun toIntArray(): IntArray {
|
||||
if (isEmpty())
|
||||
return EMPTY_ARRAY
|
||||
|
||||
return IntArray(last - first + 1) { first + it }
|
||||
}
|
||||
|
||||
override fun comparator(): IntComparator? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun subSet(fromElement: Int, toElement: Int): IntSortedSet {
|
||||
if (fromElement <= first && toElement > last)
|
||||
return this
|
||||
else if (fromElement <= first)
|
||||
return IntRange2Set(first, toElement - 1)
|
||||
else if (toElement > last)
|
||||
return IntRange2Set(fromElement, last)
|
||||
else
|
||||
return EMPTY
|
||||
}
|
||||
|
||||
override fun headSet(toElement: Int): IntSortedSet {
|
||||
if (isEmpty() || toElement <= first)
|
||||
return EMPTY
|
||||
else if (toElement < last)
|
||||
return this
|
||||
else
|
||||
return IntRange2Set(first, toElement - 1)
|
||||
}
|
||||
|
||||
override fun tailSet(fromElement: Int): IntSortedSet {
|
||||
if (isEmpty() || fromElement > last)
|
||||
return EMPTY
|
||||
else if (fromElement < first)
|
||||
return this
|
||||
else
|
||||
return IntRange2Set(fromElement, last)
|
||||
}
|
||||
|
||||
override fun firstInt(): Int {
|
||||
if (isEmpty())
|
||||
throw NoSuchElementException("Range is empty")
|
||||
|
||||
return first
|
||||
}
|
||||
|
||||
override fun lastInt(): Int {
|
||||
if (isEmpty())
|
||||
throw NoSuchElementException("Range is empty")
|
||||
|
||||
return last
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = if (last > first) 0 else last - first + 1
|
||||
|
||||
override fun toString(): String {
|
||||
if (isEmpty())
|
||||
return "IntRange2Set[EMPTY]"
|
||||
|
||||
return "IntRange2Set[$first .. $last]"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other ||
|
||||
other is IntRange2Set && (isEmpty() == other.isEmpty() || first == other.first && last == other.last) ||
|
||||
other is IntSet && other.size == size && containsAll(other) ||
|
||||
other is Set<*> && other.size == size && other.all { it is Int && it in first .. last }
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
if (isEmpty())
|
||||
return EMPTY_HASH
|
||||
|
||||
return HashCommon.murmurHash3(last - first)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Returns set containing values beginning from [from] (inclusive) to [to] (inclusive)
|
||||
*/
|
||||
fun closed(from: Int, to: Int): IntRange2Set {
|
||||
if (from > to)
|
||||
return EMPTY
|
||||
|
||||
return IntRange2Set(from, to)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set containing values beginning from [from] (inclusive) to [to] (exclusive)
|
||||
*/
|
||||
fun openEnded(from: Int, to: Int): IntRange2Set {
|
||||
return closed(from, to - 1)
|
||||
}
|
||||
|
||||
private val EMPTY_ARRAY = IntArray(0)
|
||||
val EMPTY = IntRange2Set(0, -1)
|
||||
private val EMPTY_HASH = HashCommon.murmurHash3(0)
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package ru.dbotthepony.mc.otm.core.collect
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntCollection
|
||||
import it.unimi.dsi.fastutil.ints.IntIterable
|
||||
import it.unimi.dsi.fastutil.ints.IntIterator
|
||||
import it.unimi.dsi.fastutil.ints.IntSortedSet
|
||||
|
||||
fun IntRange.asIterable(): IntIterable {
|
||||
return IntIterable {
|
||||
@ -22,3 +24,31 @@ fun IntRange.asIterable(): IntIterable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun IntCollection.ktIterator(): kotlin.collections.IntIterator {
|
||||
return object : kotlin.collections.IntIterator() {
|
||||
private val parent = this@ktIterator.intIterator()
|
||||
|
||||
override fun nextInt(): Int {
|
||||
return parent.nextInt()
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return parent.hasNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun IntSortedSet.ktIterator(fromElement: Int): kotlin.collections.IntIterator {
|
||||
return object : kotlin.collections.IntIterator() {
|
||||
private val parent = this@ktIterator.iterator(fromElement)
|
||||
|
||||
override fun nextInt(): Int {
|
||||
return parent.nextInt()
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return parent.hasNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user