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
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import net.shadowfacts.phycon.networking.ServerReceiver
/**
* @author shadowfacts
@ -18,6 +21,12 @@ object PhysicalConnectivity: ModInitializer {
PhyBlockEntities.init()
PhyItems.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
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.MinecraftClient
import net.minecraft.item.Item
import net.minecraft.item.ItemUsageContext
import net.minecraft.text.LiteralText
import net.minecraft.util.ActionResult
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
@ -38,7 +35,6 @@ class ConsoleItem: Item(Settings()) {
return ActionResult.PASS
}
@Environment(EnvType.CLIENT)
private fun openScreen(be: DeviceBlockEntity) {
val screen = DeviceConsoleScreen(be)
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.handleItemStack
import net.shadowfacts.phycon.network.packet.*
import java.lang.ref.WeakReference
import java.util.*
import kotlin.math.min
@ -55,6 +56,8 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
val cachedNetItems = ItemStackCollections.intMap()
var cachedSortedNetItems = listOf<ItemStack>()
var netItemObserver: WeakReference<NetItemObserver>? = null
init {
internalBuffer.addListener(this)
}
@ -312,6 +315,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
val netAmount = entryTag.getInt("NetAmount")
cachedNetItems[stack] = netAmount
}
netItemObserver?.get()?.netItemsChanged()
cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
val stack = it.key.copy()
stack.count = it.intValue
@ -319,6 +323,10 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
}
}
interface NetItemObserver {
fun netItemsChanged()
}
}
data class StackLocateRequest(

View File

@ -8,7 +8,7 @@ import net.minecraft.item.ItemStack
/**
* @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 {
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 {
override fun getStack(_slot: Int): ItemStack {
if (slot >= terminal.cachedSortedNetItems.size) return ItemStack.EMPTY
return terminal.cachedSortedNetItems[slot]
class FakeInventory(val screenHandler: TerminalScreenHandler): Inventory {
override fun getStack(slot: Int): ItemStack {
if (slot >= screenHandler.itemsForDisplay.size) return ItemStack.EMPTY
return screenHandler.itemsForDisplay[slot]
}
override fun markDirty() {

View File

@ -3,12 +3,15 @@ package net.shadowfacts.phycon.network.block.terminal
import com.mojang.blaze3d.platform.GlStateManager
import net.minecraft.client.gui.DrawableHelper
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.entity.player.PlayerInventory
import net.minecraft.screen.slot.Slot
import net.minecraft.text.LiteralText
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
@ -19,11 +22,33 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
}
private lateinit var searchBox: TextFieldWidget
init {
backgroundWidth = 252
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) {
textRenderer.draw(matrixStack, title, 65f, 6f, 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) {
super.render(matrixStack, mouseX, mouseY, delta)
searchBox.render(matrixStack, mouseX, mouseY, delta)
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())
}
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
import net.minecraft.client.network.ClientPlayerEntity
import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType
import net.minecraft.entity.player.PlayerEntity
@ -8,29 +9,41 @@ import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import java.lang.ref.WeakReference
import kotlin.math.ceil
import kotlin.math.min
/**
* @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 {
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):
this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!)
init {
terminal.netItemObserver = WeakReference(this)
netItemsChanged()
// network
for (y in 0 until 6) {
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 {
return true
}
@ -68,15 +106,16 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
// the slot clicked was one of the network stacks
if (actionType == SlotActionType.QUICK_MOVE) {
val stack = slots[slotId].stack
if (!stack.isEmpty && !player.world.isClient) {
terminal.requestItem(stack, min(stack.count, stack.maxCount))
if (!stack.isEmpty && player.world.isClient) {
requestItem(player, stack, min(stack.count, stack.maxCount))
}
} else if (actionType == SlotActionType.PICKUP && clickData == 1) {
if (clickData == 1) {
// right click, request half stack
val stack = slots[slotId].stack
if (!stack.isEmpty && !player.world.isClient) {
terminal.requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
if (!stack.isEmpty && player.world.isClient) {
requestItem(player, stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
}
} else {
// 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)
}
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 {
if (isNetworkSlot(slotId)) {
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
}