PhysicalConnectivity/src/main/kotlin/net/shadowfacts/phycon/block/terminal/TerminalScreenHandler.kt

260 lines
7.9 KiB
Kotlin
Raw Normal View History

2021-02-28 18:48:39 +00:00
package net.shadowfacts.phycon.block.terminal
import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
2021-02-16 03:51:33 +00:00
import net.minecraft.util.registry.Registry
2021-03-24 21:28:03 +00:00
import net.shadowfacts.phycon.DefaultPlugin
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
2021-02-21 16:13:49 +00:00
import net.shadowfacts.phycon.util.SortMode
2021-03-24 21:28:03 +00:00
import net.shadowfacts.phycon.util.TerminalSettings
2021-03-02 02:29:14 +00:00
import net.shadowfacts.phycon.util.copyWithCount
2021-02-16 03:51:33 +00:00
import java.lang.ref.WeakReference
2021-03-07 19:02:39 +00:00
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
2021-03-07 19:02:39 +00:00
import kotlin.math.roundToInt
/**
* @author shadowfacts
*/
2021-03-24 21:28:03 +00:00
class TerminalScreenHandler(
syncId: Int,
val playerInv: PlayerInventory,
val terminal: TerminalBlockEntity,
): ScreenHandler(PhyScreens.TERMINAL, syncId),
2021-02-16 03:51:33 +00:00
TerminalBlockEntity.NetItemObserver {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
}
2021-03-07 19:02:39 +00:00
private val rowsDisplayed = 6
2021-02-16 03:51:33 +00:00
private val fakeInv = FakeInventory(this)
private var searchQuery: String = ""
2021-03-24 21:28:03 +00:00
private var settings = TerminalSettings()
2021-03-07 19:02:39 +00:00
var totalEntries = 0
private set
2021-03-21 02:31:53 +00:00
var scrollPosition = 0f
private var itemEntries = listOf<Entry>()
set(value) {
field = value
if (terminal.world!!.isClient) {
itemsForDisplay = value.map {
2021-03-02 02:29:14 +00:00
it.stack.copyWithCount(it.amount)
}
}
}
2021-02-16 03:51:33 +00:00
var itemsForDisplay = listOf<ItemStack>()
private set
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
2021-03-24 21:28:03 +00:00
this(
syncId,
playerInv,
PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
)
init {
if (!terminal.world!!.isClient) {
2021-03-24 21:28:03 +00:00
assert(terminal.netItemObserver?.get() === null)
terminal.netItemObserver = WeakReference(this)
2021-03-24 21:28:03 +00:00
// intentionally don't call netItemsChanged immediately, we need to wait for the client to send us its settings
}
2021-02-16 03:51:33 +00:00
// network
for (y in 0 until 6) {
for (x in 0 until 9) {
2021-02-16 03:51:33 +00:00
addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, 66 + x * 18, 18 + y * 18))
}
}
// internal buffer
for (y in 0 until 6) {
for (x in 0 until 3) {
addSlot(Slot(terminal.internalBuffer, y * 3 + x, 8 + x * 18, 18 + y * 18))
}
}
// player inv
for (y in 0 until 3) {
for (x in 0 until 9) {
addSlot(Slot(playerInv, x + y * 9 + 9, 66 + x * 18, 140 + y * 18))
}
}
// hotbar
for (x in 0 until 9) {
addSlot(Slot(playerInv, x, 66 + x * 18, 198))
}
}
2021-02-16 03:51:33 +00:00
override fun netItemsChanged() {
val player = playerInv.player
assert(player is ServerPlayerEntity)
2021-02-21 16:13:49 +00:00
val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
2021-02-16 03:51:33 +00:00
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)
2021-02-21 16:13:49 +00:00
}
2021-03-07 19:02:39 +00:00
totalEntries = filtered.size
2021-02-21 16:13:49 +00:00
val sorted =
2021-03-24 21:28:03 +00:00
when (settings[DefaultPlugin.SORT_MODE]) {
2021-02-21 16:13:49 +00:00
SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
}
2021-03-07 19:02:39 +00:00
val offsetInItems = currentScrollOffsetInItems()
val end = min(offsetInItems + rowsDisplayed * 9, sorted.size)
itemEntries = sorted.subList(offsetInItems, end).map { Entry(it.key, it.intValue) }
// itemEntries = sorted.map { Entry(it.key, it.intValue) }
2021-03-24 21:28:03 +00:00
(player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, settings, scrollPosition, totalEntries))
2021-03-07 19:02:39 +00:00
}
fun totalRows(): Int {
return ceil(totalEntries / 9f).toInt()
}
fun maxScrollOffsetInRows(): Int {
return totalRows() - rowsDisplayed
}
fun currentScrollOffsetInRows(): Int {
return max(0, (scrollPosition * maxScrollOffsetInRows()).roundToInt())
}
fun currentScrollOffsetInItems(): Int {
return currentScrollOffsetInRows() * 9
2021-02-16 03:51:33 +00:00
}
2021-03-24 21:28:03 +00:00
fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, settings: TerminalSettings, scrollPosition: Float) {
this.searchQuery = query
2021-03-24 21:28:03 +00:00
this.settings = settings
2021-03-07 19:02:39 +00:00
this.scrollPosition = scrollPosition
2021-02-16 03:51:33 +00:00
netItemsChanged()
}
2021-03-24 21:28:03 +00:00
fun receivedUpdatedItemsFromServer(entries: List<Entry>, query: String, scrollPosition: Float, totalEntries: Int) {
assert(playerInv.player.world.isClient)
this.searchQuery = query
2021-03-07 19:02:39 +00:00
this.scrollPosition = scrollPosition
this.totalEntries = totalEntries
itemEntries = entries
}
override fun canUse(player: PlayerEntity): Boolean {
return true
}
override fun close(player: PlayerEntity) {
super.close(player)
2021-03-24 21:28:03 +00:00
terminal.netItemObserver = null
}
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
if (isBufferSlot(slotId)) {
// todo: why does this think it's quick_craft sometimes?
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) {
// placing cursor stack into buffer
val bufferSlot = slotId - bufferSlotsStart // subtract 54 to convert the handler slot ID to a valid buffer index
terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
}
}
return super.onSlotClick(slotId, clickData, actionType, player)
}
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
if (isNetworkSlot(slotId)) {
return ItemStack.EMPTY;
}
val slot = slots[slotId]
if (!slot.hasStack()) {
return ItemStack.EMPTY
}
val result = slot.stack.copy()
if (isBufferSlot(slotId)) {
// last boolean param is fromLast
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
return ItemStack.EMPTY
}
if (slot.stack.isEmpty) {
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
}
} else if (isPlayerSlot(slotId)) {
val slotsInsertedInto = tryInsertItem(slot.stack, bufferSlotsStart until playerSlotsStart) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
slotsInsertedInto.forEach { terminal.internalBuffer.markSlot(it - bufferSlotsStart, TerminalBufferInventory.Mode.TO_NETWORK) }
if (slot.stack.isEmpty) {
return ItemStack.EMPTY
}
}
return result
}
private fun tryInsertItem(stack: ItemStack, slots: IntRange, slotPredicate: (Int) -> Boolean): Collection<Int> {
val slotsInsertedInto = mutableListOf<Int>()
for (index in slots) {
if (stack.isEmpty) break
if (!slotPredicate(index)) continue
val slot = this.slots[index]
val slotStack = slot.stack
if (slotStack.isEmpty) {
slot.stack = stack.copy()
stack.count = 0
slot.markDirty()
slotsInsertedInto.add(index)
} else if (canStacksCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
val maxToMove = slotStack.maxCount - slotStack.count
val toMove = min(maxToMove, stack.count)
slotStack.increment(toMove)
stack.decrement(toMove)
slot.markDirty()
slotsInsertedInto.add(index)
}
}
return slotsInsertedInto
}
2021-03-18 22:12:01 +00:00
val networkSlotsStart = 0
val networkSlotsEnd = 54
val bufferSlotsStart = 54
2021-03-18 22:12:01 +00:00
val bufferSlotsEnd = 72
val playerSlotsStart = 72
val playerSlotsEnd = 72 + 36
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
data class Entry(val stack: ItemStack, val amount: Int)
}