From 85719ab7b755fbe8144ead1c6d6b783eb7369507 Mon Sep 17 00:00:00 2001
From: frqnny <45723631+frqnny@users.noreply.github.com>
Date: Fri, 20 May 2022 13:17:48 -0400
Subject: [PATCH] Add ModifyItemAttributeModifiersCallback (#2175)

* Add ItemAttributeModifiersCallback

* fix checkstyle errors

* fix checkstyle errors v2

* Edit javadoc per Technician's review

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/ItemAttributeModifiersCallback.java

Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com>

* Change functional interface name per Juuz's review

* Change name to ModifyItemAttributeModifiers per Juuz and Technician's request

* Forgot to add callback to the name

* Return mutable map

* Remove unused import

Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com>
---
 .../ModifyItemAttributeModifiersCallback.java | 59 +++++++++++++++++++
 .../fabric/mixin/item/ItemStackMixin.java     |  8 ++-
 ...ifyItemAttributeModifiersCallbackTest.java | 38 ++++++++++++
 .../src/testmod/resources/fabric.mod.json     |  3 +-
 4 files changed, 106 insertions(+), 2 deletions(-)
 create mode 100644 fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/ModifyItemAttributeModifiersCallback.java
 create mode 100644 fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/ModifyItemAttributeModifiersCallbackTest.java

diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/ModifyItemAttributeModifiersCallback.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/ModifyItemAttributeModifiersCallback.java
new file mode 100644
index 000000000..2c0a2475f
--- /dev/null
+++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/ModifyItemAttributeModifiersCallback.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.api.item.v1;
+
+import com.google.common.collect.Multimap;
+
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.attribute.EntityAttribute;
+import net.minecraft.entity.attribute.EntityAttributeModifier;
+import net.minecraft.item.ItemStack;
+
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+/**
+ * Stack-aware attribute modifier callback for foreign items.
+ * Instead of using Mixin to change attribute modifiers in items not in your mod,
+ * you can use this event instead, either checking the Item itself or using a tag.
+ * This event provides you with a guaranteed mutable map you can put attribute modifiers in.
+ * Do not use for your own Item classes; see {@link FabricItem#getAttributeModifiers} instead.
+ * For example, the following code modifies a Diamond Helmet to give you five extra hearts when wearing.
+ *
+ * <pre>
+ * {@code
+ * ModifyItemAttributeModifiersCallback.EVENT.register((stack, slot, attributeModifiers) -> {
+ * 	if (stack.isOf(Items.DIAMOND_HELMET) && slot.getEntitySlotId() == HEAD_SLOT_ID) {
+ * 		attributeModifiers.put(EntityAttributes.GENERIC_MAX_HEALTH, MODIFIER);
+ * 	}
+ * });
+ * }
+ * </pre>
+ */
+@FunctionalInterface
+public interface ModifyItemAttributeModifiersCallback {
+	void modifyAttributeModifiers(ItemStack stack, EquipmentSlot slot, Multimap<EntityAttribute, EntityAttributeModifier> attributeModifiers);
+
+	Event<ModifyItemAttributeModifiersCallback> EVENT = EventFactory.createArrayBacked(
+			ModifyItemAttributeModifiersCallback.class,
+			callbacks -> (stack, slot, attributeModifiers) -> {
+				for (ModifyItemAttributeModifiersCallback callback : callbacks) {
+					callback.modifyAttributeModifiers(stack, slot, attributeModifiers);
+				}
+			}
+	);
+}
diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java
index e0b559dc4..570236465 100644
--- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java
+++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java
@@ -18,6 +18,7 @@ package net.fabricmc.fabric.mixin.item;
 
 import java.util.function.Consumer;
 
+import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
@@ -37,6 +38,7 @@ import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
 
 import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
+import net.fabricmc.fabric.api.item.v1.ModifyItemAttributeModifiersCallback;
 import net.fabricmc.fabric.impl.item.ItemExtensions;
 
 @Mixin(ItemStack.class)
@@ -80,7 +82,11 @@ public abstract class ItemStackMixin {
 			)
 	)
 	public Multimap<EntityAttribute, EntityAttributeModifier> hookGetAttributeModifiers(Item item, EquipmentSlot slot) {
-		return item.getAttributeModifiers((ItemStack) (Object) this, slot);
+		ItemStack stack = (ItemStack) (Object) this;
+		//we need to ensure it is modifiable for the callback
+		Multimap<EntityAttribute, EntityAttributeModifier> attributeModifiers = HashMultimap.create(item.getAttributeModifiers(stack, slot));
+		ModifyItemAttributeModifiersCallback.EVENT.invoker().modifyAttributeModifiers(stack, slot, attributeModifiers);
+		return attributeModifiers;
 	}
 
 	@Redirect(
diff --git a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/ModifyItemAttributeModifiersCallbackTest.java b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/ModifyItemAttributeModifiersCallbackTest.java
new file mode 100644
index 000000000..72e54d9cd
--- /dev/null
+++ b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/ModifyItemAttributeModifiersCallbackTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.test.item;
+
+import net.minecraft.entity.attribute.EntityAttributeModifier;
+import net.minecraft.entity.attribute.EntityAttributes;
+import net.minecraft.item.Items;
+
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.item.v1.ModifyItemAttributeModifiersCallback;
+
+public class ModifyItemAttributeModifiersCallbackTest implements ModInitializer {
+	public static final int HEAD_SLOT_ID = 3;
+	public static final EntityAttributeModifier MODIFIER = new EntityAttributeModifier("generic_max_health_modifier", 5.0, EntityAttributeModifier.Operation.ADDITION);
+
+	@Override
+	public void onInitialize() {
+		ModifyItemAttributeModifiersCallback.EVENT.register((stack, slot, attributeModifiers) -> {
+			if (stack.isOf(Items.DIAMOND_HELMET) && slot.getEntitySlotId() == HEAD_SLOT_ID) {
+				attributeModifiers.put(EntityAttributes.GENERIC_MAX_HEALTH, MODIFIER);
+			}
+		});
+	}
+}
diff --git a/fabric-item-api-v1/src/testmod/resources/fabric.mod.json b/fabric-item-api-v1/src/testmod/resources/fabric.mod.json
index ec34716a1..2750b292a 100644
--- a/fabric-item-api-v1/src/testmod/resources/fabric.mod.json
+++ b/fabric-item-api-v1/src/testmod/resources/fabric.mod.json
@@ -12,7 +12,8 @@
     "main": [
       "net.fabricmc.fabric.test.item.CustomDamageTest",
       "net.fabricmc.fabric.test.item.FabricItemSettingsTests",
-      "net.fabricmc.fabric.test.item.ItemUpdateAnimationTest"
+      "net.fabricmc.fabric.test.item.ItemUpdateAnimationTest",
+      "net.fabricmc.fabric.test.item.ModifyItemAttributeModifiersCallbackTest"
     ],
     "client": [
       "net.fabricmc.fabric.test.item.client.TooltipTests"