Initial code for new containers

This commit is contained in:
DBotThePony 2025-02-27 19:19:33 +07:00
parent dfc35f393f
commit ca6ff30414
Signed by: DBot
GPG Key ID: DCC23B5715498507
15 changed files with 1430 additions and 205 deletions

View File

@ -9,6 +9,7 @@ import it.unimi.dsi.fastutil.ints.IntIterator
import it.unimi.dsi.fastutil.ints.IntList import it.unimi.dsi.fastutil.ints.IntList
import it.unimi.dsi.fastutil.ints.IntOpenHashSet import it.unimi.dsi.fastutil.ints.IntOpenHashSet
import it.unimi.dsi.fastutil.ints.IntSet 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.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
import it.unimi.dsi.fastutil.objects.ObjectArrayList 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.containerSlot
import ru.dbotthepony.mc.otm.container.util.slotIterator import ru.dbotthepony.mc.otm.container.util.slotIterator
import ru.dbotthepony.mc.otm.core.addAll 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.filter
import ru.dbotthepony.mc.otm.core.collect.toList import ru.dbotthepony.mc.otm.core.collect.toList
import ru.dbotthepony.mc.otm.core.isNotEmpty 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") @Suppress("nothing_to_inline")
inline operator fun IFluidHandler.get(index: Int) = getFluidInTank(index) inline operator fun IFluidHandler.get(index: Int) = getFluidInTank(index)
val Container.slotRange: IntIterable get() { val Container.slotRange: IntRange2Set get() {
return IntIterable { return IntRange2Set.openEnded(0, containerSize)
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()
}
}
}
} }
fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange): ItemStack { fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntSortedSet = slotRange): ItemStack {
if (this is IMatteryContainer) { return IEnhancedContainer.wrap(this).addItem(stack, simulate, slots)
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.vanishCursedItems() { fun Container.vanishCursedItems() {
@ -312,12 +237,18 @@ fun Container.sortWithIndices(sortedSlots: IntCollection) {
if (value in 0 until containerSize && seen.add(value)) { if (value in 0 until containerSize && seen.add(value)) {
val slot = containerSlot(value) val slot = containerSlot(value)
if ( val condition: Boolean
slot.isNotEmpty &&
!slot.isForbiddenForAutomation && if (slot is IFilteredContainerSlot) {
slot.item.count <= slot.getMaxStackSize() && condition = slot.isNotEmpty &&
(!slot.hasFilter || slot.getFilter() != slot.item.item || slot.getMaxStackSize() > 1) !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) valid.add(slot)
} }
} }
@ -335,7 +266,7 @@ fun Container.computeSortedIndices(comparator: Comparator<ItemStack> = ItemStack
if (isEmpty) if (isEmpty)
return IntList.of() 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()) if (slots.isEmpty())
return IntList.of() return IntList.of()

View 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()
}
}

View File

@ -1,9 +1,7 @@
package ru.dbotthepony.mc.otm.container package ru.dbotthepony.mc.otm.container
import net.minecraft.world.Container import net.minecraft.world.Container
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.kommons.util.Delegate
import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.isNotEmpty
@ -15,54 +13,67 @@ interface IContainerSlot : Delegate<ItemStack> {
val slot: Int val slot: Int
val container: Container val container: Container
fun setChanged()
var item: ItemStack
operator fun component1() = slot operator fun component1() = slot
operator fun component2() = item operator fun component2() = item
fun getMaxStackSize(item: ItemStack = this.item): Int { /**
return container.maxStackSize * Max stack size regardless of item
} *
* Prefer to use ItemStack version instead
val isForbiddenForAutomation: Boolean get() { */
return getFilter() === Items.AIR val maxStackSize: Int
}
fun getFilter(): Item?
/** /**
* @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 fun maxStackSize(item: ItemStack): Int {
return maxStackSize.coerceAtMost(item.maxStackSize)
val hasFilter: Boolean
get() = getFilter() != null
fun remove() {
container[slot] = ItemStack.EMPTY
} }
fun remove(count: Int): ItemStack { fun remove(): ItemStack
return container.removeItem(slot, count) fun remove(count: Int): ItemStack
}
override fun get(): ItemStack { override fun get(): ItemStack {
return container[slot] return item
} }
override fun accept(t: ItemStack) { 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 val isEmpty: Boolean
get() = container[slot].isEmpty get() = item.isEmpty
val isNotEmpty: Boolean 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)
}
}
} }

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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 * 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 { fun containerSlot(slot: Int): IContainerSlot {
return ContainerSlot(slot, this) return IContainerSlot.Simple(slot, this)
} }
fun hasSlotFilter(slot: Int) = getSlotFilter(slot) !== null fun hasSlotFilter(slot: Int) = getSlotFilter(slot) !== null
@ -234,22 +213,4 @@ interface IMatteryContainer : IContainer, RecipeInput, Iterable<ItemStack> {
return list 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
}
} }

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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 override val container: Container
get() = this@MatteryContainer 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 override val isForbiddenForAutomation: Boolean
get() = isSlotForbiddenForAutomation(slot) get() = isSlotForbiddenForAutomation(slot)
override fun getFilter(): Item? { override fun maxStackSize(item: ItemStack): Int {
return getSlotFilter(slot)
}
override fun setFilter(filter: Item?): Boolean {
return setSlotFilter(slot, filter)
}
override fun getMaxStackSize(item: ItemStack): Int {
return getMaxStackSize(slot, item) 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 indicesReferenced = true
return nonEmptyIndices.iterator().map { Slot(it) } 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) { if (!nonEmpty) {
return (0 until size).iterator().map { Slot(it) } return (0 until size).iterator().map { Slot(it) }
} else if (isEmpty) { } 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) return Slot(slot)
} }

View File

@ -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()
}
}

View File

@ -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())
}
}

View File

@ -4,52 +4,41 @@ import net.minecraft.world.Container
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.container.IContainerSlot 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.IMatteryContainer
import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.isNotEmpty 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 { fun Container.containerSlot(slot: Int): IContainerSlot {
if (this is IMatteryContainer) { if (this is IEnhancedContainer) {
return containerSlot(slot) return containerSlot(slot)
} else { } else {
return SimpleContainerSlot(slot, this) return IContainerSlot.Simple(slot, this)
} }
} }
operator fun Container.iterator() = iterator(true) operator fun Container.iterator(): Iterator<ItemStack> {
if (this is IEnhancedContainer) {
fun Container.iterator(nonEmpty: Boolean): Iterator<ItemStack> { return iterator()
if (this is IMatteryContainer) { } else {
return iterator(nonEmpty)
} else if (nonEmpty) {
return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty } 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> { fun Container.slotIterator(): Iterator<IContainerSlot> {
if (this is IMatteryContainer) { if (this is IEnhancedContainer) {
return slotIterator(nonEmpty) return slotIterator()
} else if (nonEmpty) {
return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { SimpleContainerSlot(it, this) }
} else { } 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) }
} }
} }

View File

@ -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)
}
}

View File

@ -1,7 +1,9 @@
package ru.dbotthepony.mc.otm.core.collect 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.IntIterable
import it.unimi.dsi.fastutil.ints.IntIterator import it.unimi.dsi.fastutil.ints.IntIterator
import it.unimi.dsi.fastutil.ints.IntSortedSet
fun IntRange.asIterable(): IntIterable { fun IntRange.asIterable(): IntIterable {
return 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()
}
}
}