Add search field to terminal

This commit is contained in:
Shadowfacts 2021-02-15 22:51:33 -05:00
parent 249416b8c2
commit c6a5602ec1
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
8 changed files with 192 additions and 15 deletions

View File

@ -1,10 +1,13 @@
package net.shadowfacts.phycon package net.shadowfacts.phycon
import net.fabricmc.api.ModInitializer import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import net.shadowfacts.phycon.networking.ServerReceiver
/** /**
* @author shadowfacts * @author shadowfacts
@ -18,6 +21,12 @@ object PhysicalConnectivity: ModInitializer {
PhyBlockEntities.init() PhyBlockEntities.init()
PhyItems.init() PhyItems.init()
PhyScreens.init() PhyScreens.init()
registerGlobalReceiver(C2STerminalRequestItem)
}
private fun registerGlobalReceiver(receiver: ServerReceiver) {
ServerPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver)
} }
} }

View File

@ -1,11 +1,8 @@
package net.shadowfacts.phycon.item package net.shadowfacts.phycon.item
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.item.Item import net.minecraft.item.Item
import net.minecraft.item.ItemUsageContext import net.minecraft.item.ItemUsageContext
import net.minecraft.text.LiteralText
import net.minecraft.util.ActionResult import net.minecraft.util.ActionResult
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
@ -38,7 +35,6 @@ class ConsoleItem: Item(Settings()) {
return ActionResult.PASS return ActionResult.PASS
} }
@Environment(EnvType.CLIENT)
private fun openScreen(be: DeviceBlockEntity) { private fun openScreen(be: DeviceBlockEntity) {
val screen = DeviceConsoleScreen(be) val screen = DeviceConsoleScreen(be)
MinecraftClient.getInstance().openScreen(screen) MinecraftClient.getInstance().openScreen(screen)

View File

@ -31,6 +31,7 @@ import net.shadowfacts.phycon.network.component.NetworkStackProvider
import net.shadowfacts.phycon.network.component.NetworkStackReceiver import net.shadowfacts.phycon.network.component.NetworkStackReceiver
import net.shadowfacts.phycon.network.component.handleItemStack import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.* import net.shadowfacts.phycon.network.packet.*
import java.lang.ref.WeakReference
import java.util.* import java.util.*
import kotlin.math.min import kotlin.math.min
@ -55,6 +56,8 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
val cachedNetItems = ItemStackCollections.intMap() val cachedNetItems = ItemStackCollections.intMap()
var cachedSortedNetItems = listOf<ItemStack>() var cachedSortedNetItems = listOf<ItemStack>()
var netItemObserver: WeakReference<NetItemObserver>? = null
init { init {
internalBuffer.addListener(this) internalBuffer.addListener(this)
} }
@ -312,6 +315,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
val netAmount = entryTag.getInt("NetAmount") val netAmount = entryTag.getInt("NetAmount")
cachedNetItems[stack] = netAmount cachedNetItems[stack] = netAmount
} }
netItemObserver?.get()?.netItemsChanged()
cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map { cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
val stack = it.key.copy() val stack = it.key.copy()
stack.count = it.intValue stack.count = it.intValue
@ -319,6 +323,10 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
} }
interface NetItemObserver {
fun netItemsChanged()
}
} }
data class StackLocateRequest( data class StackLocateRequest(

View File

@ -8,7 +8,7 @@ import net.minecraft.item.ItemStack
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalFakeSlot(val terminal: TerminalBlockEntity, slot: Int, x: Int, y: Int): Slot(FakeInventory(terminal, slot), slot, x, y) { class TerminalFakeSlot(fakeInv: FakeInventory, slot: Int, x: Int, y: Int): Slot(fakeInv, slot, x, y) {
override fun canInsert(stack: ItemStack): Boolean { override fun canInsert(stack: ItemStack): Boolean {
return false return false
@ -23,10 +23,10 @@ class TerminalFakeSlot(val terminal: TerminalBlockEntity, slot: Int, x: Int, y:
} }
class FakeInventory(val terminal: TerminalBlockEntity, val slot: Int): Inventory { class FakeInventory(val screenHandler: TerminalScreenHandler): Inventory {
override fun getStack(_slot: Int): ItemStack { override fun getStack(slot: Int): ItemStack {
if (slot >= terminal.cachedSortedNetItems.size) return ItemStack.EMPTY if (slot >= screenHandler.itemsForDisplay.size) return ItemStack.EMPTY
return terminal.cachedSortedNetItems[slot] return screenHandler.itemsForDisplay[slot]
} }
override fun markDirty() { override fun markDirty() {

View File

@ -3,12 +3,15 @@ package net.shadowfacts.phycon.network.block.terminal
import com.mojang.blaze3d.platform.GlStateManager import com.mojang.blaze3d.platform.GlStateManager
import net.minecraft.client.gui.DrawableHelper import net.minecraft.client.gui.DrawableHelper
import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.util.math.MatrixStack import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory import net.minecraft.entity.player.PlayerInventory
import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.Slot
import net.minecraft.text.LiteralText
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import org.lwjgl.glfw.GLFW
/** /**
* @author shadowfacts * @author shadowfacts
@ -19,11 +22,33 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png") val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
} }
private lateinit var searchBox: TextFieldWidget
init { init {
backgroundWidth = 252 backgroundWidth = 252
backgroundHeight = 222 backgroundHeight = 222
} }
override fun init() {
super.init()
client!!.keyboard.setRepeatEvents(true)
searchBox = TextFieldWidget(textRenderer, x + 138, y + 6, 80, 9, LiteralText("Search"))
searchBox.setMaxLength(50)
// setHasBorder is actually setDrawsBackground
searchBox.setHasBorder(false)
searchBox.isVisible = true
searchBox.setSelected(true)
searchBox.setEditableColor(0xffffff)
children.add(searchBox)
}
override fun tick() {
super.tick()
searchBox.tick()
}
override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) { override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
textRenderer.draw(matrixStack, title, 65f, 6f, 0x404040) textRenderer.draw(matrixStack, title, 65f, 6f, 0x404040)
textRenderer.draw(matrixStack, playerInventory.displayName, 65f, backgroundHeight - 94f, 0x404040) textRenderer.draw(matrixStack, playerInventory.displayName, 65f, backgroundHeight - 94f, 0x404040)
@ -43,6 +68,9 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
super.render(matrixStack, mouseX, mouseY, delta) super.render(matrixStack, mouseX, mouseY, delta)
searchBox.render(matrixStack, mouseX, mouseY, delta)
drawMouseoverTooltip(matrixStack, mouseX, mouseY) drawMouseoverTooltip(matrixStack, mouseX, mouseY)
} }
@ -61,4 +89,35 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt()) DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
} }
override fun charTyped(c: Char, i: Int): Boolean {
val oldText = searchBox.text
if (searchBox.charTyped(c, i)) {
if (searchBox.text != oldText) {
search()
}
return true
}
return super.charTyped(c, i)
}
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
val oldText = searchBox.text
if (searchBox.keyPressed(key, j, k)) {
if (searchBox.text != oldText) {
search()
}
return true
}
return if (searchBox.isFocused && searchBox.isVisible && key != GLFW.GLFW_KEY_ESCAPE) {
true
} else {
super.keyPressed(key, j, k)
}
}
private fun search() {
screenHandler.search(searchBox.text)
}
} }

View File

@ -1,5 +1,6 @@
package net.shadowfacts.phycon.network.block.terminal package net.shadowfacts.phycon.network.block.terminal
import net.minecraft.client.network.ClientPlayerEntity
import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType import net.minecraft.screen.slot.SlotActionType
import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerEntity
@ -8,29 +9,41 @@ import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandler
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import java.lang.ref.WeakReference
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.min import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId) { class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId),
TerminalBlockEntity.NetItemObserver {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "terminal") val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
} }
private val fakeInv = FakeInventory(this)
private var searchQuery: String = ""
var itemsForDisplay = listOf<ItemStack>()
private set
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf): constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!) this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!)
init { init {
terminal.netItemObserver = WeakReference(this)
netItemsChanged()
// network // network
for (y in 0 until 6) { for (y in 0 until 6) {
for (x in 0 until 9) { for (x in 0 until 9) {
addSlot(TerminalFakeSlot(terminal, y * 9 + x, 66 + x * 18, 18 + y * 18)) addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, 66 + x * 18, 18 + y * 18))
} }
} }
@ -53,6 +66,31 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
} }
} }
override fun netItemsChanged() {
itemsForDisplay = terminal.cachedNetItems.object2IntEntrySet().filter {
if (searchQuery.isBlank()) return@filter true
if (searchQuery.startsWith('@')) {
val unprefixed = searchQuery.drop(1)
val key = Registry.ITEM.getKey(it.key.item)
if (key.isPresent && key.get().value.namespace.startsWith(unprefixed, true)) {
return@filter true
}
}
it.key.name.string.contains(searchQuery, true)
}.sortedByDescending {
it.intValue
}.map {
val stack = it.key.copy()
stack.count = it.intValue
stack
}
}
fun search(query: String) {
searchQuery = query
netItemsChanged()
}
override fun canUse(player: PlayerEntity): Boolean { override fun canUse(player: PlayerEntity): Boolean {
return true return true
} }
@ -68,15 +106,16 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
// the slot clicked was one of the network stacks // the slot clicked was one of the network stacks
if (actionType == SlotActionType.QUICK_MOVE) { if (actionType == SlotActionType.QUICK_MOVE) {
val stack = slots[slotId].stack val stack = slots[slotId].stack
if (!stack.isEmpty && !player.world.isClient) { if (!stack.isEmpty && player.world.isClient) {
terminal.requestItem(stack, min(stack.count, stack.maxCount)) requestItem(player, stack, min(stack.count, stack.maxCount))
} }
} else if (actionType == SlotActionType.PICKUP && clickData == 1) { } else if (actionType == SlotActionType.PICKUP && clickData == 1) {
if (clickData == 1) { if (clickData == 1) {
// right click, request half stack // right click, request half stack
val stack = slots[slotId].stack val stack = slots[slotId].stack
if (!stack.isEmpty && !player.world.isClient) { if (!stack.isEmpty && player.world.isClient) {
terminal.requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt()) requestItem(player, stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
} }
} else { } else {
// todo: left click, show amount dialog // todo: left click, show amount dialog
@ -95,6 +134,13 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
return super.onSlotClick(slotId, clickData, actionType, player) return super.onSlotClick(slotId, clickData, actionType, player)
} }
private fun requestItem(player: PlayerEntity, stack: ItemStack, amount: Int) {
if (!player.world.isClient) return
val handler = (player as ClientPlayerEntity).networkHandler
val packet = C2STerminalRequestItem(terminal, stack, amount)
handler.sendPacket(packet)
}
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack { override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
if (isNetworkSlot(slotId)) { if (isNetworkSlot(slotId)) {
return ItemStack.EMPTY; return ItemStack.EMPTY;

View File

@ -0,0 +1,48 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.PacketSender
import net.minecraft.item.ItemStack
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayNetworkHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.minecraft.util.registry.RegistryKey
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
/**
* @author shadowfacts
*/
object C2STerminalRequestItem: ServerReceiver {
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_request_item")
operator fun invoke(terminal: TerminalBlockEntity, stack: ItemStack, amount: Int): Packet<*> {
val buf = PacketByteBufs.create()
buf.writeIdentifier(terminal.world!!.registryKey.value)
buf.writeBlockPos(terminal.pos)
buf.writeItemStack(stack)
buf.writeVarInt(amount)
return ClientPlayNetworking.createC2SPacket(CHANNEL, buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val stack = buf.readItemStack()
val amount = buf.readVarInt()
server.execute {
val key = RegistryKey.of(Registry.DIMENSION, dimID)
val world = server.getWorld(key) ?: return@execute
val terminal = world.getBlockEntity(pos) as? TerminalBlockEntity ?: return@execute
terminal.requestItem(stack, amount)
}
}
}

View File

@ -0,0 +1,11 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.minecraft.util.Identifier
/**
* @author shadowfacts
*/
interface ServerReceiver: ServerPlayNetworking.PlayChannelHandler {
val CHANNEL: Identifier
}