Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Item Entities and Dropping Items #66

Draft
wants to merge 25 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d09da10
Add ItemEntity and Dropping Items
tomlister Nov 21, 2021
373657e
Merge branch 'master' into feature/item-entity
tomlister Dec 6, 2021
803f036
Rename bound to offset
tomlister Dec 6, 2021
a1f29c5
Item Pickup + forEachEntityInBounds
tomlister Dec 6, 2021
1afca68
Change decimal
tomlister Dec 6, 2021
291d6a2
Add Collect Item Packet
tomlister Dec 6, 2021
59a9a49
Fix imports
tomlister Dec 6, 2021
278787a
Fix again...
tomlister Dec 6, 2021
5f58cf3
Merge remote-tracking branch 'upstream/master' into feature/item-entity
tomlister Dec 6, 2021
7df938c
NBT WTF?
tomlister Dec 6, 2021
5af34f8
Fix random entry
tomlister Dec 6, 2021
3b2fdb7
Merge remote-tracking branch 'upstream/master' into feature/item-entity
tomlister Dec 9, 2021
2b60160
Move drop method to player and add comments
tomlister Dec 9, 2021
2fd8499
Add ItemDropEvent
tomlister Dec 9, 2021
23ffb07
Drop Item on Block Break
tomlister Dec 9, 2021
db233f5
Send velocity on change
tomlister Dec 10, 2021
6b768ab
Change velocity after spawn
tomlister Dec 10, 2021
c110ed0
Fix drop direction
tomlister Dec 10, 2021
2d48c1e
Fix pickups in some cases
tomlister Dec 11, 2021
7b135be
Merge remote-tracking branch 'upstream/master' into feature/item-entity
tomlister Dec 11, 2021
97c3e2e
Merge remote-tracking branch 'upstream/master' into feature/item-entity
tomlister Dec 11, 2021
eaeea22
Merge remote-tracking branch 'upstream/master' into feature/item-entity
tomlister Dec 12, 2021
0f6d0b9
Merge remote-tracking branch 'upstream/master' into feature/item-entity
tomlister Dec 13, 2021
46ac3f6
Fix Branch
tomlister Dec 14, 2021
6e96dc4
Merge remote-tracking branch 'upstream/master' into feature/item-entity
tomlister Dec 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* This file is part of the Krypton API, licensed under the MIT license.
*
* Copyright (C) 2021 KryptonMC and the contributors to the Krypton project.
*
* This project is licensed under the terms of the MIT license.
* For more details, please reference the LICENSE file in the api top-level directory.
*/
package org.kryptonmc.api.event.player

import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.api.event.ComponentResult
import org.kryptonmc.api.event.ResultedEvent
import org.kryptonmc.api.item.ItemStack

/**
* Called when a player attempts to drop an item.
*
* @param player the player dropping the item
* @param item the item to be dropped
*/

public data class ItemDropEvent(
@get:JvmName("player") public val player: Player,
@get:JvmName("item") public val item: ItemStack
) : ResultedEvent<ComponentResult> {

override var result: ComponentResult = ComponentResult.allowed()
}

Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import org.kryptonmc.krypton.entity.projectile.KryptonSpectralArrow
import org.kryptonmc.krypton.entity.projectile.KryptonThrownPotion
import org.kryptonmc.krypton.entity.projectile.KryptonTrident
import org.kryptonmc.krypton.entity.projectile.KryptonWitherSkull
import org.kryptonmc.krypton.entity.item.KryptonItemEntity
import org.kryptonmc.krypton.registry.InternalRegistries
import org.kryptonmc.krypton.util.logger
import org.kryptonmc.krypton.world.KryptonWorld
Expand Down Expand Up @@ -122,7 +123,8 @@ object EntityFactory {
EntityTypes.WITHER_SKULL to ::KryptonWitherSkull,
EntityTypes.WOLF to ::KryptonWolf,
EntityTypes.ZOMBIE to ::KryptonZombie,
EntityTypes.FISHING_HOOK to ::KryptonFishingHook
EntityTypes.FISHING_HOOK to ::KryptonFishingHook,
EntityTypes.ITEM to ::KryptonItemEntity
)

@Suppress("UNCHECKED_CAST")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ abstract class KryptonEntity(
final override var location: Vector3d = Vector3d.ZERO
final override var rotation: Vector2f = Vector2f.ZERO
final override var velocity: Vector3d = Vector3d.ZERO
set(value) {
field = value
viewers.forEach { it.session.send(PacketOutEntityVelocity(this)) }
}
final override var boundingBox = BoundingBox.zero()
final override var dimensions = EntityDimensions.fixed(type.dimensions.width, type.dimensions.height)
final override var isOnGround = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.kryptonmc.krypton.entity.item

import net.kyori.adventure.text.Component
import org.kryptonmc.api.entity.EntityTypes
import org.kryptonmc.api.inventory.InventoryHolder
import org.kryptonmc.api.util.BoundingBox
import org.kryptonmc.krypton.entity.KryptonEntity
import org.kryptonmc.krypton.entity.KryptonLivingEntity
import org.kryptonmc.krypton.entity.metadata.MetadataKeys
import org.kryptonmc.krypton.item.KryptonItemStack
import org.kryptonmc.krypton.packet.out.play.PacketOutCollectItem
import org.kryptonmc.krypton.util.forEachEntityInBounds
import org.kryptonmc.krypton.world.KryptonWorld
import org.kryptonmc.nbt.CompoundTag
import org.kryptonmc.nbt.util.UUID
import org.spongepowered.math.vector.Vector3d

class KryptonItemEntity(world: KryptonWorld) : KryptonEntity(world, EntityTypes.ITEM) {

init {
data.add(MetadataKeys.ITEM.ITEM)
}

var age: Short = 0
var health: Short = 5
var pickupDelay: Short = 10
var owner: UUID? = null
var thrower: UUID? = null

var item: KryptonItemStack
get() = data[MetadataKeys.ITEM.ITEM]
set(value) = data.set(MetadataKeys.ITEM.ITEM, value)
override fun load(tag: CompoundTag) {
super.load(tag)
age = tag.getShort("Age")
health = tag.getShort("Health")
pickupDelay = tag.getShort("PickupDelay")
if (tag.hasUUID("Owner")) owner = tag.getUUID("Owner")
if (tag.hasUUID("Thrower")) thrower = tag.getUUID("Thrower")
val itemNBT = tag.getCompound("Item")
item = KryptonItemStack(itemNBT)
if (item.isEmpty()) remove()
}

private fun entityPickupItem(entity: KryptonLivingEntity) {
// TODO: Reduce stack to fit a full inventory and leave remainder
if (entity is InventoryHolder) {
entity.inventory.add(item)
}
// TODO: Perhaps consider only sending this to players in range?
world.playerManager.sendToAll(PacketOutCollectItem(id, entity.id, item.amount))
item.amount = 0 // destroy next tick
}

override fun tick() {
if (item.isEmpty()) {
remove()
return
}
super.tick()
if (pickupDelay > 0 && pickupDelay != DELAY_NO_PICKUP) pickupDelay--
if (pickupDelay.toInt() == 0) {
server.sendMessage(Component.text("delay 0"))
// Item pickup zone
val minimum = Vector3d.from(location.x() - 1.0, location.y() - 0.5, location.z() - 1.0)
val maximum = Vector3d.from(location.x() + 1.0, location.y() + 0.5, location.z() + 1.0)
world.entityManager.forEachEntityInBounds(BoundingBox.of(minimum, maximum)) {
if (it.isAlive && it is KryptonLivingEntity) {
// TODO: Test for mob pick up and handle item merge
server.sendMessage(Component.text("Ent found"))
server.sendMessage(it.name)
entityPickupItem(it)
return@forEachEntityInBounds
}
}
}
if (age != AGE_NO_DESPAWN) age++
if (age >= AGE_DESPAWN) remove()
}

companion object {
const val DELAY_NO_PICKUP: Short = 32767 // The delay value that prevents item pickup
const val AGE_NO_DESPAWN: Short = -32768 // The age value that prevents age from incrementing
const val AGE_DESPAWN: Short = 6000 // The age value that despawns the item entity
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ object MetadataKeys {
@JvmField val AXOLOTL = AxolotlKeys
@JvmField val BEE = BeeKeys
@JvmField val CAT = CatKeys
@JvmField val ITEM = ItemKeys
@JvmField val FOX = FoxKeys
@JvmField val GOAT = GoatKeys
@JvmField val MOOSHROOM = MooshroomKeys
Expand Down Expand Up @@ -227,6 +228,11 @@ object MetadataKeys {
@JvmField val COLLAR_COLOR = create(22, MetadataSerializers.VAR_INT, Registries.DYE_COLORS.idOf(DyeColors.RED))
}

object ItemKeys {

@JvmField val ITEM = create(8, MetadataSerializers.SLOT, KryptonItemStack.Factory.empty())
}

object FoxKeys {

@JvmField val TYPE = create(17, MetadataSerializers.VAR_INT, FoxType.RED.ordinal)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ import org.kryptonmc.api.entity.attribute.AttributeTypes
import org.kryptonmc.api.entity.player.ChatVisibility
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.api.event.player.ChangeGameModeEvent
import org.kryptonmc.api.event.player.ItemDropEvent
import org.kryptonmc.api.event.player.PerformActionEvent
import org.kryptonmc.api.item.ItemStack
import org.kryptonmc.api.item.ItemTypes
import org.kryptonmc.api.permission.PermissionFunction
import org.kryptonmc.api.permission.PermissionProvider
Expand All @@ -69,6 +71,7 @@ import org.kryptonmc.krypton.entity.EquipmentSlot
import org.kryptonmc.krypton.entity.KryptonEntity
import org.kryptonmc.krypton.entity.KryptonEquipable
import org.kryptonmc.krypton.entity.KryptonLivingEntity
import org.kryptonmc.krypton.entity.item.KryptonItemEntity
import org.kryptonmc.krypton.entity.metadata.MetadataKeys
import org.kryptonmc.krypton.inventory.KryptonPlayerInventory
import org.kryptonmc.krypton.item.KryptonItemStack
Expand Down Expand Up @@ -104,6 +107,7 @@ import org.kryptonmc.krypton.packet.out.play.PacketOutUnloadChunk
import org.kryptonmc.krypton.packet.out.play.PacketOutUpdateHealth
import org.kryptonmc.krypton.packet.out.play.PacketOutUpdateViewPosition
import org.kryptonmc.krypton.service.KryptonVanishService
import org.kryptonmc.krypton.util.*
import org.kryptonmc.krypton.statistic.KryptonStatisticsTracker
import org.kryptonmc.krypton.util.BossBarManager
import org.kryptonmc.krypton.util.Directions
Expand All @@ -122,6 +126,7 @@ import java.net.InetSocketAddress
import java.time.Instant
import java.util.Locale
import java.util.UUID
import java.util.concurrent.ThreadLocalRandom
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
Expand Down Expand Up @@ -944,6 +949,52 @@ class KryptonPlayer(
}
}

fun dropHeldItem() {
val heldItem = inventory.heldItem(Hand.MAIN)
if (heldItem.isEmpty()) return

// Update held item amount
heldItem.amount--
inventory.setHeldItem(Hand.MAIN, heldItem)

// drop the item
dropItem(heldItem)
}

fun dropItem(itemStack: ItemStack) {
server.eventManager.fire(ItemDropEvent(this, itemStack)).thenAcceptAsync({
if (!it.result.isAllowed) return@thenAcceptAsync
val singleStack = itemStack.copy() as KryptonItemStack
singleStack.amount = 1
val itemEntity = KryptonItemEntity(world)
itemEntity.thrower = uuid
itemEntity.item = singleStack
itemEntity.location = location.add(0.0, 1.62 - 0.3, 0.0) // eye height - some magic value??
val itemVelocity = getDirectionVector().mul(0.3)
// Glowstone's method. Seems sane however it uses guess work. I don't blame them. Just look at the vanilla source...
// https://github.com/GlowstoneMC/Glowstone/blob/dev/src/main/java/net/glowstone/entity/GlowHumanEntity.java
val random = ThreadLocalRandom.current()
val offset = 0.02
itemVelocity.add(
random.nextDouble(offset) - offset / 2,
random.nextDouble(0.12),
random.nextDouble(offset) - offset / 2
)
world.entityManager.spawn(itemEntity)
itemEntity.velocity = itemVelocity
}, session.channel.eventLoop())
return
}

private fun getDirectionVector(): Vector3d {
// Similar to wiki.vg/bukkit impl
val y = kotlin.math.sin(Math.toRadians(rotation.y().toDouble()))
val xz = kotlin.math.cos(Math.toRadians(rotation.y().toDouble()))
val x = -xz * kotlin.math.sin(Math.toRadians(rotation.x().toDouble()))
val z = xz * kotlin.math.cos(Math.toRadians(rotation.x().toDouble()))
return Vector3d.from(x, y, z)
}

companion object {

private const val FLYING_ACHIEVEMENT_MINIMUM_SPEED = 25
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package org.kryptonmc.krypton.entity.player

import org.kryptonmc.api.block.Block
import org.kryptonmc.api.item.ItemStack
import org.kryptonmc.krypton.entity.item.KryptonItemEntity
import org.kryptonmc.api.entity.Hand
import org.kryptonmc.api.event.player.PlaceBlockEvent
import org.kryptonmc.api.util.Direction
Expand All @@ -36,6 +38,7 @@ import org.kryptonmc.krypton.world.block.BlockHitResult
import org.kryptonmc.krypton.world.block.handler
import org.kryptonmc.krypton.world.block.isGameMasterBlock
import org.spongepowered.math.vector.Vector3d
import java.util.concurrent.ThreadLocalRandom
import org.spongepowered.math.vector.Vector3i

class PlayerBlockHandler(private val player: KryptonPlayer) {
Expand Down Expand Up @@ -229,13 +232,29 @@ class PlayerBlockHandler(private val player: KryptonPlayer) {
if (hasChanged) block.handler().destroy(world, x, y, z, block)

if (isCreative) return true // We're done, since the bit after this is for mining, which doesn't happen in creative
dropLoot(block, x, y, z)
val item = player.inventory.mainHand
val hasCorrectTool = player.hasCorrectTool(block)
item.type.handler().mineBlock(player, item, world, block, x, y, z)
if (hasChanged && hasCorrectTool) block.handler().onDestroy(player, block, x, y, z, item)
return true
}

private fun dropLoot(block: Block, x: Int, y: Int, z: Int) {
// TODO: Use loot tables
val itemEntity = KryptonItemEntity(world)
val blockItem = block.asItem() ?: return
itemEntity.item = ItemStack.of(blockItem) as KryptonItemStack
val entityHeight = itemEntity.dimensions.height
val random = ThreadLocalRandom.current()
itemEntity.location = Vector3d.from(
x.toDouble()+(0.5)+random.nextDouble(-0.25, 0.25),
y.toDouble()+(0.5)+random.nextDouble(-0.25, 0.25)-(entityHeight/2f),
z.toDouble()+(0.5)+random.nextDouble(-0.25, 0.25))
world.entityManager.spawn(itemEntity)
itemEntity.velocity = Vector3d.from(random.nextDouble() * 0.2 - 0.1, 0.2, random.nextDouble() * 0.2 - 0.1)
}

companion object {

private const val MAXIMUM_DISTANCE_FROM_BLOCK = 36.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.kryptonmc.krypton.network.handlers


import java.util.concurrent.ThreadLocalRandom
import com.mojang.brigadier.StringReader
import net.kyori.adventure.audience.MessageType
import net.kyori.adventure.key.InvalidKeyException
Expand All @@ -41,6 +43,8 @@ import org.kryptonmc.api.resource.ResourcePack
import org.kryptonmc.api.world.GameMode
import org.kryptonmc.krypton.KryptonServer
import org.kryptonmc.krypton.commands.KryptonPermission
import org.kryptonmc.krypton.entity.item.KryptonItemEntity
import org.kryptonmc.krypton.entity.monster.KryptonCreeper
import org.kryptonmc.krypton.entity.player.KryptonPlayer
import org.kryptonmc.krypton.inventory.KryptonPlayerInventory
import org.kryptonmc.krypton.item.handler
Expand Down Expand Up @@ -276,6 +280,9 @@ class PlayHandler(
PacketInPlayerDigging.Status.STARTED, PacketInPlayerDigging.Status.FINISHED, PacketInPlayerDigging.Status.CANCELLED -> {
player.blockHandler.handleBlockBreak(packet)
}
PacketInPlayerDigging.Status.DROP_ITEM -> {
player.dropHeldItem()
}
PacketInPlayerDigging.Status.UPDATE_STATE -> {
val handler = player.inventory[player.inventory.heldSlot].type.handler()
if (handler !is ItemTimedHandler) return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import org.kryptonmc.krypton.packet.out.play.PacketOutEntitySoundEffect
import org.kryptonmc.krypton.packet.out.play.PacketOutEntityStatus
import org.kryptonmc.krypton.packet.out.play.PacketOutEntityTeleport
import org.kryptonmc.krypton.packet.out.play.PacketOutEntityVelocity
import org.kryptonmc.krypton.packet.out.play.PacketOutCollectItem
import org.kryptonmc.krypton.packet.out.play.PacketOutHeadLook
import org.kryptonmc.krypton.packet.out.play.PacketOutInitializeWorldBorder
import org.kryptonmc.krypton.packet.out.play.PacketOutJoinGame
Expand Down Expand Up @@ -245,6 +246,7 @@ object PacketRegistry {
register<PacketOutStopSound>(0x5E)
register<PacketOutPlayerListHeaderFooter>(0x5F)
register<PacketOutNBTQueryResponse>(0x60)
register<PacketOutCollectItem>(0x61)
register<PacketOutEntityTeleport>(0x62)
register<PacketOutAttributes>(0x64)
register<PacketOutDeclareRecipes>(0x66)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* This file is part of the Krypton project, licensed under the GNU General Public License v3.0
*
* Copyright (C) 2021 KryptonMC and the contributors of the Krypton project
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.kryptonmc.krypton.packet.out.play

import io.netty.buffer.ByteBuf
import org.kryptonmc.krypton.packet.Packet
import org.kryptonmc.krypton.util.writeVarInt

@JvmRecord
data class PacketOutCollectItem(
val collectedEntityId: Int,
val collectorEntityId: Int,
val count: Int
) : Packet {

override fun write(buf: ByteBuf) {
buf.writeVarInt(collectedEntityId)
buf.writeVarInt(collectorEntityId)
buf.writeVarInt(count)
}
}
Loading