From 275d21a4e2d1fef752febcc49674ec9ef7b296b2 Mon Sep 17 00:00:00 2001 From: Boy Date: Sun, 31 Mar 2024 19:50:45 +0200 Subject: [PATCH] feat: rework FurnitureOutline system --- .../com/mineinabyss/blocky/BlockyConfig.kt | 38 +++++++- .../blocky/components/core/BlockyFurniture.kt | 25 ++++-- .../blocky/helpers/FurnitureHelpers.kt | 2 +- .../blocky/helpers/FurnitureOutlineHelpers.kt | 5 ++ .../blocky/helpers/FurniturePacketHelpers.kt | 90 +++++++++++-------- .../blocky/systems/FurnitureOutlineSystem.kt | 78 ++++++++++++---- 6 files changed, 174 insertions(+), 64 deletions(-) create mode 100644 src/main/kotlin/com/mineinabyss/blocky/helpers/FurnitureOutlineHelpers.kt diff --git a/src/main/kotlin/com/mineinabyss/blocky/BlockyConfig.kt b/src/main/kotlin/com/mineinabyss/blocky/BlockyConfig.kt index 0e0c8f4a..337e5e42 100644 --- a/src/main/kotlin/com/mineinabyss/blocky/BlockyConfig.kt +++ b/src/main/kotlin/com/mineinabyss/blocky/BlockyConfig.kt @@ -1,12 +1,21 @@ package com.mineinabyss.blocky import com.charleskorn.kaml.YamlComment +import com.mineinabyss.blocky.helpers.FurnitureOutlineType import com.mineinabyss.idofront.items.editItemMeta import com.mineinabyss.idofront.serialization.SerializableItemStack import com.mineinabyss.idofront.serialization.toSerializable import com.mineinabyss.idofront.textcomponents.miniMsg +import com.ticxo.modelengine.api.entity.Hitbox import kotlinx.serialization.Serializable +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.network.syncher.EntityDataSerializers +import net.minecraft.network.syncher.SynchedEntityData +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.Blocks import org.bukkit.Material +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack +import org.bukkit.entity.EntityType import org.bukkit.inventory.ItemStack @Serializable @@ -38,7 +47,34 @@ data class BlockyConfig( @Serializable data class BlockyCaveVineConfig(val isEnabled: Boolean = false) @Serializable data class BlockySlabConfig(val isEnabled: Boolean = false) @Serializable data class BlockyStairConfig(val isEnabled: Boolean = false) - @Serializable data class BlockyFurnitureConfig(val showHitboxOutline: Boolean = false, val worldEdit: Boolean = false) + @Serializable data class BlockyFurnitureConfig( + val hitboxOutlines: HitboxOutline = HitboxOutline(), + val worldEdit: Boolean = false + ) { + fun showOutlines() = hitboxOutlines.type != FurnitureOutlineType.NONE + @Serializable + data class HitboxOutline( + val type: FurnitureOutlineType = FurnitureOutlineType.ITEM, + val item: SerializableItemStack = ItemStack(Material.GLASS).toSerializable() + ) { + fun entityType(): net.minecraft.world.entity.EntityType<*>? { + return when (type) { + FurnitureOutlineType.ITEM -> net.minecraft.world.entity.EntityType.ITEM_DISPLAY + FurnitureOutlineType.BLOCK -> net.minecraft.world.entity.EntityType.BLOCK_DISPLAY + else -> null + } + } + fun outlineContent(): SynchedEntityData.DataValue<*>? { + return when (type) { + FurnitureOutlineType.ITEM -> + SynchedEntityData.DataValue(23, EntityDataSerializers.ITEM_STACK, CraftItemStack.asNMSCopy(item.toItemStack())) + FurnitureOutlineType.BLOCK -> + SynchedEntityData.DataValue(23, EntityDataSerializers.BLOCK_STATE, Block.byItem(CraftItemStack.asNMSCopy(item.toItemStack()).item).defaultBlockState()) + else -> null + } + } + } + } @Serializable data class DefaultBlockyMenu( val title: String = "", val height: Int = 5, diff --git a/src/main/kotlin/com/mineinabyss/blocky/components/core/BlockyFurniture.kt b/src/main/kotlin/com/mineinabyss/blocky/components/core/BlockyFurniture.kt index 85699547..00887e37 100644 --- a/src/main/kotlin/com/mineinabyss/blocky/components/core/BlockyFurniture.kt +++ b/src/main/kotlin/com/mineinabyss/blocky/components/core/BlockyFurniture.kt @@ -1,19 +1,19 @@ package com.mineinabyss.blocky.components.core +import com.mineinabyss.blocky.helpers.GenericHelpers.toBlockCenterLocation import com.mineinabyss.blocky.serializers.BrightnessSerializer -import com.mineinabyss.idofront.serialization.ColorSerializer -import com.mineinabyss.idofront.serialization.SerializableItemStack -import com.mineinabyss.idofront.serialization.Vector3fSerializer -import com.mineinabyss.idofront.serialization.toSerializable +import com.mineinabyss.idofront.serialization.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import org.bukkit.Location import org.bukkit.Material import org.bukkit.entity.Display.Billboard import org.bukkit.entity.Display.Brightness +import org.bukkit.entity.ItemDisplay import org.bukkit.entity.ItemDisplay.ItemDisplayTransform import org.bukkit.inventory.ItemStack import org.bukkit.util.BoundingBox +import org.bukkit.util.Vector import org.joml.Vector3f import kotlin.math.cos import kotlin.math.round @@ -30,11 +30,26 @@ data class BlockyFurniture( @Serializable @SerialName("blocky:interaction_hitbox") data class InteractionHitbox( - val originOffset: BlockLocation = BlockLocation(), + val offset: @Serializable(VectorSerializer::class) Vector = Vector(), val width: Float, val height: Float, val outline: SerializableItemStack = ItemStack(Material.GLASS).toSerializable()) { fun toBoundingBox(location: Location) = BoundingBox.of(location, width.times(0.7), height.times(0.7), width.times(0.7)) + fun location(furniture: ItemDisplay): Location { + return furniture.location.toBlockCenterLocation().add(offset(furniture.yaw)) + } + + fun offset(furnitureYaw: Float): Vector { + val angleRad = Math.toRadians(furnitureYaw.toDouble()) + + + // Get the coordinates relative to the local y-axis + val x = cos(angleRad) * offset.x + sin(angleRad) * offset.z + val y = offset.y + val z = sin(angleRad) * offset.x + cos(angleRad) * offset.z + + return Vector(x, y, z) + } } @JvmInline diff --git a/src/main/kotlin/com/mineinabyss/blocky/helpers/FurnitureHelpers.kt b/src/main/kotlin/com/mineinabyss/blocky/helpers/FurnitureHelpers.kt index 229908e4..c5e0314a 100644 --- a/src/main/kotlin/com/mineinabyss/blocky/helpers/FurnitureHelpers.kt +++ b/src/main/kotlin/com/mineinabyss/blocky/helpers/FurnitureHelpers.kt @@ -51,7 +51,7 @@ object FurnitureHelpers { center: Location, hitbox: Set ): List = - hitbox.map { c -> c.originOffset.groundRotate(rotation).add(center) } + hitbox.map { i -> center.clone().add(i.offset(rotation)) } fun rotation(yaw: Float, nullFurniture: BlockyFurniture?): Rotation { val furniture = nullFurniture ?: BlockyFurniture() diff --git a/src/main/kotlin/com/mineinabyss/blocky/helpers/FurnitureOutlineHelpers.kt b/src/main/kotlin/com/mineinabyss/blocky/helpers/FurnitureOutlineHelpers.kt new file mode 100644 index 00000000..cba6678e --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/blocky/helpers/FurnitureOutlineHelpers.kt @@ -0,0 +1,5 @@ +package com.mineinabyss.blocky.helpers + +enum class FurnitureOutlineType { + ITEM, BLOCK, NONE; +} \ No newline at end of file diff --git a/src/main/kotlin/com/mineinabyss/blocky/helpers/FurniturePacketHelpers.kt b/src/main/kotlin/com/mineinabyss/blocky/helpers/FurniturePacketHelpers.kt index b6efa317..2edf39ef 100644 --- a/src/main/kotlin/com/mineinabyss/blocky/helpers/FurniturePacketHelpers.kt +++ b/src/main/kotlin/com/mineinabyss/blocky/helpers/FurniturePacketHelpers.kt @@ -6,11 +6,11 @@ import com.comphenix.protocol.events.PacketContainer import com.comphenix.protocol.wrappers.BlockPosition import com.mineinabyss.blocky.api.BlockyFurnitures.isBlockyFurniture import com.mineinabyss.blocky.api.BlockyFurnitures.isModelEngineFurniture +import com.mineinabyss.blocky.blocky import com.mineinabyss.blocky.components.core.BlockyFurniture import com.mineinabyss.blocky.components.features.BlockyLight import com.mineinabyss.blocky.components.features.furniture.BlockyModelEngine import com.mineinabyss.blocky.helpers.FurnitureHelpers.collisionHitboxPositions -import com.mineinabyss.blocky.helpers.GenericHelpers.toBlockCenterLocation import com.mineinabyss.blocky.helpers.GenericHelpers.toEntity import com.mineinabyss.geary.papermc.tracking.entities.toGeary import com.mineinabyss.protocolburrito.dsl.sendTo @@ -36,10 +36,10 @@ import java.util.* * Typealias to make it clear that this is a UUID for a furniture entity. */ typealias FurnitureUUID = UUID -data class FurnitureInteractionHitboxIds(val furnitureUUID: FurnitureUUID, val entityIds: IntList) { +data class FurnitureSubEntity(val furnitureUUID: FurnitureUUID, val entityIds: IntList) { val furniture get() = furnitureUUID.toEntity() as? ItemDisplay } -data class FurnitureInteractionHitboxPacket(val entityId: Int, val addEntity: ClientboundAddEntityPacket, val metadata: ClientboundSetEntityDataPacket) +data class FurnitureSubEntityPacket(val entityId: Int, val addEntity: ClientboundAddEntityPacket, val metadata: ClientboundSetEntityDataPacket) object FurniturePacketHelpers { const val INTERACTION_WIDTH_ID = 8 @@ -47,13 +47,14 @@ object FurniturePacketHelpers { const val ITEM_DISPLAY_ITEMSTACK_ID = 23 private val collisionHitboxPosMap = mutableMapOf>() - private val interactionHitboxIdMap = mutableSetOf() - private val interactionHitboxPacketMap = mutableMapOf>() - private val hitboxOutlineIdMap = mutableMapOf() + private val interactionHitboxIds = mutableSetOf() + private val interactionHitboxPacketMap = mutableMapOf>() + private val outlineIds = mutableSetOf() + private val outlinePacketMap = mutableMapOf>() private val outlinePlayerMap = mutableMapOf() fun baseFurnitureFromInteractionHitbox(id: Int) = - interactionHitboxIdMap.firstOrNull { id in it.entityIds }?.furniture + interactionHitboxIds.firstOrNull { id in it.entityIds }?.furniture fun baseFurnitureFromCollisionHitbox(pos: BlockPosition) = collisionHitboxPosMap.entries.firstOrNull { pos in it.value }?.key?.toEntity() as? ItemDisplay @@ -72,14 +73,13 @@ object FurniturePacketHelpers { } val interactionHitboxes = furniture.toGeary().get()?.interactionHitbox ?: return - val baseLoc = furniture.location.toBlockCenterLocation() interactionHitboxPacketMap.computeIfAbsent(furniture.uniqueId) { - val entityIds = interactionHitboxIdMap.firstOrNull { it.furnitureUUID == furniture.uniqueId }?.entityIds ?: List(interactionHitboxes.size) { Entity.nextEntityId() }.apply { - interactionHitboxIdMap.add(FurnitureInteractionHitboxIds(furniture.uniqueId, IntList.of(*toIntArray()))) + val entityIds = interactionHitboxIds.firstOrNull { it.furnitureUUID == furniture.uniqueId }?.entityIds ?: List(interactionHitboxes.size) { Entity.nextEntityId() }.apply { + interactionHitboxIds += FurnitureSubEntity(furniture.uniqueId, IntList.of(*toIntArray())) } - mutableSetOf().apply { + mutableSetOf().apply { interactionHitboxes.zip(entityIds).forEach { (hitbox, entityId) -> - val loc = hitbox.originOffset.groundRotate(furniture.yaw).add(baseLoc) + val loc = hitbox.location(furniture) val addEntityPacket = ClientboundAddEntityPacket( entityId, UUID.randomUUID(), loc.x, loc.y, loc.z, loc.pitch, loc.yaw, @@ -93,7 +93,7 @@ object FurniturePacketHelpers { ) ) - add(FurnitureInteractionHitboxPacket(entityId, addEntityPacket, metadataPacket)) + add(FurnitureSubEntityPacket(entityId, addEntityPacket, metadataPacket)) } } }.forEach { @@ -110,7 +110,7 @@ object FurniturePacketHelpers { furniture.world.players.forEach { player -> removeInteractionHitboxPacket(furniture, player) } - interactionHitboxIdMap.removeIf { it.furnitureUUID == furniture.uniqueId } + interactionHitboxIds.removeIf { it.furnitureUUID == furniture.uniqueId } interactionHitboxPacketMap.remove(furniture.uniqueId) } @@ -119,7 +119,7 @@ object FurniturePacketHelpers { * @param furniture The furniture to remove the interaction hitbox of. */ fun removeInteractionHitboxPacket(furniture: ItemDisplay, player: Player) { - val entityIds = interactionHitboxIdMap.firstOrNull { it.furnitureUUID == furniture.uniqueId }?.entityIds ?: return + val entityIds = interactionHitboxIds.firstOrNull { it.furnitureUUID == furniture.uniqueId }?.entityIds ?: return PacketContainer.fromPacket(ClientboundRemoveEntitiesPacket(*entityIds.toIntArray())).sendTo(player) } @@ -129,25 +129,39 @@ object FurniturePacketHelpers { outlinePlayerMap[player.uniqueId] = furniture.uniqueId val interactionHitboxes = furniture.toGeary().get()?.interactionHitbox ?: return - val entityIds = hitboxOutlineIdMap.computeIfAbsent(furniture.uniqueId) { IntList.of(*IntArray(interactionHitboxes.size) { Entity.nextEntityId() }) } - val baseLoc = furniture.location.toBlockCenterLocation() - - interactionHitboxes.zip(entityIds).forEach { (hitbox, entityId) -> - val loc = hitbox.originOffset.groundRotate(furniture.yaw).add(baseLoc).apply { y += hitbox.height / 2 } - val displayEntityPacket = ClientboundAddEntityPacket( - entityId, UUID.randomUUID(), - loc.x, loc.y, loc.z, loc.pitch, loc.yaw, - EntityType.ITEM_DISPLAY, 0, Vec3.ZERO, 0.0 - ) - PacketContainer.fromPacket(displayEntityPacket).sendTo(player) - val metadataPacket = ClientboundSetEntityDataPacket( - entityId, listOf( - SynchedEntityData.DataValue(12, EntityDataSerializers.VECTOR3, Vector3f(hitbox.width, hitbox.height, hitbox.width)), - SynchedEntityData.DataValue(23, EntityDataSerializers.ITEM_STACK, CraftItemStack.asNMSCopy(hitbox.outline.toItemStack())), - SynchedEntityData.DataValue(24, EntityDataSerializers.INT, furniture.itemDisplayTransform.ordinal) - ) - ) - PacketContainer.fromPacket(metadataPacket).sendTo(player) + val outlineType = blocky.config.furniture.hitboxOutlines.entityType() ?: return + val outlineContent = blocky.config.furniture.hitboxOutlines.outlineContent() ?: return + val entityIds = outlineIds.firstOrNull { it.furnitureUUID == furniture.uniqueId }?.entityIds ?: List(interactionHitboxes.size) { Entity.nextEntityId() }.apply { + outlineIds += FurnitureSubEntity(furniture.uniqueId, IntList.of(*toIntArray())) + } + + outlinePacketMap.computeIfAbsent(furniture.uniqueId) { + mutableSetOf().apply { + interactionHitboxes.zip(entityIds).forEach { (hitbox, entityId) -> + val loc = hitbox.location(furniture).add(0.0,hitbox.height / 2.0, 0.0).apply { + if (blocky.config.furniture.hitboxOutlines.type == FurnitureOutlineType.BLOCK) + toBlockLocation() + } + val addEntityPacket = ClientboundAddEntityPacket( + entityId, UUID.randomUUID(), + loc.x, loc.y, loc.z, 0.0f, 0.0f, + outlineType, 0, Vec3.ZERO, 0.0 + ) + + val metadataPacket = ClientboundSetEntityDataPacket( + entityId, listOf( + outlineContent, + SynchedEntityData.DataValue(12, EntityDataSerializers.VECTOR3, Vector3f(hitbox.width, hitbox.height, hitbox.width)), + SynchedEntityData.DataValue(24, EntityDataSerializers.INT, furniture.itemDisplayTransform.ordinal) + ) + ) + + add(FurnitureSubEntityPacket(entityId, addEntityPacket, metadataPacket)) + } + } + }.forEach { + PacketContainer.fromPacket(it.addEntity).sendTo(player) + PacketContainer.fromPacket(it.metadata).sendTo(player) } } @@ -158,15 +172,15 @@ object FurniturePacketHelpers { } fun removeHitboxOutlinePacket(furniture: ItemDisplay, player: Player) { - val displayEntityPacket = ClientboundRemoveEntitiesPacket(hitboxOutlineIdMap[furniture.uniqueId] ?: return) + val displayEntityPacket = ClientboundRemoveEntitiesPacket(outlineIds.firstOrNull { it.furnitureUUID == furniture.uniqueId }?.entityIds ?: return) PacketContainer.fromPacket(displayEntityPacket).sendTo(player) - hitboxOutlineIdMap.remove(furniture.uniqueId) + outlineIds.removeIf { it.furnitureUUID == furniture.uniqueId } outlinePlayerMap.remove(player.uniqueId) } fun removeHitboxOutlinePacket(player: Player) { - val furniture = outlinePlayerMap[player.uniqueId]?.toEntity() ?: return - val displayEntityPacket = ClientboundRemoveEntitiesPacket(hitboxOutlineIdMap[furniture.uniqueId] ?: return) + val entityIds = outlineIds.firstOrNull { it.furnitureUUID == (outlinePlayerMap[player.uniqueId] ?: return) }?.entityIds ?: return + val displayEntityPacket = ClientboundRemoveEntitiesPacket(entityIds) PacketContainer.fromPacket(displayEntityPacket).sendTo(player) outlinePlayerMap.remove(player.uniqueId) } diff --git a/src/main/kotlin/com/mineinabyss/blocky/systems/FurnitureOutlineSystem.kt b/src/main/kotlin/com/mineinabyss/blocky/systems/FurnitureOutlineSystem.kt index a3e9377a..b01a4be8 100644 --- a/src/main/kotlin/com/mineinabyss/blocky/systems/FurnitureOutlineSystem.kt +++ b/src/main/kotlin/com/mineinabyss/blocky/systems/FurnitureOutlineSystem.kt @@ -7,9 +7,17 @@ import com.mineinabyss.geary.modules.GearyModule import com.mineinabyss.geary.papermc.tracking.entities.toGearyOrNull import com.mineinabyss.geary.systems.builders.system import com.mineinabyss.geary.systems.query.ListenerQuery +import com.mineinabyss.idofront.nms.aliases.toNMS import com.mineinabyss.idofront.time.ticks +import net.minecraft.world.entity.EntitySelector +import net.minecraft.world.phys.AABB +import net.minecraft.world.phys.Vec3 +import org.bukkit.Location +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer +import org.bukkit.entity.Entity import org.bukkit.entity.ItemDisplay import org.bukkit.entity.Player +import java.util.* fun GearyModule.createFurnitureOutlineSystem() = system( @@ -17,24 +25,56 @@ fun GearyModule.createFurnitureOutlineSystem() = system( val player by get() } ).every(1.ticks).exec { - if (!blocky.config.furniture.showHitboxOutline || !player.isConnected) return@exec - - val location = player.eyeLocation - val direction = location.direction.clone().multiply(0.1) - val result = player.rayTraceBlocks(5.0) - val distanceEyeToRaycastBlock = result?.hitBlock?.let { location.distance(it.location) } ?: (5.0 * 5.0) - - while (location.toBlockLocation().distanceSquared(player.eyeLocation) < distanceEyeToRaycastBlock) { - location.getNearbyEntities(5.0, 5.0, 5.0).filterIsInstance().firstOrNull { - it.toGearyOrNull()?.get()?.interactionHitbox?.any { i -> - it.boundingBox.overlaps(i.toBoundingBox(location)) - } == true - }?.let { - FurniturePacketHelpers.sendHitboxOutlinePacket(it, player) - return@exec - } - location.add(direction) - } - FurniturePacketHelpers.removeHitboxOutlinePacket(player) + if (!blocky.config.furniture.showOutlines() || !player.isConnected) return@exec + findTargetFurnitureHitbox(player, 5.0)?.let { + FurniturePacketHelpers.sendHitboxOutlinePacket(it, player) + } ?: FurniturePacketHelpers.removeHitboxOutlinePacket(player) + +} + +private fun findTargetFurnitureHitbox(player: Player, maxDistance: Double): ItemDisplay? { + if (maxDistance < 1 || maxDistance > 120) return null + val craftPlayer = player as CraftPlayer + val nmsPlayer: net.minecraft.world.entity.player.Player = craftPlayer.handle + val start = nmsPlayer.getEyePosition(1.0f) + val direction = nmsPlayer.lookAngle + val distanceDirection = Vec3(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance) + val end = start.add(distanceDirection) + val entities = nmsPlayer.level().getEntities( + nmsPlayer, + nmsPlayer.boundingBox.expandTowards(distanceDirection).inflate(1.0, 1.0, 1.0), + EntitySelector.NO_SPECTATORS + ) + var distance = 0.0 + val entityIterator: Iterator = entities.iterator() + var baseEntity: ItemDisplay? = null + while (true) { + var entity: net.minecraft.world.entity.Entity + var rayTrace: Vec3 + var distanceTo: Double + do { + var rayTraceResult: Optional = Optional.empty() + do { + if (!entityIterator.hasNext()) return baseEntity + + entity = entityIterator.next() + val bukkitEntity = entity.bukkitEntity as? ItemDisplay ?: continue + // If entity is furniture, check all interactionHitboxes if their "bounding box" is colliding + bukkitEntity.toGearyOrNull()?.get()?.interactionHitbox?.firstOrNull { hitbox -> + val hitboxLoc = hitbox.location(bukkitEntity).add(0.0,hitbox.height / 2.0, 0.0) + val hitboxVec = Vec3(hitboxLoc.x(), hitboxLoc.y(), hitboxLoc.z()) + val hitboxAABB = AABB.ofSize(hitboxVec, hitbox.width.toDouble(), hitbox.height.toDouble(), hitbox.width.toDouble()) + rayTraceResult = hitboxAABB.clip(start, end) + rayTraceResult.isPresent + } + } while (rayTraceResult.isEmpty) + + rayTrace = rayTraceResult.get() + distanceTo = start.distanceToSqr(rayTrace) + } while (!(distanceTo < distance) && distance != 0.0) + + baseEntity = entity.bukkitEntity as? ItemDisplay + distance = distanceTo + } }