Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify Enchantment and Fabric Component Map Builder Extensions #4085

Merged
merged 22 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fabric-item-api-v1/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
version = getSubprojectVersion(project)

moduleDependencies(project, ['fabric-api-base'])
moduleDependencies(project, ['fabric-api-base', 'fabric-resource-loader-v0'])

testDependencies(project, [
':fabric-content-registries-v0',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import net.minecraft.enchantment.Enchantment;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.entry.RegistryEntry;

import net.fabricmc.fabric.api.event.Event;
Expand Down Expand Up @@ -64,6 +65,27 @@ private EnchantmentEvents() { }
}
);

/**
* An event that allows an {@link Enchantment} to be modified without needing to fully override an enchantment.
*
* <p>This should only be used to modify the behavior of <em>external</em> enchantments, where 'external' means
* either vanilla or from another mod. For instance, a mod might add a bleed effect to Sharpness (and only Sharpness).
* For your own enchantments, you should simply define them in your mod's data pack. See the
* <a href="https://minecraft.wiki/w/Enchantment_definition">Enchantment Definition page</a> on the Minecraft Wiki
* for more information.
*
* <p>Note: If you wish to modify the exclusive set of the enchantment, consider extending the
* {@linkplain net.minecraft.registry.tag.EnchantmentTags relevant tag} through your mod's data pack instead.
*/
public static final Event<Modify> MODIFY = EventFactory.createArrayBacked(
Modify.class,
callbacks -> (key, builder, source) -> {
for (Modify callback : callbacks) {
callback.modify(key, builder, source);
}
}
);

@FunctionalInterface
public interface AllowEnchanting {
/**
Expand All @@ -82,4 +104,20 @@ TriState allowEnchanting(
EnchantingContext enchantingContext
);
}

@FunctionalInterface
public interface Modify {
/**
* Modifies the effects of an {@link Enchantment}.
*
* @param key The ID of the enchantment
* @param builder The enchantment builder
* @param source The source of the enchantment
*/
void modify(
RegistryKey<Enchantment> key,
Enchantment.Builder builder,
EnchantmentSource source
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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;

/**
* Determines where an enchantment has been loaded from.
*/
public enum EnchantmentSource {
/**
* An enchantment loaded from the vanilla data pack.
*/
VANILLA(true),
/**
* An enchantment loaded from mods' bundled resources.
*
* <p>This includes the additional builtin data packs registered by mods
* with Fabric Resource Loader.
*/
MOD(true),
/**
* An enchantment loaded from an external data pack.
*/
DATA_PACK(false);

private final boolean builtin;

EnchantmentSource(boolean builtin) {
this.builtin = builtin;
}

/**
* Returns whether this enchantment source is builtin and bundled in the vanilla or mod resources.
*
* <p>{@link #VANILLA} and {@link #MOD} are builtin.
*
* @return {@code true} if builtin, {@code false} otherwise
*/
public boolean isBuiltin() {
return builtin;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

import net.minecraft.component.ComponentType;

/**
* Fabric-provided extensions for {@link net.minecraft.component.ComponentMap.Builder}.
*
* <p>Note: This interface is automatically implemented on all component map builders via Mixin and interface injection.
*/
@ApiStatus.NonExtendable
public interface FabricComponentMapBuilder {
/**
* Gets the current value for the component type in the builder, or creates and adds a new value if it is not present.
*
* @param type The component type
* @param fallback The supplier for the default data value if the type is not in this map yet. The value given by this supplier
* may not be null.
* @param <T> The type of the component data
* @return Returns the current value in the map builder, or the default value provided by the fallback if not present
* @see #getOrEmpty(ComponentType)
*/
default <T> T getOrCreate(ComponentType<T> type, Supplier<@NotNull T> fallback) {
throw new AssertionError("Implemented in Mixin");
}

/**
* Gets the current value for the component type in the builder, or creates and adds a new value if it is not present.
*
* @param type The component type
* @param defaultValue The default data value if the type is not in this map yet
* @param <T> The type of the component data
* @return Returns the current value in the map builder, or the default value if not present
*/
default <T> T getOrDefault(ComponentType<T> type, @NotNull T defaultValue) {
Objects.requireNonNull(defaultValue, "Cannot insert null values to component map builder");
return getOrCreate(type, () -> defaultValue);
}

/**
* For list component types specifically, returns a mutable list of values currently held in the builder for the given
* component type. If the type is not registered to this builder yet, this will create and add a new empty list to the builder
* for the type, and return that.
*
* @param type The component type. The component must be a list-type.
* @param <T> The type of the component entry data
* @return Returns a mutable list of values for the type.
*/
default <T> List<T> getOrEmpty(ComponentType<List<T>> type) {
throw new AssertionError("Implemented in Mixin");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import net.fabricmc.fabric.api.util.TriState;

/*
/**
* Fabric-provided extensions for {@link ItemStack}.
* This interface is automatically implemented on all item stacks via Mixin and interface injection.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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.impl.item;

import java.util.List;

import net.minecraft.component.ComponentType;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.registry.RegistryKey;
import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourcePackSource;

import net.fabricmc.fabric.api.item.v1.EnchantmentEvents;
import net.fabricmc.fabric.api.item.v1.EnchantmentSource;
import net.fabricmc.fabric.impl.resource.loader.BuiltinModResourcePackSource;
import net.fabricmc.fabric.impl.resource.loader.FabricResource;
import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator;
import net.fabricmc.fabric.mixin.item.EnchantmentBuilderAccessor;

public class EnchantmentUtil {
@SuppressWarnings("unchecked")
public static Enchantment modify(RegistryKey<Enchantment> key, Enchantment originalEnchantment, EnchantmentSource source) {
Enchantment.Builder builder = Enchantment.builder(originalEnchantment.definition());
EnchantmentBuilderAccessor accessor = (EnchantmentBuilderAccessor) builder;

builder.exclusiveSet(originalEnchantment.exclusiveSet());
accessor.getEffectMap().addAll(originalEnchantment.effects());

originalEnchantment.effects().stream()
.forEach(component -> {
if (component.value() instanceof List<?> valueList) {
// component type cast is checked by the value
accessor.invokeGetEffectsList((ComponentType<List<Object>>) component.type())
.addAll(valueList);
}
});

EnchantmentEvents.MODIFY.invoker().modify(key, builder, source);

return new Enchantment(
originalEnchantment.description(),
accessor.getDefinition(),
accessor.getExclusiveSet(),
accessor.getEffectMap().build()
);
}

public static EnchantmentSource determineSource(Resource resource) {
if (resource != null) {
ResourcePackSource packSource = ((FabricResource) resource).getFabricPackSource();

if (packSource == ResourcePackSource.BUILTIN) {
return EnchantmentSource.VANILLA;
} else if (packSource == ModResourcePackCreator.RESOURCE_PACK_SOURCE || packSource instanceof BuiltinModResourcePackSource) {
return EnchantmentSource.MOD;
}
}

// If not builtin or mod, assume external data pack.
// It might also be a virtual enchantment injected via mixin instead of being loaded
// from a resource, but we can't determine that here.
return EnchantmentSource.DATA_PACK;
}

private EnchantmentUtil() { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.mixin.item;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

import net.minecraft.component.ComponentMap;
import net.minecraft.component.ComponentType;

import net.fabricmc.fabric.api.item.v1.FabricComponentMapBuilder;

@Mixin(ComponentMap.Builder.class)
abstract class ComponentMapBuilderMixin implements FabricComponentMapBuilder {
@Shadow
@Final
private Reference2ObjectMap<ComponentType<?>, Object> components;

@Shadow
public abstract <T> ComponentMap.Builder add(ComponentType<T> type, @Nullable T value);

@Override
@SuppressWarnings("unchecked")
public <T> T getOrCreate(ComponentType<T> type, Supplier<@NotNull T> fallback) {
if (!this.components.containsKey(type)) {
T defaultValue = fallback.get();
Objects.requireNonNull(defaultValue, "Cannot insert null values to component map builder");
this.add(type, defaultValue);
}

return (T) this.components.get(type);
}

@Override
public <T> List<T> getOrEmpty(ComponentType<List<T>> type) {
// creating a new array list guarantees that the list in the map is mutable
List<T> existing = new ArrayList<>(this.getOrCreate(type, Collections::emptyList));
this.add(type, existing);
return existing;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.mixin.item;

import java.util.List;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;

import net.minecraft.component.ComponentMap;
import net.minecraft.component.ComponentType;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.registry.entry.RegistryEntryList;

@Mixin(Enchantment.Builder.class)
public interface EnchantmentBuilderAccessor {
@Accessor("definition")
Enchantment.Definition getDefinition();

@Accessor("exclusiveSet")
RegistryEntryList<Enchantment> getExclusiveSet();

@Accessor("effectMap")
ComponentMap.Builder getEffectMap();

@Invoker("getEffectsList")
<E> List<E> invokeGetEffectsList(ComponentType<List<E>> type);
}
Loading
Loading