package net.glowstone.entity.objects;
import com.flowpowered.network.Message;
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.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 net.glowstone.util.Position;
import org.bukkit.Effect;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.*;
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.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scoreboard.Criterias;
import org.bukkit.scoreboard.Objective;
import org.bukkit.util.EulerAngle;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
public class GlowArmorStand extends GlowLivingEntity implements ArmorStand {
private static final EulerAngle[] defaultPose = new EulerAngle[6];
static {
double ten = 0.17453292519943295; // Math.toRadians(10)
double fifteen = 0.2617993877991494; // Math.toRadians(15)
double one = 0.017453292519943295; // Math.toRadians(1)
defaultPose[0] = EulerAngle.ZERO;
defaultPose[1] = EulerAngle.ZERO;
defaultPose[2] = new EulerAngle(-ten, 0, -ten);
defaultPose[3] = new EulerAngle(-fifteen, 0, ten);
defaultPose[4] = new EulerAngle(-one, 0, -one);
defaultPose[5] = new EulerAngle(one, 0, one);
}
private final ItemStack[] equipment = new ItemStack[5];
private final boolean[] changedEquip = new boolean[5];
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;
public GlowArmorStand(Location location) {
super(location, 2);
System.arraycopy(defaultPose, 0, pose, 0, 6);
for (int i = 0; i < 5; i++) {
changedEquip[i] = false;
}
}
@Override
public void reset() {
super.reset();
for (int i = 0; i < 5; i++) {
changedEquip[i] = false;
}
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;
if (source == null) {
event = new EntityDamageEvent(this, cause, amount);
} else {
event = new EntityDamageByEntityEvent(source, this, cause, amount);
}
EventFactory.callEvent(event);
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;
((GlowWorld) location.getWorld()).showParticle(location.clone().add(0, 1.317, 0), Effect.TILE_DUST, Material.WOOD.getId(), 0, 0.125f, 0.494f, 0.125f, 0.1f, 10, 10);
for (ItemStack stack : equipment) {
if (stack == null || stack.getType() == Material.AIR) 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())) {
int slot = getEditSlot(msg.getTargetY());
PlayerArmorStandManipulateEvent event = new PlayerArmorStandManipulateEvent(player, this, InventoryUtil.itemOrEmpty(null), InventoryUtil.itemOrEmpty(equipment[slot]), EquipmentSlot.values()[slot]);
EventFactory.callEvent(event);
if (event.isCancelled()) {
return false;
}
if (isEmpty(slot)) {
return false;
}
ItemStack stack = equipment[slot];
player.setItemInHand(stack);
equipment[slot] = InventoryUtil.createEmptyStack();
changedEquip[slot] = true;
return true;
} else {
int slot = getEquipType(player.getItemInHand().getType());
if (slot == 0 && !hasArms) {
return false;
}
PlayerArmorStandManipulateEvent event = new PlayerArmorStandManipulateEvent(player, this, player.getItemInHand(), InventoryUtil.itemOrEmpty(equipment[slot]), EquipmentSlot.values()[slot]);
EventFactory.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[slot];
}
if (!InventoryUtil.isEmpty(back)) {
player.setItemInHand(back);
}
equipment[slot] = stack;
changedEquip[slot] = true;
return true;
}
}
return false;
}
private int getEquipType(Material mat) {
switch (mat) {
case IRON_HELMET:
case LEATHER_HELMET:
case CHAINMAIL_HELMET:
case GOLD_HELMET:
case DIAMOND_HELMET:
case PUMPKIN:
case SKULL_ITEM:
return 4;
case IRON_CHESTPLATE:
case GOLD_CHESTPLATE:
case LEATHER_CHESTPLATE:
case CHAINMAIL_CHESTPLATE:
case DIAMOND_CHESTPLATE:
return 3;
case IRON_LEGGINGS:
case GOLD_LEGGINGS:
case LEATHER_LEGGINGS:
case CHAINMAIL_LEGGINGS:
case DIAMOND_LEGGINGS:
return 2;
case IRON_BOOTS:
case GOLD_BOOTS:
case LEATHER_BOOTS:
case CHAINMAIL_BOOTS:
case DIAMOND_BOOTS:
return 1;
default:
return 0;
}
}
private int getEditSlot(float height) {
int slot = 0;
if (isSmall) height *= 2;
if (height >= 0.1 && height < 0.1 + (isSmall ? 0.8 : 0.45) && !isEmpty(1)) {
slot = 1;
} else if (height >= 0.9 + (isSmall ? 0.3 : 0) && height < 0.9 + (isSmall ? 1 : 0.7) && !isEmpty(3)) {
slot = 3;
} else if (height >= 0.4 && height < 0.4 + (isSmall ? 1 : 0.8) && !isEmpty(2)) {
slot = 2;
} else if (height >= 1.6 && !isEmpty(4)) {
slot = 4;
}
return slot;
}
private boolean isEmpty(int slot) {
return InventoryUtil.isEmpty(equipment[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;
}
return false;
}
@Override
public List<Message> createSpawnMessage() {
double x = location.getX();
double y = location.getY();
double z = location.getZ();
int yaw = Position.getIntYaw(location);
int pitch = Position.getIntPitch(location);
return Arrays.asList(
new SpawnObjectMessage(id, UUID.randomUUID(), 78, x, y, z, pitch, yaw), // TODO: once UUID is documented, actually use the appropriate ID here
new EntityMetadataMessage(id, metadata.getEntryList()),
new EntityEquipmentMessage(id, EntityEquipmentMessage.HELD_ITEM, getItemInHand()),
new EntityEquipmentMessage(id, EntityEquipmentMessage.BOOTS_SLOT, getBoots()),
new EntityEquipmentMessage(id, EntityEquipmentMessage.LEGGINGS_SLOT, getLeggings()),
new EntityEquipmentMessage(id, EntityEquipmentMessage.CHESTPLATE_SLOT, getChestplate()),
new EntityEquipmentMessage(id, EntityEquipmentMessage.HELMET_SLOT, getHelmet())
);
}
@Override
public List<Message> createUpdateMessage() {
List<Message> messages = super.createUpdateMessage();
for (int i = 0; i < 5; i++) {
if (changedEquip[i]) {
messages.add(new EntityEquipmentMessage(id, i, equipment[i]));
}
}
if (needsKill) {
messages.add(new DestroyEntitiesMessage(Arrays.asList(id)));
}
return messages;
}
@Override
public EntityType getType() {
return EntityType.ARMOR_STAND;
}
@Override
public Location getOrigin() {
return null;
}
@Override
public ItemStack getItemInHand() {
return equipment[0];
}
@Override
public void setItemInHand(ItemStack item) {
equipment[0] = item;
changedEquip[0] = true;
}
@Override
public ItemStack getBoots() {
return equipment[1];
}
@Override
public void setBoots(ItemStack item) {
equipment[1] = item;
changedEquip[1] = true;
}
@Override
public ItemStack getLeggings() {
return equipment[2];
}
@Override
public void setLeggings(ItemStack item) {
equipment[2] = item;
changedEquip[2] = true;
}
@Override
public ItemStack getChestplate() {
return equipment[3];
}
@Override
public void setChestplate(ItemStack item) {
equipment[3] = item;
changedEquip[3] = true;
}
@Override
public ItemStack getHelmet() {
return equipment[4];
}
@Override
public void setHelmet(ItemStack item) {
equipment[4] = item;
changedEquip[4] = true;
}
@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);
}
@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() {
return false;
}
@Override
public void setCanMove(boolean move) {
}
@Override
public AttributeInstance getAttribute(Attribute attribute) {
return null;
}
@Override
public boolean isGliding() {
return false;
}
@Override
public void setGliding(boolean b) {
}
@Override
public void setAI(boolean b) {
}
@Override
public boolean hasAI() {
return false;
}
@Override
public void setCollidable(boolean b) {
}
@Override
public boolean isCollidable() {
return false;
}
@Override
public int getArrowsStuck() {
return 0;
}
@Override
public void setArrowsStuck(int i) {
}
}