diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/textfield/NumberField.kt b/src/main/kotlin/net/shadowfacts/cacao/view/textfield/NumberField.kt index 7efee2d..29b8f29 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/textfield/NumberField.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/textfield/NumberField.kt @@ -10,7 +10,7 @@ class NumberField( var number: Int? get() { - return if (text.isEmpty()) { + return if (isTextTemporarilyAllowed(text)) { null } else { try { @@ -31,7 +31,7 @@ class NumberField( } override fun validate(proposedText: String): Boolean { - return proposedText.isEmpty() || try { + return isTextTemporarilyAllowed(proposedText) || try { val value = Integer.parseInt(proposedText) validator?.invoke(value) ?: true } catch (e: NumberFormatException) { @@ -39,4 +39,8 @@ class NumberField( } } + private fun isTextTemporarilyAllowed(s: String): Boolean { + return s.isEmpty() || s == "-" + } + } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlockEntity.kt index 780a165..13e46f5 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/inserter/InserterBlockEntity.kt @@ -5,7 +5,6 @@ import alexiil.mc.lib.attributes.Simulation import alexiil.mc.lib.attributes.item.ItemAttributes import alexiil.mc.lib.attributes.item.ItemInsertable import alexiil.mc.lib.attributes.item.ItemStackUtil -import net.minecraft.block.BlockState import net.minecraft.item.ItemStack import net.minecraft.nbt.CompoundTag import net.minecraft.util.math.Direction diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/miner/MinerBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/miner/MinerBlockEntity.kt index f49e41b..bea7eef 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/miner/MinerBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/miner/MinerBlockEntity.kt @@ -44,6 +44,7 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER), override val pendingInsertions = mutableListOf() override val dispatchStackTimeout = TerminalBlockEntity.INSERTION_TIMEOUT override val controller = ActivationController(40L, this) + override var providerPriority = 0 var minerMode = MinerMode.ON_DEMAND @@ -151,6 +152,10 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER), return minerMode == MinerMode.AUTOMATIC } + override fun canConfigureProviderPriority(): Boolean { + return minerMode == MinerMode.ON_DEMAND + } + override fun toCommonTag(tag: CompoundTag) { super.toCommonTag(tag) writeDeviceConfiguration(tag) @@ -164,11 +169,13 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER), override fun writeDeviceConfiguration(tag: CompoundTag) { tag.putString("MinerMode", minerMode.name) tag.putString("ActivationMode", controller.activationMode.name) + tag.putInt("ProviderPriority", providerPriority) } override fun loadDeviceConfiguration(tag: CompoundTag) { minerMode = MinerMode.valueOf(tag.getString("MinerMode")) controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode")) + providerPriority = tag.getInt("ProviderPriority") } enum class MinerMode { diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlockEntity.kt index 3659963..33abbfd 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/netinterface/InterfaceBlockEntity.kt @@ -4,7 +4,9 @@ import alexiil.mc.lib.attributes.SearchOptions import alexiil.mc.lib.attributes.Simulation import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.item.ItemAttributes +import net.minecraft.block.BlockState import net.minecraft.item.ItemStack +import net.minecraft.nbt.CompoundTag import net.minecraft.util.math.Direction import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.init.PhyBlockEntities @@ -15,6 +17,7 @@ import net.shadowfacts.phycon.component.NetworkStackProvider import net.shadowfacts.phycon.component.NetworkStackReceiver import net.shadowfacts.phycon.component.handleItemStack import net.shadowfacts.phycon.packet.* +import net.shadowfacts.phycon.util.ClientConfigurableDevice import kotlin.math.min /** @@ -23,11 +26,14 @@ import kotlin.math.min class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemStackPacketHandler, NetworkStackProvider, - NetworkStackReceiver { + NetworkStackReceiver, + ClientConfigurableDevice { private val facing: Direction get() = cachedState[FaceDeviceBlock.FACING] + override var providerPriority = 0 + // todo: should this be a weak ref? private var inventory: GroupedItemInv? = null @@ -101,4 +107,22 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), } } + override fun toCommonTag(tag: CompoundTag) { + super.toCommonTag(tag) + writeDeviceConfiguration(tag) + } + + override fun fromCommonTag(tag: CompoundTag) { + super.fromCommonTag(tag) + loadDeviceConfiguration(tag) + } + + override fun writeDeviceConfiguration(tag: CompoundTag) { + tag.putInt("ProviderPriority", providerPriority) + } + + override fun loadDeviceConfiguration(tag: CompoundTag) { + providerPriority = tag.getInt("ProviderPriority") + } + } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/TerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/TerminalBlockEntity.kt index fedb5ae..09969a3 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/TerminalBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/TerminalBlockEntity.kt @@ -5,7 +5,6 @@ import alexiil.mc.lib.attributes.item.ItemStackCollections import alexiil.mc.lib.attributes.item.ItemStackUtil import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory -import net.minecraft.block.BlockState import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerInventory import net.minecraft.inventory.Inventory @@ -42,7 +41,9 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), NetworkStackDispatcher { companion object { - val LOCATE_REQUEST_TIMEOUT: Long = 40 // ticks + // the locate request timeout is only 1 tick because that's long enough to hear from every device on the network + // in a degraded state (when there's latency in the network), not handling interface priorities correctly is acceptable + val LOCATE_REQUEST_TIMEOUT: Long = 1 // ticks val INSERTION_TIMEOUT: Long = 40 } @@ -163,14 +164,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), override fun tick() { super.tick() - if (counter % 20 == 0L) { - if (!world!!.isClient) { - finishPendingRequests() - beginInsertions() - finishTimedOutPendingInsertions() - } + if (!world!!.isClient) { + finishPendingRequests() + finishTimedOutPendingInsertions() + } - if (observers > 0 && !world!!.isClient) { + if (counter % 20 == 0L && !world!!.isClient) { + beginInsertions() + + if (observers > 0) { updateAndSync() } } @@ -209,8 +211,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), private fun stackLocateRequestCompleted(request: StackLocateRequest) { pendingRequests.remove(request) - // todo: also sort results by interface priority - val sortedResults = request.results.sortedByDescending { it.first }.toMutableList() + val sortedResults = request.results.toMutableList() + sortedResults.sortWith { a, b -> + // sort results first by provider priority, and then by the count that it can provide + if (a.second.providerPriority == b.second.providerPriority) { + b.first - a.first + } else { + b.second.providerPriority - a.second.providerPriority + } + } var amountRequested = 0 while (amountRequested < request.amount && sortedResults.isNotEmpty()) { val (sourceAmount, sourceInterface) = sortedResults.removeAt(0) @@ -268,6 +277,8 @@ data class StackLocateRequest( get() = results.fold(0) { acc, (amount, _) -> acc + amount } fun isFinishable(currentTimestamp: Long): Boolean { - return totalResultAmount >= amount || currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT + // we can't check totalResultAmount >= amount because we need to hear back from all network stack providers to + // correctly sort by priority + return currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/component/NetworkStackProvider.kt b/src/main/kotlin/net/shadowfacts/phycon/component/NetworkStackProvider.kt index 192d642..7e1504d 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/component/NetworkStackProvider.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/component/NetworkStackProvider.kt @@ -1,9 +1,17 @@ package net.shadowfacts.phycon.component import net.shadowfacts.phycon.api.NetworkDevice +import net.shadowfacts.phycon.util.ClientConfigurableDevice /** * @author shadowfacts */ -interface NetworkStackProvider: NetworkDevice { +interface NetworkStackProvider: NetworkDevice, ClientConfigurableDevice { + + var providerPriority: Int + + fun canConfigureProviderPriority(): Boolean { + return true + } + } diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/console/DeviceConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/console/DeviceConsoleScreen.kt index fc279b8..71d53df 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/screen/console/DeviceConsoleScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/console/DeviceConsoleScreen.kt @@ -16,6 +16,7 @@ import net.shadowfacts.phycon.block.DeviceBlockEntity import net.shadowfacts.phycon.block.miner.MinerBlockEntity import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlockEntity import net.shadowfacts.phycon.component.ActivationController +import net.shadowfacts.phycon.component.NetworkStackProvider import org.lwjgl.glfw.GLFW /** @@ -63,7 +64,14 @@ class DeviceConsoleScreen( MinerViewController(device) )) } - + if (device is NetworkStackProvider) { + tabs.add(TabViewController.SimpleTab( + Label("P").apply { textColor = Color.TEXT }, + TranslatableText("gui.phycon.console.provider"), + ProviderViewController(device), + device::canConfigureProviderPriority + )) + } tabController = TabViewController(tabs) diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/console/ProviderViewController.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/console/ProviderViewController.kt new file mode 100644 index 0000000..4387e27 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/console/ProviderViewController.kt @@ -0,0 +1,57 @@ +package net.shadowfacts.phycon.screen.console + +import net.minecraft.block.entity.BlockEntity +import net.minecraft.client.MinecraftClient +import net.minecraft.text.TranslatableText +import net.shadowfacts.cacao.util.Color +import net.shadowfacts.cacao.view.Label +import net.shadowfacts.cacao.view.textfield.NumberField +import net.shadowfacts.cacao.viewcontroller.ViewController +import net.shadowfacts.kiwidsl.dsl +import net.shadowfacts.phycon.component.NetworkStackProvider +import net.shadowfacts.phycon.networking.C2SConfigureDevice + +/** + * @author shadowfacts + */ +class ProviderViewController( + private val device: T +): ViewController() where T: BlockEntity, T: NetworkStackProvider { + + override fun viewDidLoad() { + super.viewDidLoad() + + val label = Label(TranslatableText("gui.phycon.console.provider.priority")).apply { + textColor = Color.TEXT + } + view.addSubview(label) + + val field = NumberField(device.providerPriority) { + if (it.number != null) { + device.providerPriority = it.number!! + MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureDevice(device)) + } + } + view.addSubview(field) + + val desc = Label(TranslatableText("gui.phycon.console.provider.priority_desc")).apply { + textColor = Color.TEXT + } + view.addSubview(desc) + + view.solver.dsl { + field.widthAnchor equalTo 100 + field.heightAnchor equalTo 20 + field.rightAnchor equalTo view.rightAnchor + field.topAnchor equalTo view.topAnchor + + label.centerYAnchor equalTo field.centerYAnchor + label.rightAnchor equalTo (field.leftAnchor - 4) + + desc.topAnchor equalTo (field.bottomAnchor + 4) + desc.leftAnchor equalTo view.leftAnchor + desc.rightAnchor equalTo view.rightAnchor + } + } + +} diff --git a/src/main/resources/assets/phycon/lang/en_us.json b/src/main/resources/assets/phycon/lang/en_us.json index f7eae24..d615151 100644 --- a/src/main/resources/assets/phycon/lang/en_us.json +++ b/src/main/resources/assets/phycon/lang/en_us.json @@ -21,6 +21,9 @@ "gui.phycon.console.remote": "Remote Management", "gui.phycon.console.remote.mode": "Activation Mode", "gui.phycon.console.miner.mode": "Miner Mode", + "gui.phycon.console.provider": "Item Provider", + "gui.phycon.console.provider.priority": "Provider Priority", + "gui.phycon.console.provider.priority_desc": "When a device requests items from the network, it send requests to providers (e.g., interfaces) with higher priorities first. Priorities can be negative.", "gui.phycon.redstone_mode.high": "High", "gui.phycon.redstone_mode.low": "Low", "gui.phycon.redstone_mode.toggle": "Toggle",