diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackAddPacket.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackAddPacket.java deleted file mode 100644 index 3e928710b..000000000 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackAddPacket.java +++ /dev/null @@ -1,54 +0,0 @@ -package ru.dbotthepony.mc.otm.menu.data; - -import net.minecraft.client.Minecraft; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraftforge.network.NetworkEvent; - -import java.util.function.Supplier; - -public record StackAddPacket(int id, int stack_id, ItemStack stack) { - public void write(FriendlyByteBuf buffer) { - buffer.writeInt(id); - buffer.writeInt(stack_id); - buffer.writeRegistryId(stack.getItem()); - buffer.writeInt(stack.getCount()); - buffer.writeBoolean(stack.getTag() != null); - - if (stack.getTag() != null) - buffer.writeNbt(stack.getShareTag()); - } - - public static StackAddPacket read(FriendlyByteBuf buffer) { - var id = buffer.readInt(); - var stack = buffer.readInt(); - var item = buffer.readRegistryIdSafe(Item.class); - var count = buffer.readInt(); - - var state = new ItemStack(item, count); - - if (buffer.readBoolean()) - state.readShareTag(buffer.readNbt()); - - return new StackAddPacket(id, stack, state); - } - - public void play(Supplier context) { - context.get().setPacketHandled(true); - context.get().enqueueWork(() -> { - var get = Minecraft.getInstance().player.containerMenu instanceof INetworkedItemViewSupplier supplier && Minecraft.getInstance().player.containerMenu.containerId == id ? supplier.getNetworkedItemView() : null; - - if (get == null) { - throw new IllegalStateException("No such item tracker with id " + id); - } - - if (get.state.containsKey(stack_id)) { - throw new IllegalStateException("Item tracker " + id + " already has stack with id of " + stack_id); - } - - get.state.put(stack_id, new NetworkedItemView.NetworkedItem(stack_id, stack)); - get.clearCache(); - }); - } -} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackChangePacket.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackChangePacket.java deleted file mode 100644 index 2eefd7369..000000000 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackChangePacket.java +++ /dev/null @@ -1,43 +0,0 @@ -package ru.dbotthepony.mc.otm.menu.data; - -import net.minecraft.client.Minecraft; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.network.NetworkEvent; - -import java.util.function.Supplier; - -public record StackChangePacket(int id, int stack_id, int new_count) { - public void write(FriendlyByteBuf buffer) { - buffer.writeInt(id); - buffer.writeInt(stack_id); - buffer.writeInt(new_count); - } - - public static StackChangePacket read(FriendlyByteBuf buffer) { - var id = buffer.readInt(); - var stack_id = buffer.readInt(); - var new_count = buffer.readInt(); - - return new StackChangePacket(id, stack_id, new_count); - } - - public void play(Supplier context) { - context.get().setPacketHandled(true); - context.get().enqueueWork(() -> { - var get = Minecraft.getInstance().player.containerMenu instanceof INetworkedItemViewSupplier supplier && Minecraft.getInstance().player.containerMenu.containerId == id ? supplier.getNetworkedItemView() : null; - - if (get == null) { - throw new IllegalStateException("No such item tracker with id " + id); - } - - var get_state = get.state.get(stack_id); - - if (get_state == null) { - throw new IllegalStateException("Item tracker " + id + " has no stack with id of " + stack_id); - } - - get_state.stack().setCount(new_count); - get.clearCache(); - }); - } -} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackRemovePacket.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackRemovePacket.java deleted file mode 100644 index eba404e2e..000000000 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackRemovePacket.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.dbotthepony.mc.otm.menu.data; - -import net.minecraft.client.Minecraft; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.network.NetworkEvent; - -import java.util.function.Supplier; - -public record StackRemovePacket(int id, int stack_id) { - public void write(FriendlyByteBuf buffer) { - buffer.writeInt(id); - buffer.writeInt(stack_id); - } - - public static StackRemovePacket read(FriendlyByteBuf buffer) { - var id = buffer.readInt(); - var stack_id = buffer.readInt(); - - return new StackRemovePacket(id, stack_id); - } - - public void play(Supplier context) { - context.get().setPacketHandled(true); - context.get().enqueueWork(() -> { - var get = Minecraft.getInstance().player.containerMenu instanceof INetworkedItemViewSupplier supplier && Minecraft.getInstance().player.containerMenu.containerId == id ? supplier.getNetworkedItemView() : null; - - if (get == null) { - throw new IllegalStateException("No such item tracker with id " + id); - } - - var get_state = get.state.remove(stack_id); - - if (get_state == null) { - throw new IllegalStateException("Item tracker " + id + " has no stack with id of " + stack_id); - } - - get.clearCache(); - }); - } -} diff --git a/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java b/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java index 7fcaea529..a3d03a8d5 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java +++ b/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java @@ -191,7 +191,7 @@ public class MatteryNetworking { next_network_id++, StackAddPacket.class, StackAddPacket::write, - StackAddPacket::read, + StackAddPacket.Companion::read, StackAddPacket::play, Optional.of(NetworkDirection.PLAY_TO_CLIENT) ); @@ -200,7 +200,7 @@ public class MatteryNetworking { next_network_id++, StackChangePacket.class, StackChangePacket::write, - StackChangePacket::read, + StackChangePacket.Companion::read, StackChangePacket::play, Optional.of(NetworkDirection.PLAY_TO_CLIENT) ); @@ -209,7 +209,7 @@ public class MatteryNetworking { next_network_id++, StackRemovePacket.class, StackRemovePacket::write, - StackRemovePacket::read, + StackRemovePacket.Companion::read, StackRemovePacket::play, Optional.of(NetworkDirection.PLAY_TO_CLIENT) ); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt index a4e3eba92..1d5a11c9d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt @@ -5,6 +5,7 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items +import net.minecraftforge.registries.ForgeRegistries import net.minecraftforge.registries.RegistryManager import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.core.ImpreciseFraction @@ -45,13 +46,13 @@ class ItemMatteryDrive : AbstractMatteryDrive, IItemMatteryDri } override fun deserializeStack(tag: CompoundTag): ItemStackWrapper? { - val item = RegistryManager.ACTIVE.getRegistry(Item::class.java).getValue(ResourceLocation(tag.getString("item"))) + val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(tag.getString("item"))) if (item != null && item !== Items.AIR) { - val count = tag.getInt("count") - val itemstack = ItemStack(item, count) - itemstack.tag = tag["data"] as? CompoundTag? - return ItemStackWrapper(itemstack) + val count = ImpreciseFraction.deserializeNBT(tag["count"]) + val itemstack = ItemStack(item, 1) + itemstack.tag = tag["data"] as? CompoundTag + return ItemStackWrapper(itemstack, copy = false).also { it.count = count } } return null diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.kt index b3498dea6..1a6fde5c5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.kt @@ -61,12 +61,8 @@ class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Comp for (i in 0 until GRID_WIDTH * GRID_HEIGHT) { object : AbstractSlotPanel(this@DriveViewerScreen, grid, 0f, 0f) { override fun getItemStack(): ItemStack { - val index = i + scroll_bar.getScroll(menu.view.getItems().size / GRID_WIDTH) - val list = menu.view.getItems() - - return if (index >= list.size) { - ItemStack.EMPTY - } else list[index].stack + val index = i + scroll_bar.getScroll(menu.view.sortedView.size / GRID_WIDTH) + return menu.view.sortedView.getOrNull(index)?.stack ?: ItemStack.EMPTY } override fun mouseScrolledInner(mouse_x: Double, mouse_y: Double, scroll: Double): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.kt index 2a489877c..edafe3bb6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.kt @@ -25,8 +25,7 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp object : AbstractSlotPanel(this@ItemMonitorScreen, gridPanel) { override fun getItemStack(): ItemStack { val index = i + scrollBar.getScroll(menu.view.getItemCount() / GRID_WIDTH) - val list = menu.view.getItems() - return if (index >= list.size) ItemStack.EMPTY else list[index].stack + return menu.view.sortedView.getOrNull(index)?.stack ?: ItemStack.EMPTY } override fun mouseScrolledInner(mouse_x: Double, mouse_y: Double, scroll: Double): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.kt index ae4b7ad11..eeadbf6e2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.kt @@ -27,9 +27,9 @@ class DriveViewerMenu @JvmOverloads constructor( ) : MatteryPoweredMenu( MMenus.DRIVE_VIEWER, containerID, inventory, tile ), INetworkedItemViewSupplier { - @JvmField val view: NetworkedItemView + val view: NetworkedItemView private val powered: PoweredVirtualComponent? - @JvmField val driveSlot: MatterySlot + val driveSlot: MatterySlot private var lastDrive: IMatteryDrive? = null override fun getNetworkedItemView() = view diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt index 40ba04132..60042b532 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt @@ -1,12 +1,17 @@ package ru.dbotthepony.mc.otm.menu.data import com.mojang.blaze3d.platform.InputConstants +import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap +import net.minecraft.client.Minecraft import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.FriendlyByteBuf import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.ClickAction import net.minecraft.world.inventory.ClickType +import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.PacketDistributor import ru.dbotthepony.mc.otm.core.ImpreciseFraction import ru.dbotthepony.mc.otm.menu.MatteryMenu @@ -17,29 +22,185 @@ import ru.dbotthepony.mc.otm.storage.IStorageListener import ru.dbotthepony.mc.otm.storage.IStorageView import ru.dbotthepony.mc.otm.storage.ItemStackWrapper import java.util.* +import java.util.function.Supplier -/** - * Creates a virtual, slotless container for Player's interaction with. - */ -open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: Boolean) : IStorageListener { - @JvmRecord - data class NetworkedItem @JvmOverloads constructor(val id: Int, val stack: ItemStack, val upstreamId: UUID? = null) +class StackAddPacket(val id: Int, val stack_id: Int, val stack: ItemStack) { + fun write(buffer: FriendlyByteBuf) { + buffer.writeInt(id) + buffer.writeInt(stack_id) + buffer.writeRegistryId(stack.item) + buffer.writeInt(stack.count) + buffer.writeBoolean(stack.shareTag != null) - @JvmField protected var next_stack_id = 0 - @JvmField val state = HashMap() - - @JvmField protected val upstream_state = HashMap() - @JvmField protected val backlog = ArrayList() - - operator fun get(id: Int): NetworkedItem? { - return state[id] + if (stack.tag != null) buffer.writeNbt(stack.shareTag) } - private var cachedView: List? = null - @JvmField protected var provider: IStorageComponent? = null + fun play(context: Supplier) { + context.get().packetHandled = true + context.get().enqueueWork { + val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork + + if (get.containerId != id) + return@enqueueWork + + val view = (get as? INetworkedItemViewSupplier)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $id") + + if (view.localState.containsKey(stack_id)) { + throw IllegalStateException("Item tracker $id already has stack with id of $stack_id") + } + + val state = NetworkedItemView.NetworkedItem(stack_id, stack) + view.localState[stack_id] = state + + /*val iterator = view.sortedView.iterator().withIndex() + var lastCompare = 0 + var hit = false + + while (iterator.hasNext()) { + val (i, existing) = iterator.next() + val cmp = view.sorter.compare(existing.stack, stack) + + if (cmp != lastCompare && lastCompare != 0) { + hit = true + view.sortedView.add(i, state) + } else if (cmp != lastCompare) { + lastCompare = cmp + } + } + + if (!hit) {*/ + view.sortedView.add(state) + //} + + view.resort() + } + } + + companion object { + fun read(buffer: FriendlyByteBuf): StackAddPacket { + val id = buffer.readInt() + val stack = buffer.readInt() + val item = buffer.readRegistryIdSafe(Item::class.java) + val count = buffer.readInt() + val state = ItemStack(item, count) + if (buffer.readBoolean()) state.readShareTag(buffer.readNbt()) + return StackAddPacket(id, stack, state) + } + } +} + +class StackChangePacket(val id: Int, val stackID: Int, val newCount: Int) { + fun write(buffer: FriendlyByteBuf) { + buffer.writeInt(id) + buffer.writeInt(stackID) + buffer.writeInt(newCount) + } + + fun play(context: Supplier) { + context.get().packetHandled = true + context.get().enqueueWork { + val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork + + if (get.containerId != id) + return@enqueueWork + + val view = (get as? INetworkedItemViewSupplier)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $id") + val state = view.localState[stackID] ?: throw IllegalStateException("No such stack with id $stackID in $view") + + state.stack.count = newCount + view.resort() + } + } + + companion object { + fun read(buffer: FriendlyByteBuf): StackChangePacket { + val id = buffer.readInt() + val stackID = buffer.readInt() + val newCount = buffer.readInt() + return StackChangePacket(id, stackID, newCount) + } + } +} + +class StackRemovePacket(val id: Int, val stackID: Int) { + fun write(buffer: FriendlyByteBuf) { + buffer.writeInt(id) + buffer.writeInt(stackID) + } + + fun play(context: Supplier) { + context.get().packetHandled = true + context.get().enqueueWork { + val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork + + if (get.containerId != id) + return@enqueueWork + + val view = (get as? INetworkedItemViewSupplier)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $id") + val obj = view.localState.remove(stackID) ?: throw IllegalStateException("No such stack with id $stackID in $view") + view.sortedView.remove(obj) + view.resort() + } + } + + companion object { + fun read(buffer: FriendlyByteBuf): StackRemovePacket { + val id = buffer.readInt() + val stackID = buffer.readInt() + return StackRemovePacket(id, stackID) + } + } +} + +/** + * Creates a virtual, slotless container for Player to interaction with. + */ +open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: Boolean) : IStorageListener { + data class NetworkedItem constructor(val id: Int, val stack: ItemStack, val upstreamId: UUID? = null) + + protected var nextStackID = 0 + // this (how client see and interact with) + val localState = Int2ObjectAVLTreeMap() + + val sortedView = LinkedList() + var sorter: Comparator = NAME_SORTER + + companion object { + val NAME_SORTER = Comparator { o1, o2 -> + val cmp = o1.displayName.string.compareTo(o2.displayName.string) + + if (cmp != 0) + return@Comparator cmp + + return@Comparator o1.item.registryName.toString().compareTo(o2.item.registryName.toString()) + } + + val COUNT_SORTER = Comparator { o1, o2 -> + val cmp = o1.count.compareTo(o2.count) + + if (cmp != 0) + return@Comparator cmp + + return@Comparator o1.item.registryName.toString().compareTo(o2.item.registryName.toString()) + } + } + + fun resort() { + sortedView.sortWith { a, b -> + return@sortWith sorter.compare(a.stack, b.stack) + } + } + + // parent (e.g. VirtualComponent) + protected val upstreamState = HashMap() + protected val networkBacklog = ArrayList() + + operator fun get(id: Int): NetworkedItem? = localState[id] + + protected var provider: IStorageComponent? = null fun mouseClick(index: Int, mouse_click_type: Int) { - val list = getItems() + val list = sortedView val action = if (mouse_click_type == InputConstants.MOUSE_BUTTON_LEFT) ClickAction.PRIMARY else ClickAction.SECONDARY @@ -65,16 +226,8 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: provider?.removeListenerAuto(this) } - fun clearCache() { - cachedView = null - } - - fun getItems(): List { - return if (cachedView != null) cachedView!! else java.util.List.copyOf(state.values).also { cachedView = it } - } - fun getItemCount(): Int { - return state.values.size + return localState.values.size } override fun addStack(stack: ItemStackWrapper, id: UUID, provider: IStorageView) = addObject(stack.stack, id) @@ -82,60 +235,57 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: protected fun network(fn: () -> Any) { if (!remote) { - backlog.add(fn()) + networkBacklog.add(fn()) } } override fun removeStack(stack: ItemStackWrapper, id: UUID) { - val get = upstream_state[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") - upstream_state.remove(id) - state.remove(get.id) + val get = upstreamState[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") + upstreamState.remove(id) + localState.remove(get.id) network { StackRemovePacket(menu.containerId, get.id) } - clearCache() } fun addObject(stack: ItemStack, id_upstream: UUID) { - check(!upstream_state.containsKey(id_upstream)) { "Already tracking ItemStack with upstream id $id_upstream!" } + check(!upstreamState.containsKey(id_upstream)) { "Already tracking ItemStack with upstream id $id_upstream!" } - val state = NetworkedItem(next_stack_id++, stack.copy(), id_upstream) + val state = NetworkedItem(nextStackID++, stack.copy(), id_upstream) - this.state[state.id] = state - upstream_state[id_upstream] = state + this.localState[state.id] = state + upstreamState[id_upstream] = state network { StackAddPacket(menu.containerId, state.id, stack) } - clearCache() } fun changeObject(id_upstream: UUID, new_count: Int) { - val get = upstream_state[id_upstream] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id_upstream!") + val get = upstreamState[id_upstream] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id_upstream!") get.stack.count = new_count network { StackChangePacket(menu.containerId, get.id, new_count) } - clearCache() } fun clear() { - clearCache() - upstream_state.clear() - state.clear() + sortedView.clear() + upstreamState.clear() + localState.clear() if (!remote) { - backlog.clear() - backlog.add(ClearPacket(menu.containerId)) + networkBacklog.clear() + networkBacklog.add(ClearPacket(menu.containerId)) } } fun network() { check(!remote) { "Not a server" } val consumer = PacketDistributor.PLAYER.with { ply as ServerPlayer } - for (packet in backlog) MatteryNetworking.CHANNEL.send(consumer, packet) - backlog.clear() + for (packet in networkBacklog) MatteryNetworking.CHANNEL.send(consumer, packet) + networkBacklog.clear() } fun playerInteract(packet: InteractPacket) { val provider = provider ?: return - val click = packet.click() - val action = packet.action() - val stack_id = packet.stack_id() + val click = packet.click + val action = packet.action + val stack_id = packet.stack_id if (click == ClickType.CLONE) { if (stack_id < 0 || !ply.abilities.instabuild) return