--- a/net/minecraft/world/entity/decoration/ArmorStand.java
+++ b/net/minecraft/world/entity/decoration/ArmorStand.java
@@ -85,9 +_,16 @@
     private boolean invisible = false;
     public long lastHit;
     public int disabledSlots = 0;
+    public boolean canMove = true; // Paper
+    // Paper start - Allow ArmorStands not to tick
+    public boolean canTick = true;
+    public boolean canTickSetByAPI = false;
+    private boolean noTickEquipmentDirty = false;
+    // Paper end - Allow ArmorStands not to tick
 
     public ArmorStand(EntityType<? extends ArmorStand> entityType, Level level) {
         super(entityType, level);
+        if (level != null) this.canTick = level.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick
     }
 
     public ArmorStand(Level level, double x, double y, double z) {
@@ -99,6 +_,13 @@
         return createLivingAttributes().add(Attributes.STEP_HEIGHT, 0.0);
     }
 
+    // CraftBukkit start - SPIGOT-3607, SPIGOT-3637
+    @Override
+    public float getBukkitYaw() {
+        return this.getYRot();
+    }
+    // CraftBukkit end
+
     @Override
     public void refreshDimensions() {
         double x = this.getX();
@@ -134,6 +_,14 @@
         return slot != EquipmentSlot.BODY && slot != EquipmentSlot.SADDLE && !this.isDisabled(slot);
     }
 
+    // Paper - Allow ArmorStands not to tick; Still update equipment
+    @Override
+    public void setItemSlot(net.minecraft.world.entity.EquipmentSlot slot, ItemStack stack, boolean silent) {
+        super.setItemSlot(slot, stack, silent);
+        this.noTickEquipmentDirty = true;
+    }
+    // Paper - Allow ArmorStands not to tick; Still update equipment
+
     @Override
     protected void addAdditionalSaveData(ValueOutput output) {
         super.addAdditionalSaveData(output);
@@ -147,6 +_,7 @@
         }
 
         output.store("Pose", ArmorStand.ArmorStandPose.CODEC, this.getArmorStandPose());
+        if (this.canTickSetByAPI) output.putBoolean("Paper.CanTickOverride", this.canTick); // Paper - Allow ArmorStands not to tick
     }
 
     @Override
@@ -160,10 +_,16 @@
         this.setMarker(input.getBooleanOr("Marker", false));
         this.noPhysics = !this.hasPhysics();
         input.read("Pose", ArmorStand.ArmorStandPose.CODEC).ifPresent(this::setArmorStandPose);
+        // Paper start - Allow ArmorStands not to tick
+        if (input.getInt("Paper.CanTickOverride").isPresent()) { // Check if is set
+            this.canTick = input.getBooleanOr("Paper.CanTickOverride", true);
+            this.canTickSetByAPI = true;
+        }
+        // Paper end - Allow ArmorStands not to tick
     }
 
     @Override
-    public boolean isPushable() {
+    public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule
         return false;
     }
 
@@ -173,6 +_,7 @@
 
     @Override
     protected void pushEntities() {
+        if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups
         for (Entity entity : this.level().getEntities(this, this.getBoundingBox(), RIDABLE_MINECARTS)) {
             if (this.distanceToSqr(entity) <= 0.2) {
                 entity.push(this);
@@ -245,7 +_,25 @@
             return false;
         } else if (itemBySlot.isEmpty() && (this.disabledSlots & 1 << slot.getFilterBit(16)) != 0) {
             return false;
-        } else if (player.hasInfiniteMaterials() && itemBySlot.isEmpty() && !stack.isEmpty()) {
+            // CraftBukkit start
+        } else {
+            org.bukkit.inventory.ItemStack armorStandItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemBySlot);
+            org.bukkit.inventory.ItemStack playerHeldItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack);
+
+            org.bukkit.entity.Player player1 = (org.bukkit.entity.Player) player.getBukkitEntity();
+            org.bukkit.entity.ArmorStand self = (org.bukkit.entity.ArmorStand) this.getBukkitEntity();
+
+            org.bukkit.inventory.EquipmentSlot slot1 = org.bukkit.craftbukkit.CraftEquipmentSlot.getSlot(slot);
+            org.bukkit.inventory.EquipmentSlot hand1 = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand);
+            org.bukkit.event.player.PlayerArmorStandManipulateEvent armorStandManipulateEvent = new org.bukkit.event.player.PlayerArmorStandManipulateEvent(player1, self, playerHeldItem, armorStandItem, slot1, hand1);
+            this.level().getCraftServer().getPluginManager().callEvent(armorStandManipulateEvent);
+
+            if (armorStandManipulateEvent.isCancelled()) {
+                return true;
+            }
+
+        if (player.hasInfiniteMaterials() && itemBySlot.isEmpty() && !stack.isEmpty()) {
+            // CraftBukkit end
             this.setItemSlot(slot, stack.copyWithCount(1));
             return true;
         } else if (stack.isEmpty() || stack.getCount() <= 1) {
@@ -258,6 +_,7 @@
             this.setItemSlot(slot, stack.split(1));
             return true;
         }
+        } // CraftBukkit
     }
 
     @Override
@@ -267,15 +_,32 @@
         } else if (!level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && damageSource.getEntity() instanceof Mob) {
             return false;
         } else if (damageSource.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
-            this.kill(level);
+            // CraftBukkit start
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount)) {
+                return false;
+            }
+            this.kill(level, damageSource); // CraftBukkit
+            // CraftBukkit end
             return false;
-        } else if (this.isInvulnerableTo(level, damageSource) || this.invisible || this.isMarker()) {
+        } else if (this.isInvulnerableTo(level, damageSource) /*|| this.invisible*/ || this.isMarker()) { // CraftBukkit
             return false;
         } else if (damageSource.is(DamageTypeTags.IS_EXPLOSION)) {
-            this.brokenByAnything(level, damageSource);
-            this.kill(level);
+            // CraftBukkit start
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, true, this.invisible)) {
+                return false;
+            }
+            // CraftBukkit end
+            // Paper start - avoid duplicate event call
+            org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(level, damageSource);
+            if (!event.isCancelled()) this.kill(level, damageSource, false); // CraftBukkit
+            // Paper end
             return false;
         } else if (damageSource.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) {
+            // CraftBukkit start
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, true, this.invisible)) {
+                return false;
+            }
+            // CraftBukkit end
             if (this.isOnFire()) {
                 this.causeDamage(level, damageSource, 0.15F);
             } else {
@@ -284,9 +_,19 @@
 
             return false;
         } else if (damageSource.is(DamageTypeTags.BURNS_ARMOR_STANDS) && this.getHealth() > 0.5F) {
+            // CraftBukkit start
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, true, this.invisible)) {
+                return false;
+            }
+            // CraftBukkit end
             this.causeDamage(level, damageSource, 4.0F);
             return false;
         } else {
+            // CraftBukkit start
+            if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, true, this.invisible)) {
+                return false;
+            }
+            // CraftBukkit end
             boolean isCanBreakArmorStand = damageSource.is(DamageTypeTags.CAN_BREAK_ARMOR_STAND);
             boolean isAlwaysKillsArmorStands = damageSource.is(DamageTypeTags.ALWAYS_KILLS_ARMOR_STANDS);
             if (!isCanBreakArmorStand && !isAlwaysKillsArmorStands) {
@@ -296,7 +_,7 @@
             } else if (damageSource.isCreativePlayer()) {
                 this.playBrokenSound();
                 this.showBreakingParticles();
-                this.kill(level);
+                this.kill(level, damageSource); // CraftBukkit
                 return true;
             } else {
                 long gameTime = level.getGameTime();
@@ -305,9 +_,9 @@
                     this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity());
                     this.lastHit = gameTime;
                 } else {
-                    this.brokenByPlayer(level, damageSource);
+                    org.bukkit.event.entity.EntityDeathEvent event = this.brokenByPlayer(level, damageSource); // Paper
                     this.showBreakingParticles();
-                    this.kill(level);
+                    if (!event.isCancelled()) this.kill(level, damageSource, false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...)
                 }
 
                 return true;
@@ -359,31 +_,42 @@
         float health = this.getHealth();
         health -= damageAmount;
         if (health <= 0.5F) {
-            this.brokenByAnything(level, damageSource);
-            this.kill(level);
+            // Paper start - avoid duplicate event call
+            org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(level, damageSource);
+            if (!event.isCancelled()) this.kill(level, damageSource, false); // CraftBukkit
+            // Paper end
         } else {
             this.setHealth(health);
             this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity());
         }
     }
 
-    private void brokenByPlayer(ServerLevel level, DamageSource damageSource) {
+    private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel level, DamageSource damageSource) { // Paper
         ItemStack itemStack = new ItemStack(Items.ARMOR_STAND);
         itemStack.set(DataComponents.CUSTOM_NAME, this.getCustomName());
-        Block.popResource(this.level(), this.blockPosition(), itemStack);
-        this.brokenByAnything(level, damageSource);
+        this.drops.add(new DefaultDrop(itemStack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior
+        return this.brokenByAnything(level, damageSource); // Paper
     }
 
-    private void brokenByAnything(ServerLevel level, DamageSource damageSource) {
+    private org.bukkit.event.entity.EntityDeathEvent brokenByAnything(ServerLevel level, DamageSource damageSource) { // Paper
         this.playBrokenSound();
-        this.dropAllDeathLoot(level, damageSource);
+        // this.dropAllDeathLoot(level, damageSource); // CraftBukkit - moved down
 
         for (EquipmentSlot equipmentSlot : EquipmentSlot.VALUES) {
-            ItemStack itemStack = this.equipment.set(equipmentSlot, ItemStack.EMPTY);
+            ItemStack itemStack = this.equipment.get(equipmentSlot); // Paper - move equipment removal past event call
             if (!itemStack.isEmpty()) {
-                Block.popResource(this.level(), this.blockPosition().above(), itemStack);
-            }
-        }
+                this.drops.add(new DefaultDrop(itemStack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly}
+            }
+        }
+        // Paper start - move equipment removal past event call
+        org.bukkit.event.entity.EntityDeathEvent event = this.dropAllDeathLoot(level, damageSource);
+        if (!event.isCancelled()) {
+            for (EquipmentSlot equipmentSlot : EquipmentSlot.VALUES) {
+                this.equipment.set(equipmentSlot, ItemStack.EMPTY);
+            }
+        }
+        return event;
+        // Paper end - move equipment removal past event call
     }
 
     private void playBrokenSound() {
@@ -431,9 +_,40 @@
         return this.isSmall();
     }
 
+    // Paper start - Allow ArmorStands not to tick
+    @Override
+    public void tick() {
+        if (!this.canTick) {
+            if (this.noTickEquipmentDirty) {
+                this.noTickEquipmentDirty = false;
+                this.detectEquipmentUpdates();
+            }
+
+            return;
+        }
+        super.tick();
+    }
+    // Paper end - Allow ArmorStands not to tick
+
     @Override
     public void kill(ServerLevel level) {
-        this.remove(Entity.RemovalReason.KILLED);
+        // CraftBukkit start - pass DamageSource for kill
+        this.kill(level, null);
+    }
+
+    public void kill(ServerLevel level, @Nullable DamageSource damageSource) {
+        // Paper start - make cancellable
+        this.kill(level, damageSource, true);
+    }
+
+    public void kill(ServerLevel level, @Nullable DamageSource damageSource, boolean callEvent) {
+        if (callEvent) {
+            org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, (damageSource == null ? this.damageSources().genericKill() : damageSource), this.drops); // CraftBukkit - call event
+            if (event.isCancelled()) return;
+        }
+        // Paper end
+        this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
+        // CraftBukkit end
         this.gameEvent(GameEvent.ENTITY_DIE);
     }
 
@@ -684,4 +_,13 @@
                 .apply(instance, ArmorStand.ArmorStandPose::new)
         );
     }
+
+    // Paper start
+    @Override
+    public void move(net.minecraft.world.entity.MoverType type, Vec3 movement) {
+        if (this.canMove) {
+            super.move(type, movement);
+        }
+    }
+    // Paper end
 }
