From b3da8b1185f1edabe9b088abbf7dc301b3eb58a5 Mon Sep 17 00:00:00 2001 From: Bixilon Date: Sun, 31 Oct 2021 14:29:45 +0100 Subject: [PATCH] fix nbt writing, item picking, ItemStack: fix enchantment nbt generation --- .../data/entities/block/BlockEntity.kt | 1 + .../minosoft/data/inventory/ItemStack.kt | 6 +- .../bixilon/minosoft/data/player/Abilities.kt | 2 +- .../minosoft/data/player/LocalPlayerEntity.kt | 1 - .../data/registries/entities/EntityType.kt | 5 + .../other/containers/PlayerInventory.kt | 2 +- .../data/registries/versions/Version.kt | 3 + .../gui/rendering/input/LeftClickHandler.kt | 2 +- .../interaction/HotbarInteractionHandler.kt | 7 +- .../input/interaction/InteractionManager.kt | 3 + .../interaction/ItemPickInteractionHandler.kt | 99 +++++++++++++++++++ .../gui/rendering/sound/AudioPlayer.kt | 4 +- .../packets/s2c/play/PlayerAbilitiesS2CP.kt | 2 +- .../protocol/protocol/OutByteBuffer.kt | 27 ++--- 14 files changed, 135 insertions(+), 29 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/ItemPickInteractionHandler.kt diff --git a/src/main/java/de/bixilon/minosoft/data/entities/block/BlockEntity.kt b/src/main/java/de/bixilon/minosoft/data/entities/block/BlockEntity.kt index 44c3940b1..45d83f10b 100644 --- a/src/main/java/de/bixilon/minosoft/data/entities/block/BlockEntity.kt +++ b/src/main/java/de/bixilon/minosoft/data/entities/block/BlockEntity.kt @@ -20,6 +20,7 @@ import glm_.vec3.Vec3i abstract class BlockEntity( val connection: PlayConnection, ) { + open val nbt: Map = mapOf() open fun updateNBT(nbt: Map) = Unit diff --git a/src/main/java/de/bixilon/minosoft/data/inventory/ItemStack.kt b/src/main/java/de/bixilon/minosoft/data/inventory/ItemStack.kt index 18ab8bc01..93451a47a 100644 --- a/src/main/java/de/bixilon/minosoft/data/inventory/ItemStack.kt +++ b/src/main/java/de/bixilon/minosoft/data/inventory/ItemStack.kt @@ -240,10 +240,11 @@ class ItemStack( nbt[HIDE_FLAGS_TAG] = hideFlags } if (enchantments.isNotEmpty()) { + val connection = connection!! val enchantmentList: MutableList> = mutableListOf() for ((enchantment, level) in enchantments) { val enchantmentTag: MutableMap = mutableMapOf() - enchantmentTag[ENCHANTMENT_ID_TAG] = if (connection!!.version.isFlattened()) { + enchantmentTag[ENCHANTMENT_ID_TAG] = if (connection.version.isFlattened()) { enchantment.resourceLocation.full } else { connection.registries.enchantmentRegistry.getId(enchantment) @@ -254,8 +255,9 @@ class ItemStack( } else { level.toShort() } + enchantmentList += enchantmentTag } - if (connection!!.version.isFlattened()) { + if (connection.version.isFlattened()) { nbt[ENCHANTMENT_FLATTENING_TAG] = enchantmentList } else { nbt[ENCHANTMENT_PRE_FLATTENING_TAG] = enchantmentList diff --git a/src/main/java/de/bixilon/minosoft/data/player/Abilities.kt b/src/main/java/de/bixilon/minosoft/data/player/Abilities.kt index 3cc61af0b..034eafefa 100644 --- a/src/main/java/de/bixilon/minosoft/data/player/Abilities.kt +++ b/src/main/java/de/bixilon/minosoft/data/player/Abilities.kt @@ -17,7 +17,7 @@ data class Abilities( var isInvulnerable: Boolean = false, var isFlying: Boolean = false, var canFly: Boolean = false, - var canInstantBreak: Boolean = false, // ToDo: This is the check if we are in creative mode, but no exactly sure... + var creative: Boolean = false, // ToDo: This is the check if we are in creative mode, but no exactly sure... var flyingSpeed: Double = 0.05, var walkingSpeed: Double = 0.1, diff --git a/src/main/java/de/bixilon/minosoft/data/player/LocalPlayerEntity.kt b/src/main/java/de/bixilon/minosoft/data/player/LocalPlayerEntity.kt index 35078ce47..a4a9564ea 100644 --- a/src/main/java/de/bixilon/minosoft/data/player/LocalPlayerEntity.kt +++ b/src/main/java/de/bixilon/minosoft/data/player/LocalPlayerEntity.kt @@ -198,7 +198,6 @@ class LocalPlayerEntity( val swimHeight: Double get() = (eyeHeight < 0.4).decide(0.0, 0.4) - val reachDistance: Double get() = (gamemode == Gamemodes.CREATIVE).decide(5.0, 4.5) diff --git a/src/main/java/de/bixilon/minosoft/data/registries/entities/EntityType.kt b/src/main/java/de/bixilon/minosoft/data/registries/entities/EntityType.kt index 85adfaf6d..184870561 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/entities/EntityType.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/entities/EntityType.kt @@ -19,12 +19,14 @@ import de.bixilon.minosoft.data.entities.EntityRotation import de.bixilon.minosoft.data.entities.entities.Entity import de.bixilon.minosoft.data.entities.meta.EntityMetaData import de.bixilon.minosoft.data.registries.ResourceLocation +import de.bixilon.minosoft.data.registries.items.SpawnEggItem import de.bixilon.minosoft.data.registries.registries.Registries import de.bixilon.minosoft.data.registries.registries.registry.RegistryItem import de.bixilon.minosoft.data.registries.registries.registry.ResourceLocationDeserializer import de.bixilon.minosoft.data.registries.registries.registry.Translatable import de.bixilon.minosoft.datafixer.EntityAttributeFixer.fix import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection +import de.bixilon.minosoft.util.KUtil.nullCast import de.bixilon.minosoft.util.KUtil.toBoolean import de.bixilon.minosoft.util.KUtil.toResourceLocation import de.bixilon.minosoft.util.KUtil.unsafeCast @@ -41,8 +43,10 @@ data class EntityType( val fireImmune: Boolean, val attributes: Map, val factory: EntityFactory, + val spawnEgg: SpawnEggItem?, ) : RegistryItem(), Translatable { + override fun toString(): String { return resourceLocation.toString() } @@ -83,6 +87,7 @@ data class EntityType( sizeFixed = data["size_fixed"]?.toBoolean() ?: false, attributes = attributes.toMap(), factory = DefaultEntityFactories[resourceLocation] ?: error("Can not find entity factory for $resourceLocation"), + spawnEgg = registries.itemRegistry[data["spawn_egg_item"]]?.nullCast(), // ToDo: Not yet in PixLyzer ) } } diff --git a/src/main/java/de/bixilon/minosoft/data/registries/other/containers/PlayerInventory.kt b/src/main/java/de/bixilon/minosoft/data/registries/other/containers/PlayerInventory.kt index 25f5b8c0e..c8af2346e 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/other/containers/PlayerInventory.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/other/containers/PlayerInventory.kt @@ -40,7 +40,7 @@ class PlayerInventory(connection: PlayConnection) : Container( fun getHotbarSlot(hotbarSlot: Int = connection.player.selectedHotbarSlot): ItemStack? { check(hotbarSlot in 0..HOTBAR_SLOTS) { "Hotbar slot out of bounds!" } - return slots[hotbarSlot + HOTBAR_OFFSET] // ToDo + return slots[hotbarSlot + HOTBAR_OFFSET] } operator fun get(slot: InventorySlots.EquipmentSlots): ItemStack? { diff --git a/src/main/java/de/bixilon/minosoft/data/registries/versions/Version.kt b/src/main/java/de/bixilon/minosoft/data/registries/versions/Version.kt index c8161b639..63ab838d3 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/versions/Version.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/versions/Version.kt @@ -23,6 +23,7 @@ import de.bixilon.minosoft.protocol.protocol.PacketTypes.C2S import de.bixilon.minosoft.protocol.protocol.PacketTypes.S2C import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolStates +import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_15W31A import de.bixilon.minosoft.util.CountUpAndDownLatch import de.bixilon.minosoft.util.KUtil.decide import de.bixilon.minosoft.util.logging.Log @@ -152,4 +153,6 @@ data class Version( override fun toString(): String { return name } + + val hasOffhand = versionId >= V_15W31A } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/LeftClickHandler.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/LeftClickHandler.kt index 2ff36b92b..a5fbb4d42 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/input/LeftClickHandler.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/LeftClickHandler.kt @@ -145,7 +145,7 @@ class LeftClickHandler( val canStartBreaking = currentTime - breakSent >= ProtocolDefinition.TICK_TIME - val canInstantBreak = connection.player.baseAbilities.canInstantBreak || connection.player.gamemode == Gamemodes.CREATIVE + val canInstantBreak = connection.player.baseAbilities.creative || connection.player.gamemode == Gamemodes.CREATIVE if (canInstantBreak) { if (!canStartBreaking) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/HotbarInteractionHandler.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/HotbarInteractionHandler.kt index c7d94662f..b72461b74 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/HotbarInteractionHandler.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/HotbarInteractionHandler.kt @@ -35,8 +35,8 @@ class HotbarInteractionHandler( val renderWindow: RenderWindow, ) { private val connection = renderWindow.connection - private val slotLimiter = RateLimiter() - private val swapLimiter = RateLimiter(dependencies = synchronizedSetOf(slotLimiter)) // we don't want to swap wrong items + val slotLimiter = RateLimiter() + val swapLimiter = RateLimiter(dependencies = synchronizedSetOf(slotLimiter)) // we don't want to swap wrong items private var currentScrollOffset = 0.0 @@ -54,7 +54,7 @@ class HotbarInteractionHandler( } fun swapItems() { - if (connection.player.gamemode == Gamemodes.SPECTATOR) { + if (!connection.version.hasOffhand || connection.player.gamemode == Gamemodes.SPECTATOR) { return } val inventory = connection.player.inventory @@ -62,6 +62,7 @@ class HotbarInteractionHandler( val off = inventory[InventorySlots.EquipmentSlots.OFF_HAND] if (main == null && off == null) { + // ToDo: Forbid swap if both are equals? // both are air, we can't swap return } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/InteractionManager.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/InteractionManager.kt index 3ea07b0b6..470f6ac3f 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/InteractionManager.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/InteractionManager.kt @@ -19,12 +19,15 @@ class InteractionManager( val renderWindow: RenderWindow, ) { val hotbar = HotbarInteractionHandler(renderWindow) + val pick = ItemPickInteractionHandler(renderWindow, this) fun init() { hotbar.init() + pick.init() } fun draw(delta: Double) { hotbar.draw(delta) + pick.draw(delta) } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/ItemPickInteractionHandler.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/ItemPickInteractionHandler.kt new file mode 100644 index 000000000..faae01679 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/ItemPickInteractionHandler.kt @@ -0,0 +1,99 @@ +/* + * Minosoft + * Copyright (C) 2021 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.input.interaction + +import de.bixilon.minosoft.config.key.KeyAction +import de.bixilon.minosoft.config.key.KeyBinding +import de.bixilon.minosoft.config.key.KeyCodes +import de.bixilon.minosoft.data.inventory.InventorySlots +import de.bixilon.minosoft.data.inventory.ItemStack +import de.bixilon.minosoft.data.registries.other.containers.PlayerInventory +import de.bixilon.minosoft.gui.rendering.RenderWindow +import de.bixilon.minosoft.gui.rendering.input.camera.hit.BlockRaycastHit +import de.bixilon.minosoft.gui.rendering.input.camera.hit.EntityRaycastHit +import de.bixilon.minosoft.protocol.RateLimiter +import de.bixilon.minosoft.protocol.packets.c2s.play.ItemStackCreateC2SP +import de.bixilon.minosoft.util.KUtil.toResourceLocation + +class ItemPickInteractionHandler( + val renderWindow: RenderWindow, + val interactionManager: InteractionManager, +) { + private val connection = renderWindow.connection + val rateLimiter = RateLimiter() + + + fun init() { + renderWindow.inputHandler.registerKeyCallback("minosoft:pick_item".toResourceLocation(), KeyBinding( + mutableMapOf( + KeyAction.PRESS to mutableSetOf(KeyCodes.MOUSE_BUTTON_MIDDLE), + ), + )) { + pickItem(true) // ToDo: Combination for not copying nbt + } + } + + fun pickItem(copyNBT: Boolean) { + if (!connection.player.baseAbilities.creative) { + return + } + val raycast = renderWindow.inputHandler.camera.target ?: return + + if (raycast.distance > connection.player.reachDistance) { + return + } + + val itemStack: ItemStack? + + when (raycast) { + is BlockRaycastHit -> { + itemStack = ItemStack(raycast.blockState.block.item!!, connection, 1) + + if (copyNBT) { + val blockEntity = connection.world.getBlockEntity(raycast.blockPosition) + blockEntity?.nbt?.let { itemStack.nbt.putAll(it) } + } + } + is EntityRaycastHit -> { + val entity = raycast.entity + itemStack = entity.entityType.spawnEgg?.let { ItemStack(it, connection) } ?: let { + entity.equipment[InventorySlots.EquipmentSlots.MAIN_HAND]?.copy() + } + } + else -> { + itemStack = null + } + } + + if (itemStack == null) { + return + } + for (i in 0 until PlayerInventory.HOTBAR_SLOTS) { + val slot = connection.player.inventory.getHotbarSlot(i) ?: continue + if (slot != itemStack) { + continue + } + interactionManager.hotbar.selectSlot(i) + return + } + val selectedSlot = connection.player.selectedHotbarSlot + PlayerInventory.HOTBAR_OFFSET + + rateLimiter += { connection.sendPacket(ItemStackCreateC2SP(selectedSlot, itemStack)) } + connection.player.inventory[selectedSlot] = itemStack + } + + fun draw(delta: Double) { + rateLimiter.work() + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/AudioPlayer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/AudioPlayer.kt index 4ef88a049..75d2e25ba 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/AudioPlayer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/AudioPlayer.kt @@ -75,7 +75,7 @@ class AudioPlayer( private fun preloadSounds() { - Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Preloading sounds..." } + Log.log(LogMessageType.AUDIO_LOADING, LogLevels.VERBOSE) { "Preloading sounds..." } if (SoundConstants.DISABLE_PRELOADING) { return } @@ -233,7 +233,7 @@ class AudioPlayer( } private fun loadSounds() { - Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Loading sounds.json" } + Log.log(LogMessageType.AUDIO_LOADING, LogLevels.VERBOSE) { "Loading sounds.json" } val data = connection.assetsManager.readJsonAsset(SOUNDS_INDEX_FILE) for ((soundEventResourceLocation, json) in data) { diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PlayerAbilitiesS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PlayerAbilitiesS2CP.kt index afce98bbb..9ae637e7d 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PlayerAbilitiesS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PlayerAbilitiesS2CP.kt @@ -55,7 +55,7 @@ class PlayerAbilitiesS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() { abilities.isInvulnerable = isInvulnerable abilities.isFlying = isFlying abilities.canFly = canFly - abilities.canInstantBreak = canInstantBuild + abilities.creative = canInstantBuild abilities.flyingSpeed = flyingSpeed abilities.walkingSpeed = walkingSpeed diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/OutByteBuffer.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/OutByteBuffer.kt index c844fb268..3c3d92274 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/OutByteBuffer.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/OutByteBuffer.kt @@ -154,18 +154,18 @@ open class OutByteBuffer(open val connection: Connection? = null) { writeByte(type.ordinal) } - fun writeNBT(nbt: Any?, compressed: Boolean) { + fun writeNBT(nbt: Any?, compressed: Boolean = false) { if (compressed) { TODO("Can not write compressed NBT yet!") } - if (nbt is Collection<*> && nbt.isEmpty()) { - return writeNBTTag(null) - } if (nbt is Map<*, *>) { if (nbt.isEmpty()) { return writeNBTTag(null) } + writeNBTTagType(NBTTagTypes.COMPOUND) writeShort(0) // Length of compound tag name + writeNBTTag(nbt, false) + return } writeNBTTag(nbt) } @@ -203,13 +203,12 @@ open class OutByteBuffer(open val connection: Connection? = null) { writeUnprefixedByteArray(bytes) } is Collection<*> -> { - val collectionType: NBTTagTypes = if (tag.isEmpty()) { + this.writeNBTTagType(if (tag.isEmpty()) { NBTTagTypes.END } else { tag.iterator().next().type - } - this.writeNBTTagType(collectionType) - // write Type + }) + writeInt(tag.size) for (element in tag) { @@ -217,22 +216,16 @@ open class OutByteBuffer(open val connection: Connection? = null) { } } is Map<*, *> -> { - val size = tag.size - var index = 0 for ((key, value) in tag) { val valueType = value.type if (valueType == NBTTagTypes.END) { - break + error("NBT does not support null as value in a compound tag!") } this.writeNBTTagType(valueType) writeNBTTag(key?.toString() ?: "", false) - writeNBTTag(value, true) - index++ + writeNBTTag(value, false) } - if (index - 1 != size) { - error("NBT does not support null as value in a compound tag!") - } - writeNBTTagType(NBTTagTypes.END) + this.writeNBTTagType(NBTTagTypes.END) } is IntArray -> { writeInt(tag.size)