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("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("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.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<T : Any>(val cap: T, var optional: LazyOptional<T>)
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
@ -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<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> {
inner class Side(val side: RelativeSide) {
init {
check(!_sides.containsKey(side)) { "dafuq are you trying to do" }
_sides[side] = this
}
private val caps = Reference2ObjectArrayMap<Capability<*>, Cap<*>>()
private val data = Object2ObjectArrayMap<ResourceLocation, INBTSerializable<Tag>>()
private val subscriptions = Reference2ObjectArrayMap<Capability<*>, SubRef<*>>()
private val knownLOs = WeakHashSet<LazyOptional<*>>()
@ -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 <T : Any> get(capability: Capability<T>): 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() {
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> = 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) {
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<T> by object : ReadWriteProperty<Any?, LazyOptional<T>> {
private var value: LazyOptional<T>? = 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 <reified T : Tag, V : INBTSerializable<T?>> savetable(property: KProperty0<V>, 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)
}

View File

@ -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<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)
}
protected fun batteryChargeLoop() {
override fun tick() {
super.tick()
val energy = matteryEnergy
if (energy == null || !batteryContainer.any { !it.isEmpty })
return

View File

@ -233,7 +233,9 @@ abstract class MatteryWorkerBlockEntity<JobType : MatteryWorkerBlockEntity.Job>(
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<JobType : MatteryWorkerBlockEntity.Job>(
}
}
fun basicTicker() {
batteryChargeLoop()
workerLoop()
}
companion object {
const val WORK_TICKS_KEY = "workTicks"
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--
if (sleepTicks > 0) return
val level = level as? ServerLevel ?: return

View File

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

View File

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

View File

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

View File

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

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 {
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() }
}
fun tick() {
batteryChargeLoop()
override fun tick() {
super.tick()
cell.tickEnergyDemanding()
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 component: ItemHandlerComponent? = null
fun tick() {
batteryChargeLoop()
override fun tick() {
super.tick()
component?.scan()
cell.tickEnergyDemanding()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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]
historyTick = (historyTick + 1) % history.size
history[historyTick] = Decimal.ZERO

View File

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

View File

@ -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<Block, BlockState>) {

View File

@ -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<Block, BlockState>) {

View File

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

View File

@ -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<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)
}
fun intersect(other: FlowDirection): FlowDirection {
return of(other.input && input, other.output && output)
}
companion object {
@JvmStatic
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)
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 {

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

View File

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

View File

@ -569,12 +569,12 @@ open class EditablePanel<out S : Screen> @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

View File

@ -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<out S : Screen>(
screen: S,
@ -26,6 +27,16 @@ abstract class BooleanRectangleButtonPanel<out S : Screen>(
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) {
super.innerRender(stack, mouseX, mouseY, partialTick)

View File

@ -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 <S : MatteryScreen<*>> makeRedstoneSettingButton(
private fun <S : MatteryScreen<*>> makeRedstoneSettingButton(
screen: S,
parent: EditablePanel<*>?,
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(
screen: S,
parent: FramePanel<S>,
redstone: IPlayerInputWithFeedback<RedstoneSetting>? = null
redstone: IPlayerInputWithFeedback<RedstoneSetting>? = null,
itemConfig: ItemHandlerPlayerInput? = null
): EditablePanel<S> {
val panel = object : EditablePanel<S>(screen, parent, width = LargeEnumRectangleButtonPanel.SIZE, height = 0f, x = parent.width + 3f) {
override fun tickInner() {
@ -54,6 +163,23 @@ fun <S : MatteryScreen<*>> makeDeviceControls(
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)
return panel
}

View File

@ -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<out S : Screen, T : Enum<T>>(
@ -29,7 +32,19 @@ abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
val defaultValue: T,
) : RectangleButtonPanel<S>(screen, parent, x, y, width, height, null) {
val enum = EnumValueCodec.searchClass(enum)
private val constants: Array<T> = enum.enumConstants
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>>(
val key: T,
@ -40,30 +55,6 @@ abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
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> {
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> {
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<out S : Screen, T : Enum<T>>(
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<out S : Screen, T : Enum<T>>(
}
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<out S : Screen, T : Enum<T>>(
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<out S : Screen, T : Enum<T>>(
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))
}
}

View File

@ -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<out S : Screen>(
screen: S,

View File

@ -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<out S : Screen, T : Enum<T>>(
screen: S,

View File

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

View File

@ -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 <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 {
return (this as ForgeRegistry<T>).getID(value)
}

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.mc.otm.core.collect
import java.util.stream.Stream
class ConditionalSet<T> : AbstractSet<T> {
// method without boxing
fun interface Condition {
@ -16,6 +18,10 @@ class ConditionalSet<T> : AbstractSet<T> {
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() {
var i = 0

View File

@ -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<CompoundTag> {
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> {
return stateful(getter, name, T::class.java)
}
@ -42,6 +47,12 @@ class Savetables : INBTSerializable<CompoundTag> {
.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> {
return Stateful(getter, name, type)
.withSerializer { it.serializeNBT() }
@ -124,6 +135,18 @@ class Savetables : INBTSerializable<CompoundTag> {
.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 {
return CompoundTag().also(::serializeNBT)
}

View File

@ -5,6 +5,8 @@ import org.apache.logging.log4j.LogManager
class TickList {
private val conditional = ArrayDeque<IConditionalTickable>()
private val once = ArrayDeque<ITickable>()
private val always = ArrayList<ITickable>()
private val alwaysValveTime = ArrayList<ITickable>()
private val conditionalValveTime = ArrayList<IConditionalTickable>()
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) {
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)
}

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.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<RedstoneSetting>(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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB