A lot of stuff related to configurable devices sides

This commit is contained in:
DBotThePony 2023-02-19 01:06:09 +07:00
parent 993790fbb5
commit a54d2f940f
Signed by: DBot
GPG Key ID: DCC23B5715498507
50 changed files with 901 additions and 257 deletions

View File

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

View File

@ -616,6 +616,24 @@ private fun gui(provider: MatteryLanguageProvider) {
gui("item_monitor.amount.full", "Стопку ингредиентов. Создание продолжается пока не будет достигнут лимит по стопке одного из ингредиентов. Если хотя бы один из ингредиентов не может быть стопкой, будет использован режим 'одна стопка результата'") gui("item_monitor.amount.full", "Стопку ингредиентов. Создание продолжается пока не будет достигнут лимит по стопке одного из ингредиентов. Если хотя бы один из ингредиентов не может быть стопкой, будет использован режим 'одна стопка результата'")
gui("stored_amount", "Точное количество в хранилище: %s шт.") 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", "Автоматическое выталкивание")
} }
} }

View File

@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableSet
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap
import net.minecraft.client.multiplayer.ClientLevel import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
@ -12,11 +11,10 @@ import net.minecraft.core.Direction
import net.minecraft.core.SectionPos import net.minecraft.core.SectionPos
import net.minecraft.core.Vec3i import net.minecraft.core.Vec3i
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.ChunkPos import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block 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.level.LevelEvent
import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.event.server.ServerStoppingEvent
import net.minecraftforge.items.IItemHandler 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.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.MatteryCapability
import ru.dbotthepony.mc.otm.capability.UnmodifiableItemHandler
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.isMekanismLoaded 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.compat.mekanism.Mattery2MekanismEnergyWrapper
import ru.dbotthepony.mc.otm.core.collect.SupplierList import ru.dbotthepony.mc.otm.core.collect.SupplierList
import ru.dbotthepony.mc.otm.core.collect.WeakHashSet import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
import ru.dbotthepony.mc.otm.core.forValidRefs 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.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.BlockRotation
import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.math.minus import ru.dbotthepony.mc.otm.core.math.minus
import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.util.ITickable
import ru.dbotthepony.mc.otm.core.util.EnumValueCodec
import ru.dbotthepony.mc.otm.core.util.Savetables 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.BlockEntitySyncPacket
import ru.dbotthepony.mc.otm.network.FieldSynchronizer import ru.dbotthepony.mc.otm.network.FieldSynchronizer
import ru.dbotthepony.mc.otm.network.WorldNetworkChannel import ru.dbotthepony.mc.otm.network.WorldNetworkChannel
@ -88,6 +92,12 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
private data class SidelessCap<T : Any>(val cap: T, var optional: LazyOptional<T>) private data class SidelessCap<T : Any>(val cap: T, var optional: LazyOptional<T>)
private val sidelessCaps = Reference2ObjectArrayMap<Capability<*>, SidelessCap<*>>() private val sidelessCaps = Reference2ObjectArrayMap<Capability<*>, SidelessCap<*>>()
protected val tickList = TickList()
protected val savetables = Savetables()
open fun tick() {
tickList.tick()
}
/** /**
* exposes capability when no side is specified * 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) { protected fun exposeEnergySideless(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) {
exposeSideless(ForgeCapabilities.ENERGY, value) exposeSideless(ForgeCapabilities.ENERGY, value)
exposeSideless(MatteryCapability.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) { protected fun exposeItemsGlobally(value: IItemHandler) {
exposeGlobally(ForgeCapabilities.ITEM_HANDLER, value) 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 }) return SupplierList(_sides.values.map { it.track(capability)::get })
} }
enum class SideMode(val filter: FlowDirection, val automation: FlowDirection) { inner class Side(val side: RelativeSide) {
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<SideMode> = ImmutableSet.of(NONE, INPUT, OUTPUT, PULL, PUSH, PULL_ONLY, PUSH_ONLY, PULL_PUSH, DISABLED)
@JvmField
val INPUT_SET: ImmutableSet<SideMode> = ImmutableSet.of(INPUT, PULL, DISABLED)
@JvmField
val OUTPUT_SET: ImmutableSet<SideMode> = 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<CompoundTag> {
init { init {
check(!_sides.containsKey(side)) { "dafuq are you trying to do" } check(!_sides.containsKey(side)) { "dafuq are you trying to do" }
_sides[side] = this _sides[side] = this
} }
private val caps = Reference2ObjectArrayMap<Capability<*>, Cap<*>>() private val caps = Reference2ObjectArrayMap<Capability<*>, Cap<*>>()
private val data = Object2ObjectArrayMap<ResourceLocation, INBTSerializable<Tag>>()
private val subscriptions = Reference2ObjectArrayMap<Capability<*>, SubRef<*>>() private val subscriptions = Reference2ObjectArrayMap<Capability<*>, SubRef<*>>()
private val knownLOs = WeakHashSet<LazyOptional<*>>() private val knownLOs = WeakHashSet<LazyOptional<*>>()
@ -240,7 +218,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
} }
private fun updateTracked(capability: Capability<*>) { private fun updateTracked(capability: Capability<*>) {
if (isRemoved) return if (isRemoved || !SERVER_IS_LIVE) return
val dir = blockRotation.side2Dir(side) val dir = blockRotation.side2Dir(side)
val chunk = level 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 <T : Any> get(capability: Capability<T>): Cap<T>? { operator fun <T : Any> get(capability: Capability<T>): Cap<T>? {
return caps[capability] as Cap<T>? return caps[capability] as Cap<T>?
} }
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<Tag>
}
fun getData(index: ResourceLocation): INBTSerializable<*>? {
return data[index]
}
fun invalidate() { fun invalidate() {
for (cap in caps.values) for (cap in caps.values)
cap.invalidate() cap.invalidate()
@ -307,87 +274,18 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
cap.revive() 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> = SideMode.BI_SET) : INBTSerializable<StringTag> {
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<WeakReference<SideModeListener>>()
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<T : Any>(val type: Capability<in T>, val capability: T) { inner class Cap<T : Any>(val type: Capability<in T>, val capability: T) {
init { init {
check(!caps.containsKey(type)) { "Already has capability $type on side $side" } check(!caps.containsKey(type)) { "Already has capability $type on side $side" }
caps[type] = this caps[type] = this
} }
private var isExposed = true var isExposed = true
private var isValid = true private set
private var isRemoved = false var isValid = true
private set
var isRemoved = false
private set
var optional: LazyOptional<T> by object : ReadWriteProperty<Any?, LazyOptional<T>> { var optional: LazyOptional<T> by object : ReadWriteProperty<Any?, LazyOptional<T>> {
private var value: LazyOptional<T>? = null private var value: LazyOptional<T>? = null
@ -419,7 +317,9 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
if (!isRemoved && isExposed) { if (!isRemoved && isExposed) {
isExposed = false isExposed = false
optional.invalidate() 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) { if (isValid) {
optional = LazyOptional.of { capability } 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) { if (!isRemoved && isValid) {
isValid = false isValid = false
optional.invalidate() 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) { if (isExposed) {
optional = LazyOptional.of { capability } 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() side.revive()
} }
protected val savetables = Savetables()
protected inline fun <reified T : Tag, V : INBTSerializable<T?>> savetable(property: KProperty0<V>, name: String = property.name) { protected inline fun <reified T : Tag, V : INBTSerializable<T?>> savetable(property: KProperty0<V>, name: String = property.name) {
savetables.stateful(property, name, T::class.java) savetables.stateful(property, name, T::class.java)
} }
override fun saveAdditional(nbt: CompoundTag) { override fun saveAdditional(nbt: CompoundTag) {
super.saveAdditional(nbt) 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) savetables.serializeNBT(nbt)
} }
override fun load(nbt: CompoundTag) { override fun load(nbt: CompoundTag) {
super.load(nbt) 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) savetables.deserializeNBT(nbt)
} }

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.block.entity package ru.dbotthepony.mc.otm.block.entity
import com.google.common.collect.ImmutableSet
import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState 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.world.inventory.AbstractContainerMenu
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity 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.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.getJson
import ru.dbotthepony.mc.otm.core.nbt.putJson import ru.dbotthepony.mc.otm.core.nbt.putJson
import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.util.ITickable
import ru.dbotthepony.mc.otm.oncePre import ru.dbotthepony.mc.otm.oncePre
/** /**
@ -61,4 +74,231 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo
customDisplayName = nbt.getJson("Name")?.let(Component.Serializer::fromJson) customDisplayName = nbt.getJson("Name")?.let(Component.Serializer::fromJson)
redstoneControl.deserializeNBT(nbt[REDSTONE_CONTROL_KEY] as? CompoundTag) 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<IItemHandler>()
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<ItemHandlerView>
val inputOutput: IItemHandler?
init {
tickList.always(this)
val builder = ImmutableSet.Builder<ItemHandlerView>()
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
}
}
} }

View File

@ -33,7 +33,9 @@ abstract class MatteryPoweredBlockEntity(p_155228_: BlockEntityType<*>, p_155229
savetable(::batteryContainer, BATTERY_KEY) savetable(::batteryContainer, BATTERY_KEY)
} }
protected fun batteryChargeLoop() { override fun tick() {
super.tick()
val energy = matteryEnergy val energy = matteryEnergy
if (energy == null || !batteryContainer.any { !it.isEmpty }) if (energy == null || !batteryContainer.any { !it.isEmpty })
return return

View File

@ -233,7 +233,9 @@ abstract class MatteryWorkerBlockEntity<JobType : MatteryWorkerBlockEntity.Job>(
isIdling = newBlocked 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) { 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) level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.ERROR), Block.UPDATE_CLIENTS)
} }
@ -379,11 +381,6 @@ abstract class MatteryWorkerBlockEntity<JobType : MatteryWorkerBlockEntity.Job>(
} }
} }
fun basicTicker() {
batteryChargeLoop()
workerLoop()
}
companion object { companion object {
const val WORK_TICKS_KEY = "workTicks" const val WORK_TICKS_KEY = "workTicks"
const val JOB_KEY = "job" const val JOB_KEY = "job"

View File

@ -215,7 +215,8 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
} }
} }
fun tick() { override fun tick() {
super.tick()
sleepTicks-- sleepTicks--
if (sleepTicks > 0) return if (sleepTicks > 0) return
val level = level as? ServerLevel ?: return val level = level as? ServerLevel ?: return

View File

@ -169,8 +169,8 @@ class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
matterNode.destroy(::MatterNetworkGraph) matterNode.destroy(::MatterNetworkGraph)
} }
fun tick() { override fun tick() {
batteryChargeLoop() super.tick()
if (redstoneControl.isBlockedByRedstone) { if (redstoneControl.isBlockedByRedstone) {
if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.IDLE) { if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.IDLE) {

View File

@ -215,9 +215,8 @@ class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState)
return matter return matter
} }
fun tick() { override fun tick() {
batteryChargeLoop() super.tick()
workerLoop()
val grid = matterNode.graph as MatterNetworkGraph? ?: return val grid = matterNode.graph as MatterNetworkGraph? ?: return

View File

@ -154,8 +154,8 @@ class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState)
return Status.SUCCESS return Status.SUCCESS
} }
fun tick() { override fun tick() {
basicTicker() super.tick()
val graph = matterNode.graph as MatterNetworkGraph? ?: return val graph = matterNode.graph as MatterNetworkGraph? ?: return
val received = graph.receiveMatter(matter.storedMatter, false) val received = graph.receiveMatter(matter.storedMatter, false)

View File

@ -57,8 +57,8 @@ class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
StorageNetworkGraph.discoverFull(this, cell.storageNode) StorageNetworkGraph.discoverFull(this, cell.storageNode)
} }
fun tick() { override fun tick() {
batteryChargeLoop() super.tick()
cell.tickEnergyDemanding() cell.tickEnergyDemanding()
} }

View File

@ -81,8 +81,4 @@ class DriveViewerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
return DriveViewerMenu(containerID, inventory, this) return DriveViewerMenu(containerID, inventory, this)
} }
fun tick() {
batteryChargeLoop()
}
} }

View File

@ -481,8 +481,9 @@ class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
return settings.computeIfAbsent(ply.uuid) { ItemMonitorPlayerSettings() } return settings.computeIfAbsent(ply.uuid) { ItemMonitorPlayerSettings() }
} }
fun tick() { override fun tick() {
batteryChargeLoop() super.tick()
cell.tickEnergyDemanding() cell.tickEnergyDemanding()
if (craftingAmount.size != 0) if (craftingAmount.size != 0)

View File

@ -123,8 +123,8 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
private var neighbour: LazyOptional<IItemHandler>? = null private var neighbour: LazyOptional<IItemHandler>? = null
private var component: ItemHandlerComponent? = null private var component: ItemHandlerComponent? = null
fun tick() { override fun tick() {
batteryChargeLoop() super.tick()
component?.scan() component?.scan()
cell.tickEnergyDemanding() cell.tickEnergyDemanding()
} }

View File

@ -175,11 +175,12 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState)
return filter.match(stack) return filter.match(stack)
} }
fun tick() { override fun tick() {
super.tick()
if (redstoneControl.isBlockedByRedstone) if (redstoneControl.isBlockedByRedstone)
return return
batteryChargeLoop()
cell.tickEnergyDemanding() cell.tickEnergyDemanding()
nextTick-- nextTick--
@ -287,11 +288,12 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) :
return relevantTuples.stream().map { it to view[it] } return relevantTuples.stream().map { it to view[it] }
} }
fun tick() { override fun tick() {
super.tick()
if (redstoneControl.isBlockedByRedstone) if (redstoneControl.isBlockedByRedstone)
return return
batteryChargeLoop()
cell.tickEnergyDemanding() cell.tickEnergyDemanding()
nextTick-- nextTick--

View File

@ -53,12 +53,12 @@ class StoragePowerSupplierBlockEntity(blockPos: BlockPos, blockState: BlockState
cell.destroy(level) cell.destroy(level)
} }
fun tick() { override fun tick() {
super.tick()
if (redstoneControl.isBlockedByRedstone) if (redstoneControl.isBlockedByRedstone)
return return
batteryChargeLoop()
if (energy.batteryLevel.isZero) if (energy.batteryLevel.isZero)
return return

View File

@ -65,7 +65,9 @@ class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
private var tickedOnce = false private var tickedOnce = false
fun tick() { override fun tick() {
super.tick()
if (!tickedOnce) { if (!tickedOnce) {
tickedOnce = true tickedOnce = true
@ -76,8 +78,6 @@ class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
} }
} }
batteryChargeLoop()
if (redstoneControl.isBlockedByRedstone) return if (redstoneControl.isBlockedByRedstone) return
val level = level ?: return val level = level ?: return
val x = blockPos.x.toDouble() val x = blockPos.x.toDouble()

View File

@ -206,7 +206,9 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte
private val consumers by front.track(ForgeCapabilities.ENERGY) private val consumers by front.track(ForgeCapabilities.ENERGY)
fun tick() { override fun tick() {
super.tick()
if (redstoneControl.isBlockedByRedstone) if (redstoneControl.isBlockedByRedstone)
return return

View File

@ -89,7 +89,9 @@ class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDe
} }
} }
fun tick() { override fun tick() {
super.tick()
if (workTicks > 0) { if (workTicks > 0) {
workTicks-- workTicks--
energy.receiveEnergy(GENERATION_SPEED, false) energy.receiveEnergy(GENERATION_SPEED, false)

View File

@ -22,14 +22,15 @@ class CobblerBlockEntity(blockPos: BlockPos, blockState: BlockState)
override val droppableContainer = MatteryContainer(this::itemContainerUpdated, CONTAINER_SIZE) 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 { override fun canInsert(slot: Int, stack: ItemStack): Boolean {
return false return false
} }
}) })
val itemConfig = GlobalItemHandler(output = itemHandler)
init { init {
exposeItemsGlobally(handler)
savetable(::droppableContainer, INVENTORY_KEY) savetable(::droppableContainer, INVENTORY_KEY)
} }

View File

@ -285,7 +285,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
} }
} }
fun tick() { override fun tick() {
super.tick()
lastTick = history[historyTick] lastTick = history[historyTick]
historyTick = (historyTick + 1) % history.size historyTick = (historyTick + 1) % history.size
history[historyTick] = Decimal.ZERO history[historyTick] = Decimal.ZERO

View File

@ -97,7 +97,9 @@ class EnergyServoBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
return EnergyServoMenu(containerID, inventory, this) return EnergyServoMenu(containerID, inventory, this)
} }
fun tick() { override fun tick() {
super.tick()
if (redstoneControl.isBlockedByRedstone) if (redstoneControl.isBlockedByRedstone)
return return

View File

@ -35,7 +35,7 @@ class MatterReplicatorBlock : RotatableMatteryBlock(), EntityBlock {
if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.MATTER_REPLICATOR) if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.MATTER_REPLICATOR)
return null 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<Block, BlockState>) { override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {

View File

@ -35,7 +35,7 @@ class MatterScannerBlock : RotatableMatteryBlock(), EntityBlock {
if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.MATTER_SCANNER) if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.MATTER_SCANNER)
return null 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<Block, BlockState>) { override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {

View File

@ -21,7 +21,7 @@ class CobblerBlock : RotatableMatteryBlock(), EntityBlock {
pBlockEntityType: BlockEntityType<T> pBlockEntityType: BlockEntityType<T>
): BlockEntityTicker<T>? { ): BlockEntityTicker<T>? {
if (!pLevel.isClientSide) { if (!pLevel.isClientSide) {
return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is CobblerBlockEntity) pBlockEntity.basicTicker() } return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is CobblerBlockEntity) pBlockEntity.tick() }
} }
return null return null

View File

@ -39,7 +39,7 @@ class PlatePressBlock(properties: Properties = DEFAULT_PROPERTIES) : RotatableMa
if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.PLATE_PRESS) if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.PLATE_PRESS)
return null 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<Block, BlockState>) { override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {

View File

@ -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>) : IItemHandler {
constructor(handlers: Stream<IItemHandler>) : this(handlers.collect(ImmutableList.toImmutableList()))
constructor(handlers: Collection<IItemHandler>) : 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<Mapping>()
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)
}
}

View File

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

View File

@ -45,6 +45,10 @@ enum class FlowDirection(val input: Boolean, val output: Boolean) : Predicate<Fl
return t === this || (!input || t.input) && (!output || t.output) return t === this || (!input || t.input) && (!output || t.output)
} }
fun intersect(other: FlowDirection): FlowDirection {
return of(other.input && input, other.output && output)
}
companion object { companion object {
@JvmStatic @JvmStatic
fun of(input: Boolean, output: Boolean): FlowDirection { fun of(input: Boolean, output: Boolean): FlowDirection {

View File

@ -0,0 +1,43 @@
package ru.dbotthepony.mc.otm.capability
import net.minecraftforge.items.IItemHandler
/**
* Attempts to safely exchange/move item between slots of two handlers
*
* @return pair of new (advanced) [sourceSlot] and [destinationSlot]
*/
fun moveBetweenSlots(source: IItemHandler, sourceSlot: Int, destination: IItemHandler, destinationSlot: Int): Pair<Int, Int> {
val getItem = source.extractItem(sourceSlot, Int.MAX_VALUE, true)
if (getItem.isEmpty) {
return sourceSlot + 1 to destinationSlot
} else {
val leftover = destination.insertItem(destinationSlot, getItem, true)
if (leftover.count == getItem.count) {
return sourceSlot to destinationSlot + 1
} else {
val getItem2 = source.extractItem(sourceSlot, getItem.count - leftover.count, true)
if (getItem2.isEmpty) {
return sourceSlot + 1 to destinationSlot
} else {
val leftover2 = destination.insertItem(destinationSlot, getItem2, true)
if (leftover2.isEmpty) {
source.extractItem(sourceSlot, getItem2.count, false)
destination.insertItem(destinationSlot, getItem2, false)
if (getItem2.count == getItem.count) {
return sourceSlot + 1 to destinationSlot
} else {
return sourceSlot to destinationSlot
}
}
}
}
}
return sourceSlot to destinationSlot
}

View File

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

View File

@ -162,13 +162,13 @@ interface IMatteryEnergyStorage : IEnergyStorage {
if (!energyFlow.input) if (!energyFlow.input)
return 0 return 0
val received = receiveEnergy(Decimal(maxReceive), true).toInt() val received = receiveEnergyChecked(Decimal(maxReceive), true).toInt()
// Receiving only a fraction // Receiving only a fraction
if (received == 0) if (received == 0)
return 0 return 0
return receiveEnergy(Decimal(received), simulate).toInt() return receiveEnergyChecked(Decimal(received), simulate).toInt()
} }
override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int { override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int {
@ -176,13 +176,13 @@ interface IMatteryEnergyStorage : IEnergyStorage {
if (energyFlow.output) if (energyFlow.output)
return 0 return 0
val extracted = extractEnergy(Decimal(maxReceive), true).toInt() val extracted = extractEnergyChecked(Decimal(maxReceive), true).toInt()
// Extracting only a fraction // Extracting only a fraction
if (extracted == 0) if (extracted == 0)
return 0 return 0
return extractEnergy(Decimal(extracted), simulate).toInt() return extractEnergyChecked(Decimal(extracted), simulate).toInt()
} }
override fun getEnergyStored(): Int { override fun getEnergyStored(): Int {

View File

@ -17,4 +17,5 @@ object WidgetLocation {
val HORIZONTAL_GAUGES = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/horizontal_gauges.png"), 96f, 54f) 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 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 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)
} }

View File

@ -55,4 +55,17 @@ object Widgets18 {
val REDSTONE_IGNORED = redstoneGrid.next() val REDSTONE_IGNORED = redstoneGrid.next()
val REDSTONE_LOW = redstoneGrid.next() val REDSTONE_LOW = redstoneGrid.next()
val REDSTONE_HIGH = 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()
} }

View File

@ -569,12 +569,12 @@ open class EditablePanel<out S : Screen> @JvmOverloads constructor(
val old = field val old = field
field = value field = value
onFocusChanged(old, value) onFocusChanged(value, old)
findAbsoluteRoot().updateFocus() findAbsoluteRoot().updateFocus()
} }
} }
var autoKillFocus = false var autoKillFocus = true
private var focusedAsParent = false private var focusedAsParent = false
val font: Font get() = if (screen is MatteryScreen<*>) screen.font else minecraft.font val font: Font get() = if (screen is MatteryScreen<*>) screen.font else minecraft.font

View File

@ -1,12 +1,13 @@
package ru.dbotthepony.mc.otm.client.screen.panels.button package ru.dbotthepony.mc.otm.client.screen.panels.button
import com.mojang.blaze3d.platform.InputConstants
import com.mojang.blaze3d.vertex.PoseStack import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.gui.screens.Screen 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.AbstractMatterySprite
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.value import ru.dbotthepony.mc.otm.core.value
import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback
abstract class BooleanRectangleButtonPanel<out S : Screen>( abstract class BooleanRectangleButtonPanel<out S : Screen>(
screen: S, screen: S,
@ -26,6 +27,16 @@ abstract class BooleanRectangleButtonPanel<out S : Screen>(
onChange?.invoke(newValue) onChange?.invoke(newValue)
} }
override var isDisabled: Boolean
get() {
if (prop is IPlayerInputWithFeedback<Boolean>) {
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) { override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
super.innerRender(stack, mouseX, mouseY, partialTick) super.innerRender(stack, mouseX, mouseY, partialTick)

View File

@ -1,17 +1,21 @@
package ru.dbotthepony.mc.otm.client.screen.panels.button package ru.dbotthepony.mc.otm.client.screen.panels.button
import com.mojang.blaze3d.vertex.PoseStack import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import net.minecraft.world.item.Items
import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen 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.EditablePanel
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel 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.IPlayerInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.ItemHandlerPlayerInput
import java.util.function.Predicate
fun <S : MatteryScreen<*>> makeRedstoneSettingButton( private fun <S : MatteryScreen<*>> makeRedstoneSettingButton(
screen: S, screen: S,
parent: EditablePanel<*>?, parent: EditablePanel<*>?,
x: Float = 0f, x: Float = 0f,
@ -35,10 +39,115 @@ fun <S : MatteryScreen<*>> makeRedstoneSettingButton(
} }
} }
private fun <S : MatteryScreen<*>> makeItemModeButton(screen: S, parent: FramePanel<S>, input: ItemHandlerPlayerInput.Piece): LargeEnumRectangleButtonPanel<S, MatteryDeviceBlockEntity.ItemHandlerView> {
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 <S : MatteryScreen<*>> makeItemHandlerControlPanel(
screen: S,
inputs: ItemHandlerPlayerInput
): FramePanel<S> {
val frame = object : FramePanel<S>(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 <S : MatteryScreen<*>> makeDeviceControls( fun <S : MatteryScreen<*>> makeDeviceControls(
screen: S, screen: S,
parent: FramePanel<S>, parent: FramePanel<S>,
redstone: IPlayerInputWithFeedback<RedstoneSetting>? = null redstone: IPlayerInputWithFeedback<RedstoneSetting>? = null,
itemConfig: ItemHandlerPlayerInput? = null
): EditablePanel<S> { ): EditablePanel<S> {
val panel = object : EditablePanel<S>(screen, parent, width = LargeEnumRectangleButtonPanel.SIZE, height = 0f, x = parent.width + 3f) { val panel = object : EditablePanel<S>(screen, parent, width = LargeEnumRectangleButtonPanel.SIZE, height = 0f, x = parent.width + 3f) {
override fun tickInner() { override fun tickInner() {
@ -54,6 +163,23 @@ fun <S : MatteryScreen<*>> makeDeviceControls(
y += makeRedstoneSettingButton(screen, panel, y = y, control = redstone).height + 2f y += makeRedstoneSettingButton(screen, panel, y = y, control = redstone).height + 2f
} }
if (itemConfig != null) {
y += object : LargeRectangleButtonPanel<S>(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) panel.height = (y - 2f).coerceAtLeast(0f)
return panel return panel
} }

View File

@ -5,6 +5,7 @@ import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.ChatFormatting import net.minecraft.ChatFormatting
import net.minecraft.client.gui.screens.Screen import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component 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.AbstractMatterySprite
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel 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.prev
import ru.dbotthepony.mc.otm.core.util.EnumValueCodec import ru.dbotthepony.mc.otm.core.util.EnumValueCodec
import ru.dbotthepony.mc.otm.core.value import ru.dbotthepony.mc.otm.core.value
import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback
import java.util.* import java.util.*
import java.util.function.Predicate
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>( abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
@ -29,7 +32,19 @@ abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
val defaultValue: T, val defaultValue: T,
) : RectangleButtonPanel<S>(screen, parent, x, y, width, height, null) { ) : RectangleButtonPanel<S>(screen, parent, x, y, width, height, null) {
val enum = EnumValueCodec.searchClass(enum) val enum = EnumValueCodec.searchClass(enum)
private val constants: Array<T> = enum.enumConstants
private var isBuilding = true private var isBuilding = true
var predicate: Predicate<T> = Predicate { true }
override var isDisabled: Boolean
get() {
if (prop is IPlayerInputWithFeedback<T>) {
return !prop.test(minecraft.player)
} else {
return super.isDisabled
}
}
set(value) { super.isDisabled = value }
data class Entry<T : Enum<T>>( data class Entry<T : Enum<T>>(
val key: T, val key: T,
@ -40,30 +55,6 @@ abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
protected val enumMapping = EnumMap<T, Entry<T>>(enum) protected val enumMapping = EnumMap<T, Entry<T>>(enum)
fun isFullyDefined(): Boolean {
if (!isBuilding) return true
for (value in enum.enumConstants) {
if (enumMapping[value] == null) {
return false
}
}
return true
}
val missingValues: List<T> get() {
val missing = ArrayList<T>()
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<S, T> { fun add(key: T, skinElement: AbstractMatterySprite? = null, tooltip: Component? = null, winding: UVWindingOrder = UVWindingOrder.NORMAL): EnumRectangleButtonPanel<S, T> {
return add(Entry(key = key, skinElement = skinElement, winding = winding, tooltip = tooltip)) return add(Entry(key = key, skinElement = skinElement, winding = winding, tooltip = tooltip))
} }
@ -76,8 +67,9 @@ abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
} }
fun finish(): EnumRectangleButtonPanel<S, T> { fun finish(): EnumRectangleButtonPanel<S, T> {
check(isBuilding) { "Not building" } if (!isBuilding) return this
check(isFullyDefined()) { "Not all enums having their mapping defined, missing are: ${missingValues.joinToString(", ")}" } check(enumMapping.isNotEmpty()) { "No enums were defined, like, at all." }
check(enumMapping.containsKey(defaultValue)) { "Default value $defaultValue is missing from mapping" }
isBuilding = false isBuilding = false
return this return this
} }
@ -85,7 +77,7 @@ abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
check(!isBuilding) { "Still building this button!" } check(!isBuilding) { "Still building this button!" }
super.innerRender(stack, mouseX, mouseY, partialTick) 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) entry.skinElement?.render(stack, 0f, 0f, width, height, entry.winding)
} }
@ -106,9 +98,48 @@ abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
} }
override fun onClick(mouseButton: Int) { override fun onClick(mouseButton: Int) {
check(!isBuilding) { "Still building this button!" }
if (enumMapping.size == 1) return
when (mouseButton) { when (mouseButton) {
InputConstants.MOUSE_BUTTON_LEFT -> prop.value = prop.value.next(enum.enumConstants) InputConstants.MOUSE_BUTTON_LEFT -> {
InputConstants.MOUSE_BUTTON_RIGHT -> prop.value = prop.value.prev(enum.enumConstants) 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 -> { InputConstants.MOUSE_BUTTON_MIDDLE -> {
if (prop.value != defaultValue) { if (prop.value != defaultValue) {
prop.value = defaultValue prop.value = defaultValue
@ -122,7 +153,7 @@ abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick) 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) return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick)
} }
@ -136,8 +167,8 @@ abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
listing.add(SPACE) listing.add(SPACE)
} }
for (entry in enumMapping.values) { for ((k, entry) in enumMapping) {
if (entry.tooltip != null) { if (entry.tooltip != null && predicate.test(k)) {
listing.add(entry.tooltip.copy().withStyle(if (entry.key == prop.get()) ChatFormatting.WHITE else ChatFormatting.GRAY)) listing.add(entry.tooltip.copy().withStyle(if (entry.key == prop.get()) ChatFormatting.WHITE else ChatFormatting.GRAY))
} }
} }

View File

@ -1,10 +1,12 @@
package ru.dbotthepony.mc.otm.client.screen.panels.button package ru.dbotthepony.mc.otm.client.screen.panels.button
import net.minecraft.client.gui.screens.Screen 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.AbstractMatterySprite
import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback
open class LargeBooleanRectangleButtonPanel<out S : Screen>( open class LargeBooleanRectangleButtonPanel<out S : Screen>(
screen: S, screen: S,

View File

@ -1,9 +1,11 @@
package ru.dbotthepony.mc.otm.client.screen.panels.button package ru.dbotthepony.mc.otm.client.screen.panels.button
import net.minecraft.client.gui.screens.Screen 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.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback
open class LargeEnumRectangleButtonPanel<out S : Screen, T : Enum<T>>( open class LargeEnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
screen: S, screen: S,

View File

@ -20,7 +20,7 @@ class CobblerScreen(menu: CobblerMenu, inventory: Inventory, title: Component) :
for (column in 0 .. 2) for (column in 0 .. 2)
SlotPanel(this, frame, menu.storageSlots[row * 3 + column], 80f + column * AbstractSlotPanel.SIZE, 26f + row * AbstractSlotPanel.SIZE) 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 return frame
} }

View File

@ -4,6 +4,7 @@
package ru.dbotthepony.mc.otm.core package ru.dbotthepony.mc.otm.core
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
@ -128,6 +129,12 @@ inline fun <T : Any> ImmutableList(size: Int, initializer: (index: Int) -> T): I
} }
} }
inline fun <K : Any, V : Any> immutableMap(initializer: ImmutableMap.Builder<K, V>.() -> Unit): ImmutableMap<K, V> {
val builder = ImmutableMap.Builder<K, V>()
initializer.invoke(builder)
return builder.build()
}
fun <T> IForgeRegistry<T>.getID(value: T): Int { fun <T> IForgeRegistry<T>.getID(value: T): Int {
return (this as ForgeRegistry<T>).getID(value) return (this as ForgeRegistry<T>).getID(value)
} }

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.mc.otm.core.collect package ru.dbotthepony.mc.otm.core.collect
import java.util.stream.Stream
class ConditionalSet<T> : AbstractSet<T> { class ConditionalSet<T> : AbstractSet<T> {
// method without boxing // method without boxing
fun interface Condition { fun interface Condition {
@ -16,6 +18,10 @@ class ConditionalSet<T> : AbstractSet<T> {
this.getters = Array(getters.size) { getters[it] } this.getters = Array(getters.size) { getters[it] }
} }
constructor(getters: Stream<Pair<Condition, T>>) : super() {
this.getters = getters.toArray { arrayOfNulls<Pair<Condition, T>>(it) }
}
override val size: Int get() { override val size: Int get() {
var i = 0 var i = 0

View File

@ -6,6 +6,7 @@ import net.minecraft.nbt.DoubleTag
import net.minecraft.nbt.FloatTag import net.minecraft.nbt.FloatTag
import net.minecraft.nbt.IntTag import net.minecraft.nbt.IntTag
import net.minecraft.nbt.NumericTag import net.minecraft.nbt.NumericTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag import net.minecraft.nbt.Tag
import net.minecraftforge.common.util.INBTSerializable import net.minecraftforge.common.util.INBTSerializable
import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.GetterSetter
@ -32,6 +33,10 @@ class Savetables : INBTSerializable<CompoundTag> {
return stateful(getter, name, T::class.java) return stateful(getter, name, T::class.java)
} }
inline fun <V : INBTSerializable<T?>, reified T : Tag> stateful(getter: V, name: String): Stateful<V, T> {
return stateful(getter, name, T::class.java)
}
inline fun <V : INBTSerializable<T?>, reified T : Tag> stateful(getter: KProperty0<V>, name: String = getter.name): Stateful<V, T> { inline fun <V : INBTSerializable<T?>, reified T : Tag> stateful(getter: KProperty0<V>, name: String = getter.name): Stateful<V, T> {
return stateful(getter, name, T::class.java) return stateful(getter, name, T::class.java)
} }
@ -42,6 +47,12 @@ class Savetables : INBTSerializable<CompoundTag> {
.withDeserializer { v, t -> v.deserializeNBT(t) } .withDeserializer { v, t -> v.deserializeNBT(t) }
} }
fun <V : INBTSerializable<T?>, T : Tag> stateful(getter: V, name: String, type: Class<T>): Stateful<V, T> {
return Stateful({ getter }, name, type)
.withSerializer { it.serializeNBT() }
.withDeserializer { v, t -> v.deserializeNBT(t) }
}
fun <V : INBTSerializable<T?>, T : Tag> stateful(getter: KProperty0<V>, name: String = getter.name, type: Class<T>): Stateful<V, T> { fun <V : INBTSerializable<T?>, T : Tag> stateful(getter: KProperty0<V>, name: String = getter.name, type: Class<T>): Stateful<V, T> {
return Stateful(getter, name, type) return Stateful(getter, name, type)
.withSerializer { it.serializeNBT() } .withSerializer { it.serializeNBT() }
@ -124,6 +135,18 @@ class Savetables : INBTSerializable<CompoundTag> {
.withDeserializer { it.asByte > 0 } .withDeserializer { it.asByte > 0 }
} }
fun <E : Enum<E>> enum(prop: GetterSetter<E>, name: String, map: (String) -> E): Stateless<E, StringTag> {
return Stateless(prop, name, StringTag::class.java)
.withSerializer { StringTag.valueOf(it.name) }
.withDeserializer { map.invoke(it.asString) }
}
fun <E : Enum<E>> enum(prop: KMutableProperty0<E>, name: String = prop.name, map: (String) -> E): Stateless<E, StringTag> {
return Stateless(prop, name, StringTag::class.java)
.withSerializer { StringTag.valueOf(it.name) }
.withDeserializer { map.invoke(it.asString) }
}
override fun serializeNBT(): CompoundTag { override fun serializeNBT(): CompoundTag {
return CompoundTag().also(::serializeNBT) return CompoundTag().also(::serializeNBT)
} }

View File

@ -5,6 +5,8 @@ import org.apache.logging.log4j.LogManager
class TickList { class TickList {
private val conditional = ArrayDeque<IConditionalTickable>() private val conditional = ArrayDeque<IConditionalTickable>()
private val once = ArrayDeque<ITickable>() private val once = ArrayDeque<ITickable>()
private val always = ArrayList<ITickable>()
private val alwaysValveTime = ArrayList<ITickable>()
private val conditionalValveTime = ArrayList<IConditionalTickable>() private val conditionalValveTime = ArrayList<IConditionalTickable>()
private val onceValveTime = ArrayList<ITickable>() private val onceValveTime = ArrayList<ITickable>()
@ -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) { fun add(ticker: IConditionalTickable, condition: Boolean, reason: String) {
if (!condition) { if (!condition) {
LOGGER.error("Refusing to add tickable $ticker because we $reason", IllegalStateException(reason)) LOGGER.error("Refusing to add tickable $ticker because we $reason", IllegalStateException(reason))
@ -79,6 +89,13 @@ class TickList {
once.clear() once.clear()
for (ticker in always) {
ticker.tick()
}
always.addAll(alwaysValveTime)
alwaysValveTime.clear()
for (ticker in conditionalValveTime) { for (ticker in conditionalValveTime) {
conditional.addFirst(ticker) conditional.addFirst(ticker)
} }

View File

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

View File

@ -8,6 +8,7 @@ import ru.dbotthepony.mc.otm.core.ImmutableList
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback 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.menu.widget.ProgressGaugeWidget
import ru.dbotthepony.mc.otm.registry.MMenus import ru.dbotthepony.mc.otm.registry.MMenus
@ -18,6 +19,7 @@ class CobblerMenu @JvmOverloads constructor(
) : MatteryMenu(MMenus.COBBLESTONE_GENERATOR, p_38852_, inventory, tile) { ) : 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)) } } override val storageSlots = (tile?.droppableContainer ?: SimpleContainer(CobblerBlockEntity.CONTAINER_SIZE)).let { c -> ImmutableList(c.containerSize) { addSlot(MachineOutputSlot(c, it)) } }
val redstone = EnumInputWithFeedback<RedstoneSetting>(this) val redstone = EnumInputWithFeedback<RedstoneSetting>(this)
val itemConfig = ItemHandlerPlayerInput(this, false, false)
val progress: ProgressGaugeWidget val progress: ProgressGaugeWidget
@ -27,6 +29,7 @@ class CobblerMenu @JvmOverloads constructor(
else { else {
progress = ProgressGaugeWidget(this, tile::workProgress, tile::isUnableToProcess) progress = ProgressGaugeWidget(this, tile::workProgress, tile::isUnableToProcess)
redstone.with(tile.redstoneControl::redstoneSetting) redstone.with(tile.redstoneControl::redstoneSetting)
itemConfig.configure(tile.itemConfig)
} }
addInventorySlots() addInventorySlots()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB