diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index cdf677bb2..328d94b0f 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -611,6 +611,24 @@ private fun gui(provider: MatteryLanguageProvider) { gui("item_monitor.amount.full", "Stack of ingredients. Craft until reaching stack size of one of ingredients. If at least one of inputs is not stackable then 'one stack' mode is used instead") gui("stored_amount", "Exact amount stored: %s") + + gui("sides.item_config", "Item Configuration") + + gui("sides.top", "Top") + gui("sides.bottom", "Bottom") + gui("sides.front", "Front") + gui("sides.back", "Back") + gui("sides.left", "Left") + gui("sides.right", "Right") + + gui("side_mode.disabled", "Disabled") + gui("side_mode.input", "Input only") + gui("side_mode.output", "Output only") + gui("side_mode.input_output", "Input/Output") + gui("side_mode.battery", "Battery") + + gui("side_mode.pull", "Pull") + gui("side_mode.push", "Push") } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt index f333f0d35..84a5e547d 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt @@ -616,6 +616,24 @@ private fun gui(provider: MatteryLanguageProvider) { gui("item_monitor.amount.full", "Стопку ингредиентов. Создание продолжается пока не будет достигнут лимит по стопке одного из ингредиентов. Если хотя бы один из ингредиентов не может быть стопкой, будет использован режим 'одна стопка результата'") gui("stored_amount", "Точное количество в хранилище: %s шт.") + + gui("sides.item_config", "Настройка Предметов") + + gui("sides.top", "Верхняя сторона") + gui("sides.bottom", "Нижняя сторона") + gui("sides.front", "Передняя сторона") + gui("sides.back", "Задняя сторона") + gui("sides.left", "Левая сторона") + gui("sides.right", "Правая сторона") + + gui("side_mode.disabled", "Отключено") + gui("side_mode.input", "Только вход") + gui("side_mode.output", "Только выход") + gui("side_mode.input_output", "Вход/Выход") + gui("side_mode.battery", "Батарея") + + gui("side_mode.pull", "Автоматическое вытягивание") + gui("side_mode.push", "Автоматическое выталкивание") } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt index ec3dc18d8..d2f74287d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableSet import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.longs.Long2ObjectFunction import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap import net.minecraft.client.multiplayer.ClientLevel import net.minecraft.core.BlockPos @@ -12,11 +11,10 @@ import net.minecraft.core.Direction import net.minecraft.core.SectionPos import net.minecraft.core.Vec3i import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.StringTag import net.minecraft.nbt.Tag -import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.item.ItemStack import net.minecraft.world.level.ChunkPos import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block @@ -35,24 +33,30 @@ import net.minecraftforge.event.level.ChunkWatchEvent import net.minecraftforge.event.level.LevelEvent import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.items.IItemHandler +import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock -import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.CombinedItemHandler +import ru.dbotthepony.mc.otm.capability.EmptyItemHandler import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.UnmodifiableItemHandler import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.isMekanismLoaded +import ru.dbotthepony.mc.otm.capability.moveBetweenSlots import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper import ru.dbotthepony.mc.otm.core.collect.SupplierList import ru.dbotthepony.mc.otm.core.collect.WeakHashSet import ru.dbotthepony.mc.otm.core.forValidRefs -import ru.dbotthepony.mc.otm.core.forValidRefsBreak import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.immutableMap import ru.dbotthepony.mc.otm.core.math.BlockRotation import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.minus import ru.dbotthepony.mc.otm.core.math.plus -import ru.dbotthepony.mc.otm.core.nbt.set -import ru.dbotthepony.mc.otm.core.util.EnumValueCodec +import ru.dbotthepony.mc.otm.core.util.ITickable import ru.dbotthepony.mc.otm.core.util.Savetables +import ru.dbotthepony.mc.otm.core.util.TickList import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket import ru.dbotthepony.mc.otm.network.FieldSynchronizer import ru.dbotthepony.mc.otm.network.WorldNetworkChannel @@ -88,6 +92,12 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc private data class SidelessCap(val cap: T, var optional: LazyOptional) private val sidelessCaps = Reference2ObjectArrayMap, SidelessCap<*>>() + protected val tickList = TickList() + protected val savetables = Savetables() + + open fun tick() { + tickList.tick() + } /** * exposes capability when no side is specified @@ -138,16 +148,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc } } - protected fun exposeEnergy(side: RelativeSide, value: IMatteryEnergyStorage) { - _sides[side]!!.Cap(ForgeCapabilities.ENERGY, value) - _sides[side]!!.Cap(MatteryCapability.ENERGY, value) - - if (isMekanismLoaded) { - _sides[side]!!.Cap(MatteryCapability.MEKANISM_ENERGY, Mattery2MekanismEnergyWrapper(value)) - } - } - - protected fun exposeEnergySideless( value: IMatteryEnergyStorage) { + protected fun exposeEnergySideless(value: IMatteryEnergyStorage) { exposeSideless(ForgeCapabilities.ENERGY, value) exposeSideless(MatteryCapability.ENERGY, value) @@ -156,6 +157,17 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc } } + protected fun exposeEnergy(side: RelativeSide, value: IMatteryEnergyStorage) { + val thisSide = _sides[side]!! + + thisSide.Cap(ForgeCapabilities.ENERGY, value) + thisSide.Cap(MatteryCapability.ENERGY, value) + + if (isMekanismLoaded) { + thisSide.Cap(MatteryCapability.MEKANISM_ENERGY, Mattery2MekanismEnergyWrapper(value)) + } + } + protected fun exposeItemsGlobally(value: IItemHandler) { exposeGlobally(ForgeCapabilities.ITEM_HANDLER, value) } @@ -165,47 +177,13 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc return SupplierList(_sides.values.map { it.track(capability)::get }) } - enum class SideMode(val filter: FlowDirection, val automation: FlowDirection) { - DISABLED (FlowDirection.NONE, FlowDirection.NONE), - NONE (FlowDirection.BI_DIRECTIONAL, FlowDirection.NONE), - INPUT (FlowDirection.INPUT, FlowDirection.NONE), - OUTPUT (FlowDirection.OUTPUT, FlowDirection.NONE), - - PULL (FlowDirection.INPUT, FlowDirection.INPUT), - PUSH (FlowDirection.OUTPUT, FlowDirection.OUTPUT), - PULL_ONLY (FlowDirection.BI_DIRECTIONAL, FlowDirection.INPUT), - PUSH_ONLY (FlowDirection.BI_DIRECTIONAL, FlowDirection.OUTPUT), - PULL_PUSH (FlowDirection.BI_DIRECTIONAL, FlowDirection.BI_DIRECTIONAL); - - val isOpen = filter != FlowDirection.NONE - - companion object { - @JvmField - val BI_SET: ImmutableSet = ImmutableSet.of(NONE, INPUT, OUTPUT, PULL, PUSH, PULL_ONLY, PUSH_ONLY, PULL_PUSH, DISABLED) - - @JvmField - val INPUT_SET: ImmutableSet = ImmutableSet.of(INPUT, PULL, DISABLED) - - @JvmField - val OUTPUT_SET: ImmutableSet = ImmutableSet.of(OUTPUT, PUSH, DISABLED) - - @JvmField - val CODEC = EnumValueCodec(SideMode::class.java) - } - } - - fun interface SideModeListener { - fun sideModeChanges(config: Side.ModeState, new: SideMode, old: SideMode) - } - - inner class Side(val side: RelativeSide) : INBTSerializable { + inner class Side(val side: RelativeSide) { init { check(!_sides.containsKey(side)) { "dafuq are you trying to do" } _sides[side] = this } private val caps = Reference2ObjectArrayMap, Cap<*>>() - private val data = Object2ObjectArrayMap>() private val subscriptions = Reference2ObjectArrayMap, SubRef<*>>() private val knownLOs = WeakHashSet>() @@ -240,7 +218,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc } private fun updateTracked(capability: Capability<*>) { - if (isRemoved) return + if (isRemoved || !SERVER_IS_LIVE) return val dir = blockRotation.side2Dir(side) val chunk = level @@ -282,21 +260,10 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc } } - val isEmpty get() = caps.isEmpty() && data.isEmpty() - operator fun get(capability: Capability): Cap? { return caps[capability] as Cap? } - fun addData(index: ResourceLocation, data: INBTSerializable<*>) { - require(!this.data.containsKey(index)) { "Already has data with ID $index on $side!" } - this.data[index] = data as INBTSerializable - } - - fun getData(index: ResourceLocation): INBTSerializable<*>? { - return data[index] - } - fun invalidate() { for (cap in caps.values) cap.invalidate() @@ -307,87 +274,18 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc cap.revive() } - override fun serializeNBT(): CompoundTag { - return CompoundTag().also { - for ((k, v) in data) { - it[k.toString()] = v.serializeNBT() - } - } - } - - override fun deserializeNBT(nbt: CompoundTag) { - for ((k, v) in data) { - val tag = nbt[k.toString()] - - if (tag != null) { - v.deserializeNBT(tag) - } - } - } - - inner class ModeState(val name: String, val possibleValues: ImmutableSet = SideMode.BI_SET) : INBTSerializable { - init { - addData(ResourceLocation(name), this) - } - - val side get() = this@Side.side - - var mode by synchronizer.Field(possibleValues.first(), SideMode.CODEC, setter = { value, access, setByRemote -> - require(value in possibleValues) { "Value $value is not allowed (allowed values: $possibleValues)" } - val old = access.read() - - if (value != old) { - access.write(value) - - listeners.forValidRefs { - it.sideModeChanges(this, value, old) - } - } - }, name = "SideConfig/$side/$name") - - fun isAllowed(value: SideMode): Boolean = value in possibleValues - - private val listeners = ArrayList>() - - fun addListener(listener: SideModeListener) { - var hit = false - - listeners.forValidRefsBreak { - if (it === listener) { - hit = true - return@forValidRefsBreak true - } - - return@forValidRefsBreak false - } - - if (!hit) { - listeners.add(WeakReference(listener)) - } - } - - override fun serializeNBT(): StringTag { - return StringTag.valueOf(mode.name) - } - - override fun deserializeNBT(nbt: StringTag) { - mode = SideMode.valueOf(nbt.asString) - } - - fun deserializeNBT(nbt: String) { - mode = SideMode.valueOf(nbt) - } - } - inner class Cap(val type: Capability, val capability: T) { init { check(!caps.containsKey(type)) { "Already has capability $type on side $side" } caps[type] = this } - private var isExposed = true - private var isValid = true - private var isRemoved = false + var isExposed = true + private set + var isValid = true + private set + var isRemoved = false + private set var optional: LazyOptional by object : ReadWriteProperty> { private var value: LazyOptional? = null @@ -419,7 +317,9 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc if (!isRemoved && isExposed) { isExposed = false optional.invalidate() - setChanged() + + if (SERVER_IS_LIVE) + level?.once { setChanged() } } } @@ -429,7 +329,9 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc if (isValid) { optional = LazyOptional.of { capability } - setChanged() + + if (SERVER_IS_LIVE) + level?.once { setChanged() } } } } @@ -438,7 +340,9 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc if (!isRemoved && isValid) { isValid = false optional.invalidate() - setChanged() + + if (SERVER_IS_LIVE) + level?.once { setChanged() } } } @@ -448,7 +352,9 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc if (isExposed) { optional = LazyOptional.of { capability } - setChanged() + + if (SERVER_IS_LIVE) + level?.once { setChanged() } } } } @@ -489,41 +395,17 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc side.revive() } - protected val savetables = Savetables() - protected inline fun > savetable(property: KProperty0, name: String = property.name) { savetables.stateful(property, name, T::class.java) } override fun saveAdditional(nbt: CompoundTag) { super.saveAdditional(nbt) - - if (_sides.values.any { !it.isEmpty }) { - nbt["Sides"] = CompoundTag().also { - for (side in _sides.values) { - if (!side.isEmpty) { - it[side.side.name] = side.serializeNBT() - } - } - } - } - savetables.serializeNBT(nbt) } override fun load(nbt: CompoundTag) { super.load(nbt) - - if (nbt.contains("Sides")) { - val tag = nbt.getCompound("Sides") - - for (side in _sides.values) { - if (side.side.name in tag) { - side.deserializeNBT(tag.getCompound(side.side.name)) - } - } - } - savetables.deserializeNBT(nbt) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt index b2e43f7db..9366184be 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.block.entity +import com.google.common.collect.ImmutableSet import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.core.BlockPos import net.minecraft.world.level.block.state.BlockState @@ -13,12 +14,24 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.items.IItemHandler +import ru.dbotthepony.mc.otm.capability.CombinedItemHandler +import ru.dbotthepony.mc.otm.capability.EmptyItemHandler +import ru.dbotthepony.mc.otm.capability.UnmodifiableItemHandler +import ru.dbotthepony.mc.otm.capability.moveBetweenSlots import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.nbt.getJson import ru.dbotthepony.mc.otm.core.nbt.putJson import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.ITickable import ru.dbotthepony.mc.otm.oncePre /** @@ -61,4 +74,231 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo customDisplayName = nbt.getJson("Name")?.let(Component.Serializer::fromJson) redstoneControl.deserializeNBT(nbt[REDSTONE_CONTROL_KEY] as? CompoundTag) } + + + enum class ItemHandlerView(val translationKey: String) { + DISABLED("otm.gui.side_mode.disabled"), + INPUT("otm.gui.side_mode.input"), + OUTPUT("otm.gui.side_mode.output"), + INPUT_OUTPUT("otm.gui.side_mode.input_output"), + BATTERY("otm.gui.side_mode.battery"); + } + + inner class GlobalItemHandler( + input: IItemHandler? = null, + output: IItemHandler? = null, + battery: IItemHandler? = null, + val frontDefault: ItemHandlerView = determineDefaultMode(input, output), + val backDefault: ItemHandlerView = determineDefaultMode(input, output), + val leftDefault: ItemHandlerView = determineDefaultMode(input, output), + val rightDefault: ItemHandlerView = determineDefaultMode(input, output), + val topDefault: ItemHandlerView = determineDefaultMode(input, output), + val bottomDefault: ItemHandlerView = determineDefaultMode(input, output), + ) { + val sideless: IItemHandler + + init { + val caps = ArrayList() + + if (input != null) caps.add(input) + if (output != null) caps.add(output) + if (battery != null) caps.add(battery) + + sideless = UnmodifiableItemHandler(CombinedItemHandler(caps)) + exposeSideless(ForgeCapabilities.ITEM_HANDLER, sideless) + } + + val front = ConfigurableItemHandler(RelativeSide.FRONT, input, output, battery).also { it.mode = frontDefault } + val back = ConfigurableItemHandler(RelativeSide.BACK, input, output, battery).also { it.mode = backDefault } + val left = ConfigurableItemHandler(RelativeSide.LEFT, input, output, battery).also { it.mode = leftDefault } + val right = ConfigurableItemHandler(RelativeSide.RIGHT, input, output, battery).also { it.mode = rightDefault } + val top = ConfigurableItemHandler(RelativeSide.TOP, input, output, battery).also { it.mode = topDefault } + val bottom = ConfigurableItemHandler(RelativeSide.BOTTOM, input, output, battery).also { it.mode = bottomDefault } + + val sides = immutableMap { + put(RelativeSide.FRONT, this@GlobalItemHandler.front) + put(RelativeSide.BACK, this@GlobalItemHandler.back) + put(RelativeSide.LEFT, this@GlobalItemHandler.left) + put(RelativeSide.RIGHT, this@GlobalItemHandler.right) + put(RelativeSide.TOP, this@GlobalItemHandler.top) + put(RelativeSide.BOTTOM, this@GlobalItemHandler.bottom) + } + + val defaults = immutableMap { + put(RelativeSide.FRONT, frontDefault) + put(RelativeSide.BACK, backDefault) + put(RelativeSide.LEFT, leftDefault) + put(RelativeSide.RIGHT, rightDefault) + put(RelativeSide.TOP, topDefault) + put(RelativeSide.BOTTOM, bottomDefault) + } + } + + inner class ConfigurableItemHandler( + side: RelativeSide, + val input: IItemHandler? = null, + val output: IItemHandler? = null, + val battery: IItemHandler? = null, + ) : IItemHandler, ITickable { + private var currentHandler: IItemHandler = EmptyItemHandler + val capController = sides[side]!!.Cap(ForgeCapabilities.ITEM_HANDLER, this) + private val neighbour by sides[side]!!.track(ForgeCapabilities.ITEM_HANDLER) + + val possibleViews: ImmutableSet + val inputOutput: IItemHandler? + + init { + tickList.always(this) + + val builder = ImmutableSet.Builder() + + builder.add(ItemHandlerView.DISABLED) + + if (input != null) builder.add(ItemHandlerView.INPUT) + if (output != null) builder.add(ItemHandlerView.OUTPUT) + if (input != null && output != null) builder.add(ItemHandlerView.INPUT_OUTPUT) + if (battery != null) builder.add(ItemHandlerView.BATTERY) + + possibleViews = builder.build() + + capController.close() + + if (input != null && output != null) { + inputOutput = CombinedItemHandler(input, output) + } else { + inputOutput = null + } + } + + var mode by synchronizer.enum(ItemHandlerView.DISABLED, setter = { value, access, setByRemote -> + require(value in possibleViews) { "View type $value is not allowed (allowed views: $possibleViews)" } + + if (access.read() != value) { + access.write(value) + + if (value == ItemHandlerView.DISABLED) { + capController.close() + } else { + capController.close() + capController.expose() + } + + when (value) { + ItemHandlerView.DISABLED -> currentHandler = EmptyItemHandler + ItemHandlerView.INPUT -> currentHandler = input!! + ItemHandlerView.OUTPUT -> currentHandler = output!! + ItemHandlerView.INPUT_OUTPUT -> currentHandler = inputOutput!! + ItemHandlerView.BATTERY -> currentHandler = battery!! + } + } + }) + + var automatePull = false + set(value) { + if (field != value) { + field = value + + if (value) { + innerSlotPull = 0 + outerSlotPull = 0 + } + } + } + + var automatePush = false + set(value) { + if (field != value) { + field = value + + if (value) { + innerSlotPush = 0 + outerSlotPush = 0 + } + } + } + + init { + savetables.bool(::automatePull, "itemhandler_${side}_automatePull") + savetables.bool(::automatePush, "itemhandler_${side}_automatePush") + savetables.enum(::mode, "itemhandler_${side}_mode", ItemHandlerView::valueOf) + } + + private var innerSlotPull = 0 + private var outerSlotPull = 0 + + private var innerSlotPush = 0 + private var outerSlotPush = 0 + + override fun tick() { + if (mode == ItemHandlerView.DISABLED || currentHandler.slots == 0 || redstoneControl.isBlockedByRedstone) + return + + neighbour.ifPresentK { + if (it.slots == 0) + return + + if (automatePull) { + if (innerSlotPull !in 0 until currentHandler.slots) + innerSlotPull = 0 + + if (outerSlotPull !in 0 until it.slots) + outerSlotPull = 0 + + val (outerSlotPull, innerSlotPull) = moveBetweenSlots(it, outerSlotPull, currentHandler, innerSlotPull) + this.innerSlotPull = innerSlotPull + this.outerSlotPull = outerSlotPull + } + + if (automatePush) { + if (innerSlotPush !in 0 until currentHandler.slots) + innerSlotPush = 0 + + if (outerSlotPush !in 0 until it.slots) + outerSlotPush = 0 + + val (innerSlotPush, outerSlotPush) = moveBetweenSlots(currentHandler, innerSlotPush, it, outerSlotPush) + this.innerSlotPush = innerSlotPush + this.outerSlotPush = outerSlotPush + } + } + } + + override fun getSlots(): Int { + return currentHandler.slots + } + + override fun getStackInSlot(slot: Int): ItemStack { + return currentHandler.getStackInSlot(slot) + } + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + return currentHandler.insertItem(slot, stack, simulate) + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return currentHandler.extractItem(slot, amount, simulate) + } + + override fun getSlotLimit(slot: Int): Int { + return currentHandler.getSlotLimit(slot) + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return currentHandler.isItemValid(slot, stack) + } + } + + companion object { + + private fun determineDefaultMode(input: IItemHandler?, output: IItemHandler?): ItemHandlerView { + if (input == null && output == null) + return ItemHandlerView.DISABLED + else if (input != null && output != null) + return ItemHandlerView.INPUT_OUTPUT + else if (output != null) + return ItemHandlerView.OUTPUT + else + return ItemHandlerView.INPUT + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt index ed857c2dd..fde001d37 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt @@ -33,7 +33,9 @@ abstract class MatteryPoweredBlockEntity(p_155228_: BlockEntityType<*>, p_155229 savetable(::batteryContainer, BATTERY_KEY) } - protected fun batteryChargeLoop() { + override fun tick() { + super.tick() + val energy = matteryEnergy if (energy == null || !batteryContainer.any { !it.isEmpty }) return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt index 882bb93af..bb7e717c7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt @@ -233,7 +233,9 @@ abstract class MatteryWorkerBlockEntity( isIdling = newBlocked } - protected fun workerLoop() { + override fun tick() { + super.tick() + if (errorTicksAnim > 20 && blockState.hasProperty(WorkerState.WORKER_STATE) && blockState.getValue(WorkerState.WORKER_STATE) != WorkerState.ERROR) { level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.ERROR), Block.UPDATE_CLIENTS) } @@ -379,11 +381,6 @@ abstract class MatteryWorkerBlockEntity( } } - fun basicTicker() { - batteryChargeLoop() - workerLoop() - } - companion object { const val WORK_TICKS_KEY = "workTicks" const val JOB_KEY = "job" diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt index 9d1477b0e..52df29f0b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt @@ -215,7 +215,8 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery } } - fun tick() { + override fun tick() { + super.tick() sleepTicks-- if (sleepTicks > 0) return val level = level as? ServerLevel ?: return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt index 036077d4f..fc1fe4522 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt @@ -169,8 +169,8 @@ class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : matterNode.destroy(::MatterNetworkGraph) } - fun tick() { - batteryChargeLoop() + override fun tick() { + super.tick() if (redstoneControl.isBlockedByRedstone) { if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.IDLE) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt index 3fd64d919..90fdf93c6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt @@ -215,9 +215,8 @@ class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState) return matter } - fun tick() { - batteryChargeLoop() - workerLoop() + override fun tick() { + super.tick() val grid = matterNode.graph as MatterNetworkGraph? ?: return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt index 0426dee94..1811434d7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt @@ -154,8 +154,8 @@ class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) return Status.SUCCESS } - fun tick() { - basicTicker() + override fun tick() { + super.tick() val graph = matterNode.graph as MatterNetworkGraph? ?: return val received = graph.receiveMatter(matter.storedMatter, false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt index e36f1b0fc..7a7856fa3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt @@ -57,8 +57,8 @@ class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : StorageNetworkGraph.discoverFull(this, cell.storageNode) } - fun tick() { - batteryChargeLoop() + override fun tick() { + super.tick() cell.tickEnergyDemanding() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt index f9b176dd5..44c6c3eb1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt @@ -81,8 +81,4 @@ class DriveViewerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return DriveViewerMenu(containerID, inventory, this) } - - fun tick() { - batteryChargeLoop() - } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt index 03a5be273..224c4df97 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt @@ -481,8 +481,9 @@ class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : return settings.computeIfAbsent(ply.uuid) { ItemMonitorPlayerSettings() } } - fun tick() { - batteryChargeLoop() + override fun tick() { + super.tick() + cell.tickEnergyDemanding() if (craftingAmount.size != 0) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt index de9319380..f2258b9f6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt @@ -123,8 +123,8 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter private var neighbour: LazyOptional? = null private var component: ItemHandlerComponent? = null - fun tick() { - batteryChargeLoop() + override fun tick() { + super.tick() component?.scan() cell.tickEnergyDemanding() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt index f4ac6cedc..f38b98c93 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt @@ -175,11 +175,12 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) return filter.match(stack) } - fun tick() { + override fun tick() { + super.tick() + if (redstoneControl.isBlockedByRedstone) return - batteryChargeLoop() cell.tickEnergyDemanding() nextTick-- @@ -287,11 +288,12 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : return relevantTuples.stream().map { it to view[it] } } - fun tick() { + override fun tick() { + super.tick() + if (redstoneControl.isBlockedByRedstone) return - batteryChargeLoop() cell.tickEnergyDemanding() nextTick-- diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt index 458e8a257..fa0dbb017 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt @@ -53,12 +53,12 @@ class StoragePowerSupplierBlockEntity(blockPos: BlockPos, blockState: BlockState cell.destroy(level) } - fun tick() { + override fun tick() { + super.tick() + if (redstoneControl.isBlockedByRedstone) return - batteryChargeLoop() - if (energy.batteryLevel.isZero) return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt index a51524825..378b917a9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt @@ -65,7 +65,9 @@ class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : private var tickedOnce = false - fun tick() { + override fun tick() { + super.tick() + if (!tickedOnce) { tickedOnce = true @@ -76,8 +78,6 @@ class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : } } - batteryChargeLoop() - if (redstoneControl.isBlockedByRedstone) return val level = level ?: return val x = blockPos.x.toDouble() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt index 24863d985..6ae004b60 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt @@ -206,7 +206,9 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte private val consumers by front.track(ForgeCapabilities.ENERGY) - fun tick() { + override fun tick() { + super.tick() + if (redstoneControl.isBlockedByRedstone) return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt index 8f131b122..9cdd3c35c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt @@ -89,7 +89,9 @@ class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDe } } - fun tick() { + override fun tick() { + super.tick() + if (workTicks > 0) { workTicks-- energy.receiveEnergy(GENERATION_SPEED, false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt index 52de77096..499daad92 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt @@ -22,14 +22,15 @@ class CobblerBlockEntity(blockPos: BlockPos, blockState: BlockState) override val droppableContainer = MatteryContainer(this::itemContainerUpdated, CONTAINER_SIZE) - val handler = droppableContainer.handler(object : HandlerFilter { + val itemHandler = droppableContainer.handler(object : HandlerFilter { override fun canInsert(slot: Int, stack: ItemStack): Boolean { return false } }) + val itemConfig = GlobalItemHandler(output = itemHandler) + init { - exposeItemsGlobally(handler) savetable(::droppableContainer, INVENTORY_KEY) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt index 32c0b6782..82fcf0fee 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt @@ -285,7 +285,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat } } - fun tick() { + override fun tick() { + super.tick() + lastTick = history[historyTick] historyTick = (historyTick + 1) % history.size history[historyTick] = Decimal.ZERO diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt index 0656a01dc..7cd689f7a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt @@ -97,7 +97,9 @@ class EnergyServoBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte return EnergyServoMenu(containerID, inventory, this) } - fun tick() { + override fun tick() { + super.tick() + if (redstoneControl.isBlockedByRedstone) return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReplicatorBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReplicatorBlock.kt index b190c8e4d..a483c570a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReplicatorBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReplicatorBlock.kt @@ -35,7 +35,7 @@ class MatterReplicatorBlock : RotatableMatteryBlock(), EntityBlock { if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.MATTER_REPLICATOR) return null - return BlockEntityTicker { _, _, _, tile -> if (tile is MatterReplicatorBlockEntity) tile.basicTicker() } + return BlockEntityTicker { _, _, _, tile -> if (tile is MatterReplicatorBlockEntity) tile.tick() } } override fun createBlockStateDefinition(builder: StateDefinition.Builder) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterScannerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterScannerBlock.kt index 370c77b05..7a80592c2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterScannerBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterScannerBlock.kt @@ -35,7 +35,7 @@ class MatterScannerBlock : RotatableMatteryBlock(), EntityBlock { if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.MATTER_SCANNER) return null - return BlockEntityTicker { _, _, _, tile -> if (tile is MatterScannerBlockEntity) tile.basicTicker() } + return BlockEntityTicker { _, _, _, tile -> if (tile is MatterScannerBlockEntity) tile.tick() } } override fun createBlockStateDefinition(builder: StateDefinition.Builder) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/CobblerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/CobblerBlock.kt index c92f427ae..0271d346c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/CobblerBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/CobblerBlock.kt @@ -21,7 +21,7 @@ class CobblerBlock : RotatableMatteryBlock(), EntityBlock { pBlockEntityType: BlockEntityType ): BlockEntityTicker? { if (!pLevel.isClientSide) { - return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is CobblerBlockEntity) pBlockEntity.basicTicker() } + return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is CobblerBlockEntity) pBlockEntity.tick() } } return null diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PlatePressBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PlatePressBlock.kt index 341adcadb..c9fc9e6ce 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PlatePressBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PlatePressBlock.kt @@ -39,7 +39,7 @@ class PlatePressBlock(properties: Properties = DEFAULT_PROPERTIES) : RotatableMa if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.PLATE_PRESS) return null - return BlockEntityTicker { _, _, _, tile -> if (tile is PlatePressBlockEntity) tile.basicTicker() } + return BlockEntityTicker { _, _, _, tile -> if (tile is PlatePressBlockEntity) tile.tick() } } override fun createBlockStateDefinition(builder: StateDefinition.Builder) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/CombinedItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/CombinedItemHandler.kt new file mode 100644 index 000000000..b43fc9121 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/CombinedItemHandler.kt @@ -0,0 +1,80 @@ +package ru.dbotthepony.mc.otm.capability + +import com.google.common.collect.ImmutableList +import net.minecraft.world.item.ItemStack +import net.minecraftforge.items.IItemHandler +import java.util.stream.Stream + +class CombinedItemHandler(val handlers: ImmutableList) : IItemHandler { + constructor(handlers: Stream) : this(handlers.collect(ImmutableList.toImmutableList())) + constructor(handlers: Collection) : this(ImmutableList.copyOf(handlers)) + constructor(vararg handlers: IItemHandler) : this(ImmutableList.copyOf(handlers)) + + private val lastSizes = IntArray(this.handlers.size) + private var totalSize = 0 + private val mappings = ArrayList() + private data class Mapping(val handler: IItemHandler, val slot: Int) + + private fun check() { + for ((i, handler) in handlers.withIndex()) { + var oldSize = lastSizes[i] + + if (oldSize != handler.slots) { + totalSize += handler.slots - lastSizes[i] + var edge = 0 + + for (i2 in 0 .. i) { + edge += lastSizes[i2] + } + + edge-- + + while (oldSize > handler.slots) { + mappings.removeAt(edge--) + oldSize-- + } + + while (oldSize < handler.slots) { + mappings.add(++edge, Mapping(handler, oldSize++)) + } + + lastSizes[i] = handler.slots + } + } + } + + override fun getSlots(): Int { + check() + return totalSize + } + + override fun getStackInSlot(slot: Int): ItemStack { + check() + val mapping = mappings.getOrNull(slot) ?: return ItemStack.EMPTY + return mapping.handler.getStackInSlot(mapping.slot) + } + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + check() + val mapping = mappings.getOrNull(slot) ?: return stack + return mapping.handler.insertItem(mapping.slot, stack, simulate) + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + check() + val mapping = mappings.getOrNull(slot) ?: return ItemStack.EMPTY + return mapping.handler.extractItem(mapping.slot, amount, simulate) + } + + override fun getSlotLimit(slot: Int): Int { + check() + val mapping = mappings.getOrNull(slot) ?: return 0 + return mapping.handler.getSlotLimit(mapping.slot) + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + check() + val mapping = mappings.getOrNull(slot) ?: return false + return mapping.handler.isItemValid(mapping.slot, stack) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EmptyItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EmptyItemHandler.kt new file mode 100644 index 000000000..0cf000529 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EmptyItemHandler.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.mc.otm.capability + +import net.minecraft.world.item.ItemStack +import net.minecraftforge.items.IItemHandler + +object EmptyItemHandler : IItemHandler { + override fun getSlots(): Int { + return 0 + } + + override fun getStackInSlot(slot: Int): ItemStack { + return ItemStack.EMPTY + } + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + return stack + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return ItemStack.EMPTY + } + + override fun getSlotLimit(slot: Int): Int { + return 0 + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt index e13186bf6..d49194b2d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt @@ -45,6 +45,10 @@ enum class FlowDirection(val input: Boolean, val output: Boolean) : Predicate { + val getItem = source.extractItem(sourceSlot, Int.MAX_VALUE, true) + + if (getItem.isEmpty) { + return sourceSlot + 1 to destinationSlot + } else { + val leftover = destination.insertItem(destinationSlot, getItem, true) + + if (leftover.count == getItem.count) { + return sourceSlot to destinationSlot + 1 + } else { + val getItem2 = source.extractItem(sourceSlot, getItem.count - leftover.count, true) + + if (getItem2.isEmpty) { + return sourceSlot + 1 to destinationSlot + } else { + val leftover2 = destination.insertItem(destinationSlot, getItem2, true) + + if (leftover2.isEmpty) { + source.extractItem(sourceSlot, getItem2.count, false) + destination.insertItem(destinationSlot, getItem2, false) + + if (getItem2.count == getItem.count) { + return sourceSlot + 1 to destinationSlot + } else { + return sourceSlot to destinationSlot + } + } + } + } + } + + return sourceSlot to destinationSlot +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/UnmodifiableItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/UnmodifiableItemHandler.kt new file mode 100644 index 000000000..3546158a6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/UnmodifiableItemHandler.kt @@ -0,0 +1,14 @@ +package ru.dbotthepony.mc.otm.capability + +import net.minecraft.world.item.ItemStack +import net.minecraftforge.items.IItemHandler + +class UnmodifiableItemHandler(private val parent: IItemHandler) : IItemHandler by parent { + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return ItemStack.EMPTY + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt index e83f907a4..3a4087f97 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt @@ -162,13 +162,13 @@ interface IMatteryEnergyStorage : IEnergyStorage { if (!energyFlow.input) return 0 - val received = receiveEnergy(Decimal(maxReceive), true).toInt() + val received = receiveEnergyChecked(Decimal(maxReceive), true).toInt() // Receiving only a fraction if (received == 0) return 0 - return receiveEnergy(Decimal(received), simulate).toInt() + return receiveEnergyChecked(Decimal(received), simulate).toInt() } override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int { @@ -176,13 +176,13 @@ interface IMatteryEnergyStorage : IEnergyStorage { if (energyFlow.output) return 0 - val extracted = extractEnergy(Decimal(maxReceive), true).toInt() + val extracted = extractEnergyChecked(Decimal(maxReceive), true).toInt() // Extracting only a fraction if (extracted == 0) return 0 - return extractEnergy(Decimal(extracted), simulate).toInt() + return extractEnergyChecked(Decimal(extracted), simulate).toInt() } override fun getEnergyStored(): Int { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt index 6caaabfee..0b4725b34 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt @@ -17,4 +17,5 @@ object WidgetLocation { val HORIZONTAL_GAUGES = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/horizontal_gauges.png"), 96f, 54f) val VERTICAL_GAUGES = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/vertical_gauges.png"), 90f, 48f) val REDSTONE_CONTROLS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/redstone.png"), 54f, 18f) + val SIDE_CONTROLS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/side_controls.png"), 72f, 72f) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt index 540fe2035..3bc229a6c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt @@ -55,4 +55,17 @@ object Widgets18 { val REDSTONE_IGNORED = redstoneGrid.next() val REDSTONE_LOW = redstoneGrid.next() val REDSTONE_HIGH = redstoneGrid.next() + + private val controlsGrid = WidgetLocation.SIDE_CONTROLS.grid(rows = 4, columns = 4) + + val PULL = controlsGrid.next() + val PUSH = controlsGrid.next() + val PULL_DISABLED = controlsGrid.next() + val PUSH_DISABLED = controlsGrid.next() + val DISABLED = controlsGrid.next() + val INPUT_ONLY = controlsGrid.next() + val OUTPUT_ONLY = controlsGrid.next() + val INPUT_OUTPUT = controlsGrid.next() + val BATTERY_ONLY = controlsGrid.next() + val ITEMS_CONFIGURATION = controlsGrid.next() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt index b4c168828..23b8853fb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt @@ -569,12 +569,12 @@ open class EditablePanel @JvmOverloads constructor( val old = field field = value - onFocusChanged(old, value) + onFocusChanged(value, old) findAbsoluteRoot().updateFocus() } } - var autoKillFocus = false + var autoKillFocus = true private var focusedAsParent = false val font: Font get() = if (screen is MatteryScreen<*>) screen.font else minecraft.font diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt index 5d2563433..8d568d90c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt @@ -1,12 +1,13 @@ package ru.dbotthepony.mc.otm.client.screen.panels.button -import com.mojang.blaze3d.platform.InputConstants import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.AbstractMatterySprite import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback abstract class BooleanRectangleButtonPanel( screen: S, @@ -26,6 +27,16 @@ abstract class BooleanRectangleButtonPanel( onChange?.invoke(newValue) } + override var isDisabled: Boolean + get() { + if (prop is IPlayerInputWithFeedback) { + return !prop.test(minecraft.player) + } else { + return super.isDisabled + } + } + set(value) { super.isDisabled = value } + override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { super.innerRender(stack, mouseX, mouseY, partialTick) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt index c9ab1997f..8b1e4d6fc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt @@ -1,17 +1,21 @@ package ru.dbotthepony.mc.otm.client.screen.panels.button -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items +import com.mojang.blaze3d.platform.InputConstants +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemHandlerPlayerInput +import java.util.function.Predicate -fun > makeRedstoneSettingButton( +private fun > makeRedstoneSettingButton( screen: S, parent: EditablePanel<*>?, x: Float = 0f, @@ -35,10 +39,115 @@ fun > makeRedstoneSettingButton( } } +private fun > makeItemModeButton(screen: S, parent: FramePanel, input: ItemHandlerPlayerInput.Piece): LargeEnumRectangleButtonPanel { + val button = LargeEnumRectangleButtonPanel(screen, parent, enum = MatteryDeviceBlockEntity.ItemHandlerView::class.java, prop = input.input, defaultValue = input.default) + + val values = listOf( + MatteryDeviceBlockEntity.ItemHandlerView.DISABLED to Widgets18.DISABLED, + MatteryDeviceBlockEntity.ItemHandlerView.INPUT to Widgets18.INPUT_ONLY, + MatteryDeviceBlockEntity.ItemHandlerView.OUTPUT to Widgets18.OUTPUT_ONLY, + MatteryDeviceBlockEntity.ItemHandlerView.INPUT_OUTPUT to Widgets18.INPUT_OUTPUT, + MatteryDeviceBlockEntity.ItemHandlerView.BATTERY to Widgets18.BATTERY_ONLY, + ) + + for ((k, v) in values) { + button.add(k, skinElement = v, tooltip = TranslatableComponent(k.translationKey)) + } + + button.finish() + button.predicate = Predicate { input.isAllowed(it) } + + return button +} + +private fun > makeItemHandlerControlPanel( + screen: S, + inputs: ItemHandlerPlayerInput +): FramePanel { + val frame = object : FramePanel(screen, 78f, 80f, TranslatableComponent("otm.gui.sides.item_config")) { + override fun tickInner() { + super.tickInner() + + if (!isEverFocused()) { + remove() + } + } + } + + val front = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.FRONT]!!) + val back = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.BACK]!!) + val left = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.LEFT]!!) + val right = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.RIGHT]!!) + val top = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.TOP]!!) + val bottom = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.BOTTOM]!!) + + if (inputs.pull.test(minecraft.player)) { + val pull = LargeBooleanRectangleButtonPanel( + screen, + frame, + skinElementActive = Widgets18.PULL, + skinElementInactive = Widgets18.PULL_DISABLED, + prop = inputs.pull, + ) + + pull.tooltip = TranslatableComponent("otm.gui.side_mode.pull") + + pull.x = 30f - 20f + pull.y = 14f + } + + if (inputs.push.test(minecraft.player)) { + val push = LargeBooleanRectangleButtonPanel( + screen, + frame, + skinElementActive = Widgets18.PUSH, + skinElementInactive = Widgets18.PUSH_DISABLED, + prop = inputs.push, + ) + + push.tooltip = TranslatableComponent("otm.gui.side_mode.push") + + push.x = 30f + 20f + push.y = 14f + } + + top.tooltip = TranslatableComponent("otm.gui.sides.top") + bottom.tooltip = TranslatableComponent("otm.gui.sides.bottom") + back.tooltip = TranslatableComponent("otm.gui.sides.back") + front.tooltip = TranslatableComponent("otm.gui.sides.front") + left.tooltip = TranslatableComponent("otm.gui.sides.left") + right.tooltip = TranslatableComponent("otm.gui.sides.right") + + top.x = 30f + top.y = 14f + + right.x = 30f - 20f + right.y = 14f + 20f + + left.x = 30f + 20f + left.y = 14f + 20f + + front.x = 30f + front.y = 14f + 20f + + bottom.x = 30f + bottom.y = 14f + 42f + + back.x = 30f - 20f + back.y = 14f + 42f + + screen.addPanel(frame) + + frame.requestFocus() + + return frame +} + fun > makeDeviceControls( screen: S, parent: FramePanel, - redstone: IPlayerInputWithFeedback? = null + redstone: IPlayerInputWithFeedback? = null, + itemConfig: ItemHandlerPlayerInput? = null ): EditablePanel { val panel = object : EditablePanel(screen, parent, width = LargeEnumRectangleButtonPanel.SIZE, height = 0f, x = parent.width + 3f) { override fun tickInner() { @@ -54,6 +163,23 @@ fun > makeDeviceControls( y += makeRedstoneSettingButton(screen, panel, y = y, control = redstone).height + 2f } + if (itemConfig != null) { + y += object : LargeRectangleButtonPanel(screen, panel, y = y, skinElement = Widgets18.ITEMS_CONFIGURATION) { + init { + tooltip = TranslatableComponent("otm.gui.sides.item_config") + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + val frame = makeItemHandlerControlPanel(screen, itemConfig) + + frame.x = absoluteX + width / 2f - frame.width / 2f + frame.y = absoluteY + height + 8f + } + } + }.height + 2f + } + panel.height = (y - 2f).coerceAtLeast(0f) return panel } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt index e6e8e65f2..b699f1bfb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt @@ -5,6 +5,7 @@ import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.ChatFormatting import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.AbstractMatterySprite import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel @@ -14,7 +15,9 @@ import ru.dbotthepony.mc.otm.core.next import ru.dbotthepony.mc.otm.core.prev import ru.dbotthepony.mc.otm.core.util.EnumValueCodec import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback import java.util.* +import java.util.function.Predicate import kotlin.collections.ArrayList abstract class EnumRectangleButtonPanel>( @@ -29,7 +32,19 @@ abstract class EnumRectangleButtonPanel>( val defaultValue: T, ) : RectangleButtonPanel(screen, parent, x, y, width, height, null) { val enum = EnumValueCodec.searchClass(enum) + private val constants: Array = enum.enumConstants private var isBuilding = true + var predicate: Predicate = Predicate { true } + + override var isDisabled: Boolean + get() { + if (prop is IPlayerInputWithFeedback) { + return !prop.test(minecraft.player) + } else { + return super.isDisabled + } + } + set(value) { super.isDisabled = value } data class Entry>( val key: T, @@ -40,30 +55,6 @@ abstract class EnumRectangleButtonPanel>( protected val enumMapping = EnumMap>(enum) - fun isFullyDefined(): Boolean { - if (!isBuilding) return true - - for (value in enum.enumConstants) { - if (enumMapping[value] == null) { - return false - } - } - - return true - } - - val missingValues: List get() { - val missing = ArrayList() - - for (value in enum.enumConstants) { - if (enumMapping[value] == null) { - missing.add(value) - } - } - - return missing - } - fun add(key: T, skinElement: AbstractMatterySprite? = null, tooltip: Component? = null, winding: UVWindingOrder = UVWindingOrder.NORMAL): EnumRectangleButtonPanel { return add(Entry(key = key, skinElement = skinElement, winding = winding, tooltip = tooltip)) } @@ -76,8 +67,9 @@ abstract class EnumRectangleButtonPanel>( } fun finish(): EnumRectangleButtonPanel { - check(isBuilding) { "Not building" } - check(isFullyDefined()) { "Not all enums having their mapping defined, missing are: ${missingValues.joinToString(", ")}" } + if (!isBuilding) return this + check(enumMapping.isNotEmpty()) { "No enums were defined, like, at all." } + check(enumMapping.containsKey(defaultValue)) { "Default value $defaultValue is missing from mapping" } isBuilding = false return this } @@ -85,7 +77,7 @@ abstract class EnumRectangleButtonPanel>( override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { check(!isBuilding) { "Still building this button!" } super.innerRender(stack, mouseX, mouseY, partialTick) - val entry = checkNotNull(enumMapping[prop.get()]) { "HOW" } + val entry = enumMapping[prop.get()] ?: return entry.skinElement?.render(stack, 0f, 0f, width, height, entry.winding) } @@ -106,9 +98,48 @@ abstract class EnumRectangleButtonPanel>( } override fun onClick(mouseButton: Int) { + check(!isBuilding) { "Still building this button!" } + if (enumMapping.size == 1) return + when (mouseButton) { - InputConstants.MOUSE_BUTTON_LEFT -> prop.value = prop.value.next(enum.enumConstants) - InputConstants.MOUSE_BUTTON_RIGHT -> prop.value = prop.value.prev(enum.enumConstants) + InputConstants.MOUSE_BUTTON_LEFT -> { + var i = constants.size + var ordinal = prop.value.ordinal + + while (i >= 0) { + i-- + ordinal++ + + if (ordinal >= constants.size) { + ordinal = 0 + } + + if (enumMapping.containsKey(constants[ordinal]) && predicate.test(constants[ordinal])) { + prop.value = constants[ordinal] + break + } + } + } + + InputConstants.MOUSE_BUTTON_RIGHT -> { + var i = constants.size + var ordinal = prop.value.ordinal + + while (i >= 0) { + i-- + ordinal-- + + if (ordinal < 0) { + ordinal = constants.size - 1 + } + + if (enumMapping.containsKey(constants[ordinal]) && predicate.test(constants[ordinal])) { + prop.value = constants[ordinal] + break + } + } + } + InputConstants.MOUSE_BUTTON_MIDDLE -> { if (prop.value != defaultValue) { prop.value = defaultValue @@ -122,7 +153,7 @@ abstract class EnumRectangleButtonPanel>( return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick) } - if (tooltip == null && tooltipList == null && enumMapping.values.none { it.tooltip != null }) { + if (tooltip == null && tooltipList == null && enumMapping.entries.none { predicate.test(it.key) && it.value.tooltip != null }) { return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick) } @@ -136,8 +167,8 @@ abstract class EnumRectangleButtonPanel>( listing.add(SPACE) } - for (entry in enumMapping.values) { - if (entry.tooltip != null) { + for ((k, entry) in enumMapping) { + if (entry.tooltip != null && predicate.test(k)) { listing.add(entry.tooltip.copy().withStyle(if (entry.key == prop.get()) ChatFormatting.WHITE else ChatFormatting.GRAY)) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt index 0c70d2968..531f520cc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt @@ -1,10 +1,12 @@ package ru.dbotthepony.mc.otm.client.screen.panels.button import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.AbstractMatterySprite import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback open class LargeBooleanRectangleButtonPanel( screen: S, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeEnumRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeEnumRectangleButtonPanel.kt index 2e0925ad0..d4bff231b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeEnumRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeEnumRectangleButtonPanel.kt @@ -1,9 +1,11 @@ package ru.dbotthepony.mc.otm.client.screen.panels.button import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback open class LargeEnumRectangleButtonPanel>( screen: S, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/CobblerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/CobblerScreen.kt index ae546eff5..d50eb7f5a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/CobblerScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/CobblerScreen.kt @@ -20,7 +20,7 @@ class CobblerScreen(menu: CobblerMenu, inventory: Inventory, title: Component) : for (column in 0 .. 2) SlotPanel(this, frame, menu.storageSlots[row * 3 + column], 80f + column * AbstractSlotPanel.SIZE, 26f + row * AbstractSlotPanel.SIZE) - makeDeviceControls(this, frame, redstone = menu.redstone) + makeDeviceControls(this, frame, redstone = menu.redstone, itemConfig = menu.itemConfig) return frame } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index a7eed1b49..fc6e51c1b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -4,6 +4,7 @@ package ru.dbotthepony.mc.otm.core import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive @@ -128,6 +129,12 @@ inline fun ImmutableList(size: Int, initializer: (index: Int) -> T): I } } +inline fun immutableMap(initializer: ImmutableMap.Builder.() -> Unit): ImmutableMap { + val builder = ImmutableMap.Builder() + initializer.invoke(builder) + return builder.build() +} + fun IForgeRegistry.getID(value: T): Int { return (this as ForgeRegistry).getID(value) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt index 04e3ff6e7..781f9ef47 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt @@ -1,5 +1,7 @@ package ru.dbotthepony.mc.otm.core.collect +import java.util.stream.Stream + class ConditionalSet : AbstractSet { // method without boxing fun interface Condition { @@ -16,6 +18,10 @@ class ConditionalSet : AbstractSet { this.getters = Array(getters.size) { getters[it] } } + constructor(getters: Stream>) : super() { + this.getters = getters.toArray { arrayOfNulls>(it) } + } + override val size: Int get() { var i = 0 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt index 44c2ddc36..81a89bdcd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt @@ -6,6 +6,7 @@ import net.minecraft.nbt.DoubleTag import net.minecraft.nbt.FloatTag import net.minecraft.nbt.IntTag import net.minecraft.nbt.NumericTag +import net.minecraft.nbt.StringTag import net.minecraft.nbt.Tag import net.minecraftforge.common.util.INBTSerializable import ru.dbotthepony.mc.otm.core.GetterSetter @@ -32,6 +33,10 @@ class Savetables : INBTSerializable { return stateful(getter, name, T::class.java) } + inline fun , reified T : Tag> stateful(getter: V, name: String): Stateful { + return stateful(getter, name, T::class.java) + } + inline fun , reified T : Tag> stateful(getter: KProperty0, name: String = getter.name): Stateful { return stateful(getter, name, T::class.java) } @@ -42,6 +47,12 @@ class Savetables : INBTSerializable { .withDeserializer { v, t -> v.deserializeNBT(t) } } + fun , T : Tag> stateful(getter: V, name: String, type: Class): Stateful { + return Stateful({ getter }, name, type) + .withSerializer { it.serializeNBT() } + .withDeserializer { v, t -> v.deserializeNBT(t) } + } + fun , T : Tag> stateful(getter: KProperty0, name: String = getter.name, type: Class): Stateful { return Stateful(getter, name, type) .withSerializer { it.serializeNBT() } @@ -124,6 +135,18 @@ class Savetables : INBTSerializable { .withDeserializer { it.asByte > 0 } } + fun > enum(prop: GetterSetter, name: String, map: (String) -> E): Stateless { + return Stateless(prop, name, StringTag::class.java) + .withSerializer { StringTag.valueOf(it.name) } + .withDeserializer { map.invoke(it.asString) } + } + + fun > enum(prop: KMutableProperty0, name: String = prop.name, map: (String) -> E): Stateless { + return Stateless(prop, name, StringTag::class.java) + .withSerializer { StringTag.valueOf(it.name) } + .withDeserializer { map.invoke(it.asString) } + } + override fun serializeNBT(): CompoundTag { return CompoundTag().also(::serializeNBT) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt index dcdb6134c..f244f5381 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt @@ -5,6 +5,8 @@ import org.apache.logging.log4j.LogManager class TickList { private val conditional = ArrayDeque() private val once = ArrayDeque() + private val always = ArrayList() + private val alwaysValveTime = ArrayList() private val conditionalValveTime = ArrayList() private val onceValveTime = ArrayList() @@ -27,6 +29,14 @@ class TickList { } } + fun always(ticker: ITickable) { + if (inTicker) { + alwaysValveTime.add(ticker) + } else { + always.add(ticker) + } + } + fun add(ticker: IConditionalTickable, condition: Boolean, reason: String) { if (!condition) { LOGGER.error("Refusing to add tickable $ticker because we $reason", IllegalStateException(reason)) @@ -79,6 +89,13 @@ class TickList { once.clear() + for (ticker in always) { + ticker.tick() + } + + always.addAll(alwaysValveTime) + alwaysValveTime.clear() + for (ticker in conditionalValveTime) { conditional.addFirst(ticker) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemHandlerPlayerInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemHandlerPlayerInput.kt new file mode 100644 index 000000000..2c96cea3a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemHandlerPlayerInput.kt @@ -0,0 +1,66 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.menu.MatteryMenu + +class ItemHandlerPlayerInput(val menu: MatteryMenu, val allowPull: Boolean = true, val allowPush: Boolean = true) { + inner class Piece(val side: RelativeSide) { + var isPresent by menu.mSynchronizer.bool() + private set + + private val allowedFlags = MatteryDeviceBlockEntity.ItemHandlerView.values().map { menu.mSynchronizer.bool() to it } + + fun isAllowed(value: MatteryDeviceBlockEntity.ItemHandlerView) = allowedFlags[value.ordinal].first.value + + val pull = BooleanInputWithFeedback(menu) + val push = BooleanInputWithFeedback(menu) + val input = EnumInputWithFeedback(menu) + + var default by menu.mSynchronizer.enum(MatteryDeviceBlockEntity.ItemHandlerView.DISABLED) + + init { + pull.filter { allowPull } + push.filter { allowPush } + } + + fun configure(config: MatteryDeviceBlockEntity.ConfigurableItemHandler) { + isPresent = true + + for ((f, v) in allowedFlags) { + f.value = v in config.possibleViews + } + + pull.with(config::automatePull) + push.with(config::automatePush) + input.withSupplier { config.mode }.withConsumer { if (it in config.possibleViews) config.mode = it } + } + } + + val pieces = immutableMap { for (side in RelativeSide.values()) put(side, Piece(side)) } + + // TODO + val pull = BooleanInputWithFeedback(menu) + + // TODO + val push = BooleanInputWithFeedback(menu) + + init { + pull.filter { allowPull } + push.filter { allowPush } + } + + fun configure(config: MatteryDeviceBlockEntity.GlobalItemHandler) { + for ((side, v) in config.sides) { + pieces[side]!!.configure(v) + pieces[side]!!.default = config.defaults[side]!! + } + + pull.withSupplier { pieces.values.all { it.pull.value } } + push.withSupplier { pieces.values.all { it.push.value } } + + pull.withConsumer { v -> pieces.values.forEach { it.pull.input.input(v) } } + push.withConsumer { v -> pieces.values.forEach { it.push.input.input(v) } } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt index 0ee1adb62..bb14c3f44 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt @@ -8,6 +8,7 @@ import ru.dbotthepony.mc.otm.core.ImmutableList import ru.dbotthepony.mc.otm.menu.MachineOutputSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemHandlerPlayerInput import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget import ru.dbotthepony.mc.otm.registry.MMenus @@ -18,6 +19,7 @@ class CobblerMenu @JvmOverloads constructor( ) : MatteryMenu(MMenus.COBBLESTONE_GENERATOR, p_38852_, inventory, tile) { override val storageSlots = (tile?.droppableContainer ?: SimpleContainer(CobblerBlockEntity.CONTAINER_SIZE)).let { c -> ImmutableList(c.containerSize) { addSlot(MachineOutputSlot(c, it)) } } val redstone = EnumInputWithFeedback(this) + val itemConfig = ItemHandlerPlayerInput(this, false, false) val progress: ProgressGaugeWidget @@ -27,6 +29,7 @@ class CobblerMenu @JvmOverloads constructor( else { progress = ProgressGaugeWidget(this, tile::workProgress, tile::isUnableToProcess) redstone.with(tile.redstoneControl::redstoneSetting) + itemConfig.configure(tile.itemConfig) } addInventorySlots() diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png new file mode 100644 index 000000000..4e8c82af2 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.xcf new file mode 100644 index 000000000..a44512a59 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.xcf differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf index 594d60564..7b7a7bc10 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf differ