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/enchantment_ticking/LivingEntityMixin.java" * "net/caffeinemc/mods/lithium/mixin/entity/equipment_tracking/equipment_changes/LivingEntityMixin.java" * "net/caffeinemc/mods/lithium/mixin/entity/equipment_tracking/EntityEquipmentMixin.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 3af6c1e2549ba3aeb60aa9d498a976be3680c0ee..57e0fc928cf97bac8d6ed3f2b746c2677a90c1b5 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.lithium$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 lithium$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 lithium$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/EntityEquipment.java b/net/minecraft/world/entity/EntityEquipment.java index 90814ad07a2686c5a274860395f5aca29cc3bf13..369f0176a636def905fb2a2df1a0e2cadd1eb3b2 100644 --- a/net/minecraft/world/entity/EntityEquipment.java +++ b/net/minecraft/world/entity/EntityEquipment.java @@ -7,7 +7,7 @@ import java.util.Objects; import java.util.Map.Entry; import net.minecraft.world.item.ItemStack; -public class EntityEquipment { +public class EntityEquipment implements net.caffeinemc.mods.lithium.common.entity.EquipmentInfo, net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber.CountChangeSubscriber { // Leaf - Lithium - equipment tracking public static final Codec CODEC = Codec.unboundedMap(EquipmentSlot.CODEC, ItemStack.CODEC).xmap(map -> { EnumMap map1 = new EnumMap<>(EquipmentSlot.class); map1.putAll((Map)map); @@ -18,6 +18,11 @@ public class EntityEquipment { return map; }); private final EnumMap items; + // Leaf start - Lithium - equipment tracking + boolean shouldTickEnchantments = false; + ItemStack recheckEnchantmentForStack = null; + boolean hasUnsentEquipmentChanges = true; + // Leaf end - Lithium - equipment tracking private EntityEquipment(EnumMap items) { this.items = items; @@ -29,7 +34,11 @@ public class EntityEquipment { public ItemStack set(EquipmentSlot slot, ItemStack stack) { stack.getItem().verifyComponentsAfterLoad(stack); - return Objects.requireNonNullElse(this.items.put(slot, stack), ItemStack.EMPTY); + // Leaf start - Lithium - equipment tracking + ItemStack newStack = Objects.requireNonNullElse(this.items.put(slot, stack), ItemStack.EMPTY); + this.onEquipmentReplaced(this.get(slot), newStack); + return newStack; + // Leaf end - Lithium - equipment tracking } public ItemStack get(EquipmentSlot slot) { @@ -56,8 +65,21 @@ public class EntityEquipment { } public void setAll(EntityEquipment equipment) { + this.onClear(); // Leaf - Lithium - equipment tracking this.items.clear(); this.items.putAll(equipment.items); + // Leaf start - Lithium - equipment tracking + for (ItemStack newStack : this.items.values()) { + if (!newStack.isEmpty()) { + if (!this.shouldTickEnchantments) { + this.shouldTickEnchantments = stackHasTickableEnchantment(newStack); + } + if (!newStack.isEmpty()) { + newStack.lithium$subscribe(this, 0); + } + } + } + // Leaf end - Lithium - equipment tracking } public void dropAll(LivingEntity entity) { @@ -70,6 +92,7 @@ public class EntityEquipment { public void clear() { this.items.replaceAll((equipmentSlot, itemStack) -> ItemStack.EMPTY); + this.onClear(); // Leaf - Lithium - equipment tracking } // Paper start - EntityDeathEvent @@ -78,4 +101,99 @@ public class EntityEquipment { return this.items.containsKey(slot); } // Paper end - EntityDeathEvent + + @Override + public boolean lithium$shouldTickEnchantments() { + this.processScheduledEnchantmentCheck(null); + return this.shouldTickEnchantments; + } + + @Override + public boolean lithium$hasUnsentEquipmentChanges() { + return this.hasUnsentEquipmentChanges; + } + + @Override + public void lithium$onEquipmentChangesSent() { + this.hasUnsentEquipmentChanges = false; + } + + private void onClear() { + this.shouldTickEnchantments = false; + this.recheckEnchantmentForStack = null; + this.hasUnsentEquipmentChanges = true; + + for (ItemStack oldStack : this.items.values()) { + if (!oldStack.isEmpty()) { + oldStack.lithium$unsubscribeWithData(this, 0); + } + } + } + + private void onEquipmentReplaced(ItemStack oldStack, ItemStack newStack) { + if (!this.shouldTickEnchantments) { + if (this.recheckEnchantmentForStack == oldStack) { + this.recheckEnchantmentForStack = null; + } + this.shouldTickEnchantments = stackHasTickableEnchantment(newStack); + } + + this.hasUnsentEquipmentChanges = true; + + if (!oldStack.isEmpty()) { + oldStack.lithium$unsubscribeWithData(this, 0); + } + if (!newStack.isEmpty()) { + newStack.lithium$subscribe(this, 0); + } + } + + private static boolean stackHasTickableEnchantment(ItemStack stack) { + if (!stack.isEmpty()) { + net.minecraft.world.item.enchantment.ItemEnchantments enchantments = stack.get(net.minecraft.core.component.DataComponents.ENCHANTMENTS); + if (enchantments != null && !enchantments.isEmpty()) { + for (net.minecraft.core.Holder enchantmentEntry : enchantments.keySet()) { + if (!enchantmentEntry.value().getEffects(net.minecraft.world.item.enchantment.EnchantmentEffectComponents.TICK).isEmpty()) { + return true; + } + } + } + } + return false; + } + + @Override + public void lithium$notify(@org.jetbrains.annotations.Nullable ItemStack publisher, int zero) { + this.hasUnsentEquipmentChanges = true; + + if (!this.shouldTickEnchantments) { + this.processScheduledEnchantmentCheck(publisher); + this.scheduleEnchantmentCheck(publisher); + } + } + + private void scheduleEnchantmentCheck(@org.jetbrains.annotations.Nullable ItemStack toCheck) { + this.recheckEnchantmentForStack = toCheck; + } + + private void processScheduledEnchantmentCheck(@org.jetbrains.annotations.Nullable ItemStack ignoredStack) { + if (this.recheckEnchantmentForStack != null && this.recheckEnchantmentForStack != ignoredStack) { + this.shouldTickEnchantments = stackHasTickableEnchantment(this.recheckEnchantmentForStack); + this.recheckEnchantmentForStack = null; + } + } + + @Override + public void lithium$notifyCount(ItemStack publisher, int zero, int newCount) { + if (newCount == 0) { + publisher.lithium$unsubscribeWithData(this, zero); + } + + this.onEquipmentReplaced(publisher, ItemStack.EMPTY); + } + + @Override + public void lithium$forceUnsubscribe(ItemStack publisher, int zero) { + throw new UnsupportedOperationException(); + } } diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java index b3e9ad0669bb4b91d5d991f106b225e914a4e68f..a927e8a7d9149f5b7abaae50ba8d4fdc6ec87b55 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -417,7 +417,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.getSleepingPos().ifPresent(this::setPosToBed); } - if (this.level() instanceof ServerLevel serverLevel) { + if ((this instanceof Player || this.equipment.lithium$shouldTickEnchantments()) && this.level() instanceof ServerLevel serverLevel) { // Leaf - Lithium - equipment tracking EnchantmentHelper.tickEffects(serverLevel, this); } @@ -3394,6 +3394,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public void detectEquipmentUpdates() { Map map = this.collectEquipmentChanges(); if (map != null) { + if (!(this instanceof net.minecraft.world.entity.player.Player)) this.equipment.lithium$onEquipmentChangesSent();; // Leaf - Lithium - equipment tracking this.handleHandSwap(map); if (!map.isEmpty()) { this.handleEquipmentChanges(map); @@ -3403,6 +3404,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.equipment.lithium$hasUnsentEquipmentChanges()) 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 { diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java index 0417175c7beabbca53cd080d158001eabe3941f0..cb2a8f9cff99a7a906bc7be09d301728742bc11e 100644 --- a/net/minecraft/world/entity/decoration/ArmorStand.java +++ b/net/minecraft/world/entity/decoration/ArmorStand.java @@ -559,8 +559,8 @@ public class ArmorStand extends LivingEntity { } if (this.noTickEquipmentDirty) { - this.noTickEquipmentDirty = false; this.detectEquipmentUpdates(); + this.noTickEquipmentDirty = false; // Leaf - Lithium - equipment tracking - Remove dirty mark after handling equipment update for armor stand } return; diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java index 07f1b27116baf2a06e6cd4eeaa8639e166fb362d..d1c8b4a2a32d49d90f2f2aa460915d319a781535 100644 --- a/net/minecraft/world/item/ItemStack.java +++ b/net/minecraft/world/item/ItemStack.java @@ -97,7 +97,7 @@ import net.minecraft.world.level.block.state.pattern.BlockInWorld; 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), @@ -168,6 +168,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 public static DataResult validateStrict(ItemStack stack) { DataResult dataResult = validateComponents(stack.getComponents()); @@ -1402,6 +1407,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.lithium$notifyCount(this, this.subscriberData, count); + } + if (count == 0) { + this.components.lithium$unsubscribe(this); + if (this.subscriber != null) { + this.subscriber.lithium$forceUnsubscribe(this, this.subscriberData); + this.subscriber = null; + this.subscriberData = 0; + } + } + } + // Leaf end - Lithium - equipment tracking this.count = count; } @@ -1457,4 +1477,87 @@ public final class ItemStack implements DataComponentHolder { public boolean canDestroyBlock(BlockState state, Level level, BlockPos pos, Player player) { return this.getItem().canDestroyBlock(this, state, level, pos, player); } + + // Leaf start - Lithium - equipment tracking + @Override + public void lithium$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 lithium$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.lithium$unsubscribe(this); + } + return retval; + } + + @Override + public void lithium$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.lithium$unsubscribe(this); + } + } + + @Override + public boolean lithium$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 lithium$forceUnsubscribe(PatchedDataComponentMap publisher, int subscriberData) { + if (publisher != this.components) { + throw new IllegalStateException("Invalid publisher, expected " + this.components + " but got " + publisher); + } + this.subscriber.lithium$forceUnsubscribe(this, this.subscriberData); + this.subscriber = null; + this.subscriberData = 0; + } + + @Override + public void lithium$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.lithium$notify(this, this.subscriberData); + } + } + + private void startTrackingChanges() { + this.components.lithium$subscribe(this, 0); + } + // Leaf end - Lithium - equipment tracking }