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/net/minecraft/core/component/PatchedDataComponentMap.java b/net/minecraft/core/component/PatchedDataComponentMap.java index a8c6549f772208cd543607224fef2c2389b14f24..709631db548a16a969a373e26ebbcd6983e35590 100644 --- a/net/minecraft/core/component/PatchedDataComponentMap.java +++ b/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 prototype) { this(prototype, Reference2ObjectMaps.emptyMap(), true); @@ -135,6 +136,11 @@ public final class PatchedDataComponentMap implements DataComponentMap { } private void ensureMapOwnership() { + // Leaf start - Lithium - equipment tracking + if (this.subscriber != null) { + this.subscriber.notify(this, 0); + } + // Leaf end - Lithium - equipment tracking if (this.copyOnWrite) { this.patch = new Reference2ObjectArrayMap<>(this.patch); this.copyOnWrite = false; @@ -221,6 +227,22 @@ public final class PatchedDataComponentMap implements DataComponentMap { return (DataComponentMap)(this.patch.isEmpty() ? this.prototype : this.copy()); } + // 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 other) { return this == other diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java index 0138bd4d95a592bfa5ccbb33fa6c1201f289fd2a..00233a7066d751821566b43993e8c45e7dad95d0 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -159,7 +159,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"; private static final ResourceLocation SPEED_MODIFIER_POWDER_SNOW_ID = ResourceLocation.withDefaultNamespace("powder_snow"); @@ -305,6 +305,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(); @@ -445,7 +449,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.getSleepingPos().ifPresent(this::setPosToBed); } - if (this.level() instanceof ServerLevel serverLevel) { + if (this.maybeHasTickableEnchantments && this.level() instanceof ServerLevel serverLevel) { // Leaf - Lithium - equipment tracking EnchantmentHelper.tickEffects(serverLevel, this); } @@ -738,6 +742,7 @@ public abstract class LivingEntity extends Entity implements Attackable { if (!this.level().isClientSide() && !this.isSpectator()) { boolean flag = newItem.isEmpty() && oldItem.isEmpty(); if (!flag && !ItemStack.isSameItemSameComponents(oldItem, newItem) && !this.firstTick) { + this.onEquipmentReplaced(oldItem, newItem); // Leaf - Lithium - equipment tracking Equippable equippable = newItem.get(DataComponents.EQUIPPABLE); if (!this.isSilent() && equippable != null && slot == equippable.slot() && !silent) { // CraftBukkit this.level() @@ -3355,6 +3360,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public void detectEquipmentUpdatesPublic() { // CraftBukkit 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); @@ -3364,6 +3370,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; // Paper start - EntityEquipmentChangedEvent record EquipmentChangeImpl(org.bukkit.inventory.ItemStack oldItem, org.bukkit.inventory.ItemStack newItem) implements io.papermc.paper.event.entity.EntityEquipmentChangedEvent.EquipmentChange { @@ -4723,6 +4733,81 @@ public abstract class LivingEntity extends Entity implements Attackable { return this.lastHurtByPlayerTime; } + // Leaf start - Lithium - 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 - equipment tracking + public record Fallsounds(SoundEvent small, SoundEvent big) { } } diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java index faf05f0c8f273bc723bbe54c70aebdd26c479a6b..54eeb0b112112bc5d3f4165c0ea43cf67931a739 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java @@ -94,7 +94,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; private static final int MOB_FLAG_LEFTHANDED = 2; @@ -516,6 +516,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @Override public void readAdditionalSaveData(CompoundTag compound) { + ItemStack prevBodyArmor = this.bodyArmorItem; // Leaf - Lithium - equipment tracking super.readAdditionalSaveData(compound); // CraftBukkit start - If looting or persistence is false only use it if it was set after we started using it if (compound.contains("CanPickUpLoot", 99)) { @@ -535,7 +536,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab for (int i = 0; i < this.armorItems.size(); i++) { CompoundTag compound1 = list.getCompound(i); - this.armorItems.set(i, ItemStack.parseOptional(this.registryAccess(), compound1)); + // Leaf start - Lithium - equipment tracking + ItemStack currStack = ItemStack.parseOptional(this.registryAccess(), compound1); + ItemStack prevStack = this.armorItems.set(i, currStack); + this.trackEquipChange(prevStack, currStack); + // Leaf end - Lithium - equipment tracking } } else { this.armorItems.replaceAll(itemStack -> ItemStack.EMPTY); @@ -556,7 +561,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab for (int i = 0; i < this.handItems.size(); i++) { CompoundTag compound1 = list.getCompound(i); - this.handItems.set(i, ItemStack.parseOptional(this.registryAccess(), compound1)); + // Leaf start - Lithium - equipment tracking + ItemStack currStack = ItemStack.parseOptional(this.registryAccess(), compound1); + ItemStack prevStack = this.handItems.set(i, currStack); + this.trackEquipChange(prevStack, currStack); + // Leaf end - Lithium - equipment tracking } } else { this.handItems.replaceAll(itemStack -> ItemStack.EMPTY); @@ -599,6 +608,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab this.ticksSinceLastInteraction = compound.getInt("Purpur.ticksSinceLastInteraction"); } // Purpur end - Entity lifespan + // Leaf start - Lithium - equipment tracking + if (prevBodyArmor != this.bodyArmorItem) { + this.trackEquipChange(prevBodyArmor, this.bodyArmorItem); + } + // Leaf end - Lithium - equipment tracking } @Override @@ -1750,4 +1764,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab } } // Purpur end - Ridables + + // Leaf start - Lithium - equipment tracking + private void trackEquipChange(ItemStack prevStack, ItemStack currStack) { + this.onEquipmentReplaced(prevStack, currStack); + } + // Leaf end - Lithium - equipment tracking } diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java index a31bbd8f3fff4fb4b1b33877d5835b93fc248f65..21153f37c169e987d7876d1b914105223ac10ee7 100644 --- a/net/minecraft/world/entity/decoration/ArmorStand.java +++ b/net/minecraft/world/entity/decoration/ArmorStand.java @@ -46,7 +46,7 @@ import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; -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; public static final Rotations DEFAULT_HEAD_POSE = new Rotations(0.0F, 0.0F, 0.0F); @@ -233,7 +233,11 @@ public class ArmorStand extends LivingEntity { for (int i = 0; i < this.armorItems.size(); i++) { CompoundTag compound1 = list.getCompound(i); - this.armorItems.set(i, ItemStack.parseOptional(this.registryAccess(), compound1)); + // Leaf start - Lithium - equipment tracking + ItemStack currElement = ItemStack.parseOptional(this.registryAccess(), compound1); + ItemStack prevElement = this.armorItems.set(i, currElement); + this.trackEquipChange(prevElement, currElement); + // Leaf end - Lithium - equipment tracking } } @@ -242,7 +246,11 @@ public class ArmorStand extends LivingEntity { for (int i = 0; i < this.handItems.size(); i++) { CompoundTag compound1 = list.getCompound(i); - this.handItems.set(i, ItemStack.parseOptional(this.registryAccess(), compound1)); + // Leaf start - Lithium - equipment tracking + ItemStack currStack = ItemStack.parseOptional(this.registryAccess(), compound1); + ItemStack prevStack = this.handItems.set(i, currStack); + this.trackEquipChange(prevStack, currStack); + // Leaf end - Lithium - equipment tracking } } @@ -578,7 +586,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 } } @@ -586,7 +598,11 @@ public class ArmorStand extends LivingEntity { ItemStack itemStack = this.armorItems.get(ix); 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(ix, ItemStack.EMPTY); + // Leaf start - Lithium - equipment tracking + ItemStack emptyStack = ItemStack.EMPTY; + ItemStack prevStack = this.armorItems.set(ix, ItemStack.EMPTY); + this.trackEquipChange(prevStack, emptyStack); + // Leaf end - Lithium - equipment tracking } } return this.dropAllDeathLoot(level, damageSource); // CraftBukkit - moved from above // Paper @@ -632,10 +648,12 @@ public class ArmorStand extends LivingEntity { this.updatePose(); } + // Leaf start - Lithium - equipment tracking if (this.noTickEquipmentDirty) { - this.noTickEquipmentDirty = false; this.detectEquipmentUpdatesPublic(); + this.noTickEquipmentDirty = false; // Remove dirty mark after handling equipment update for armor stand } + // Leaf end - Lithium - equipment tracking return; } @@ -968,4 +986,10 @@ public class ArmorStand extends LivingEntity { if (this.canMovementTick && this.canMove) super.aiStep(); } // Purpur end - Movement options for armor stands + + // Leaf start - Lithium - equipment tracking + private void trackEquipChange(ItemStack prevStack, ItemStack currStack) { + this.onEquipmentReplaced(prevStack, currStack); + } + // Leaf end - Lithium - equipment tracking } diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java index aa2c00be86f42a6674694a20545399e441b75199..fd7c1e800cbd4919a1a47f6c468c8776535bd028 100644 --- a/net/minecraft/world/item/ItemStack.java +++ b/net/minecraft/world/item/ItemStack.java @@ -95,7 +95,7 @@ import net.minecraft.world.level.saveddata.maps.MapId; import org.apache.commons.lang3.mutable.MutableBoolean; import org.slf4j.Logger; -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 private static final List OP_NBT_WARNING = List.of( Component.translatable("item.op_warning.line1").withStyle(ChatFormatting.RED, ChatFormatting.BOLD), Component.translatable("item.op_warning.line2").withStyle(ChatFormatting.RED), @@ -202,6 +202,11 @@ public final class ItemStack implements DataComponentHolder { 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 = validateComponents(stack.getComponents()); @@ -1375,6 +1380,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; } @@ -1430,4 +1450,87 @@ public final class ItemStack implements DataComponentHolder { Repairable repairable = this.get(DataComponents.REPAIRABLE); return repairable != null && repairable.isValidRepairItem(item); } + + // 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 }