diff --git a/gradle.properties b/gradle.properties index 91abf9c..b629a89 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ loader_version_range=[4,) mod_id=frozenlib mod_name=FrozenLib mod_license=All Rights Reserved -mod_version=1.0.5 +mod_version=1.2.0 mod_group_id=net.frozenblock.frozenlib mod_authors=LiukRast, Lunade, Treetrain1 mod_description=NeoForge port of FrozenLib diff --git a/src/main/java/net/frozenblock/lib/FrozenMain.java b/src/main/java/net/frozenblock/lib/FrozenMain.java index d4ecd75..befd56d 100644 --- a/src/main/java/net/frozenblock/lib/FrozenMain.java +++ b/src/main/java/net/frozenblock/lib/FrozenMain.java @@ -7,6 +7,7 @@ import net.frozenblock.lib.entrypoint.api.FrozenModInitializer; import net.frozenblock.lib.gravity.api.GravityAPI; import net.frozenblock.lib.ingamedevtools.RegisterInGameDevTools; +import net.frozenblock.lib.item.api.FrozenCreativeModeTabs; import net.frozenblock.lib.networking.FrozenNetworking; import net.frozenblock.lib.particle.api.FrozenParticleTypes; import net.frozenblock.lib.registry.api.FrozenRegistry; @@ -28,6 +29,7 @@ import net.neoforged.fml.common.Mod; import net.neoforged.neoforge.client.gui.IConfigScreenFactory; import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; import net.neoforged.neoforge.registries.RegisterEvent; import org.quiltmc.qsl.frozenblock.core.registry.api.sync.ModProtocol; import org.quiltmc.qsl.frozenblock.core.registry.impl.sync.server.ServerRegistrySync; @@ -89,4 +91,9 @@ public void registerEvent(RegisterEvent event) { ); }); } + + @SubscribeEvent + public void registerTabs(BuildCreativeModeTabContentsEvent event) { + FrozenCreativeModeTabs.build(event); + } } diff --git a/src/main/java/net/frozenblock/lib/block/api/FrozenTypeBuilders.java b/src/main/java/net/frozenblock/lib/block/api/FrozenTypeBuilders.java new file mode 100644 index 0000000..e5bc7d3 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/block/api/FrozenTypeBuilders.java @@ -0,0 +1,38 @@ +package net.frozenblock.lib.block.api; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.state.properties.BlockSetType; +import net.minecraft.world.level.block.state.properties.WoodType; + +public class FrozenTypeBuilders { + + public static BlockSetType copyOf(BlockSetType original, ResourceLocation id) { + return new BlockSetType( + id.toString(), + original.canOpenByHand(), + original.canOpenByWindCharge(), + original.canButtonBeActivatedByArrows(), + original.pressurePlateSensitivity(), + original.soundType(), + original.doorClose(), + original.doorOpen(), + original.trapdoorClose(), + original.trapdoorOpen(), + original.pressurePlateClickOff(), + original.pressurePlateClickOn(), + original.buttonClickOff(), + original.buttonClickOn() + ); + } + + public static WoodType copyOf(WoodType original, BlockSetType setType, ResourceLocation id) { + return new WoodType( + id.toString(), + setType, + original.soundType(), + original.hangingSignSoundType(), + original.fenceGateClose(), + original.fenceGateOpen() + ); + } +} diff --git a/src/main/java/net/frozenblock/lib/block/api/fire/FireBlockInfo.java b/src/main/java/net/frozenblock/lib/block/api/fire/FireBlockInfo.java new file mode 100644 index 0000000..77cb36a --- /dev/null +++ b/src/main/java/net/frozenblock/lib/block/api/fire/FireBlockInfo.java @@ -0,0 +1,8 @@ +package net.frozenblock.lib.block.api.fire; + +import net.minecraft.world.level.block.state.BlockState; + +public interface FireBlockInfo { + int getSpreadChance(BlockState block); + int getBurnChance(BlockState block); +} diff --git a/src/main/java/net/frozenblock/lib/block/api/fire/FrozenFlammableBlockRegistry.java b/src/main/java/net/frozenblock/lib/block/api/fire/FrozenFlammableBlockRegistry.java new file mode 100644 index 0000000..9839e0a --- /dev/null +++ b/src/main/java/net/frozenblock/lib/block/api/fire/FrozenFlammableBlockRegistry.java @@ -0,0 +1,45 @@ +package net.frozenblock.lib.block.api.fire; + +import lombok.Getter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; + +import java.util.HashMap; +import java.util.Map; + +public class FrozenFlammableBlockRegistry { + + + private static final Map map = new HashMap<>(); + + public static FrozenFlammableBlock get(Block block) { + if(!map.containsKey(block)) map.put(block, new FrozenFlammableBlock()); + return map.get(block); + } + + public static FrozenFlammableBlock get() { + return get(Blocks.FIRE); + } + + public static class FrozenFlammableBlock { + private FrozenFlammableBlock() {} + private final HashMap map = new HashMap<>(); + public void add(Block block, int spreadChance, int burnChance) { + this.map.put(block, new Entry(spreadChance, burnChance)); + } + + public Entry get(Block block) { + return map.get(block); + } + + @Getter + public static class Entry { + private final int spreadChance,burnChance; + private Entry(int spreadChance, int burnChance) { + this.spreadChance = spreadChance; + this.burnChance = burnChance; + } + + } + } +} diff --git a/src/main/java/net/frozenblock/lib/block/api/fuel/FuelEvent.java b/src/main/java/net/frozenblock/lib/block/api/fuel/FuelEvent.java new file mode 100644 index 0000000..0b9af20 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/block/api/fuel/FuelEvent.java @@ -0,0 +1,30 @@ +package net.frozenblock.lib.block.api.fuel; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; +import net.neoforged.bus.api.Event; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public class FuelEvent extends Event { + private final Map itemLike = new HashMap<>(); + private final Map, Integer> itemTag = new HashMap<>(); + public void add(ItemLike item, int time) { + itemLike.put(item,time); + } + + public void add(TagKey tag, int time) { + itemTag.put(tag, time); + } + + public void forItemLike(BiConsumer consumer) { + itemLike.forEach(consumer); + } + + public void forItemtag(BiConsumer, Integer> consumer) { + itemTag.forEach(consumer); + } +} diff --git a/src/main/java/net/frozenblock/lib/block/mixin/fire/FireBlockMixin.java b/src/main/java/net/frozenblock/lib/block/mixin/fire/FireBlockMixin.java new file mode 100644 index 0000000..7f6b13b --- /dev/null +++ b/src/main/java/net/frozenblock/lib/block/mixin/fire/FireBlockMixin.java @@ -0,0 +1,72 @@ +package net.frozenblock.lib.block.mixin.fire; + +import net.frozenblock.lib.block.api.fire.FireBlockInfo; +import net.frozenblock.lib.block.api.fire.FrozenFlammableBlockRegistry; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FireBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(FireBlock.class) +public abstract class FireBlockMixin implements FireBlockInfo { + + @Shadow + public abstract int getBurnOdds(BlockState state); + + @Shadow + public abstract int getIgniteOdds(BlockState state); + + @Unique + private FrozenFlammableBlockRegistry.FrozenFlammableBlock frozenLibNeoForge$block; + + @Inject(at = @At("RETURN"), method = "") + private void init(BlockBehaviour.Properties properties, CallbackInfo ci) { + frozenLibNeoForge$block = FrozenFlammableBlockRegistry.get((Block)(Object)this); + } + + @SuppressWarnings("all") + @Inject(at = @At("HEAD"), method = "getBurnOdds", cancellable = true) + private void addBurn(BlockState state, CallbackInfoReturnable cir) { + final Integer get = frozenLibNeoForge$get(state, true); + if(get != null) + cir.setReturnValue(get); + } + + @SuppressWarnings("all") + @Inject(at = @At("HEAD"), method = "getIgniteOdds", cancellable = true) + private void addIgnite(BlockState state, CallbackInfoReturnable cir) { + final Integer get = frozenLibNeoForge$get(state, false); + if(get != null) + cir.setReturnValue(get); + } + + @Unique + private Integer frozenLibNeoForge$get(BlockState state, boolean burn) { + final FrozenFlammableBlockRegistry.FrozenFlammableBlock.Entry i = frozenLibNeoForge$block.get(state.getBlock()); + if(i != null) { + if(state.hasProperty(BlockStateProperties.WATERLOGGED) && state.hasProperty(BlockStateProperties.WATERLOGGED)) + return 0; + else + return burn ? i.getBurnChance() : i.getSpreadChance(); + } + return null; + } + + @Override + public int getSpreadChance(BlockState block) { + return getIgniteOdds(block); + } + + @Override + public int getBurnChance(BlockState block) { + return getBurnOdds(block); + } +} diff --git a/src/main/java/net/frozenblock/lib/block/mixin/fuel/AbstractFurnaceBlockEntityMixin.java b/src/main/java/net/frozenblock/lib/block/mixin/fuel/AbstractFurnaceBlockEntityMixin.java new file mode 100644 index 0000000..d6cbbaa --- /dev/null +++ b/src/main/java/net/frozenblock/lib/block/mixin/fuel/AbstractFurnaceBlockEntityMixin.java @@ -0,0 +1,41 @@ +package net.frozenblock.lib.block.mixin.fuel; + +import com.mojang.datafixers.util.Either; +import net.frozenblock.lib.FrozenSharedConstants; +import net.frozenblock.lib.block.api.fuel.FuelEvent; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; +import net.neoforged.neoforge.common.NeoForge; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.function.ObjIntConsumer; + +@Mixin(AbstractFurnaceBlockEntity.class) +public abstract class AbstractFurnaceBlockEntityMixin { + + @Shadow + private static void add(ObjIntConsumer>> consumer, ItemLike item, int time) { + FrozenSharedConstants.LOGGER.warn("Injection failed {}add({};{};{}I)V", AbstractFurnaceBlockEntityMixin.class, ObjIntConsumer.class, ItemLike.class, ItemLike.class); + } + + @Shadow + private static void add(ObjIntConsumer>> consumer, TagKey tag, int time) { + FrozenSharedConstants.LOGGER.warn("Injection failed {}add({};{};{}I)V", AbstractFurnaceBlockEntityMixin.class, ObjIntConsumer.class, TagKey.class, ItemLike.class); + + } + + @Inject(at = @At("HEAD"), method = "buildFuels") + private static void addCustom(ObjIntConsumer>> map1, CallbackInfo ci) { + final FuelEvent event = new FuelEvent(); + NeoForge.EVENT_BUS.post(event); + event.forItemLike((itemLike, i) -> add(map1, itemLike, i)); + event.forItemtag((tag, i) -> add(map1, tag, i)); + } + +} diff --git a/src/main/java/net/frozenblock/lib/blockEntity/api/FrozenBlockEntityType.java b/src/main/java/net/frozenblock/lib/blockEntity/api/FrozenBlockEntityType.java new file mode 100644 index 0000000..e2f9e64 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/blockEntity/api/FrozenBlockEntityType.java @@ -0,0 +1,9 @@ +package net.frozenblock.lib.blockEntity.api; + +import net.minecraft.world.level.block.Block; + +public interface FrozenBlockEntityType { + /** + * Allows registration of valid blocks to an already existing block entity*/ + void addValidBlocks(Block ... blocks); +} diff --git a/src/main/java/net/frozenblock/lib/blockEntity/mixin/BlockEntityTypeMixin.java b/src/main/java/net/frozenblock/lib/blockEntity/mixin/BlockEntityTypeMixin.java new file mode 100644 index 0000000..d864dee --- /dev/null +++ b/src/main/java/net/frozenblock/lib/blockEntity/mixin/BlockEntityTypeMixin.java @@ -0,0 +1,22 @@ +package net.frozenblock.lib.blockEntity.mixin; + +import net.frozenblock.lib.blockEntity.api.FrozenBlockEntityType; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Arrays; +import java.util.Set; + +@Mixin(BlockEntityType.class) +public class BlockEntityTypeMixin implements FrozenBlockEntityType { + + @Shadow @Final private Set validBlocks; + + @Override + public void addValidBlocks(Block... blocks) { + validBlocks.addAll(Arrays.asList(blocks)); + } +} diff --git a/src/main/java/net/frozenblock/lib/item/api/FrozenCreativeModeTabs.java b/src/main/java/net/frozenblock/lib/item/api/FrozenCreativeModeTabs.java new file mode 100644 index 0000000..049b427 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/item/api/FrozenCreativeModeTabs.java @@ -0,0 +1,162 @@ +package net.frozenblock.lib.item.api; + +import net.frozenblock.lib.FrozenLogUtils; +import net.frozenblock.lib.FrozenSharedConstants; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.*; +import net.minecraft.world.level.ItemLike; +import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.Consumer; + +/** + * Since forge handles creative mode tabs during an event, we first store all the actions to execute here, and then execute them during the event + */ +@ApiStatus.Experimental +public class FrozenCreativeModeTabs { + private static boolean frozen = false; + private static final ArrayList> events = new ArrayList<>(); + + public static void build(BuildCreativeModeTabContentsEvent event) { + frozen = true; + events.forEach(consumer -> consumer.accept(event)); + } + + public static void register(final Consumer consumer) { + if(frozen) FrozenSharedConstants.LOGGER.error("FrozenCreativeModeTabs registered an event after the main executor has been executed"); + events.add(consumer); + } + + public static void add(ItemLike item, @NotNull ResourceKey... tabs) { + if(item != null) { + item.asItem(); + register(event -> { + if (contains(tabs, event.getTabKey())) { + ItemStack stack = new ItemStack(item); + stack.setCount(1); + event.accept(stack); + } + }); + } + } + + public static void addBefore(ItemLike comparedItem, ItemLike item, CreativeModeTab.TabVisibility tabVisibility, @NotNull ResourceKey... tabs) { + if (comparedItem != null) { + if (item != null) { + register(event -> { + if (contains(tabs, event.getTabKey())) { + ItemStack stack = new ItemStack(item); + stack.setCount(1); + event.insertBefore(comparedItem.asItem().getDefaultInstance(), stack, tabVisibility); + } + }); + } + } + } + + public static void addBefore(ItemLike comparedItem, ItemLike item, String path, CreativeModeTab.TabVisibility tabVisibility, @NotNull ResourceKey... tabs) { + if (comparedItem != null) { + if (item != null) { + register(event -> { + if(contains(tabs, event.getTabKey())) { + ItemStack stack = new ItemStack(item); + stack.setCount(1); + FrozenLogUtils.logError("EMPTY ITEM IN CREATIVE INVENTORY: " + path, stack.isEmpty(), (Throwable) null); + event.insertBefore(comparedItem.asItem().getDefaultInstance(), stack, tabVisibility); + } + }); + } + } + } + + public static void addAfter(ItemLike comparedItem, ItemLike item, ResourceKey... tabs) { + addAfter(comparedItem, item, CreativeModeTab.TabVisibility.PARENT_AND_SEARCH_TABS, tabs); + } + + public static void addAfter(ItemLike comparedItem, ItemLike item, CreativeModeTab.TabVisibility tabVisibility, @NotNull ResourceKey... tabs) { + if (comparedItem != null) { + if (item != null) { + register(event -> { + if(contains(tabs, event.getTabKey())) { + ItemStack stack = new ItemStack(item); + stack.setCount(1); + event.insertAfter(comparedItem.asItem().getDefaultInstance(), stack, tabVisibility); + } + }); + } + } + } + + public static void addAfter(ItemLike comparedItem, ItemLike item, String path, CreativeModeTab.TabVisibility tabVisibility, @NotNull ResourceKey... tabs) { + if (comparedItem != null) { + if (item != null) { + register(event -> { + if(contains(tabs, event.getTabKey())) { + ItemStack stack = new ItemStack(item); + stack.setCount(1); + FrozenLogUtils.logError("EMPTY ITEM IN CREATIVE INVENTORY: " + path, stack.isEmpty(), (Throwable) null); + event.insertAfter(comparedItem.asItem().getDefaultInstance(), stack, tabVisibility); + } + }); + } + } + } + + public static void addInstrument(Item instrument, TagKey tagKey, CreativeModeTab.TabVisibility tabVisibility, @NotNull ResourceKey... tabs) { + if (instrument != null) { + register(event -> { + if(contains(tabs, event.getTabKey())) { + for (Holder instrumentHolder : BuiltInRegistries.INSTRUMENT.getTagOrEmpty(tagKey)) { + ItemStack stack = InstrumentItem.create(instrument, instrumentHolder); + stack.setCount(1); + event.accept(stack, tabVisibility); + } + } + }); + } + } + + public static void addInstrumentBefore(ItemLike comparedItem, Item instrument, TagKey tagKey, CreativeModeTab.TabVisibility tabVisibility, @NotNull ResourceKey... tabs) { + if (comparedItem != null) { + if (instrument != null) { + register(event -> { + if (contains(tabs, event.getTabKey())) { + for (Holder holder : BuiltInRegistries.INSTRUMENT.getTagOrEmpty(tagKey)) { + ItemStack stack = InstrumentItem.create(instrument, holder); + stack.setCount(1); + event.insertBefore(comparedItem.asItem().getDefaultInstance(), stack, tabVisibility); + } + + } + }); + } + } + } + + public static void addInstrumentAfter(Item comparedItem, Item instrument, TagKey tagKey, CreativeModeTab.TabVisibility tabVisibility, @NotNull ResourceKey... tabs) { + if (comparedItem != null) { + if (instrument != null) { + register(event -> { + if(contains(tabs, event.getTabKey())) { + for (Holder instrumentHolder : BuiltInRegistries.INSTRUMENT.getTagOrEmpty(tagKey)) { + ItemStack stack = InstrumentItem.create(instrument, instrumentHolder); + stack.setCount(1); + event.insertAfter(comparedItem.getDefaultInstance(), stack, tabVisibility); + } + } + }); + } + } + } + + private static boolean contains(T[] list, T entry) { + return Arrays.stream(list).toList().contains(entry); + } + +} diff --git a/src/main/java/net/frozenblock/lib/item/api/axe/FrozenStrippableRegistry.java b/src/main/java/net/frozenblock/lib/item/api/axe/FrozenStrippableRegistry.java new file mode 100644 index 0000000..0b09a72 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/item/api/axe/FrozenStrippableRegistry.java @@ -0,0 +1,21 @@ +package net.frozenblock.lib.item.api.axe; + +import com.google.common.collect.ImmutableMap; +import net.frozenblock.lib.item.mixin.axe.AxeItemAccessor; +import net.minecraft.world.level.block.Block; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class FrozenStrippableRegistry { + public static void register(Block original, Block stripped) { + AxeItemAccessor.setStrip(new ImmutableMap.Builder().putAll(AxeItemAccessor.accessStrip()).put(original, stripped).build()); + } + public static void register(Consumer> consumer) { + final Map toAdd = new HashMap<>(); + consumer.accept(toAdd); + AxeItemAccessor.setStrip(new ImmutableMap.Builder().putAll(AxeItemAccessor.accessStrip()).putAll(toAdd).build()); + + } +} diff --git a/src/main/java/net/frozenblock/lib/item/mixin/axe/AxeItemAccessor.java b/src/main/java/net/frozenblock/lib/item/mixin/axe/AxeItemAccessor.java new file mode 100644 index 0000000..cac2293 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/item/mixin/axe/AxeItemAccessor.java @@ -0,0 +1,25 @@ +package net.frozenblock.lib.item.mixin.axe; + +import net.minecraft.world.item.AxeItem; +import net.minecraft.world.level.block.Block; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(AxeItem.class) +public interface AxeItemAccessor { + @Accessor("STRIPPABLES") + static Map accessStrip() { + throw new AssertionError(); + } + + @Accessor("STRIPPABLES") + @Mutable + static void setStrip(Map map) { + throw new AssertionError(); + } + + +} diff --git a/src/main/java/net/frozenblock/lib/loot/api/ApplyInterface.java b/src/main/java/net/frozenblock/lib/loot/api/ApplyInterface.java new file mode 100644 index 0000000..ffd66f8 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/loot/api/ApplyInterface.java @@ -0,0 +1,12 @@ +package net.frozenblock.lib.loot.api; + +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.functions.LootItemFunction; + +import java.util.Collection; + +public interface ApplyInterface { + LootTable.Builder pools(Collection pools); + LootTable.Builder apply(Collection functions); +} diff --git a/src/main/java/net/frozenblock/lib/loot/api/FrozenLootTableSource.java b/src/main/java/net/frozenblock/lib/loot/api/FrozenLootTableSource.java new file mode 100644 index 0000000..e0ef49b --- /dev/null +++ b/src/main/java/net/frozenblock/lib/loot/api/FrozenLootTableSource.java @@ -0,0 +1,34 @@ +package net.frozenblock.lib.loot.api; + +import lombok.experimental.UtilityClass; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.repository.PackSource; +import net.minecraft.server.packs.resources.Resource; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + +@UtilityClass +public class FrozenLootTableSource { + public static final ThreadLocal> SOURCES = ThreadLocal.withInitial(HashMap::new); + private static final WeakHashMap PACK_SOURCES = new WeakHashMap<>(); + + public static boolean determineSource(@Nullable Resource value) { + if(value != null) { + PackSource source = getSource(value.source()); + return source == PackSource.BUILT_IN; + } + return false; + } + + private static PackSource getSource(PackResources source) { + return PACK_SOURCES.getOrDefault(source, PackSource.DEFAULT); + } + + public static void setSource(PackResources pack, PackSource source) { + PACK_SOURCES.put(pack, source); + } +} diff --git a/src/main/java/net/frozenblock/lib/loot/api/LootTableModifyEvent.java b/src/main/java/net/frozenblock/lib/loot/api/LootTableModifyEvent.java new file mode 100644 index 0000000..bc84d32 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/loot/api/LootTableModifyEvent.java @@ -0,0 +1,20 @@ +package net.frozenblock.lib.loot.api; + +import net.minecraft.core.HolderLookup; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.storage.loot.LootTable; +import net.neoforged.bus.api.Event; + +public class LootTableModifyEvent extends Event { + public final ResourceKey key; + public final HolderLookup.Provider registryLookup; + public final LootTable.Builder builder; + public final boolean isBuiltin; + + public LootTableModifyEvent(final ResourceKey key, LootTable.Builder builder, final HolderLookup.Provider registryLookup, final boolean isBuiltin) { + this.key = key; + this.registryLookup = registryLookup; + this.builder = builder; + this.isBuiltin = isBuiltin; + } +} diff --git a/src/main/java/net/frozenblock/lib/loot/mixin/LootTableAccessor.java b/src/main/java/net/frozenblock/lib/loot/mixin/LootTableAccessor.java new file mode 100644 index 0000000..3085116 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/loot/mixin/LootTableAccessor.java @@ -0,0 +1,21 @@ +package net.frozenblock.lib.loot.mixin; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.functions.LootItemFunction; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; +import java.util.Optional; + +@Mixin(LootTable.class) +public interface LootTableAccessor { + @Accessor("pools") + List getPools(); + @Accessor("functions") + List getFunctions(); + @Accessor("randomSequence") + Optional getRandomSequence(); +} diff --git a/src/main/java/net/frozenblock/lib/loot/mixin/LootTableBuilderMixin.java b/src/main/java/net/frozenblock/lib/loot/mixin/LootTableBuilderMixin.java new file mode 100644 index 0000000..8d788bf --- /dev/null +++ b/src/main/java/net/frozenblock/lib/loot/mixin/LootTableBuilderMixin.java @@ -0,0 +1,32 @@ +package net.frozenblock.lib.loot.mixin; + +import com.google.common.collect.ImmutableList; +import net.frozenblock.lib.loot.api.ApplyInterface; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.functions.LootItemFunction; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Collection; + +@Mixin(LootTable.Builder.class) +public class LootTableBuilderMixin implements ApplyInterface { + + @Shadow @Final private ImmutableList.Builder functions; + + @Shadow @Final private ImmutableList.Builder pools; + + @Override + public LootTable.Builder pools(Collection pools) { + this.pools.addAll(pools); + return (LootTable.Builder)(Object)this; + } + + @Override + public LootTable.Builder apply(Collection functions) { + this.functions.addAll(functions); + return (LootTable.Builder)(Object)this; + } +} diff --git a/src/main/java/net/frozenblock/lib/loot/mixin/PackMixin.java b/src/main/java/net/frozenblock/lib/loot/mixin/PackMixin.java new file mode 100644 index 0000000..120723e --- /dev/null +++ b/src/main/java/net/frozenblock/lib/loot/mixin/PackMixin.java @@ -0,0 +1,22 @@ +package net.frozenblock.lib.loot.mixin; + +import net.frozenblock.lib.loot.api.FrozenLootTableSource; +import net.minecraft.server.packs.PackLocationInfo; +import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.repository.Pack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Pack.class) +public abstract class PackMixin { + + @Shadow public abstract PackLocationInfo location(); + + @Inject(method = "open", at = @At("RETURN")) + private void save(CallbackInfoReturnable info) { + FrozenLootTableSource.setSource(info.getReturnValue(), location().source()); + } +} diff --git a/src/main/java/net/frozenblock/lib/loot/mixin/ReloadableServerRegistriesMixin.java b/src/main/java/net/frozenblock/lib/loot/mixin/ReloadableServerRegistriesMixin.java new file mode 100644 index 0000000..f5a7e8d --- /dev/null +++ b/src/main/java/net/frozenblock/lib/loot/mixin/ReloadableServerRegistriesMixin.java @@ -0,0 +1,111 @@ +package net.frozenblock.lib.loot.mixin; + +import com.google.gson.JsonElement; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.serialization.DynamicOps; +import net.frozenblock.lib.loot.api.ApplyInterface; +import net.frozenblock.lib.loot.api.FrozenLootTableSource; +import net.frozenblock.lib.loot.api.LootTableModifyEvent; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.core.WritableRegistry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.RegistryLayer; +import net.minecraft.server.ReloadableServerRegistries; +import net.minecraft.world.level.storage.loot.LootTable; +import net.neoforged.neoforge.common.NeoForge; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.WeakHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Function; + +@Mixin(ReloadableServerRegistries.class) +public class ReloadableServerRegistriesMixin { + + @Unique + private static final WeakHashMap, HolderLookup.Provider> frozenLibNeoForge$weakMap = new WeakHashMap<>(); + + @WrapOperation( + method = "reload", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/ReloadableServerRegistries$EmptyTagLookupWrapper;createSerializationContext(Lcom/mojang/serialization/DynamicOps;)Lnet/minecraft/resources/RegistryOps;" + ) + ) + private static RegistryOps saveOps(ReloadableServerRegistries.EmptyTagLookupWrapper instance, DynamicOps dynamicOps, Operation> original) { + @SuppressWarnings("all") + RegistryOps created = original.call(instance, dynamicOps); + frozenLibNeoForge$weakMap.put(created, instance); + return created; + } + + @WrapOperation( + method = "reload", + at = @At( + value = "INVOKE", + target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;" + ) + ) + private static CompletableFuture> remove(CompletableFuture>> instance, Function>, ? extends LayeredRegistryAccess> fn, Executor executor, Operation>> original, @Local RegistryOps ops) { + return original.call(instance.thenApply(v -> { + frozenLibNeoForge$weakMap.remove(ops); + return v; + }), fn, executor); + } + + @WrapOperation( + method = "lambda$scheduleElementParse$3", + at = @At( + value = "INVOKE", + target = "Ljava/util/Optional;ifPresent(Ljava/util/function/Consumer;)V" + ) + ) + private static void modify(@SuppressWarnings("all") Optional optionalTable, Consumer action, Operation original, @Local(argsOnly = true) ResourceLocation id, @Local(argsOnly = true) RegistryOps ops) { + original.call(optionalTable.map(table -> frozenLibNeoForge$modifyLootTable(table, id, ops)), action); + } + + @SuppressWarnings("unchecked") + @Unique + private static T frozenLibNeoForge$modifyLootTable(T value, ResourceLocation id, RegistryOps ops) { + if(value instanceof LootTable table) { + if(table == LootTable.EMPTY) { + return value; + } else { + ResourceKey key = ResourceKey.create(Registries.LOOT_TABLE, id); + HolderLookup.Provider registries = frozenLibNeoForge$weakMap.get(ops); + LootTable.Builder builder = frozenLibNeoForge$createBuilder(table); + NeoForge.EVENT_BUS.post(new LootTableModifyEvent(key, builder, registries, FrozenLootTableSource.SOURCES.get().getOrDefault(id, false))); + return (T) builder.build(); + } + } else { + return value; + } + } + + @Unique + private static LootTable.Builder frozenLibNeoForge$createBuilder(LootTable table) { + LootTable.Builder builder = LootTable.lootTable(); + LootTableAccessor accessor = (LootTableAccessor)table; + builder.setParamSet(table.getParamSet()); + ((ApplyInterface)builder).pools(accessor.getPools()); + ((ApplyInterface)builder).apply(accessor.getFunctions()); + Optional idk = accessor.getRandomSequence(); + Objects.requireNonNull(builder); + idk.ifPresent(builder::setRandomSequence); + return builder; + } + +} diff --git a/src/main/java/net/frozenblock/lib/loot/mixin/SimpleJsonResourceReloadListenerMixin.java b/src/main/java/net/frozenblock/lib/loot/mixin/SimpleJsonResourceReloadListenerMixin.java new file mode 100644 index 0000000..bf9927e --- /dev/null +++ b/src/main/java/net/frozenblock/lib/loot/mixin/SimpleJsonResourceReloadListenerMixin.java @@ -0,0 +1,33 @@ +package net.frozenblock.lib.loot.mixin; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.llamalad7.mixinextras.sugar.Local; +import net.frozenblock.lib.loot.api.FrozenLootTableSource; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import net.minecraft.world.level.storage.loot.LootDataType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Map; + +@Mixin(SimpleJsonResourceReloadListener.class) +public class SimpleJsonResourceReloadListenerMixin { + @Inject( + method = "scanDirectory", + at = @At( + value = "INVOKE_ASSIGN", + target = "Lnet/minecraft/resources/FileToIdConverter;fileToId(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/resources/ResourceLocation;", + shift = At.Shift.AFTER + ) + ) + private static void inject(ResourceManager resourceManager, String name, Gson gson, Map output, CallbackInfo ci, @Local Map.Entry entry, @Local(ordinal = 1) ResourceLocation id) { + if(LootDataType.TABLE.registryKey().location().getPath().equals(name)) + FrozenLootTableSource.SOURCES.get().put(id, FrozenLootTableSource.determineSource(entry.getValue())); + } +} diff --git a/src/main/java/net/frozenblock/lib/potion/api/FrozenPotionBrewingBuilder.java b/src/main/java/net/frozenblock/lib/potion/api/FrozenPotionBrewingBuilder.java new file mode 100644 index 0000000..9321abf --- /dev/null +++ b/src/main/java/net/frozenblock/lib/potion/api/FrozenPotionBrewingBuilder.java @@ -0,0 +1,14 @@ +package net.frozenblock.lib.potion.api; + +import net.minecraft.core.Holder; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.crafting.Ingredient; + +public interface FrozenPotionBrewingBuilder { + void registerItemRecipe(Item input, Ingredient ingredient, Item output); + void registerPotionRecipe(Holder input, Ingredient ingredient, Holder output); + void registerRecipes(Ingredient ingredient, Holder potion); + FeatureFlagSet getEnableFeatures(); +} diff --git a/src/main/java/net/frozenblock/lib/potion/mixin/PotionBrewingBuilderMixin.java b/src/main/java/net/frozenblock/lib/potion/mixin/PotionBrewingBuilderMixin.java new file mode 100644 index 0000000..841284f --- /dev/null +++ b/src/main/java/net/frozenblock/lib/potion/mixin/PotionBrewingBuilderMixin.java @@ -0,0 +1,57 @@ +package net.frozenblock.lib.potion.mixin; + +import net.frozenblock.lib.potion.api.FrozenPotionBrewingBuilder; +import net.minecraft.core.Holder; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.alchemy.PotionBrewing; +import net.minecraft.world.item.alchemy.Potions; +import net.minecraft.world.item.crafting.Ingredient; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.List; + +@Mixin(PotionBrewing.Builder.class) +public abstract class PotionBrewingBuilderMixin implements FrozenPotionBrewingBuilder { + + @Shadow @Final private FeatureFlagSet enabledFeatures; + + @Shadow + private static void expectPotion(Item item) {} + + @Shadow @Final private List> containerMixes; + + @Shadow @Final private List> potionMixes; + + @Override + public void registerItemRecipe(Item input, Ingredient ingredient, Item output) { + if(input.isEnabled(this.enabledFeatures) && output.isEnabled(enabledFeatures)) { + expectPotion(input); + expectPotion(output); + this.containerMixes.add(new PotionBrewing.Mix(input.builtInRegistryHolder(), ingredient, output.builtInRegistryHolder())); + } + } + + @Override + public void registerPotionRecipe(Holder input, Ingredient ingredient, Holder output) { + if(input.value().isEnabled(this.enabledFeatures) && output.value().isEnabled(this.enabledFeatures)) { + this.potionMixes.add(new PotionBrewing.Mix<>(input, ingredient, output)); + } + } + + @Override + public void registerRecipes(Ingredient ingredient, Holder potion) { + if(potion.value().isEnabled(this.enabledFeatures)) { + this.registerPotionRecipe(Potions.WATER, ingredient, Potions.MUNDANE); + this.registerPotionRecipe(Potions.AWKWARD, ingredient, potion); + } + } + + @Override + public FeatureFlagSet getEnableFeatures() { + return this.enabledFeatures; + } +} diff --git a/src/main/java/net/frozenblock/lib/resourcepack/api/FrozenPackSource.java b/src/main/java/net/frozenblock/lib/resourcepack/api/FrozenPackSource.java new file mode 100644 index 0000000..e2da8c4 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/resourcepack/api/FrozenPackSource.java @@ -0,0 +1,10 @@ +package net.frozenblock.lib.resourcepack.api; + +import net.frozenblock.lib.resourcepack.mixin.PackSourceAccessor; +import net.minecraft.server.packs.repository.PackSource; + +public class FrozenPackSource { + /** + * A built in resourcepack, but not enabled by default.*/ + public static final PackSource BUILT_IN_NODEFAULT = PackSource.create(PackSourceAccessor.invokeDecorateWithSource("pack.source.builtin"), false); +} diff --git a/src/main/java/net/frozenblock/lib/resourcepack/mixin/PackSourceAccessor.java b/src/main/java/net/frozenblock/lib/resourcepack/mixin/PackSourceAccessor.java new file mode 100644 index 0000000..5133f08 --- /dev/null +++ b/src/main/java/net/frozenblock/lib/resourcepack/mixin/PackSourceAccessor.java @@ -0,0 +1,16 @@ +package net.frozenblock.lib.resourcepack.mixin; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.packs.repository.PackSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.function.UnaryOperator; + +@Mixin(PackSource.class) +public interface PackSourceAccessor { + @Invoker("decorateWithSource") + static UnaryOperator invokeDecorateWithSource(String translationKey) { + throw new AssertionError("Mixin injection failed"); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index bb6dc87..ab7ff61 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -197,4 +197,12 @@ public-f net.minecraft.world.flag.FeatureFlags CODEC public net.minecraft.client.renderer.LevelRenderer cullingFrustum public net.minecraft.world.level.gameevent.EntityPositionSource resolveEntity(Lnet/minecraft/world/level/Level;)V public net.minecraft.world.level.gameevent.EntityPositionSource entityOrUuidOrId -public net.minecraft.world.level.gameevent.EntityPositionSource yOffset \ No newline at end of file +public net.minecraft.world.level.gameevent.EntityPositionSource yOffset + +public net.minecraft.server.ReloadableServerRegistries$EmptyTagLookupWrapper +public net.minecraft.world.level.storage.loot.LootTable$Builder polls +public net.minecraft.world.level.storage.loot.LootTable polls +public net.minecraft.world.level.storage.loot.LootTable functions +public net.minecraft.world.level.storage.loot.LootTable randomSequence + +public net.minecraft.world.item.alchemy.PotionBrewing$Mix \ No newline at end of file diff --git a/src/main/resources/mixin/frozenlib.block.mixins.json b/src/main/resources/mixin/frozenlib.block.mixins.json index 9bb73db..cf7c8d0 100644 --- a/src/main/resources/mixin/frozenlib.block.mixins.json +++ b/src/main/resources/mixin/frozenlib.block.mixins.json @@ -8,6 +8,8 @@ }, "mixins": [ "dripstone.PointedDripstoneBlockMixin", + "fire.FireBlockMixin", + "fuel.AbstractFurnaceBlockEntityMixin", "tick.BlockBehaviourMixin" ], "client": [ diff --git a/src/main/resources/mixin/frozenlib.block_entity.mixins.json b/src/main/resources/mixin/frozenlib.block_entity.mixins.json new file mode 100644 index 0000000..36ad169 --- /dev/null +++ b/src/main/resources/mixin/frozenlib.block_entity.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.frozenblock.lib.blockEntity.mixin", + "compatibilityLevel": "JAVA_21", + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "BlockEntityTypeMixin" + ] +} diff --git a/src/main/resources/mixin/frozenlib.item.mixins.json b/src/main/resources/mixin/frozenlib.item.mixins.json index dcd7e2c..dff0aa3 100644 --- a/src/main/resources/mixin/frozenlib.item.mixins.json +++ b/src/main/resources/mixin/frozenlib.item.mixins.json @@ -13,6 +13,7 @@ "LivingEntityMixin", "ServerItemCooldownsMixin", "ServerPlayerMixin", + "axe.AxeItemAccessor", "axe.AxeItemMixin", "bonemeal.BoneMealItemMixin", "shovel.ShovelItemMixin" diff --git a/src/main/resources/mixin/frozenlib.loot.mixins.json b/src/main/resources/mixin/frozenlib.loot.mixins.json new file mode 100644 index 0000000..dfd15a7 --- /dev/null +++ b/src/main/resources/mixin/frozenlib.loot.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.frozenblock.lib.loot.mixin", + "compatibilityLevel": "JAVA_21", + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "LootTableAccessor", + "LootTableBuilderMixin", + "PackMixin", + "ReloadableServerRegistriesMixin", + "SimpleJsonResourceReloadListenerMixin" + ] +} diff --git a/src/main/resources/mixin/frozenlib.potion.mixins.json b/src/main/resources/mixin/frozenlib.potion.mixins.json new file mode 100644 index 0000000..e87c01d --- /dev/null +++ b/src/main/resources/mixin/frozenlib.potion.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.frozenblock.lib.potion.mixin", + "compatibilityLevel": "JAVA_21", + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "PotionBrewingBuilderMixin", + ] +} diff --git a/src/main/resources/mixin/frozenlib.resourcepack.mixins.json b/src/main/resources/mixin/frozenlib.resourcepack.mixins.json new file mode 100644 index 0000000..1c12b7b --- /dev/null +++ b/src/main/resources/mixin/frozenlib.resourcepack.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.frozenblock.lib.resourcepack.mixin", + "compatibilityLevel": "JAVA_21", + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "PackSourceAccessor" + ] +} diff --git a/src/main/templates/META-INF/neoforge.mods.toml b/src/main/templates/META-INF/neoforge.mods.toml index 3db8a0a..a22157b 100644 --- a/src/main/templates/META-INF/neoforge.mods.toml +++ b/src/main/templates/META-INF/neoforge.mods.toml @@ -69,43 +69,28 @@ config="mixin/${mod_id}_quiltmc_registry.mixins.json" config="mixin/${mod_id}_quiltmc_resource_loader.mixins.json" [[mixins]] config="mixin/${mod_id}.debug.mixins.json" -# The [[accessTransformers]] block allows you to declare where your AT file is. -# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg +[[mixins]] +config="mixin/${mod_id}.block_entity.mixins.json" +[[mixins]] +config="mixin/${mod_id}.loot.mixins.json" +[[mixins]] +config="mixin/${mod_id}.potion.mixins.json" +[[mixins]] +config="mixin/${mod_id}.resourcepack.mixins.json" + [[accessTransformers]] file="META-INF/accesstransformer.cfg" -# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json - -# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. -[[dependencies.${mod_id}]] #optional - # the modid of the dependency - modId="neoforge" #mandatory - # The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). - # 'required' requires the mod to exist, 'optional' does not - # 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning - type="required" #mandatory - # Optional field describing why the dependency is required or why it is incompatible - # reason="..." - # The version range of the dependency - versionRange="${neo_version_range}" #mandatory - # An ordering relationship for the dependency. - # BEFORE - This mod is loaded BEFORE the dependency - # AFTER - This mod is loaded AFTER the dependency +[[dependencies.${mod_id}]] + modId="neoforge" + type="required" + versionRange="${neo_version_range}" ordering="NONE" - # Side this dependency is applied on - BOTH, CLIENT, or SERVER side="BOTH" -# Here's another dependency [[dependencies.${mod_id}]] modId="minecraft" type="required" - # This version range declares a minimum of the current minecraft version up to but not including the next major version versionRange="${minecraft_version_range}" ordering="NONE" side="BOTH" - -# Features are specific properties of the game environment, that you may want to declare you require. This example declares -# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't -# stop your mod loading on the server for example. -#[features.${mod_id}] -#openGLVersion="[3.2,)"