From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Tue, 9 Nov 2077 00:00:00 +0800 Subject: [PATCH] Lithium: equipment tracking Should have special treatment to ArmorStand, since Paper introduced the configurable ArmorStand no-tick, and still gives it ability to update equipment changes. Thus added a bypass condition in LivingEntity#collectEquipmentChanges, always send ArmorStand equipment changes even if the ArmorStand is no-tick This patch is based on the following mixins: * "net/caffeinemc/mods/lithium/mixin/util/item_component_and_count_tracking/PatchedDataComponentMapMixin.java" * "net/caffeinemc/mods/lithium/mixin/util/item_component_and_count_tracking/ItemStackMixin.java" * "net/caffeinemc/mods/lithium/mixin/entity/equipment_tracking/equipment_changes/LivingEntityMixin.java" * "net/caffeinemc/mods/lithium/mixin/entity/equipment_tracking/ArmorStandMixin.java" * "net/caffeinemc/mods/lithium/mixin/entity/equipment_tracking/LivingEntityMixin.java" * "net/caffeinemc/mods/lithium/mixin/entity/equipment_tracking/MobMixin.java" * "net/caffeinemc/mods/lithium/mixin/entity/equipment_tracking/enchantment_ticking/LivingEntityMixin.java" * "net/caffeinemc/mods/lithium/common/entity/EquipmentEntity.java" * "net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java" * "net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java" By: 2No2Name <2No2Name@web.de> As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) diff --git a/src/main/java/net/caffeinemc/mods/lithium/common/entity/EquipmentEntity.java b/src/main/java/net/caffeinemc/mods/lithium/common/entity/EquipmentEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..05604b8f149c13454983d29a2c4dfe1e11dec1fb --- /dev/null +++ b/src/main/java/net/caffeinemc/mods/lithium/common/entity/EquipmentEntity.java @@ -0,0 +1,16 @@ +package net.caffeinemc.mods.lithium.common.entity; + +import net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber; +import net.minecraft.world.item.ItemStack; + +public interface EquipmentEntity { + void onEquipmentReplaced(ItemStack oldStack, ItemStack newStack); + + interface EquipmentTrackingEntity { + void onEquipmentChanged(); + } + + interface TickableEnchantmentTrackingEntity extends ChangeSubscriber.EnchantmentSubscriber { + void updateHasTickableEnchantments(ItemStack oldStack, ItemStack newStack); + } +} \ No newline at end of file diff --git a/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java b/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java new file mode 100644 index 0000000000000000000000000000000000000000..9a39b58e8aa3a472a9b5a5af1e5ee6d4eaa4d6e4 --- /dev/null +++ b/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangePublisher.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.lithium.common.util.change_tracking; + +import net.minecraft.world.item.ItemStack; + +public interface ChangePublisher { + void subscribe(ChangeSubscriber subscriber, int subscriberData); + + int unsubscribe(ChangeSubscriber subscriber); + + default void unsubscribeWithData(ChangeSubscriber subscriber, int index) { + throw new UnsupportedOperationException("Only implemented for ItemStacks"); + } + + default boolean isSubscribedWithData(ChangeSubscriber subscriber, int subscriberData) { + throw new UnsupportedOperationException("Only implemented for ItemStacks"); + } +} diff --git a/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java b/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java new file mode 100644 index 0000000000000000000000000000000000000000..ef206b9ebbd555a786dad37e1ab1bc48e11961cb --- /dev/null +++ b/src/main/java/net/caffeinemc/mods/lithium/common/util/change_tracking/ChangeSubscriber.java @@ -0,0 +1,190 @@ +package net.caffeinemc.mods.lithium.common.util.change_tracking; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import net.minecraft.world.item.ItemStack; + +public interface ChangeSubscriber { + + static ChangeSubscriber combine(ChangeSubscriber prevSubscriber, int prevSData, @NotNull ChangeSubscriber newSubscriber, int newSData) { + if (prevSubscriber == null) { + return newSubscriber; + } else if (prevSubscriber instanceof Multi) { + ArrayList> subscribers = new ArrayList<>(((Multi) prevSubscriber).subscribers); + IntArrayList subscriberDatas = new IntArrayList(((Multi) prevSubscriber).subscriberDatas); + subscribers.add(newSubscriber); + subscriberDatas.add(newSData); + return new Multi<>(subscribers, subscriberDatas); + } else { + ArrayList> subscribers = new ArrayList<>(); + IntArrayList subscriberDatas = new IntArrayList(); + subscribers.add(prevSubscriber); + subscriberDatas.add(prevSData); + subscribers.add(newSubscriber); + subscriberDatas.add(newSData); + return new Multi<>(subscribers, subscriberDatas); + } + } + static ChangeSubscriber without(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber) { + return without(prevSubscriber, removedSubscriber, 0, false); + } + + static ChangeSubscriber without(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber, int removedSubscriberData, boolean matchData) { + if (prevSubscriber == removedSubscriber) { + return null; + } else if (prevSubscriber instanceof Multi multi) { + int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData); + if (index != -1) { + if (multi.subscribers.size() == 2) { + return multi.subscribers.get(1 - index); + } else { + ArrayList> subscribers = new ArrayList<>(multi.subscribers); + IntArrayList subscriberDatas = new IntArrayList(multi.subscriberDatas); + subscribers.remove(index); + subscriberDatas.removeInt(index); + + return new Multi<>(subscribers, subscriberDatas); + } + } else { + return prevSubscriber; + } + } else { + return prevSubscriber; + } + } + + static int dataWithout(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber, int subscriberData) { + return dataWithout(prevSubscriber, removedSubscriber, subscriberData, 0, false); + } + + static int dataWithout(ChangeSubscriber prevSubscriber, ChangeSubscriber removedSubscriber, int subscriberData, int removedSubscriberData, boolean matchData) { + if (prevSubscriber instanceof Multi multi) { + int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData); + if (index != -1) { + if (multi.subscribers.size() == 2) { + return multi.subscriberDatas.getInt(1 - index); + } else { + return subscriberData; + } + } else { + return subscriberData; + } + } + return prevSubscriber == removedSubscriber ? 0 : subscriberData; + } + + static int dataOf(ChangeSubscriber subscribers, ChangeSubscriber subscriber, int subscriberData) { + return subscribers instanceof Multi multi ? multi.subscriberDatas.getInt(multi.subscribers.indexOf(subscriber)) : subscriberData; + } + + static boolean containsSubscriber(ChangeSubscriber subscriber, int subscriberData, ChangeSubscriber subscriber1, int subscriberData1) { + if (subscriber instanceof Multi multi) { + return multi.indexOf(subscriber1, subscriberData1, true) != -1; + } + return subscriber == subscriber1 && subscriberData == subscriberData1; + } + + + /** + * Notify the subscriber that the publisher will be changed immediately after this call. + * @param publisher The publisher that is about to change + * @param subscriberData The data associated with the subscriber, given when the subscriber was added + */ + void notify(@Nullable T publisher, int subscriberData); + + /** + * Notify the subscriber about being unsubscribed from the publisher. Used when the publisher becomes invalid. + * The subscriber should not attempt to unsubscribe itself from the publisher in this method. + * + * @param publisher The publisher unsubscribed from + * @param subscriberData The data associated with the subscriber, given when the subscriber was added + */ + void forceUnsubscribe(T publisher, int subscriberData); + + interface CountChangeSubscriber extends ChangeSubscriber { + + /** + * Notify the subscriber that the publisher's count data will be changed immediately after this call. + * @param publisher The publisher that is about to change + * @param subscriberData The data associated with the subscriber, given when the subscriber was added + * @param newCount The new count of the publisher + */ + void notifyCount(T publisher, int subscriberData, int newCount); + } + + interface EnchantmentSubscriber extends ChangeSubscriber { + + /** + * Notify the subscriber that the publisher's enchantment data has been changed immediately before this call. + * @param publisher The publisher that has changed + * @param subscriberData The data associated with the subscriber, given when the subscriber was added + */ + void notifyAfterEnchantmentChange(T publisher, int subscriberData); + } + + class Multi implements CountChangeSubscriber, EnchantmentSubscriber { + private final ArrayList> subscribers; + private final IntArrayList subscriberDatas; + + public Multi(ArrayList> subscribers, IntArrayList subscriberDatas) { + this.subscribers = subscribers; + this.subscriberDatas = subscriberDatas; + } + + @Override + public void notify(T publisher, int subscriberData) { + ArrayList> changeSubscribers = this.subscribers; + for (int i = 0; i < changeSubscribers.size(); i++) { + ChangeSubscriber subscriber = changeSubscribers.get(i); + subscriber.notify(publisher, this.subscriberDatas.getInt(i)); + } + } + + @Override + public void forceUnsubscribe(T publisher, int subscriberData) { + ArrayList> changeSubscribers = this.subscribers; + for (int i = 0; i < changeSubscribers.size(); i++) { + ChangeSubscriber subscriber = changeSubscribers.get(i); + subscriber.forceUnsubscribe(publisher, this.subscriberDatas.getInt(i)); + } + } + + @Override + public void notifyCount(T publisher, int subscriberData, int newCount) { + ArrayList> changeSubscribers = this.subscribers; + for (int i = 0; i < changeSubscribers.size(); i++) { + ChangeSubscriber subscriber = changeSubscribers.get(i); + if (subscriber instanceof ChangeSubscriber.CountChangeSubscriber countChangeSubscriber) { + countChangeSubscriber.notifyCount(publisher, this.subscriberDatas.getInt(i), newCount); + } + } + } + + int indexOf(ChangeSubscriber subscriber, int subscriberData, boolean matchData) { + if (!matchData) { + return this.subscribers.indexOf(subscriber); + } else { + for (int i = 0; i < this.subscribers.size(); i++) { + if (this.subscribers.get(i) == subscriber && this.subscriberDatas.getInt(i) == subscriberData) { + return i; + } + } + return -1; + } + } + + @Override + public void notifyAfterEnchantmentChange(T publisher, int subscriberData) { + ArrayList> changeSubscribers = this.subscribers; + for (int i = 0; i < changeSubscribers.size(); i++) { + ChangeSubscriber subscriber = changeSubscribers.get(i); + if (subscriber instanceof ChangeSubscriber.EnchantmentSubscriber enchantmentSubscriber) { + enchantmentSubscriber.notifyAfterEnchantmentChange(publisher, this.subscriberDatas.getInt(i)); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java b/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java index 22da75d8197de29a150c9eade7994deecae53a10..aa8a5938984d6860deb67a36f85c83d96057d753 100644 --- a/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java +++ b/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java @@ -14,10 +14,11 @@ import java.util.Map.Entry; import java.util.stream.Collectors; import javax.annotation.Nullable; -public final class PatchedDataComponentMap implements DataComponentMap { +public final class PatchedDataComponentMap implements DataComponentMap, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangePublisher { // Leaf - Lithium equipment tracking private final DataComponentMap prototype; private Reference2ObjectMap, Optional> patch; private boolean copyOnWrite; + private net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber; // Leaf - Lithium equipment tracking public PatchedDataComponentMap(DataComponentMap baseComponents) { this(baseComponents, Reference2ObjectMaps.emptyMap(), true); @@ -128,6 +129,9 @@ public final class PatchedDataComponentMap implements DataComponentMap { } private void ensureMapOwnership() { + if (this.subscriber != null) { + this.subscriber.notify(this, 0); + } if (this.copyOnWrite) { this.patch = new Reference2ObjectArrayMap<>(this.patch); this.copyOnWrite = false; @@ -210,6 +214,22 @@ public final class PatchedDataComponentMap implements DataComponentMap { return new PatchedDataComponentMap(this.prototype, this.patch, true); } + // Leaf start - Lithium equipment tracking + @Override + public void subscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { + if (subscriberData != 0) { + throw new UnsupportedOperationException("ComponentMapImpl does not support subscriber data"); + } + this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.combine(this.subscriber, 0, subscriber, 0); + } + + @Override + public int unsubscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber) { + this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.without(this.subscriber, subscriber); + return 0; + } + // Leaf end - Lithium equipment tracking + @Override public boolean equals(Object object) { if (this == object) { diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java index 9de85972ba32fd2373f70f708aa1bfc6067e6e1c..47e031130ca30cbe3bb9917cb9a43612ec73b538 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -157,7 +157,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; // CraftBukkit end -public abstract class LivingEntity extends Entity implements Attackable { +public abstract class LivingEntity extends Entity implements Attackable, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.CountChangeSubscriber, net.caffeinemc.mods.lithium.common.entity.EquipmentEntity, net.caffeinemc.mods.lithium.common.entity.EquipmentEntity.TickableEnchantmentTrackingEntity, net.caffeinemc.mods.lithium.common.entity.EquipmentEntity.EquipmentTrackingEntity { // Leaf - Lithium equipment tracking private static final Logger LOGGER = LogUtils.getLogger(); private static final String TAG_ACTIVE_EFFECTS = "active_effects"; @@ -290,6 +290,10 @@ public abstract class LivingEntity extends Entity implements Attackable { public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API protected boolean shouldBurnInDay = false; public boolean shouldBurnInDay() { return this.shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Purpur - API for any mob to burn daylight + // Leaf start - Lithium equipment tracking + private boolean maybeHasTickableEnchantments = this instanceof net.minecraft.world.entity.player.Player; + private boolean equipmentChanged = true; + // Leaf end - Lithium equipment tracking @Override public float getBukkitYaw() { return this.getYHeadRot(); @@ -442,7 +446,7 @@ public abstract class LivingEntity extends Entity implements Attackable { Level world = this.level(); - if (world instanceof ServerLevel worldserver) { + if (this.maybeHasTickableEnchantments && world instanceof ServerLevel worldserver) { // Leaf - Lithium equipment tracking EnchantmentHelper.tickEffects(worldserver, this); } @@ -735,6 +739,7 @@ public abstract class LivingEntity extends Entity implements Attackable { boolean flag = itemstack1.isEmpty() && itemstack.isEmpty(); if (!flag && !ItemStack.isSameItemSameComponents(itemstack, itemstack1) && !this.firstTick) { + this.onEquipmentReplaced(itemstack, itemstack1); // Leaf - Lithium equipment tracking Equipable equipable = Equipable.get(itemstack1); if (!this.level().isClientSide() && !this.isSpectator()) { @@ -3382,6 +3387,7 @@ public abstract class LivingEntity extends Entity implements Attackable { Map map = this.collectEquipmentChanges(); if (map != null) { + if (!(this instanceof net.minecraft.world.entity.player.Player)) this.equipmentChanged = false; // Leaf - Lithium equipment tracking this.handleHandSwap(map); if (!map.isEmpty()) { this.handleEquipmentChanges(map); @@ -3392,6 +3398,10 @@ public abstract class LivingEntity extends Entity implements Attackable { @Nullable private Map collectEquipmentChanges() { + // Leaf start - Lithium equipment tracking + final boolean isArmorStandUpdateNoTick = this instanceof net.minecraft.world.entity.decoration.ArmorStand stand && !stand.canTick && stand.noTickEquipmentDirty; + if (!isArmorStandUpdateNoTick && !this.equipmentChanged) return null; + // Leaf end - Lithium equipment tracking Map map = null; EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // Gale - JettPack - reduce array allocations int i = aenumitemslot.length; @@ -4848,6 +4858,79 @@ public abstract class LivingEntity extends Entity implements Attackable { flag = true; return flag; } + // Leaf start - Lithium entity equipment tracking + @Override + public void updateHasTickableEnchantments(ItemStack oldStack, ItemStack newStack) { + if (!this.maybeHasTickableEnchantments) { + this.maybeHasTickableEnchantments = stackHasTickableEnchantment(newStack); + } + } + + @Override + public void notifyAfterEnchantmentChange(ItemStack publisher, int subscriberData) { + if (!this.maybeHasTickableEnchantments) { + this.maybeHasTickableEnchantments = stackHasTickableEnchantment(publisher); + } + } + + @Override + public void onEquipmentChanged() { + this.equipmentChanged = true; + } + + private static boolean stackHasTickableEnchantment(ItemStack stack) { + if (!stack.isEmpty()) { + net.minecraft.world.item.enchantment.ItemEnchantments enchantments = stack.get(DataComponents.ENCHANTMENTS); + if (enchantments != null && !enchantments.isEmpty()) { + for (Holder enchantmentEntry : enchantments.keySet()) { + if (!enchantmentEntry.value().getEffects(net.minecraft.world.item.enchantment.EnchantmentEffectComponents.TICK).isEmpty()) { + return true; + } + } + return false; + } + } + return false; + } + @Override + public void notify(@Nullable ItemStack publisher, int zero) { + if (this instanceof EquipmentTrackingEntity equipmentTrackingEntity) { + equipmentTrackingEntity.onEquipmentChanged(); + } + } + + @Override + public void notifyCount(ItemStack publisher, int zero, int newCount) { + if (newCount == 0) { + publisher.unsubscribeWithData(this, zero); + } + + this.onEquipmentReplaced(publisher, ItemStack.EMPTY); + } + + @Override + public void forceUnsubscribe(ItemStack publisher, int zero) { + throw new UnsupportedOperationException(); + } + + @Override + public void onEquipmentReplaced(ItemStack oldStack, ItemStack newStack) { + if (this instanceof TickableEnchantmentTrackingEntity enchantmentTrackingEntity) { + enchantmentTrackingEntity.updateHasTickableEnchantments(oldStack, newStack); + } + + if (this instanceof EquipmentTrackingEntity equipmentTrackingEntity) { + equipmentTrackingEntity.onEquipmentChanged(); + } + + if (!oldStack.isEmpty()) { + oldStack.unsubscribeWithData(this, 0); + } + if (!newStack.isEmpty()) { + newStack.subscribe(this, 0); + } + } + // Leaf end - Lithium entity equipment tracking public static record Fallsounds(SoundEvent small, SoundEvent big) { diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java index 9d196c8a8a0dc49a54264471429b6ff6da8c2b06..2ba607df6ee0500a7ac51cefd076840ae35e4827 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -97,7 +97,7 @@ import org.bukkit.event.entity.EntityUnleashEvent; import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; // CraftBukkit end -public abstract class Mob extends LivingEntity implements EquipmentUser, Leashable, Targeting { +public abstract class Mob extends LivingEntity implements EquipmentUser, Leashable, Targeting, net.caffeinemc.mods.lithium.common.entity.EquipmentEntity { // Leaf - Lithium equipment tracking private static final EntityDataAccessor DATA_MOB_FLAGS_ID = SynchedEntityData.defineId(Mob.class, EntityDataSerializers.BYTE); private static final int MOB_FLAG_NO_AI = 1; @@ -583,6 +583,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @Override public void readAdditionalSaveData(CompoundTag nbt) { + ItemStack prevBodyArmor = this.bodyArmorItem; // Leaf - Lithium equipment tracking super.readAdditionalSaveData(nbt); // CraftBukkit start - If looting or persistence is false only use it if it was set after we started using it @@ -607,7 +608,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab for (i = 0; i < this.armorItems.size(); ++i) { nbttagcompound1 = nbttaglist.getCompound(i); - this.armorItems.set(i, ItemStack.parseOptional(this.registryAccess(), nbttagcompound1)); + // Leaf start - Lithium equipment tracking + ItemStack currStack = ItemStack.parseOptional(this.registryAccess(), nbttagcompound1); + ItemStack prevStack = this.armorItems.set(i, currStack); + this.trackEquipChange(prevStack, currStack); + // Leaf end - Lithium equipment tracking } } @@ -624,7 +629,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab for (i = 0; i < this.handItems.size(); ++i) { nbttagcompound1 = nbttaglist.getCompound(i); - this.handItems.set(i, ItemStack.parseOptional(this.registryAccess(), nbttagcompound1)); + // Leaf start - Lithium equipment tracking + ItemStack currStack = ItemStack.parseOptional(this.registryAccess(), nbttagcompound1); + ItemStack prevStack = this.handItems.set(i, currStack); + this.trackEquipChange(prevStack, currStack); + // Leaf end - Lithium equipment tracking } } @@ -661,6 +670,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab this.ticksSinceLastInteraction = nbt.getInt("Purpur.ticksSinceLastInteraction"); } // Purpur end + // Leaf start - Lithium equipment tracking + if (prevBodyArmor != this.bodyArmorItem) { + this.trackEquipChange(prevBodyArmor, this.bodyArmorItem); + } + // Leaf end - Lithium equipment tracking } @Override @@ -1865,4 +1879,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab } } // Purpur end + + // Leaf start - Lithium equipment tracking + private void trackEquipChange(ItemStack prevStack, ItemStack currStack) { + this.onEquipmentReplaced(prevStack, currStack); + } + // Leaf end - Lithium equipment tracking } diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java index 3bb46ed871fd56bbbe52cfd2575f9e853e03cd73..7e3dedc1df905ec8da637915649f9a840cbb4ed4 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java @@ -52,7 +52,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerArmorStandManipulateEvent; // CraftBukkit end -public class ArmorStand extends LivingEntity { +public class ArmorStand extends LivingEntity implements net.caffeinemc.mods.lithium.common.entity.EquipmentEntity { // Leaf - Lithium equipment tracking public static final int WOBBLE_TIME = 5; private static final boolean ENABLE_ARMS = true; @@ -100,7 +100,7 @@ public class ArmorStand extends LivingEntity { public boolean canTick = true; public boolean canTickSetByAPI = false; private boolean noTickPoseDirty = false; - private boolean noTickEquipmentDirty = false; + public boolean noTickEquipmentDirty = false; // Leaf - Lithium equipment tracking - private -> public // Paper end - Allow ArmorStands not to tick public boolean canMovementTick = true; // Purpur @@ -269,7 +269,11 @@ public class ArmorStand extends LivingEntity { for (i = 0; i < this.armorItems.size(); ++i) { nbttagcompound1 = nbttaglist.getCompound(i); - this.armorItems.set(i, ItemStack.parseOptional(this.registryAccess(), nbttagcompound1)); + // Leaf start - Lithium equipment tracking + ItemStack currElement = ItemStack.parseOptional(this.registryAccess(), nbttagcompound1); + ItemStack prevElement = this.armorItems.set(i, currElement); + this.trackEquipChange(prevElement, currElement); + // Leaf end - Lithium equipment tracking } } @@ -278,7 +282,11 @@ public class ArmorStand extends LivingEntity { for (i = 0; i < this.handItems.size(); ++i) { nbttagcompound1 = nbttaglist.getCompound(i); - this.handItems.set(i, ItemStack.parseOptional(this.registryAccess(), nbttagcompound1)); + // Leaf start - Lithium equipment tracking + ItemStack currStack = ItemStack.parseOptional(this.registryAccess(), nbttagcompound1); + ItemStack prevStack = this.handItems.set(i, currStack); + this.trackEquipChange(prevStack, currStack); + // Leaf end - Lithium equipment tracking } } @@ -640,7 +648,11 @@ public class ArmorStand extends LivingEntity { itemstack = (ItemStack) this.handItems.get(i); if (!itemstack.isEmpty()) { 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 - this.handItems.set(i, ItemStack.EMPTY); + // Leaf start - Lithium equipment tracking + ItemStack emptyStack = ItemStack.EMPTY; + ItemStack prevStack = this.handItems.set(i, emptyStack); + this.trackEquipChange(prevStack, emptyStack); + // Leaf end - Lithium equipment tracking } } @@ -648,7 +660,11 @@ public class ArmorStand extends LivingEntity { itemstack = (ItemStack) this.armorItems.get(i); if (!itemstack.isEmpty()) { 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 - this.armorItems.set(i, ItemStack.EMPTY); + // Leaf start - Lithium equipment tracking + ItemStack emptyStack = ItemStack.EMPTY; + ItemStack prevStack = this.armorItems.set(i, ItemStack.EMPTY); + this.trackEquipChange(prevStack, emptyStack); + // Leaf end - Lithium equipment tracking } } return this.dropAllDeathLoot(world, damageSource); // CraftBukkit - moved from above // Paper @@ -695,10 +711,12 @@ public class ArmorStand extends LivingEntity { this.updatePose(); } + // Leaf start - Lithium equipment tracking if (this.noTickEquipmentDirty) { - this.noTickEquipmentDirty = false; this.detectEquipmentUpdatesPublic(); + this.noTickEquipmentDirty = false; } + // Leaf end - Lithium equipment tracking return; } @@ -1034,4 +1052,10 @@ public class ArmorStand extends LivingEntity { if (this.canMovementTick && this.canMove) super.aiStep(); } // Purpur end + + // Leaf start - Lithium equipment tracking + private void trackEquipChange(ItemStack prevStack, ItemStack currStack) { + this.onEquipmentReplaced(prevStack, currStack); + } + // Leaf end - Lithium equipment tracking } diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index 933b7519da5330ea8acd05c337201f52cab12c3c..e5579b15f305fb216dadd8023c16178a342b4add 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -120,7 +120,7 @@ import org.bukkit.event.player.PlayerItemDamageEvent; import org.bukkit.event.world.StructureGrowEvent; // CraftBukkit end -public final class ItemStack implements DataComponentHolder { +public final class ItemStack implements DataComponentHolder, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangePublisher, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber { // Leaf - Lithium equipment tracking public static final Codec> ITEM_NON_AIR_CODEC = BuiltInRegistries.ITEM.holderByNameCodec().validate((holder) -> { return holder.is((Holder) Items.AIR.builtInRegistryHolder()) ? DataResult.error(() -> { @@ -228,6 +228,11 @@ public final class ItemStack implements DataComponentHolder { private PatchedDataComponentMap components; @Nullable private Entity entityRepresentation; + // Leaf start - Lithium equipment tracking + @Nullable + private net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber; + private int subscriberData; + // Leaf end - Lithium equipment tracking private static DataResult validateStrict(ItemStack stack) { DataResult dataresult = ItemStack.validateComponents(stack.getComponents()); @@ -1368,6 +1373,21 @@ public final class ItemStack implements DataComponentHolder { } public void setCount(int count) { + // Leaf start - Lithium equipment tracking + if (count != this.count) { + if (this.subscriber instanceof net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.CountChangeSubscriber countChangeSubscriber) { + countChangeSubscriber.notifyCount(this, this.subscriberData, count); + } + if (count == 0) { + this.components.unsubscribe(this); + if (this.subscriber != null) { + this.subscriber.forceUnsubscribe(this, this.subscriberData); + this.subscriber = null; + this.subscriberData = 0; + } + } + } + // Leaf end - Lithium equipment tracking this.count = count; } @@ -1423,4 +1443,87 @@ public final class ItemStack implements DataComponentHolder { public boolean canBeHurtBy(DamageSource source) { return !this.has(DataComponents.FIRE_RESISTANT) || !source.is(DamageTypeTags.IS_FIRE); } + + // Leaf start - Lithium equipment tracking + @Override + public void subscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { + if (this.isEmpty()) { + throw new IllegalStateException("Cannot subscribe to an empty ItemStack!"); + } + + if (this.subscriber == null) { + this.startTrackingChanges(); + } + this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.combine(this.subscriber, this.subscriberData, subscriber, subscriberData); + if (this.subscriber instanceof net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.Multi) { + this.subscriberData = 0; + } else { + this.subscriberData = subscriberData; + } + } + + @Override + public int unsubscribe(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber) { + if (this.isEmpty()) { + throw new IllegalStateException("Cannot unsubscribe from an empty ItemStack!"); + } + + int retval = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.dataOf(this.subscriber, subscriber, this.subscriberData); + this.subscriberData = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.dataWithout(this.subscriber, subscriber, this.subscriberData); + this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.without(this.subscriber, subscriber); + + if (this.subscriber == null) { + this.components.unsubscribe(this); + } + return retval; + } + + @Override + public void unsubscribeWithData(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { + if (this.isEmpty()) { + throw new IllegalStateException("Cannot unsubscribe from an empty ItemStack!"); + } + + this.subscriberData = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.dataWithout(this.subscriber, subscriber, this.subscriberData, subscriberData, true); + this.subscriber = net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.without(this.subscriber, subscriber, subscriberData, true); + + if (this.subscriber == null) { + this.components.unsubscribe(this); + } + } + + @Override + public boolean isSubscribedWithData(net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { + if (this.isEmpty()) { + throw new IllegalStateException("Cannot be subscribed to an empty ItemStack!"); + } + + return net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.containsSubscriber(this.subscriber, this.subscriberData, subscriber, subscriberData); + } + + @Override + public void forceUnsubscribe(PatchedDataComponentMap publisher, int subscriberData) { + if (publisher != this.components) { + throw new IllegalStateException("Invalid publisher, expected " + this.components + " but got " + publisher); + } + this.subscriber.forceUnsubscribe(this, this.subscriberData); + this.subscriber = null; + this.subscriberData = 0; + } + + @Override + public void notify(PatchedDataComponentMap publisher, int subscriberData) { + if (publisher != this.components) { + throw new IllegalStateException("Invalid publisher, expected " + this.components + " but got " + publisher); + } + + if (this.subscriber != null) { + this.subscriber.notify(this, this.subscriberData); + } + } + + private void startTrackingChanges() { + this.components.subscribe(this, 0); + } + // Leaf end - Lithium equipment tracking }