diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/HoloSignScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/HoloSignScreen.kt index 2b42df530..b2742e0f3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/HoloSignScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/HoloSignScreen.kt @@ -6,15 +6,33 @@ import ru.dbotthepony.mc.otm.client.screen.panels.Dock import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel import ru.dbotthepony.mc.otm.client.screen.panels.TextInputPanel import ru.dbotthepony.mc.otm.menu.HoloSignMenu +import ru.dbotthepony.mc.otm.milliTime class HoloSignScreen(menu: HoloSignMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, title) { override fun makeMainFrame(): FramePanel> { val frame = FramePanel(this, null, 0f, 0f, 200f, 200f, getTitle()) - val textbox = TextInputPanel(this, frame) - textbox.dock = Dock.FILL - textbox.multiLine = true - textbox.text = "МОГУС\nБОНУС\n\nСУС" + object : TextInputPanel(this@HoloSignScreen, frame) { + init { + dock = Dock.FILL + multiLine = true + } + + private var lastChanges = 0L + + override fun onTextChanged(old: String, new: String) { + lastChanges = milliTime + 1000L + menu.textInput.input(new) + } + + override fun tick() { + super.tick() + + if (milliTime >= lastChanges) { + text = menu.text + } + } + } return frame } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/TextInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/TextInputPanel.kt index 02d4ea70f..66d879741 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/TextInputPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/TextInputPanel.kt @@ -80,14 +80,24 @@ open class TextInputPanel( private val cursorLine = this@TextInputPanel.cursorLine private val cursorCharacter = this@TextInputPanel.cursorCharacter private val selections = Int2ObjectAVLTreeMap(this@TextInputPanel.selections) + private val multiLine = this@TextInputPanel.multiLine fun apply() { this@TextInputPanel.lines.clear() - this@TextInputPanel.lines.addAll(lines) + + if (this@TextInputPanel.multiLine) + this@TextInputPanel.lines.addAll(lines) + else + this@TextInputPanel.lines.add(lines.joinToString("")) + + this@TextInputPanel.selections.clear() - this@TextInputPanel.selections.putAll(selections) + if (this@TextInputPanel.multiLine && multiLine) + this@TextInputPanel.selections.putAll(selections) + this@TextInputPanel.cursorCharacter = cursorCharacter this@TextInputPanel.cursorLine = cursorLine + triggerChangeCallback() } override fun equals(other: Any?): Boolean { @@ -114,18 +124,41 @@ open class TextInputPanel( var debugDraw = false var multiLine = false + set(value) { + if (field == value) return + + if (!value && lines.size > 1) { + val merge = lines.joinToString("") + lines.clear() + lines.add(merge) + triggerChangeCallback() + } + + field = value + } + var cursorLine = 0 var cursorCharacter = 0 var textColor = RGBAColor.WHITE var cursorColor = RGBAColor.GREEN var backgroundColor = RGBAColor.BLACK + private var oldText = ArrayList() private var textCache: String? = null private val lines = ArrayList() private val selections = Int2ObjectAVLTreeMap() private val undo = ArrayDeque() private val redo = ArrayDeque() + private fun triggerChangeCallback() { + if (oldText != lines) { + textCache = null + val old = oldText.joinToString("\n") + oldText = ArrayList(lines) + onTextChanged(old, textCache()) + } + } + private var snapshotTimer: Long? = null override fun tick() { @@ -214,9 +247,11 @@ open class TextInputPanel( } operator fun set(index: Int, value: String) { - if (index < 0) { + if (index < 0) throw IndexOutOfBoundsException("negative index $index") - } + + if (!multiLine && index != 0) + throw IllegalStateException("Not accepting newlines") lines.ensureCapacity(index) @@ -241,6 +276,9 @@ open class TextInputPanel( } fun insertLine(index: Int, value: String = "") { + if (!multiLine && lines.isNotEmpty()) + throw IllegalStateException("Not accepting newlines") + lines.ensureCapacity(index) while (lines.size < index) { @@ -262,9 +300,8 @@ open class TextInputPanel( } fun removeLine(index: Int): Boolean { - if (index < 0 || index >= lines.size) { + if (index < 0 || index >= lines.size) return false - } if (cursorLine == index) { if (index != 0) @@ -363,6 +400,8 @@ open class TextInputPanel( cursorCharacter = selection.start } } + + triggerChangeCallback() } private fun textCache(): String { @@ -391,6 +430,7 @@ open class TextInputPanel( undo.clear() cursorLine = 0 cursorCharacter = 0 + textCache = null if (multiLine) { lines.addAll(value.split(NEWLINES)) @@ -636,9 +676,12 @@ open class TextInputPanel( cursorCharacter = 0 } + triggerChangeCallback() + return true } else { killFocus() + triggerChangeCallback() return true } } @@ -718,6 +761,7 @@ open class TextInputPanel( } recordHistory() + triggerChangeCallback() } } else { if (line != null) { @@ -728,13 +772,14 @@ open class TextInputPanel( val newLine = line.substring(0, line.length - 1) moveCursors(cursorLine, cursorCharacter, -1) this[cursorLine] = newLine - recordHistory() } else { val newLine = line.substring(0, cursorCharacter - 1) + line.substring(cursorCharacter) moveCursors(cursorLine, cursorCharacter, -1) this[cursorLine] = newLine - recordHistory() } + + recordHistory() + triggerChangeCallback() } else if (cursorLine > 0) { cursorLine-- recordHistory() @@ -751,7 +796,7 @@ open class TextInputPanel( } if (cursorLine !in 0 until lines.size) { - cursorLine = lines.size - 1 + cursorLine = (lines.size - 1).coerceAtLeast(0) return true } @@ -764,6 +809,7 @@ open class TextInputPanel( cursorCharacter = 0 recordHistory(true) + triggerChangeCallback() return true } @@ -780,8 +826,6 @@ open class TextInputPanel( cursorCharacter = line.length this[cursorLine] = line + bottomLine removeLine(cursorLine + 1) - - recordHistory() } else { pushbackSnapshotIfNoTimer() @@ -791,10 +835,10 @@ open class TextInputPanel( if (cursorCharacter != 0) this.cursorCharacter++ - - recordHistory() } + recordHistory() + triggerChangeCallback() return true } @@ -860,6 +904,7 @@ open class TextInputPanel( } pushbackSnapshot() + triggerChangeCallback() return true } @@ -924,6 +969,10 @@ open class TextInputPanel( override fun charTypedInternal(codepoint: Char, mods: Int): Boolean { wipeSelection() + + if (!multiLine) + cursorLine = 0 + var line = this[cursorLine] if (line == null) { @@ -942,6 +991,7 @@ open class TextInputPanel( set(cursorLine, line) moveCursors(cursorLine, cursorCharacter, 1) recordHistory() + triggerChangeCallback() return true } @@ -1171,6 +1221,16 @@ open class TextInputPanel( return true } + protected open fun onTextChanged(old: String, new: String) { + changeCallback?.invoke(old, new) + } + + protected var changeCallback: ((old: String, new: String) -> Unit)? = null + + fun onTextChange(callback: (old: String, new: String) -> Unit) { + changeCallback = callback + } + private enum class CharType { SPACES { override fun contains(input: Char): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/HoloSignMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/HoloSignMenu.kt index 8ad731f18..0d6dba642 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/HoloSignMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/HoloSignMenu.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.menu import net.minecraft.world.entity.player.Inventory import net.minecraft.world.inventory.Slot import ru.dbotthepony.mc.otm.block.entity.decorative.HoloSignBlockEntity +import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec import ru.dbotthepony.mc.otm.registry.MMenus class HoloSignMenu @JvmOverloads constructor( @@ -12,6 +13,11 @@ class HoloSignMenu @JvmOverloads constructor( ) : MatteryMenu(MMenus.HOLO_SIGN, containerId, inventory, tile) { var text by mSynchronizer.string(name = "text") + val textInput = PlayerInput(BinaryStringCodec) { + if (tile is HoloSignBlockEntity) + tile.text = it + } + override val storageSlots: Collection get() = listOf() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt index 8c77b7a51..1d68ed134 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -2,7 +2,10 @@ package ru.dbotthepony.mc.otm.menu import com.google.common.collect.ImmutableList import com.mojang.datafixers.util.Pair +import it.unimi.dsi.fastutil.io.FastByteArrayInputStream +import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import net.minecraft.network.FriendlyByteBuf import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.world.Container @@ -12,17 +15,26 @@ import net.minecraft.world.inventory.* import net.minecraft.world.item.ItemStack import net.minecraft.world.item.enchantment.EnchantmentHelper.hasBindingCurse import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.PacketDistributor import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorSlots import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot +import ru.dbotthepony.mc.otm.core.util.IStreamCodec import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget import ru.dbotthepony.mc.otm.network.FieldSynchronizer +import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.MenuFieldPacket import ru.dbotthepony.mc.otm.network.MenuNetworkChannel +import ru.dbotthepony.mc.otm.network.packetHandled +import ru.dbotthepony.mc.otm.network.sender +import java.io.DataInputStream +import java.io.DataOutputStream import java.util.* +import java.util.function.Supplier +import kotlin.collections.ArrayList abstract class MatteryMenu @JvmOverloads protected constructor( menuType: MenuType<*>?, @@ -43,6 +55,45 @@ abstract class MatteryMenu @JvmOverloads protected constructor( private val _playerExoSuitSlots = ArrayList() private val _playerCombinedInventorySlots = ArrayList() + private val playerInputs = ArrayList>() + + class PlayerInputPacket(val containerId: Int, val inputId: Int, val payload: ByteArray) : MatteryPacket { + constructor(buff: FriendlyByteBuf) : this(buff.readVarInt(), buff.readVarInt(), ByteArray(buff.readableBytes()).also { buff.readBytes(it) }) + + override fun write(buff: FriendlyByteBuf) { + buff.writeVarInt(containerId) + buff.writeVarInt(inputId) + buff.writeBytes(payload) + } + + override fun play(context: Supplier) { + context.packetHandled = true + val menu = context.sender?.containerMenu as? MatteryMenu ?: return + if (menu.containerId != containerId) return + val input = menu.playerInputs.getOrNull(inputId) ?: return + if (!input.allowSpectators && context.sender!!.isSpectator) return + input.invoke(input.codec.read(DataInputStream(FastByteArrayInputStream(payload)))) + } + } + + inner class PlayerInput(val codec: IStreamCodec, val allowSpectators: Boolean = false, val handler: (V) -> Unit) { + val id = playerInputs.size + + init { + playerInputs.add(this) + } + + fun input(value: V) { + val stream = FastByteArrayOutputStream() + codec.write(DataOutputStream(stream), value) + MenuNetworkChannel.sendToServer(PlayerInputPacket(containerId, id, stream.array.copyOfRange(0, stream.length))) + } + + internal fun invoke(value: Any?) { + handler.invoke(value as V) + } + } + /** * inventory + exosuit + hotbar (in this order) */ diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt index d09bb9034..b22cae9f8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt @@ -65,7 +65,7 @@ class SetCarriedPacket(val item: ItemStack) : MatteryPacket { } object MenuNetworkChannel : MatteryNetworkChannel( - version = "1", + version = "2", name = "menu" ) { fun register() { @@ -86,6 +86,7 @@ object MenuNetworkChannel : MatteryNetworkChannel( add(NumberPlayerInputPacket::class.java, NumberPlayerInputPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) add(OneWayPlayerInputPacket::class.java, OneWayPlayerInputPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) add(BooleanPlayerInputPacket::class.java, BooleanPlayerInputPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) + add(MatteryMenu.PlayerInputPacket::class.java, MatteryMenu::PlayerInputPacket, NetworkDirection.PLAY_TO_SERVER) // menu specific