blob: 6804e3008dd84458cb04018e318cb80190378ae3 [file] [log] [blame] [raw]
package net.glowstone.entity.objects;
import com.flowpowered.network.Message;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
import net.glowstone.EventFactory;
import net.glowstone.entity.GlowEntity;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.entity.meta.MetadataIndex;
import net.glowstone.net.message.play.entity.CollectItemMessage;
import net.glowstone.net.message.play.entity.EntityMetadataMessage;
import net.glowstone.net.message.play.entity.EntityTeleportMessage;
import net.glowstone.net.message.play.entity.EntityVelocityMessage;
import net.glowstone.net.message.play.entity.SpawnObjectMessage;
import net.glowstone.util.TickUtil;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Item;
import org.bukkit.event.entity.ItemDespawnEvent;
import org.bukkit.event.entity.ItemMergeEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
/**
* Represents an item that is also an {@link GlowEntity} within the world.
*
* @author Graham Edgecombe
*/
public class GlowItem extends GlowEntity implements Item {
private static final double VERTICAL_GRAVITY_ACCEL = -0.04;
/**
* The number of ticks (equal to 5 minutes) that item entities should live for.
*/
private static final int LIFETIME = TickUtil.minutesToTicks(5);
/**
* The remaining delay until this item may be picked up.
*/
private int pickupDelay;
/**
* A player to bias this item's pickup selection towards.
*/
private GlowPlayer biasPlayer;
@Getter
@Setter
@Nullable
private UUID owner;
@Getter
@Setter
@Nullable
private UUID thrower;
/**
* Creates a new item entity.
*
* @param location The location of the entity.
* @param item The item stack the entity is carrying.
*/
public GlowItem(Location location, ItemStack item) {
super(location);
setItemStack(item);
setBoundingBox(0.25, 0.25);
setAirDrag(0.98);
setGravityAccel(new Vector(0, VERTICAL_GRAVITY_ACCEL, 0));
setApplyDragBeforeAccel(true);
pickupDelay = 20;
}
private boolean getPickedUp(GlowPlayer player) {
// todo: fire PlayerPickupItemEvent in a way that allows for 'remaining' calculations
HashMap<Integer, ItemStack> map = player.getInventory().addItem(getItemStack());
player
.updateInventory(); // workaround for player editing slot & it immediately being
// filled again
if (!map.isEmpty()) {
setItemStack(map.values().iterator().next());
return false;
} else {
CollectItemMessage message = new CollectItemMessage(getEntityId(), player.getEntityId(),
getItemStack().getAmount());
world.playSound(location, Sound.ENTITY_ITEM_PICKUP, 0.3f, (float) (1 + Math.random()));
world.getRawPlayers().stream().filter(other -> other.canSeeEntity(this))
.forEach(other -> other.getSession().send(message));
remove();
return true;
}
}
public void setBias(GlowPlayer player) {
biasPlayer = player;
}
////////////////////////////////////////////////////////////////////////////
// Overrides
@Override
public @NotNull BoundingBox getBoundingBox() {
return null;
}
@Override
public EntityType getType() {
return EntityType.DROPPED_ITEM;
}
@Override
public void pulse() {
super.pulse();
// decrement pickupDelay if it's less than the NBT maximum
if (pickupDelay > 0) {
if (pickupDelay < Short.MAX_VALUE) {
--pickupDelay;
}
if (pickupDelay < 20 && biasPlayer != null) {
// check for the bias player
for (Entity entity : getNearbyEntities(1, 0.5, 1)) {
if (entity.isDead()) {
continue;
}
if (entity == biasPlayer && getPickedUp((GlowPlayer) entity)) {
break;
}
}
}
} else {
// check for nearby players
for (Entity entity : getNearbyEntities(1, 0.5, 1)) {
if (entity.isDead()) {
continue;
}
if (entity instanceof GlowPlayer && getPickedUp((GlowPlayer) entity)) {
break;
}
if (entity instanceof GlowItem) {
if (entity != this && ((GlowItem) entity).getItemStack()
.isSimilar(getItemStack())) {
ItemStack clone = getItemStack().clone();
ItemMergeEvent event = EventFactory.getInstance()
.callEvent(new ItemMergeEvent((GlowItem) entity, this));
if (!event.isCancelled()) {
clone.setAmount(
((GlowItem) entity).getItemStack().getAmount()
+ clone.getAmount());
entity.remove();
setItemStack(clone);
}
}
}
}
}
// disappear if we've lived too long
if (getTicksLived() >= LIFETIME) {
ItemDespawnEvent event = EventFactory.getInstance()
.callEvent(new ItemDespawnEvent(this, getLocation()));
if (event.isCancelled()) {
// Allow it to live for 5 more minutes, according to docs
ticksLived -= LIFETIME;
return;
}
remove();
}
}
@Override
protected void pulsePhysics() {
if (location.getBlock().getType().isSolid()) {
setRawLocation(location.clone().add(0, 0.2, 0), false);
}
super.pulsePhysics();
}
@Override
public List<Message> createSpawnMessage() {
return Arrays.asList(
new SpawnObjectMessage(entityId, getUniqueId(), SpawnObjectMessage.ITEM, location),
new EntityMetadataMessage(entityId, metadata.getEntryList()),
// these keep the client from assigning a random velocity
new EntityTeleportMessage(entityId, location),
new EntityVelocityMessage(entityId, getVelocity())
);
}
////////////////////////////////////////////////////////////////////////////
// Item stuff
@Override
public int getPickupDelay() {
return pickupDelay;
}
@Override
public void setPickupDelay(int delay) {
pickupDelay = delay;
}
@Override
public boolean canMobPickup() {
// TODO: Implementation (1.12.1)
return true;
}
@Override
public void setCanMobPickup(boolean pickup) {
// TODO: Implementation (1.12.1)
}
@Override
public ItemStack getItemStack() {
return metadata.getItem(MetadataIndex.ITEM_ITEM);
}
@Override
public void setItemStack(ItemStack stack) {
// stone is the "default state" for the item stack according to the client
metadata.set(MetadataIndex.ITEM_ITEM,
stack == null ? new ItemStack(Material.STONE) : stack.clone());
}
}