Add Miner automatic mode

This commit is contained in:
Shadowfacts 2021-03-03 17:23:57 -05:00
parent 2958fa295a
commit ca090d0924
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
8 changed files with 271 additions and 25 deletions

View File

@ -6,6 +6,7 @@ import net.shadowfacts.cacao.geometry.Axis
import net.shadowfacts.cacao.geometry.AxisPosition import net.shadowfacts.cacao.geometry.AxisPosition
import net.shadowfacts.cacao.geometry.AxisPosition.* import net.shadowfacts.cacao.geometry.AxisPosition.*
import no.birkett.kiwi.Constraint import no.birkett.kiwi.Constraint
import java.lang.RuntimeException
import java.util.* import java.util.*
/** /**
@ -18,6 +19,7 @@ import java.util.*
* @param axis The primary axis that this stack lays out its children along. * @param axis The primary axis that this stack lays out its children along.
* @param distribution The mode by which this stack lays out its children along the axis perpendicular to the * @param distribution The mode by which this stack lays out its children along the axis perpendicular to the
* primary [axis]. * primary [axis].
* @param spacing The distance between arranged subviews along the primary axis.
*/ */
open class StackView( open class StackView(
val axis: Axis, val axis: Axis,
@ -25,7 +27,7 @@ open class StackView(
val spacing: Double = 0.0 val spacing: Double = 0.0
): View() { ): View() {
// the internal mutable, list of arranged subviews // the internal, mutable list of arranged subviews
private val _arrangedSubviews = LinkedList<View>() private val _arrangedSubviews = LinkedList<View>()
/** /**
* The list of arranged subviews belonging to this stack view. * The list of arranged subviews belonging to this stack view.
@ -57,6 +59,64 @@ open class StackView(
return view return view
} }
/**
* Removes the given arranged subview from this stack view's arranged subviews.
*/
fun removeArrangedSubview(view: View) {
val index = arrangedSubviews.indexOf(view)
if (index < 0) {
throw RuntimeException("Cannot remove view that is not arranged subview")
}
if (index == 0) {
solver.removeConstraint(leadingConnection)
val next = arrangedSubviews.getOrNull(1)
if (next != null) {
solver.dsl {
leadingConnection = anchor(LEADING) equalTo anchor(LEADING, next)
}
} else {
leadingConnection = null
}
}
if (index == arrangedSubviews.size - 1) {
solver.removeConstraint(trailingConnection)
val prev = arrangedSubviews.getOrNull(arrangedSubviews.size - 2)
if (prev != null) {
solver.dsl {
trailingConnection = anchor(TRAILING) equalTo anchor(TRAILING, prev)
}
} else {
trailingConnection = null
}
}
// if the removed view is in the middle
if (arrangedSubviews.size >= 3 && index > 0 && index < arrangedSubviews.size - 1) {
val prev = arrangedSubviews[index - 1]
val next = arrangedSubviews[index + 1]
solver.dsl {
solver.removeConstraint(arrangedSubviewConnections[index - 1])
solver.removeConstraint(arrangedSubviewConnections[index])
// todo: double check me
arrangedSubviewConnections[index - 1] = anchor(TRAILING, prev) equalTo anchor(LEADING, next)
arrangedSubviewConnections.removeAt(index)
}
}
_arrangedSubviews.remove(view)
removeSubview(view)
}
override fun removeSubview(view: View) {
if (arrangedSubviews.contains(view)) {
removeArrangedSubview(view)
} else {
super.removeSubview(view)
}
}
private fun addConstraintsForArrangedView(view: View, index: Int) { private fun addConstraintsForArrangedView(view: View, index: Int) {
if (index == 0) { if (index == 0) {
if (leadingConnection != null) { if (leadingConnection != null) {

View File

@ -197,10 +197,13 @@ open class View(): Responder {
* its children (recursively) to a view outside of the subview's hierarchy. Constraints internal to the subview's * its children (recursively) to a view outside of the subview's hierarchy. Constraints internal to the subview's
* hierarchy (e.g., one between the subview and its child) will be left in place. * hierarchy (e.g., one between the subview and its child) will be left in place.
* *
* This method may be overridden by layout-providing views (such as [StackView]) to update its layout when a managed
* subview is removed.
*
* @param view The view to removed as a child of this view. * @param view The view to removed as a child of this view.
* @throws RuntimeException If the given [view] is not a subview of this view. * @throws RuntimeException If the given [view] is not a subview of this view.
*/ */
fun removeSubview(view: View) { open fun removeSubview(view: View) {
if (view.superview !== this) { if (view.superview !== this) {
throw RuntimeException("Cannot remove subview whose superview is not this view") throw RuntimeException("Cannot remove subview whose superview is not this view")
} }

View File

@ -61,6 +61,13 @@ class TabViewController<T: TabViewController.Tab>(
* may be reused or created from scratch each time. * may be reused or created from scratch each time.
*/ */
val controller: ViewController val controller: ViewController
/**
* Used by the tab view controller to determine whether the button for this tab should be displayed.
* If the conditions that control this change, call [TabViewController.visibleTabsChanged].
*/
val isVisible: Boolean
get() = true
} }
/** /**
@ -68,12 +75,17 @@ class TabViewController<T: TabViewController.Tab>(
* @param tabView The view to display on the tab's button. * @param tabView The view to display on the tab's button.
* @param tooltip The tooltip to display when the tab's button is hovered (or `null`, if none). * @param tooltip The tooltip to display when the tab's button is hovered (or `null`, if none).
* @param controller The content view controller for this tab. * @param controller The content view controller for this tab.
* @param visible A function that determines if the tab should currently be visible.
*/ */
class SimpleTab( class SimpleTab(
override val tabView: View, override val tabView: View,
override val tooltip: Text? = null, override val tooltip: Text? = null,
override val controller: ViewController, override val controller: ViewController,
): Tab private val visible: (() -> Boolean)? = null
): Tab {
override val isVisible: Boolean
get() = visible?.invoke() ?: true
}
/** /**
* The currently selected tab. * The currently selected tab.
@ -100,20 +112,7 @@ class TabViewController<T: TabViewController.Tab>(
tabStack = StackView(Axis.HORIZONTAL, StackView.Distribution.FILL) tabStack = StackView(Axis.HORIZONTAL, StackView.Distribution.FILL)
tabStack.zIndex = 1.0 tabStack.zIndex = 1.0
outerStack.addArrangedSubview(tabStack) outerStack.addArrangedSubview(tabStack)
updateTabButtons()
tabButtons = tabs.mapIndexed { index, tab ->
val btn = TabButton(tab)
btn.handler = { selectTab(it.tab) }
if (tab == currentTab) {
btn.setSelected(true)
}
btn
}
// todo: batch calls to addArrangedSubview
tabButtons.forEach(tabStack::addArrangedSubview)
// spacer
tabStack.addArrangedSubview(View())
val background = NinePatchView(NinePatchTexture.PANEL_BG) val background = NinePatchView(NinePatchTexture.PANEL_BG)
outerStack.addArrangedSubview(background) outerStack.addArrangedSubview(background)
@ -137,6 +136,37 @@ class TabViewController<T: TabViewController.Tab>(
} }
} }
private fun updateTabButtons() {
while (tabStack.arrangedSubviews.isNotEmpty()) tabStack.removeArrangedSubview(tabStack.arrangedSubviews.first())
tabButtons = tabs.mapNotNull { tab ->
if (!tab.isVisible) {
return@mapNotNull null
}
val btn = TabButton(tab)
btn.handler = { selectTab(it.tab) }
if (tab == currentTab) {
btn.setSelected(true)
}
btn
}
// todo: batch calls to addArrangedSubview
tabButtons.forEach(tabStack::addArrangedSubview)
// spacer
tabStack.addArrangedSubview(View())
window!!.layout()
}
/**
* Call this method when the conditions that make the configured tabs visible change.
*/
fun visibleTabsChanged() {
updateTabButtons()
}
/** /**
* Sets the provided tab as the currently active tab for this controller. Updates the state of tab bar buttons and * Sets the provided tab as the currently active tab for this controller. Updates the state of tab bar buttons and
* swaps the content view controller. * swaps the content view controller.

View File

@ -6,7 +6,10 @@ import alexiil.mc.lib.attributes.item.filter.ItemFilter
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.world.ServerWorld import net.minecraft.server.world.ServerWorld
import net.minecraft.text.TranslatableText
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.minecraft.world.World import net.minecraft.world.World
@ -14,10 +17,13 @@ import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.block.DeviceBlockEntity import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.component.ActivationController
import net.shadowfacts.phycon.component.NetworkStackDispatcher import net.shadowfacts.phycon.component.NetworkStackDispatcher
import net.shadowfacts.phycon.component.NetworkStackProvider import net.shadowfacts.phycon.component.NetworkStackProvider
import net.shadowfacts.phycon.component.handleItemStack import net.shadowfacts.phycon.component.handleItemStack
import net.shadowfacts.phycon.packet.* import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.ActivationMode
import net.shadowfacts.phycon.util.ClientConfigurableDevice
import net.shadowfacts.phycon.util.copyWithCount import net.shadowfacts.phycon.util.copyWithCount
import kotlin.math.min import kotlin.math.min
@ -26,7 +32,9 @@ import kotlin.math.min
*/ */
class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER), class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
NetworkStackProvider, NetworkStackProvider,
NetworkStackDispatcher<MinerBlockEntity.PendingInsertion> { NetworkStackDispatcher<MinerBlockEntity.PendingInsertion>,
ActivationController.ActivatableDevice,
ClientConfigurableDevice {
private val facing: Direction private val facing: Direction
get() = cachedState[MinerBlock.FACING] get() = cachedState[MinerBlock.FACING]
@ -35,6 +43,9 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
override val pendingInsertions = mutableListOf<PendingInsertion>() override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = TerminalBlockEntity.INSERTION_TIMEOUT override val dispatchStackTimeout = TerminalBlockEntity.INSERTION_TIMEOUT
override val controller = ActivationController(40L, this)
var minerMode = MinerMode.ON_DEMAND
override fun handle(packet: Packet) { override fun handle(packet: Packet) {
when (packet) { when (packet) {
@ -47,10 +58,16 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
} }
private fun handleRequestInventory(packet: RequestInventoryPacket) { private fun handleRequestInventory(packet: RequestInventoryPacket) {
if (minerMode != MinerMode.ON_DEMAND) {
return
}
sendPacket(ReadInventoryPacket(invProxy, ipAddress, packet.source)) sendPacket(ReadInventoryPacket(invProxy, ipAddress, packet.source))
} }
private fun handleLocateStack(packet: LocateStackPacket) { private fun handleLocateStack(packet: LocateStackPacket) {
if (minerMode != MinerMode.ON_DEMAND) {
return
}
val amount = invProxy.getAmount(packet.stack) val amount = invProxy.getAmount(packet.stack)
if (amount > 0) { if (amount > 0) {
sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source)) sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source))
@ -58,6 +75,10 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
} }
private fun handleExtractStack(packet: ExtractStackPacket) { private fun handleExtractStack(packet: ExtractStackPacket) {
if (minerMode != MinerMode.ON_DEMAND) {
return
}
// always recalculate immediately before breaking // always recalculate immediately before breaking
val drops = invProxy.getDrops(recalculate = true) val drops = invProxy.getDrops(recalculate = true)
if (invProxy.getAmount(packet.stack) > 0) { if (invProxy.getAmount(packet.stack) > 0) {
@ -97,7 +118,70 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter) override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
override fun tick() {
super.tick()
if (!world!!.isClient && minerMode == MinerMode.AUTOMATIC) {
controller.tick()
}
}
override fun activate(): Boolean {
if (minerMode == MinerMode.ON_DEMAND) {
return false
}
val drops = invProxy.getDrops(recalculate = true)
if (!world!!.getBlockState(pos.offset(facing)).isAir) {
world!!.breakBlock(pos.offset(facing), false)
for (stack in drops) {
if (stack.isEmpty) continue
dispatchItemStack(stack)
}
return true
} else {
return false
}
}
override fun canConfigureActivationController(): Boolean {
return minerMode == MinerMode.AUTOMATIC
}
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.putString("MinerMode", minerMode.name)
tag.putString("ActivationMode", controller.activationMode.name)
}
override fun loadDeviceConfiguration(tag: CompoundTag) {
minerMode = MinerMode.valueOf(tag.getString("MinerMode"))
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
}
enum class MinerMode {
ON_DEMAND, AUTOMATIC;
val friendlyName = TranslatableText("gui.phycon.miner_mode.${name.toLowerCase()}")
}
class MinerInvProxy(val miner: MinerBlockEntity): GroupedItemInvView { class MinerInvProxy(val miner: MinerBlockEntity): GroupedItemInvView {
companion object {
val TOOL = ItemStack(Items.DIAMOND_PICKAXE)
}
private var cachedState: BlockState? = null private var cachedState: BlockState? = null
private var cachedDrops: List<ItemStack>? = null private var cachedDrops: List<ItemStack>? = null
@ -115,12 +199,15 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
if (cachedDrops == null || realState != cachedState || recalculate) { if (cachedDrops == null || realState != cachedState || recalculate) {
cachedState = realState cachedState = realState
val be = if (realState.block.hasBlockEntity()) world.getBlockEntity(targetPos) else null val be = if (realState.block.hasBlockEntity()) world.getBlockEntity(targetPos) else null
cachedDrops = Block.getDroppedStacks(realState, world as ServerWorld, targetPos, be) cachedDrops = Block.getDroppedStacks(realState, world as ServerWorld, targetPos, be, null, TOOL)
} }
return cachedDrops!! return cachedDrops!!
} }
override fun getStoredStacks(): Set<ItemStack> { override fun getStoredStacks(): Set<ItemStack> {
if (miner.minerMode != MinerMode.ON_DEMAND) {
return setOf()
}
return getDrops().toSet() return getDrops().toSet()
} }
@ -130,7 +217,7 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic { override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic {
var totalCount = 0 var totalCount = 0
for (s in getDrops()) { for (s in storedStacks) {
if (filter.matches(s)) { if (filter.matches(s)) {
totalCount += s.count totalCount += s.count
} }

View File

@ -57,5 +57,9 @@ class ActivationController<T>(
val counter: Long val counter: Long
fun activate(): Boolean fun activate(): Boolean
fun canConfigureActivationController(): Boolean {
return true
}
} }
} }

View File

@ -13,6 +13,7 @@ import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.cacao.window.Window import net.shadowfacts.cacao.window.Window
import net.shadowfacts.kiwidsl.dsl import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.phycon.block.DeviceBlockEntity 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.block.redstone_controller.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.component.ActivationController import net.shadowfacts.phycon.component.ActivationController
import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW
@ -40,12 +41,12 @@ class DeviceConsoleScreen(
intrinsicContentSize = Size(16.0, 16.0) intrinsicContentSize = Size(16.0, 16.0)
}, },
TranslatableText("gui.phycon.console.remote"), TranslatableText("gui.phycon.console.remote"),
ActivatableDeviceViewController(device) ActivatableDeviceViewController(device),
device::canConfigureActivationController
)) ))
} }
if (device is RedstoneControllerBlockEntity) { if (device is RedstoneControllerBlockEntity) {
tabs.add( tabs.add(TabViewController.SimpleTab(
TabViewController.SimpleTab(
TextureView(Texture(Identifier("textures/block/redstone_torch.png"), 0, 0, 16, 16)).apply { TextureView(Texture(Identifier("textures/block/redstone_torch.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(16.0, 16.0) intrinsicContentSize = Size(16.0, 16.0)
}, },
@ -53,6 +54,15 @@ class DeviceConsoleScreen(
RedstoneControllerViewController(device) RedstoneControllerViewController(device)
)) ))
} }
if (device is MinerBlockEntity) {
tabs.add(TabViewController.SimpleTab(
TextureView(Texture(Identifier("textures/item/diamond_pickaxe.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(16.0, 16.0)
},
TranslatableText("block.phycon.miner"),
MinerViewController(device)
))
}
tabController = TabViewController(tabs) tabController = TabViewController(tabs)

View File

@ -0,0 +1,49 @@
package net.shadowfacts.phycon.screen.console
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.button.EnumButton
import net.shadowfacts.cacao.viewcontroller.TabViewController
import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.phycon.block.miner.MinerBlockEntity
import net.shadowfacts.phycon.networking.C2SConfigureDevice
/**
* @author shadowfacts
*/
class MinerViewController(
val device: MinerBlockEntity,
): ViewController() {
override fun viewDidLoad() {
super.viewDidLoad()
val label = Label(TranslatableText("gui.phycon.console.miner.mode")).apply {
textColor = Color.TEXT
}
view.addSubview(label)
val mode = EnumButton(device.minerMode, MinerBlockEntity.MinerMode::friendlyName)
mode.handler = {
device.minerMode = it.value
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureDevice(device))
(parent as TabViewController<TabViewController.SimpleTab>).visibleTabsChanged()
}
view.addSubview(mode)
view.solver.dsl {
mode.widthAnchor equalTo 100
mode.heightAnchor equalTo 20
mode.topAnchor equalTo view.topAnchor
mode.rightAnchor equalTo view.rightAnchor
label.centerYAnchor equalTo mode.centerYAnchor
label.rightAnchor equalTo (mode.leftAnchor - 4)
}
}
}

View File

@ -20,6 +20,7 @@
"gui.phycon.console.redstone.devices": "Managed Devices", "gui.phycon.console.redstone.devices": "Managed Devices",
"gui.phycon.console.remote": "Remote Management", "gui.phycon.console.remote": "Remote Management",
"gui.phycon.console.remote.mode": "Activation Mode", "gui.phycon.console.remote.mode": "Activation Mode",
"gui.phycon.console.miner.mode": "Miner Mode",
"gui.phycon.redstone_mode.high": "High", "gui.phycon.redstone_mode.high": "High",
"gui.phycon.redstone_mode.low": "Low", "gui.phycon.redstone_mode.low": "Low",
"gui.phycon.redstone_mode.toggle": "Toggle", "gui.phycon.redstone_mode.toggle": "Toggle",
@ -27,5 +28,7 @@
"gui.phycon.redstone_mode.falling_edge": "Falling Edge", "gui.phycon.redstone_mode.falling_edge": "Falling Edge",
"gui.phycon.activation_mode.automatic": "Automatic", "gui.phycon.activation_mode.automatic": "Automatic",
"gui.phycon.activation_mode.managed": "Managed", "gui.phycon.activation_mode.managed": "Managed",
"gui.phycon.emitter.count": "%d Item(s)" "gui.phycon.emitter.count": "%d Item(s)",
"gui.phycon.miner_mode.automatic": "Automatic",
"gui.phycon.miner_mode.on_demand": "On Demand"
} }