diff --git a/leaves-server/minecraft-patches/features/0140-Lithium-Equipment-Tracking.patch b/leaves-server/minecraft-patches/features/0140-Lithium-Equipment-Tracking.patch new file mode 100644 index 00000000..51b82a79 --- /dev/null +++ b/leaves-server/minecraft-patches/features/0140-Lithium-Equipment-Tracking.patch @@ -0,0 +1,267 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MC_XiaoHei +Date: Tue, 9 Nov 2077 00:00:00 +0800 +Subject: [PATCH] Lithium Equipment Tracking + +Origin patch author: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> +Origin patch link: https://github.com/Winds-Studio/Leaf/blob/ver/1.21.8/leaf-server/minecraft-patches/features/0268-Lithium-equipment-tracking.patch + +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) +Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) + +diff --git a/net/minecraft/world/entity/EntityEquipment.java b/net/minecraft/world/entity/EntityEquipment.java +index 90814ad07a2686c5a274860395f5aca29cc3bf13..758a8bd797f06cd6998f71b095c475e09906e343 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 org.leavesmc.leaves.lithium.common.util.change_tracking.ChangeSubscriber.CountChangeSubscriber { // Leaves - 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; ++ // Leaves start - Lithium - equipment tracking ++ boolean shouldTickEnchantments = false; ++ ItemStack recheckEnchantmentForStack = null; ++ boolean hasUnsentEquipmentChanges = true; ++ // Leaves end - Lithium - equipment tracking + + private EntityEquipment(EnumMap items) { + this.items = items; +@@ -29,7 +34,13 @@ public class EntityEquipment { + + public ItemStack set(EquipmentSlot slot, ItemStack stack) { + stack.getItem().verifyComponentsAfterLoad(stack); +- return Objects.requireNonNullElse(this.items.put(slot, stack), ItemStack.EMPTY); ++ // Leaves start - Lithium - equipment tracking ++ ItemStack oldStack = Objects.requireNonNullElse(this.items.put(slot, stack), ItemStack.EMPTY); ++ if (org.leavesmc.leaves.LeavesConfig.performance.equipmentTracking) { ++ this.onEquipmentReplaced(oldStack, stack); ++ } ++ return oldStack; ++ // Leaves end - Lithium - equipment tracking + } + + public ItemStack get(EquipmentSlot slot) { +@@ -56,8 +67,23 @@ public class EntityEquipment { + } + + public void setAll(EntityEquipment equipment) { ++ if (org.leavesmc.leaves.LeavesConfig.performance.equipmentTracking) this.onClear(); // Leaves - Lithium - equipment tracking + this.items.clear(); + this.items.putAll(equipment.items); ++ // Leaves start - Lithium - equipment tracking ++ if (org.leavesmc.leaves.LeavesConfig.performance.equipmentTracking) { ++ for (net.minecraft.world.item.ItemStack newStack : this.items.values()) { ++ if (!newStack.isEmpty()) { ++ if (!this.shouldTickEnchantments) { ++ this.shouldTickEnchantments = stackHasTickableEnchantment(newStack); ++ } ++ if (!newStack.isEmpty()) { ++ newStack.lithium$subscribe(this, 0); ++ } ++ } ++ } ++ } ++ // Leaves end - Lithium - equipment tracking + } + + public void dropAll(LivingEntity entity) { +@@ -70,6 +96,7 @@ public class EntityEquipment { + + public void clear() { + this.items.replaceAll((equipmentSlot, itemStack) -> ItemStack.EMPTY); ++ if (org.leavesmc.leaves.LeavesConfig.performance.equipmentTracking) this.onClear(); // Leaves - Lithium - equipment tracking + } + + // Paper start - EntityDeathEvent +@@ -78,4 +105,98 @@ public class EntityEquipment { + return this.items.containsKey(slot); + } + // Paper end - EntityDeathEvent ++ ++ // Leaves start - Lithium - equipment tracking ++ public boolean lithium$shouldTickEnchantments() { ++ this.processScheduledEnchantmentCheck(null); ++ return this.shouldTickEnchantments; ++ } ++ ++ public boolean lithium$hasUnsentEquipmentChanges() { ++ return this.hasUnsentEquipmentChanges; ++ } ++ ++ 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(); ++ } ++ // Leaves end - Lithium - equipment tracking + } +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index 951300caeca0421cabda44496ed2f09fc2258dd0..eb1a6644670c126c03ef871c8ac4bc2d0e389872 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -425,9 +425,17 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + this.getSleepingPos().ifPresent(this::setPosToBed); + } + +- if (this.level() instanceof ServerLevel serverLevel) { +- EnchantmentHelper.tickEffects(serverLevel, this); ++ // Leaves start - Lithium - equipment tracking ++ if (org.leavesmc.leaves.LeavesConfig.performance.equipmentTracking) { ++ if ((this instanceof Player || this.equipment.lithium$shouldTickEnchantments()) && this.level() instanceof ServerLevel serverLevel) { ++ EnchantmentHelper.tickEffects(serverLevel, this); ++ } ++ } else { ++ if (this.level() instanceof ServerLevel serverLevel) { ++ EnchantmentHelper.tickEffects(serverLevel, this); ++ } + } ++ // Leaves end - Lithium - equipment tracking + + super.baseTick(); + ProfilerFiller profilerFiller = Profiler.get(); +@@ -3348,6 +3356,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + public void detectEquipmentUpdates() { + Map map = this.collectEquipmentChanges(); + if (map != null) { ++ if (org.leavesmc.leaves.LeavesConfig.performance.equipmentTracking && !(this instanceof net.minecraft.world.entity.player.Player)) this.equipment.lithium$onEquipmentChangesSent(); // Leaves - Lithium - equipment tracking + this.handleHandSwap(map); + if (!map.isEmpty()) { + this.handleEquipmentChanges(map); +@@ -3357,6 +3366,14 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + + @Nullable + private Map collectEquipmentChanges() { ++ // Leaves start - Lithium - equipment tracking ++ if (org.leavesmc.leaves.LeavesConfig.performance.equipmentTracking) { ++ final boolean isArmorStandUpdateNoTick = this instanceof net.minecraft.world.entity.decoration.ArmorStand stand && !stand.canTick && stand.noTickEquipmentDirty; ++ if (!isArmorStandUpdateNoTick && !this.equipment.lithium$hasUnsentEquipmentChanges()) { ++ return null; ++ } ++ } ++ // Leaves 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 d7725b5ca689e3d5b512baab04e113be77c0b2ee..abe5d681f81f60facc019660a6eae833e5742cef 100644 +--- a/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -89,7 +89,7 @@ public class ArmorStand extends LivingEntity { + // Paper start - Allow ArmorStands not to tick + public boolean canTick = true; + public boolean canTickSetByAPI = false; +- private boolean noTickEquipmentDirty = false; ++ public boolean noTickEquipmentDirty = false; // Leaves - private -> public + // Paper end - Allow ArmorStands not to tick + + public ArmorStand(EntityType entityType, Level level) { +@@ -530,8 +530,9 @@ public class ArmorStand extends LivingEntity { + public void tick() { + if (!this.canTick) { + if (this.noTickEquipmentDirty) { +- this.noTickEquipmentDirty = false; ++ if (!org.leavesmc.leaves.LeavesConfig.performance.equipmentTracking) this.noTickEquipmentDirty = false; // Leaves - Lithium - equipment tracking - move down when enable + this.detectEquipmentUpdates(); ++ if (org.leavesmc.leaves.LeavesConfig.performance.equipmentTracking) this.noTickEquipmentDirty = false; // Leaves - Lithium - equipment tracking + } + + return; diff --git a/leaves-server/src/main/java/org/leavesmc/leaves/LeavesConfig.java b/leaves-server/src/main/java/org/leavesmc/leaves/LeavesConfig.java index cb314cdf..3ba74ebc 100644 --- a/leaves-server/src/main/java/org/leavesmc/leaves/LeavesConfig.java +++ b/leaves-server/src/main/java/org/leavesmc/leaves/LeavesConfig.java @@ -812,6 +812,9 @@ public final class LeavesConfig { @GlobalConfig(value = "sleeping-block-entity", lock = true) public boolean sleepingBlockEntity = false; + + @GlobalConfig(value = "equipment-tracking", lock = true) + public boolean equipmentTracking = false; } public static ProtocolConfig protocol = new ProtocolConfig();