blob: 899b3c9bed4134d3629647a438f4ad96a1ba84c4 [file] [log] [blame] [raw]
package net.glowstone.entity.objects;
import com.flowpowered.network.Message;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.glowstone.EventFactory;
import net.glowstone.GlowWorld;
import net.glowstone.entity.GlowLivingEntity;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.entity.meta.MetadataIndex;
import net.glowstone.entity.meta.MetadataIndex.ArmorStandFlags;
import net.glowstone.entity.meta.MetadataIndex.StatusFlags;
import net.glowstone.inventory.ClothType;
import net.glowstone.inventory.GlowEntityEquipment;
import net.glowstone.net.GlowSession;
import net.glowstone.net.message.play.entity.DestroyEntitiesMessage;
import net.glowstone.net.message.play.entity.EntityEquipmentMessage;
import net.glowstone.net.message.play.entity.EntityMetadataMessage;
import net.glowstone.net.message.play.entity.SpawnObjectMessage;
import net.glowstone.net.message.play.player.InteractEntityMessage;
import net.glowstone.net.message.play.player.InteractEntityMessage.Action;
import net.glowstone.util.InventoryUtil;
import org.bukkit.Effect;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Projectile;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scoreboard.Criterias;
import org.bukkit.scoreboard.Objective;
import org.bukkit.util.EulerAngle;
public class GlowArmorStand extends GlowLivingEntity implements ArmorStand {
private static final EulerAngle[] defaultPose = new EulerAngle[6];
static {
defaultPose[0] = EulerAngle.ZERO;
defaultPose[1] = EulerAngle.ZERO;
double ten = 0.17453292519943295; // Math.toRadians(10)
defaultPose[2] = new EulerAngle(-ten, 0, -ten);
double fifteen = 0.2617993877991494; // Math.toRadians(15)
defaultPose[3] = new EulerAngle(-fifteen, 0, ten);
double one = 0.017453292519943295; // Math.toRadians(1)
defaultPose[4] = new EulerAngle(-one, 0, -one);
defaultPose[5] = new EulerAngle(one, 0, one);
}
private final GlowEntityEquipment equipment;
private final EulerAngle[] pose = new EulerAngle[6];
private boolean isMarker;
private boolean isVisible = true;
private boolean isSmall;
private boolean hasBasePlate = true;
private boolean hasGravity = true;
private boolean hasArms;
private boolean needsKill;
/**
* Creates an armor stand.
*
* @param location the location of the armor stand
*/
public GlowArmorStand(Location location) {
super(location, 2);
equipment = new GlowEntityEquipment(this);
System.arraycopy(defaultPose, 0, pose, 0, 6);
this.getEquipmentMonitor().resetChanges();
setSize(false);
}
@Override
public void reset() {
super.reset();
if (needsKill) {
needsKill = false;
}
}
@Override
public void pulse() {
super.pulse();
if (isDead()) {
remove();
needsKill = true;
} else if (ticksLived % 10 == 0) { //player needs to click fast (2 times) to kill the entity
setHealth(2);
}
}
@Override
public void damage(double amount, Entity source, DamageCause cause) {
if (getNoDamageTicks() > 0 || health <= 0 || !canTakeDamage(cause)) {
return;
}
if (source instanceof Projectile && !(source instanceof Arrow)) {
return;
}
EntityDamageEvent event = EventFactory.getInstance().onEntityDamage(source == null
? new EntityDamageEvent(this, cause, amount)
: new EntityDamageByEntityEvent(source, this, cause, amount));
if (event.isCancelled()) {
return;
}
boolean drop = false;
if (source instanceof GlowPlayer || source instanceof Arrow && ((Projectile) source)
.getShooter() instanceof GlowPlayer) {
GlowPlayer damager = (GlowPlayer) (source instanceof GlowPlayer ? source
: ((Arrow) source).getShooter());
if (damager.getGameMode() == GameMode.ADVENTURE) {
return;
} else if (damager.getGameMode() == GameMode.CREATIVE) {
amount = 2; //Instantly kill the entity
} else {
amount = 1; //Needs two hits
drop = true;
}
}
setLastDamage(amount);
setHealth(health - amount, drop);
}
@Override
public void setHealth(double health) {
setHealth(health, false);
}
private void setHealth(double health, boolean drop) {
if (health < 0) {
health = 0;
}
if (health > getMaxHealth()) {
health = getMaxHealth();
}
this.health = health;
metadata.set(MetadataIndex.HEALTH, (float) health);
for (Objective objective : getServer().getScoreboardManager().getMainScoreboard()
.getObjectivesByCriteria(Criterias.HEALTH)) {
objective.getScore(getName()).setScore((int) health);
}
if (health == 0) {
kill(drop);
}
}
private void kill(boolean dropArmorStand) {
active = false;
// TODO: set block to oak wood, requires flattening (numerical ID)
location.getWorld().spawnParticle(
Particle.BLOCK_DUST,
location.clone().add(0, 1.317, 0), 1, 0.125f, 0.494f, 0.125f);
for (ItemStack stack : equipment.getArmorContents()) {
if (InventoryUtil.isEmpty(stack)) {
continue;
}
getWorld().dropItemNaturally(location, stack);
}
if (dropArmorStand) {
getWorld().dropItemNaturally(location, new ItemStack(Material.ARMOR_STAND));
}
}
@Override
public boolean entityInteract(GlowPlayer player, InteractEntityMessage msg) {
if (player.getGameMode() == GameMode.SPECTATOR || isMarker) {
return false;
}
if (msg.getAction() == Action.INTERACT_AT.ordinal()) {
if (InventoryUtil.isEmpty(player.getItemInHand())) {
EquipmentSlot slot = getEditSlot(msg.getTargetY());
PlayerArmorStandManipulateEvent event = new PlayerArmorStandManipulateEvent(player,
this, InventoryUtil.itemOrEmpty(null),
InventoryUtil.itemOrEmpty(equipment.getItem(slot)), slot);
EventFactory.getInstance().callEvent(event);
if (event.isCancelled()) {
return false;
}
if (isEmpty(slot)) {
return false;
}
ItemStack stack = equipment.getItem(slot);
player.setItemInHand(stack);
equipment.setItem(slot, InventoryUtil.createEmptyStack());
return true;
} else {
EquipmentSlot slot = getEquipType(player.getItemInHand().getType());
if ((slot == EquipmentSlot.HAND || slot == EquipmentSlot.OFF_HAND) && !hasArms) {
return false;
}
PlayerArmorStandManipulateEvent event = new PlayerArmorStandManipulateEvent(player,
this, player.getItemInHand(),
InventoryUtil.itemOrEmpty(equipment.getItem(slot)), slot);
EventFactory.getInstance().callEvent(event);
if (event.isCancelled()) {
return false;
}
ItemStack stack = player.getItemInHand();
ItemStack back;
if (isEmpty(slot)) {
if (stack.getAmount() > 1) {
stack.setAmount(stack.getAmount() - 1);
back = stack;
} else {
back = InventoryUtil.createEmptyStack();
}
} else {
if (stack.getAmount() > 1) {
return false;
}
back = equipment.getItem(slot);
}
if (!InventoryUtil.isEmpty(back)) {
player.setItemInHand(back);
}
equipment.setItem(slot, stack);
player.playSound(location, getEquipSound(stack.getType()), SoundCategory.NEUTRAL, 1,
1);
return true;
}
}
return false;
}
private Sound getEquipSound(Material mat) {
if (mat == Material.ELYTRA) {
return Sound.ITEM_ARMOR_EQUIP_ELYTRA;
}
if (ClothType.LEATHER.matches(mat)) {
return Sound.ITEM_ARMOR_EQUIP_LEATHER;
}
if (ClothType.CHAINMAIL.matches(mat)) {
return Sound.ITEM_ARMOR_EQUIP_CHAIN;
}
if (ClothType.IRON.matches(mat)) {
return Sound.ITEM_ARMOR_EQUIP_IRON;
}
if (ClothType.GOLD.matches(mat)) {
return Sound.ITEM_ARMOR_EQUIP_GOLD;
}
if (ClothType.DIAMOND.matches(mat)) {
return Sound.ITEM_ARMOR_EQUIP_DIAMOND;
}
return Sound.ITEM_ARMOR_EQUIP_GENERIC;
}
private EquipmentSlot getEquipType(Material mat) {
switch (mat) {
case IRON_HELMET:
case LEATHER_HELMET:
case CHAINMAIL_HELMET:
case GOLDEN_HELMET:
case DIAMOND_HELMET:
case PUMPKIN:
case TURTLE_HELMET:
case SKELETON_SKULL: // TODO: 1.13, Skull Tag
case WITHER_SKELETON_SKULL:
case ZOMBIE_HEAD:
case PLAYER_HEAD:
case CREEPER_HEAD:
case DRAGON_HEAD:
return EquipmentSlot.HEAD;
case IRON_CHESTPLATE:
case GOLDEN_CHESTPLATE:
case LEATHER_CHESTPLATE:
case CHAINMAIL_CHESTPLATE:
case DIAMOND_CHESTPLATE:
case ELYTRA:
return EquipmentSlot.CHEST;
case IRON_LEGGINGS:
case GOLDEN_LEGGINGS:
case LEATHER_LEGGINGS:
case CHAINMAIL_LEGGINGS:
case DIAMOND_LEGGINGS:
return EquipmentSlot.LEGS;
case IRON_BOOTS:
case GOLDEN_BOOTS:
case LEATHER_BOOTS:
case CHAINMAIL_BOOTS:
case DIAMOND_BOOTS:
return EquipmentSlot.FEET;
case SHIELD:
return EquipmentSlot.OFF_HAND;
default:
return EquipmentSlot.HAND;
}
}
private EquipmentSlot getEditSlot(float height) {
if (isSmall) {
height *= 2;
}
if (height >= 0.1 && height < 0.1 + (isSmall ? 0.8 : 0.45) && !isEmpty(
EquipmentSlot.FEET)) {
return EquipmentSlot.FEET;
} else if (height >= 0.9 + (isSmall ? 0.3 : 0) && height < 0.9 + (isSmall ? 1 : 0.7)
&& !isEmpty(EquipmentSlot.CHEST)) {
return EquipmentSlot.CHEST;
} else if (height >= 0.4 && height < 0.4 + (isSmall ? 1 : 0.8) && !isEmpty(
EquipmentSlot.LEGS)) {
return EquipmentSlot.LEGS;
} else if (height >= 1.6 && !isEmpty(EquipmentSlot.HEAD)) {
return EquipmentSlot.HEAD;
}
return EquipmentSlot.HAND;
}
private boolean isEmpty(EquipmentSlot slot) {
return InventoryUtil.isEmpty(equipment.getItem(slot));
}
@Override
public boolean canTakeDamage(DamageCause cause) {
switch (cause) {
case ENTITY_ATTACK:
case PROJECTILE:
case FIRE_TICK:
case BLOCK_EXPLOSION:
case ENTITY_EXPLOSION:
case VOID:
case CUSTOM:
return true;
default:
return false;
}
}
@Override
public List<Message> createSpawnMessage() {
return Arrays.asList(
new SpawnObjectMessage(entityId, UUID.randomUUID(), 78, location),
// TODO: once UUID is documented, actually use the appropriate ID here
new EntityMetadataMessage(entityId, metadata.getEntryList()),
new EntityEquipmentMessage(entityId, EntityEquipmentMessage.HELD_ITEM,
getItemInHand()),
new EntityEquipmentMessage(entityId, EntityEquipmentMessage.OFF_HAND,
equipment.getItemInOffHand()),
new EntityEquipmentMessage(entityId, EntityEquipmentMessage.BOOTS_SLOT, getBoots()),
new EntityEquipmentMessage(entityId, EntityEquipmentMessage.LEGGINGS_SLOT,
getLeggings()),
new EntityEquipmentMessage(entityId, EntityEquipmentMessage.CHESTPLATE_SLOT,
getChestplate()),
new EntityEquipmentMessage(entityId, EntityEquipmentMessage.HELMET_SLOT,
getHelmet())
);
}
@Override
public List<Message> createUpdateMessage(GlowSession session) {
List<Message> messages = super.createUpdateMessage(session);
if (needsKill) {
messages.add(new DestroyEntitiesMessage(Collections.singletonList(entityId)));
}
return messages;
}
@Override
public EntityType getType() {
return EntityType.ARMOR_STAND;
}
@Override
public ItemStack getItemInHand() {
return equipment.getItemInHand();
}
@Override
public void setItemInHand(ItemStack item) {
equipment.setItemInHand(item);
}
@Override
public ItemStack getBoots() {
return equipment.getBoots();
}
@Override
public void setBoots(ItemStack item) {
equipment.setBoots(item);
}
@Override
public ItemStack getLeggings() {
return equipment.getLeggings();
}
@Override
public void setLeggings(ItemStack item) {
equipment.setLeggings(item);
}
@Override
public ItemStack getChestplate() {
return equipment.getChestplate();
}
@Override
public void setChestplate(ItemStack item) {
equipment.setChestplate(item);
}
@Override
public ItemStack getHelmet() {
return equipment.getHelmet();
}
@Override
public void setHelmet(ItemStack item) {
equipment.setHelmet(item);
}
@Override
public EulerAngle getHeadPose() {
return pose[0];
}
@Override
public void setHeadPose(EulerAngle pose) {
this.pose[0] = pose;
metadata.set(MetadataIndex.ARMORSTAND_HEAD_POSITION, pose);
}
@Override
public EulerAngle getBodyPose() {
return pose[1];
}
@Override
public void setBodyPose(EulerAngle pose) {
this.pose[1] = pose;
metadata.set(MetadataIndex.ARMORSTAND_BODY_POSITION, pose);
}
@Override
public EulerAngle getLeftArmPose() {
return pose[2];
}
@Override
public void setLeftArmPose(EulerAngle pose) {
this.pose[2] = pose;
metadata.set(MetadataIndex.ARMORSTAND_LEFT_ARM_POSITION, pose);
}
@Override
public EulerAngle getRightArmPose() {
return pose[3];
}
@Override
public void setRightArmPose(EulerAngle pose) {
this.pose[3] = pose;
metadata.set(MetadataIndex.ARMORSTAND_RIGHT_ARM_POSITION, pose);
}
@Override
public EulerAngle getLeftLegPose() {
return pose[4];
}
@Override
public void setLeftLegPose(EulerAngle pose) {
this.pose[4] = pose;
metadata.set(MetadataIndex.ARMORSTAND_LEFT_LEG_POSITION, pose);
}
@Override
public EulerAngle getRightLegPose() {
return pose[5];
}
@Override
public void setRightLegPose(EulerAngle pose) {
this.pose[5] = pose;
metadata.set(MetadataIndex.ARMORSTAND_RIGHT_LEG_POSITION, pose);
}
@Override
public boolean hasBasePlate() {
return hasBasePlate;
}
@Override
public void setBasePlate(boolean basePlate) {
hasBasePlate = basePlate;
metadata.setBit(MetadataIndex.ARMORSTAND_FLAGS, ArmorStandFlags.NO_BASE_PLATE, !basePlate);
}
@Override
public boolean hasGravity() {
return hasGravity;
}
@Override
public void setGravity(boolean gravity) {
hasGravity = gravity;
metadata.setBit(MetadataIndex.ARMORSTAND_FLAGS, ArmorStandFlags.HAS_GRAVITY, gravity);
}
@Override
public boolean isVisible() {
return isVisible;
}
@Override
public void setVisible(boolean visible) {
isVisible = visible;
metadata.setBit(MetadataIndex.STATUS, StatusFlags.INVISIBLE, !visible);
}
@Override
public boolean hasArms() {
return hasArms;
}
@Override
public void setArms(boolean arms) {
hasArms = arms;
metadata.setBit(MetadataIndex.ARMORSTAND_FLAGS, ArmorStandFlags.HAS_ARMS, arms);
}
@Override
public boolean isSmall() {
return isSmall;
}
@Override
public void setSmall(boolean small) {
isSmall = small;
metadata.setBit(MetadataIndex.ARMORSTAND_FLAGS, ArmorStandFlags.IS_SMALL, small);
setSize(small);
}
private void setSize(boolean small) {
if (small) {
setSize(0.25f, 0.9875f);
} else {
setSize(0.5f, 1.975f);
}
}
@Override
public boolean isMarker() {
return isMarker;
}
@Override
public void setMarker(boolean marker) {
isMarker = marker;
metadata.setBit(MetadataIndex.ARMORSTAND_FLAGS, ArmorStandFlags.IS_MARKER, marker);
}
@Override
public boolean canMove() {
// TODO
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public void setCanMove(boolean move) {
// TODO
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public ItemStack getItem(EquipmentSlot equipmentSlot) {
return equipment.getItem(equipmentSlot);
}
@Override
public void setItem(EquipmentSlot equipmentSlot, ItemStack itemStack) {
equipment.setItem(equipmentSlot, itemStack);
}
@Override
public Set<EquipmentSlot> getDisabledSlots() {
// TODO: 1.13
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public void setDisabledSlots(EquipmentSlot... equipmentSlots) {
// TODO: 1.13
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public void addDisabledSlots(EquipmentSlot... equipmentSlots) {
// TODO: 1.13
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public void removeDisabledSlots(EquipmentSlot... equipmentSlots) {
// TODO: 1.13
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public boolean isSlotDisabled(EquipmentSlot equipmentSlot) {
// TODO: 1.13
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public EntityEquipment getEquipment() {
return this.equipment;
}
}