diff --git a/common/src/main/java/muramasa/antimatter/Antimatter.java b/common/src/main/java/muramasa/antimatter/Antimatter.java index 31b556113..af2339e33 100644 --- a/common/src/main/java/muramasa/antimatter/Antimatter.java +++ b/common/src/main/java/muramasa/antimatter/Antimatter.java @@ -8,6 +8,7 @@ import muramasa.antimatter.data.AntimatterMaterials; import muramasa.antimatter.data.AntimatterStoneTypes; import muramasa.antimatter.datagen.AntimatterDynamics; +import muramasa.antimatter.datagen.AntimatterLoot; import muramasa.antimatter.datagen.loaders.MaterialRecipes; import muramasa.antimatter.datagen.loaders.StoneRecipes; import muramasa.antimatter.datagen.providers.AntimatterBlockLootProvider; @@ -149,7 +150,7 @@ protected void processTags(String domain) { public void onRegistrationEvent(RegistrationEvent event, Side side) { if (event == RegistrationEvent.DATA_INIT) { Recipe.init(); - + AntimatterLoot.RandomWeightLootFunction.init(); SlotType.init(); RecipeBuilders.init(); MachineState.init(); diff --git a/common/src/main/java/muramasa/antimatter/datagen/AntimatterLoot.java b/common/src/main/java/muramasa/antimatter/datagen/AntimatterLoot.java new file mode 100644 index 000000000..4c74d5e0f --- /dev/null +++ b/common/src/main/java/muramasa/antimatter/datagen/AntimatterLoot.java @@ -0,0 +1,197 @@ +package muramasa.antimatter.datagen; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; +import muramasa.antimatter.Antimatter; +import muramasa.antimatter.Ref; +import muramasa.antimatter.mixin.LootPoolAccessor; +import muramasa.antimatter.recipe.RecipeUtil; +import muramasa.antimatter.util.AntimatterPlatformUtils; +import muramasa.antimatter.util.ItemStackHashStrategy; +import net.minecraft.core.Registry; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.storage.loot.LootContext; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.entries.LootItem; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import net.minecraft.world.level.storage.loot.functions.LootItemConditionalFunction; +import net.minecraft.world.level.storage.loot.functions.LootItemFunction; +import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType; +import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction; +import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; +import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; +import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator; +import org.apache.commons.lang3.ArrayUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +public class AntimatterLoot { + + private static final Map> lootEntryItems = new Object2ObjectOpenHashMap<>(); + private static final Map rollValues = new Object2ObjectOpenHashMap<>(); + + private static final LootItemCondition[] NO_CONDITIONS = new LootItemCondition[0]; + + public static void onLootTableLoad(LootPool mainPool, ResourceLocation name) { + if (mainPool == null) return; + + if (lootEntryItems.containsKey(name)) { + List entryItems = lootEntryItems.get(name); + for (LootEntryItem entry : entryItems) { + /*if (ConfigHolder.INSTANCE.dev.debug) { + Antimatter.LOGGER.info("adding {} to lootTable {}", entry, name); + }*/ + + try { + LootPoolEntryContainer[] entries = ((LootPoolAccessor) mainPool).getEntries(); + entries = ArrayUtils.add(entries, entry); + ((LootPoolAccessor) mainPool).setEntries(entries); + } catch (RuntimeException e) { + Antimatter.LOGGER.error("Couldn't add {} to lootTable {}: {}", entry, name, e.getMessage()); + } + } + } + + if (rollValues.containsKey(name)) { + NumberProvider rangeAdd = rollValues.get(name); + NumberProvider range = ((LootPoolAccessor)mainPool).getRolls(); + // mainPool.setRolls(UniformGenerator.between(range.getMin() + rangeAdd.getMin(), range.getMax() + + // rangeAdd.getMax())); TODO additional rolls + } + } + + public static void addItem(@NotNull ResourceLocation lootTable, @NotNull ItemStack stack, int minAmount, + int maxAmount, int weight) { + RandomWeightLootFunction lootFunction = new RandomWeightLootFunction(stack, minAmount, maxAmount); + String modid = Objects.requireNonNull(AntimatterPlatformUtils.INSTANCE.getIdFromItem(stack.getItem())).getNamespace(); + String entryName = createEntryName(stack, modid, weight, lootFunction); + LootEntryItem itemEntry = new LootEntryItem(stack, weight, lootFunction, entryName); + lootEntryItems.computeIfAbsent(lootTable, $ -> new ArrayList<>()).add(itemEntry); + } + + public static void addRolls(ResourceLocation tableLocation, int minAdd, int maxAdd) { + rollValues.put(tableLocation, UniformGenerator.between(minAdd, maxAdd)); + } + + private static final ItemStackHashStrategy HASH_STRATEGY = ItemStackHashStrategy.comparingAllButCount(); + + private static @NotNull String createEntryName(@NotNull ItemStack stack, @NotNull String modid, int weight, + @NotNull RandomWeightLootFunction function) { + int hashCode = Objects.hash(HASH_STRATEGY.hashCode(stack), modid, weight, function.getMinAmount(), + function.getMaxAmount()); + return String.format("#%s:loot_%s", modid, hashCode); + } + + private static class LootEntryItem extends LootItem { + + private final ItemStack stack; + private final String entryName; + + public LootEntryItem(@NotNull ItemStack stack, int weight, LootItemFunction lootFunction, + @NotNull String entryName) { + super(stack.getItem(), weight, 1, NO_CONDITIONS, new LootItemFunction[] { lootFunction }); + this.stack = stack; + this.entryName = entryName; + } + + public void createItemStack(Consumer stackConsumer, LootContext lootContext) { + stackConsumer.accept(this.stack.copy()); + } + + @Override + public @NotNull String toString() { + return "LootEntryItem{name=" + entryName + ", stack=" + stack.toString() + '}'; + } + } + + public static class RandomWeightLootFunction extends LootItemConditionalFunction implements LootItemFunction { + + public static final LootItemFunctionType TYPE = Registry.register(Registry.LOOT_FUNCTION_TYPE, + new ResourceLocation(Ref.ID,"random_weight"), new LootItemFunctionType(new Serializer())); + + private final ItemStack stack; + @Getter + private final int minAmount; + @Getter + private final int maxAmount; + + public RandomWeightLootFunction(@NotNull ItemStack stack, int minAmount, int maxAmount) { + super(NO_CONDITIONS); + Preconditions.checkArgument(minAmount <= maxAmount, "minAmount must be <= maxAmount"); + this.stack = stack; + this.minAmount = minAmount; + this.maxAmount = maxAmount; + } + + public static void init() { + // Do nothing here. This just ensures that TYPE is being set immediately when called. + } + + @Override + public LootItemFunctionType getType() { + return TYPE; + } + + @Override + protected ItemStack run(ItemStack itemStack, LootContext context) { + if (stack.getDamageValue() != 0) { + itemStack.setDamageValue(stack.getDamageValue()); + } + CompoundTag tagCompound = stack.getTag(); + if (tagCompound != null) { + itemStack.setTag(tagCompound.copy()); + } + + if (minAmount == maxAmount) { + itemStack.setCount(minAmount); + return itemStack; + } + + int count = Math.min(minAmount + context.getRandom().nextInt(maxAmount - minAmount + 1), + stack.getMaxStackSize()); + itemStack.setCount(count); + return itemStack; + } + + public static class Serializer extends LootItemConditionalFunction.Serializer { + + /** + * Serialize the {@link SetItemCountFunction} by putting its data into the JsonObject. + */ + public void serialize(JsonObject json, RandomWeightLootFunction setItemCountFunction, + JsonSerializationContext serializationContext) { + super.serialize(json, setItemCountFunction, serializationContext); + json.add("min", serializationContext.serialize(setItemCountFunction.minAmount)); + json.add("max", serializationContext.serialize(setItemCountFunction.maxAmount)); + JsonObject stack = new JsonObject(); + stack.addProperty("item", + AntimatterPlatformUtils.INSTANCE.getIdFromItem(setItemCountFunction.stack.getItem()).toString()); + stack.addProperty("count", setItemCountFunction.stack.getCount()); + if (setItemCountFunction.stack.hasTag()) + stack.addProperty("nbt", setItemCountFunction.stack.getTag().toString()); + json.add("stack", stack); + } + + public RandomWeightLootFunction deserialize(JsonObject object, + JsonDeserializationContext deserializationContext, + LootItemCondition[] conditions) { + ItemStack stack = RecipeUtil.INSTANCE.getItemStack(object.getAsJsonObject("stack"), true); + int min = GsonHelper.getAsInt(object, "min"); + int max = GsonHelper.getAsInt(object, "max"); + return new RandomWeightLootFunction(stack, min, max); + } + } + } +} diff --git a/common/src/main/java/muramasa/antimatter/mixin/LootPoolAccessor.java b/common/src/main/java/muramasa/antimatter/mixin/LootPoolAccessor.java new file mode 100644 index 000000000..1907647da --- /dev/null +++ b/common/src/main/java/muramasa/antimatter/mixin/LootPoolAccessor.java @@ -0,0 +1,21 @@ +package muramasa.antimatter.mixin; + +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(LootPool.class) +public interface LootPoolAccessor { + @Accessor + LootPoolEntryContainer[] getEntries(); + + @Accessor + @Mutable + void setEntries(LootPoolEntryContainer[] entries); + + @Accessor + NumberProvider getRolls(); +} diff --git a/common/src/main/java/muramasa/antimatter/util/ItemStackHashStrategy.java b/common/src/main/java/muramasa/antimatter/util/ItemStackHashStrategy.java new file mode 100644 index 000000000..1299f4fe2 --- /dev/null +++ b/common/src/main/java/muramasa/antimatter/util/ItemStackHashStrategy.java @@ -0,0 +1,118 @@ +package muramasa.antimatter.util; + +import net.minecraft.world.item.ItemStack; + +import it.unimi.dsi.fastutil.Hash; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/** + * A configurable generator of hashing strategies, allowing for consideration of select properties of ItemStacks when + * considering equality. + */ +public interface ItemStackHashStrategy extends Hash.Strategy { + + /** + * @return a builder object for producing a custom ItemStackHashStrategy. + */ + static ItemStackHashStrategyBuilder builder() { + return new ItemStackHashStrategyBuilder(); + } + + /** + * Generates an ItemStackHash configured to compare every aspect of ItemStacks. + * + * @return the ItemStackHashStrategy as described above. + */ + static ItemStackHashStrategy comparingAll() { + return builder().compareItem(true) + .compareCount(true) + .compareTag(true) + .build(); + } + + /** + * Generates an ItemStackHash configured to compare every aspect of ItemStacks except the number + * of items in the stack. + * + * @return the ItemStackHashStrategy as described above. + */ + static ItemStackHashStrategy comparingAllButCount() { + return builder().compareItem(true) + .compareTag(true) + .build(); + } + + static ItemStackHashStrategy comparingItem() { + return builder().compareItem(true) + .build(); + } + + /** + * Builder pattern class for generating customized ItemStackHashStrategy + */ + class ItemStackHashStrategyBuilder { + + private boolean item, count, tag; + + /** + * Defines whether the Item type should be considered for equality. + * + * @param choice {@code true} to consider this property, {@code false} to ignore it. + * @return {@code this} + */ + public ItemStackHashStrategyBuilder compareItem(boolean choice) { + item = choice; + return this; + } + + /** + * Defines whether stack size should be considered for equality. + * + * @param choice {@code true} to consider this property, {@code false} to ignore it. + * @return {@code this} + */ + public ItemStackHashStrategyBuilder compareCount(boolean choice) { + count = choice; + return this; + } + + /** + * Defines whether NBT Tags should be considered for equality. + * + * @param choice {@code true} to consider this property, {@code false} to ignore it. + * @return {@code this} + */ + public ItemStackHashStrategyBuilder compareTag(boolean choice) { + tag = choice; + return this; + } + + /** + * @return the ItemStackHashStrategy as configured by "compare" methods. + */ + public ItemStackHashStrategy build() { + return new ItemStackHashStrategy() { + + @Override + public int hashCode(@Nullable ItemStack o) { + return o == null || o.isEmpty() ? 0 : Objects.hash( + item ? o.getItem() : null, + count ? o.getCount() : null, + tag ? o.getTag() : null); + } + + @Override + public boolean equals(@Nullable ItemStack a, @Nullable ItemStack b) { + if (a == null || a.isEmpty()) return b == null || b.isEmpty(); + if (b == null || b.isEmpty()) return false; + + return (!item || a.getItem() == b.getItem()) && + (!count || a.getCount() == b.getCount()) && + (!tag || Objects.equals(a.getTag(), b.getTag())); + } + }; + } + } +} diff --git a/common/src/main/resources/antimatter.accesswidener b/common/src/main/resources/antimatter.accesswidener index 78f081712..8004fb646 100644 --- a/common/src/main/resources/antimatter.accesswidener +++ b/common/src/main/resources/antimatter.accesswidener @@ -19,4 +19,5 @@ accessible method net/minecraft/world/item/crafting/ShapedRecipe matches (Lnet/m accessible method net/minecraft/world/item/context/UseOnContext (Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/phys/BlockHitResult;)V accessible method net/minecraft/world/item/context/UseOnContext getHitResult ()Lnet/minecraft/world/phys/BlockHitResult; accessible class net/minecraft/client/gui/screens/MenuScreens$ScreenConstructor -accessible class net/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier \ No newline at end of file +accessible class net/minecraft/world/level/block/entity/BlockEntityType$BlockEntitySupplier +accessible method net/minecraft/world/level/storage/loot/entries/LootItem (Lnet/minecraft/world/item/Item;II[Lnet/minecraft/world/level/storage/loot/predicates/LootItemCondition;[Lnet/minecraft/world/level/storage/loot/functions/LootItemFunction;)V \ No newline at end of file diff --git a/common/src/main/resources/antimatter.mixins.json b/common/src/main/resources/antimatter.mixins.json index 62d13aae4..6d5dd3011 100644 --- a/common/src/main/resources/antimatter.mixins.json +++ b/common/src/main/resources/antimatter.mixins.json @@ -34,6 +34,7 @@ "LeavesBlockMixin", "LivingEntityAccessor", "LivingEntityMixin", + "LootPoolAccessor", "PickaxeItemMixin", "PlayerMixin", "RecipeManagerMixin", @@ -43,5 +44,5 @@ "TagLoaderMixin" ], "minVersion": "0.8", - "refmap" : "antimatter.refmap.json" + "refmap": "antimatter.refmap.json" } \ No newline at end of file diff --git a/forge/src/main/java/muramasa/antimatter/common/event/forge/ForgeCommonEvents.java b/forge/src/main/java/muramasa/antimatter/common/event/forge/ForgeCommonEvents.java index 38576cbdb..fc4c41c37 100644 --- a/forge/src/main/java/muramasa/antimatter/common/event/forge/ForgeCommonEvents.java +++ b/forge/src/main/java/muramasa/antimatter/common/event/forge/ForgeCommonEvents.java @@ -6,6 +6,7 @@ import muramasa.antimatter.capability.Holder; import muramasa.antimatter.common.event.CommonEvents; import muramasa.antimatter.data.AntimatterMaterialTypes; +import muramasa.antimatter.datagen.AntimatterLoot; import muramasa.antimatter.material.Material; import muramasa.antimatter.ore.BlockOre; import muramasa.antimatter.pipe.TileTicker; @@ -74,6 +75,7 @@ public static void onAnvilUpdated(AnvilUpdateEvent event) { @SubscribeEvent(priority = EventPriority.LOWEST) public static void onLootTableLoad(LootTableLoadEvent event) { CommonEvents.lootTableLoad(event.getTable(), event.getName()); + AntimatterLoot.onLootTableLoad(event.getTable().getPool("main"), event.getName()); } @SubscribeEvent diff --git a/forge/src/main/resources/META-INF/accesstransformer.cfg b/forge/src/main/resources/META-INF/accesstransformer.cfg index 16fa25f39..8edbb983c 100644 --- a/forge/src/main/resources/META-INF/accesstransformer.cfg +++ b/forge/src/main/resources/META-INF/accesstransformer.cfg @@ -16,3 +16,4 @@ public net.minecraft.world.item.crafting.Ingredient m_43919_(Lcom/google/gson/Js public net.minecraft.world.item.crafting.Ingredient m_43938_(Ljava/util/stream/Stream;)Lnet/minecraft/world/item/crafting/Ingredient; public net.minecraft.world.item.context.UseOnContext m_43718_()Lnet/minecraft/world/phys/BlockHitResult; public net.minecraft.world.item.context.UseOnContext (Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/phys/BlockHitResult;)V +public net.minecraft.world.level.storage.loot.entries.LootItem (Lnet/minecraft/world/item/Item;II[Lnet/minecraft/world/level/storage/loot/predicates/LootItemCondition;[Lnet/minecraft/world/level/storage/loot/functions/LootItemFunction;)V # \ No newline at end of file