This commit is contained in:
thedarkcolour 2023-08-15 18:46:14 -07:00
parent e2598ad366
commit 82b1ecd90e
18 changed files with 446 additions and 44 deletions

View File

@ -12,6 +12,7 @@ base {
}
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
java.withSourcesJar()
minecraft {
mappings channel: 'parchment', version: "$parchment_mappings-$mc_version"

View File

@ -12,4 +12,5 @@ replacing where regular Water would be used. (configurable)
- Added the Barrel, which has all of its functionality from Ex Nihilo. (Composting and Mixing recipes are configurable through data pack)
- Added the Silk Worm, which can infest a tree so that can be harvested for string. It can also be cooked and eaten... if you're desperate.
- Added the Crook, which can be used to break leaves and infested leaves for increased drop rates.
- Added the Hammer, which can be used to crush and smash blocks. (recipes are configurable through data pack)
- Added the Hammer, which can be used to crush and smash blocks. (recipes are configurable through data pack)
- Added the porcelain bucket, a cheap alternative to a regular bucket in the early game with the catch that it breaks after pouring out lava.

204
logo.pdn Normal file

File diff suppressed because one or more lines are too long

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -19,6 +19,6 @@
package thedarkcolour.exdeorum.compat;
public class ModIds {
public static final String THERMAL_FOUNDATION = "thermalfoundation";
public static final String THE_ONE_PROBE = "theoneprobe";
public static final String TINKERS_CONSTRUCT = "tconstruct";
}

View File

@ -18,15 +18,21 @@
package thedarkcolour.exdeorum.config;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.common.ForgeConfigSpec.BooleanValue;
import net.minecraftforge.common.ForgeConfigSpec.ConfigValue;
import net.minecraftforge.common.ForgeConfigSpec.DoubleValue;
import org.apache.commons.lang3.text.WordUtils;
import org.apache.commons.lang3.tuple.Pair;
import thedarkcolour.exdeorum.compat.ModIds;
public class EConfig {
public static final ForgeConfigSpec CLIENT_SPEC;
public static final ForgeConfigSpec COMMON_SPEC;
public static final ForgeConfigSpec SERVER_SPEC;
public static final Client CLIENT;
public static final Common COMMON;
public static final Server SERVER;
public static class Client {
@ -47,6 +53,44 @@ public class EConfig {
}
}
// Needed because common configs load before Tags
public static class Common {
public final ConfigValue<ResourceLocation> preferredAluminumOre;
public final ConfigValue<ResourceLocation> preferredCobaltOre;
public final ConfigValue<ResourceLocation> preferredSilverOre;
public final ConfigValue<ResourceLocation> preferredLeadOre;
public final ConfigValue<ResourceLocation> preferredPlatinumOre;
public final ConfigValue<ResourceLocation> preferredNickelOre;
public final ConfigValue<ResourceLocation> preferredUraniumOre;
public final ConfigValue<ResourceLocation> preferredOsmiumOre;
public final ConfigValue<ResourceLocation> preferredTinOre;
public final ConfigValue<ResourceLocation> preferredZincOre;
public final ConfigValue<ResourceLocation> preferredIridiumOre;
public Common(ForgeConfigSpec.Builder builder) {
// Preferred items
builder.comment("Common configuration for Ex Deorum").push("common");
builder.comment("For recipes automatically added by Ex Deorum for other mods, some mods may add two of the same item (ex. Tin Ore). When Ex Deorum adds a recipe for those kinds of items, you may choose which item of the two (or more) is chosen as the crafting result.").push("preferred_tag_items");
var airId = new ResourceLocation("air");
this.preferredAluminumOre = preferredOreConfig(builder, "aluminum_ore", airId);
this.preferredCobaltOre = preferredOreConfig(builder, "cobalt_ore", new ResourceLocation(ModIds.TINKERS_CONSTRUCT, "cobalt_ore"));
this.preferredSilverOre = preferredOreConfig(builder, "silver_ore", airId);
this.preferredLeadOre = preferredOreConfig(builder, "lead_ore", airId);
this.preferredPlatinumOre = preferredOreConfig(builder, "platinum_ore", airId);
this.preferredNickelOre = preferredOreConfig(builder, "nickel_ore", airId);
this.preferredUraniumOre = preferredOreConfig(builder, "uranium_ore", airId);
this.preferredOsmiumOre = preferredOreConfig(builder, "osmium_ore", airId);
this.preferredTinOre = preferredOreConfig(builder, "tin_ore", airId);
this.preferredZincOre = preferredOreConfig(builder, "zinc_ore", airId);
this.preferredIridiumOre = preferredOreConfig(builder, "iridium_ore", airId);
builder.pop(2);
}
}
public static class Server {
public final BooleanValue startingTorch;
public final BooleanValue startingWateringCan;
@ -55,19 +99,6 @@ public class EConfig {
public final BooleanValue witchWaterNetherrackGenerator;
public final BooleanValue setVoidWorldAsDefault;
// todo preferred ores
//public final ConfigValue<ResourceLocation> preferredAluminumOre;
//public final ConfigValue<ResourceLocation> preferredSilverOre;
//public final ConfigValue<ResourceLocation> preferredLeadOre;
//public final ConfigValue<ResourceLocation> preferredNickelOre;
//public final ConfigValue<ResourceLocation> preferredUraniumOre;
//public final ConfigValue<ResourceLocation> preferredOsmiumOre;
//public final ConfigValue<ResourceLocation> preferredTinOre;
//public final ConfigValue<ResourceLocation> preferredZincOre;
//public final ConfigValue<ResourceLocation> preferredCobaltOre;
//public final ConfigValue<ResourceLocation> preferredIridiumOre;
public Server(ForgeConfigSpec.Builder builder) {
builder.comment("Server configuration for Ex Deorum").push("server");
@ -90,28 +121,32 @@ public class EConfig {
.comment("Whether the Void World type is used by default in the \"server.properties\" file when creating a server.")
.define("set_void_world_as_default", true);
// Preferred ore items
//builder.push("preferred_tag_items");
//this.preferredTinOre = builder
// .comment("The ID of the item to use for Ex Deorum recipes that craft into tin ore.")
// .define("preferred_tin_ore", (ResourceLocation) null);
//builder.pop();
builder.pop();
}
}
@SuppressWarnings("deprecation")
private static ConfigValue<ResourceLocation> preferredOreConfig(ForgeConfigSpec.Builder builder, String name, ResourceLocation defaultId) {
return builder
.comment("The ID of the item to use for Ex Deorum recipes that craft into " + WordUtils.capitalize(name.replace('_', ' ')) + ". Leave as air for default preference, which chooses alphabetically by mod name.")
.define("preferred_" + name, defaultId);
}
static {
{
Pair<Server, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Server::new);
SERVER = specPair.getLeft();
SERVER_SPEC = specPair.getRight();
}
{
Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
CLIENT = specPair.getLeft();
CLIENT_SPEC = specPair.getRight();
}
{
Pair<Common, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Common::new);
COMMON = specPair.getLeft();
COMMON_SPEC = specPair.getRight();
}
{
Pair<Server, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Server::new);
SERVER = specPair.getLeft();
SERVER_SPEC = specPair.getRight();
}
}
}

View File

@ -18,13 +18,23 @@
package thedarkcolour.exdeorum.item;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.animal.Cow;
import net.minecraft.world.entity.animal.MushroomCow;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.block.state.BlockState;
import thedarkcolour.exdeorum.registry.EItems;
import java.util.function.Supplier;
@ -65,6 +75,39 @@ public class GrassSpreaderItem extends Item {
return InteractionResult.PASS;
}
@Override
public InteractionResult interactLivingEntity(ItemStack stack, Player player, LivingEntity target, InteractionHand pUsedHand) {
if (stack.getItem() == EItems.MYCELIUM_SPORES.get() && target instanceof Cow cow) {
var mushroomCow = EntityType.MOOSHROOM.create(cow.level());
if (mushroomCow != null) {
if (!player.getAbilities().instabuild) {
stack.shrink(1);
}
cow.discard();
mushroomCow.moveTo(cow.getX(), cow.getY(), cow.getZ());
mushroomCow.setHealth(cow.getHealth());
mushroomCow.yBodyRot = cow.yBodyRot;
if (cow.hasCustomName()) {
mushroomCow.setCustomName(cow.getCustomName());
mushroomCow.setCustomNameVisible(cow.isCustomNameVisible());
}
if (cow.isPersistenceRequired()) {
mushroomCow.setPersistenceRequired();
}
mushroomCow.setInvulnerable(cow.isInvulnerable());
cow.level().addFreshEntity(mushroomCow);
((ServerLevel)cow.level()).sendParticles(ParticleTypes.EXPLOSION, cow.getX(), cow.getY(0.5D), cow.getZ(), 1, 0.0D, 0.0D, 0.0D, 0.0D);
cow.playSound(SoundEvents.MOOSHROOM_CONVERT, 2.0F, 1.0F);
}
}
return InteractionResult.PASS;
}
public boolean canSpread(BlockState state) {
return state.is(BlockTags.DIRT);
}

View File

@ -22,18 +22,26 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.Container;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.storage.LevelResource;
import net.minecraft.world.level.storage.loot.LootDataType;
import net.minecraft.world.level.storage.loot.providers.number.BinomialDistributionGenerator;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
@ -44,14 +52,20 @@ import net.minecraftforge.common.util.Lazy;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.registries.ForgeRegistries;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.config.EConfig;
import thedarkcolour.exdeorum.recipe.barrel.BarrelCompostRecipe;
import thedarkcolour.exdeorum.recipe.barrel.BarrelMixingRecipe;
import thedarkcolour.exdeorum.recipe.crucible.CrucibleRecipe;
import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe;
import thedarkcolour.exdeorum.registry.ERecipeTypes;
import thedarkcolour.exdeorum.tag.EItemTags;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -67,6 +81,7 @@ public final class RecipeUtil {
private static Lazy<Map<Item, BarrelCompostRecipe>> barrelCompostRecipeCache;
private static Lazy<Map<Item, CrucibleRecipe>> lavaCrucibleRecipeCache;
private static Lazy<Map<Item, CrucibleRecipe>> waterCrucibleRecipeCache;
private static final Map<TagKey<Item>, Item> preferredTagItems = new Object2ObjectOpenHashMap<>(11, Hash.DEFAULT_LOAD_FACTOR);
public static void reload(RecipeManager recipes) {
SIEVE_RECIPE_CACHE.invalidateAll();
@ -74,6 +89,32 @@ public final class RecipeUtil {
barrelCompostRecipeCache = Lazy.of(() -> loadSimpleRecipeCache(recipes, ERecipeTypes.BARREL_COMPOST));
lavaCrucibleRecipeCache = Lazy.of(() -> loadSimpleRecipeCache(recipes, ERecipeTypes.LAVA_CRUCIBLE));
waterCrucibleRecipeCache = Lazy.of(() -> loadSimpleRecipeCache(recipes, ERecipeTypes.WATER_CRUCIBLE));
preferredTagItems.clear();
preferredTagItems.put(EItemTags.ORES_ALUMINUM, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredAluminumOre.get()));
preferredTagItems.put(EItemTags.ORES_COBALT, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredCobaltOre.get()));
preferredTagItems.put(EItemTags.ORES_SILVER, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredSilverOre.get()));
preferredTagItems.put(EItemTags.ORES_LEAD, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredLeadOre.get()));
preferredTagItems.put(EItemTags.ORES_PLATINUM, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredPlatinumOre.get()));
preferredTagItems.put(EItemTags.ORES_NICKEL, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredNickelOre.get()));
preferredTagItems.put(EItemTags.ORES_URANIUM, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredUraniumOre.get()));
preferredTagItems.put(EItemTags.ORES_OSMIUM, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredOsmiumOre.get()));
preferredTagItems.put(EItemTags.ORES_TIN, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredTinOre.get()));
preferredTagItems.put(EItemTags.ORES_ZINC, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredZincOre.get()));
preferredTagItems.put(EItemTags.ORES_IRIDIUM, ForgeRegistries.ITEMS.getValue(EConfig.SERVER.preferredIridiumOre.get()));
}
// Copied from ServerLifecycleHooks.getServerConfigPath
private static Path getServerConfigPath(final MinecraftServer server) {
final Path serverConfig = server.getWorldPath(new LevelResource("serverconfig"));
if (!Files.isDirectory(serverConfig)) {
try {
Files.createDirectories(serverConfig);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return serverConfig;
}
private static <T extends SingleIngredientRecipe> ImmutableMap<Item, T> loadSimpleRecipeCache(RecipeManager recipes, Supplier<RecipeType<T>> recipeType) {
@ -289,6 +330,25 @@ public final class RecipeUtil {
}
}
@SuppressWarnings("deprecation")
public static Item getPreferredItem(TagKey<Item> tag) {
Item preferred = preferredTagItems.get(tag);
if (preferred != null && preferred != Items.AIR) {
return preferred;
} else {
var collection = Lists.newArrayList(BuiltInRegistries.ITEM.getTagOrEmpty(tag));
if (collection.isEmpty()) {
return Items.AIR;
} else {
collection.sort(Comparator.comparing(holder -> BuiltInRegistries.ITEM.getKey(holder.get())));
return collection.get(0).get();
}
}
}
private record SieveCacheKey(Item mesh, Item ingredient) {
}
}

View File

@ -24,49 +24,45 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.Container;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.Level;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.registry.ERecipeSerializers;
import thedarkcolour.exdeorum.registry.ERecipeTypes;
// A recipe whose result is an item tag. Tag can be empty.
@SuppressWarnings({"rawtypes", "unchecked"})
public class TagResultRecipe implements Recipe<Container> {
private final ResourceLocation id;
private final Recipe<Container> wrapped;
private final TagKey<Item> result;
private final Recipe<Container> originalRecipe;
public TagResultRecipe(ResourceLocation id, Recipe wrapped, TagKey<Item> result) {
public TagResultRecipe(ResourceLocation id, Recipe originalRecipe) {
this.id = id;
this.wrapped = wrapped;
this.result = result;
this.originalRecipe = originalRecipe;
}
@Override
public boolean matches(Container container, Level level) {
return this.wrapped.matches(container, level);
return this.originalRecipe.matches(container, level);
}
@Override
public ItemStack assemble(Container container, RegistryAccess access) {
access.registryOrThrow(Registries.ITEM).getTag(result);
throw new UnsupportedOperationException("TagResultRecipe#assemble");
return this.originalRecipe.assemble(container, access);
}
@Override
public boolean canCraftInDimensions(int width, int height) {
return this.wrapped.canCraftInDimensions(width, height);
return this.originalRecipe.canCraftInDimensions(width, height);
}
@Override
public ItemStack getResultItem(RegistryAccess access) {
throw new UnsupportedOperationException("TagResultRecipe#getResultItem");
return this.getResultItem(access);
}
@Override
@ -81,13 +77,23 @@ public class TagResultRecipe implements Recipe<Container> {
@Override
public RecipeType<?> getType() {
return ERecipeTypes.TAG_RESULT.get();
return this.originalRecipe.getType();
}
public static class Serializer implements RecipeSerializer<TagResultRecipe> {
@Override
public TagResultRecipe fromJson(ResourceLocation id, JsonObject json) {
return null;
var tag = TagKey.create(Registries.ITEM, new ResourceLocation(GsonHelper.getAsString(json, "result_tag")));
var newResult = RecipeUtil.getPreferredItem(tag);
var originalRecipeJson = GsonHelper.getAsJsonObject(json, "original_recipe");
if (json.has("result")) {
var resultElement = json.get("result");
if (resultElement.isJsonObject()) {
}
}
return null;//return new TagResultRecipe(id, );
}
@Override
@ -97,7 +103,7 @@ public class TagResultRecipe implements Recipe<Container> {
@Override
public void toNetwork(FriendlyByteBuf buffer, TagResultRecipe recipe) {
((RecipeSerializer) recipe.wrapped.getSerializer()).toNetwork(buffer, recipe.wrapped);
((RecipeSerializer) recipe.originalRecipe.getSerializer()).toNetwork(buffer, recipe.originalRecipe);
//buffer.writeResourceLocation(recipe.tag.location());
}
}

View File

@ -86,6 +86,18 @@ public class EItems {
public static final RegistryObject<Item> IRON_ORE_CHUNK = registerSimpleItem("iron_ore_chunk");
public static final RegistryObject<Item> COPPER_ORE_CHUNK = registerSimpleItem("copper_ore_chunk");
public static final RegistryObject<Item> GOLD_ORE_CHUNK = registerSimpleItem("gold_ore_chunk");
// Modded Ore Chunks
public static final RegistryObject<Item> ALUMINUM_ORE_CHUNK = registerSimpleItem("aluminum_ore_chunk");
public static final RegistryObject<Item> COBALT_ORE_CHUNK = registerSimpleItem("cobalt_ore_chunk");
public static final RegistryObject<Item> SILVER_ORE_CHUNK = registerSimpleItem("silver_ore_chunk");
public static final RegistryObject<Item> LEAD_ORE_CHUNK = registerSimpleItem("lead_ore_chunk");
public static final RegistryObject<Item> PLATINUM_ORE_CHUNK = registerSimpleItem("platinum_ore_chunk");
public static final RegistryObject<Item> NICKEL_ORE_CHUNK = registerSimpleItem("nickel_ore_chunk");
public static final RegistryObject<Item> URANIUM_ORE_CHUNK = registerSimpleItem("uranium_ore_chunk");
public static final RegistryObject<Item> OSMIUM_ORE_CHUNK = registerSimpleItem("osmium_ore_chunk");
public static final RegistryObject<Item> TIN_ORE_CHUNK = registerSimpleItem("tin_ore_chunk");
public static final RegistryObject<Item> ZINC_ORE_CHUNK = registerSimpleItem("zinc_ore_chunk");
public static final RegistryObject<Item> IRIDIUM_ORE_CHUNK = registerSimpleItem("iridium_ore_chunk");
// Pebbles
public static final RegistryObject<Item> STONE_PEBBLE = registerSimpleItem("stone_pebble");

View File

@ -35,7 +35,23 @@ public class EItemTags {
public static final TagKey<Item> STONE_BARRELS = tag("stone_barrels");
public static final TagKey<Item> BARRELS = tag("barrels");
public static final TagKey<Item> ORES_ALUMINUM = tag("ores/aluminum");
public static final TagKey<Item> ORES_COBALT = tag("ores/cobalt");
public static final TagKey<Item> ORES_SILVER = tag("ores/silver");
public static final TagKey<Item> ORES_LEAD = tag("ores/lead");
public static final TagKey<Item> ORES_PLATINUM = tag("ores/platinum");
public static final TagKey<Item> ORES_NICKEL = tag("ores/nickel");
public static final TagKey<Item> ORES_URANIUM = tag("ores/uranium");
public static final TagKey<Item> ORES_OSMIUM = tag("ores/osmium");
public static final TagKey<Item> ORES_TIN = tag("ores/tin");
public static final TagKey<Item> ORES_ZINC = tag("ores/zinc");
public static final TagKey<Item> ORES_IRIDIUM = tag("ores/iridium");
public static TagKey<Item> tag(String name) {
return ItemTags.create(new ResourceLocation(ExDeorum.ID, name));
}
public static TagKey<Item> forgeTag(String name) {
return ItemTags.create(new ResourceLocation("forge", name));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

24
todo.md Normal file
View File

@ -0,0 +1,24 @@
- ~~Amethyst~~
- Suspicious Sand drops (Sherds are done)
- Armor trims
- Guardian, Strider, ~~Warden~~
- Glow squid ?
- ~~Coral fans~~ These can be obtained by bonemealing in a warm ocean or by smashing a coral block
- ~~Warped/Crimson grass~~
- Dripstone, Moss, Dripleaf, Glowberries
- ~~Sweet berries~~
- Sponge
- Easy access to a wandering trader
- Sniffer
- Turtle
- ~~Kelp~~
- Some way to get Bad Omen
- ~~Bamboo~~
- Frogs
- Axolotl
- Panda
- ~~Prismarine~~
Watering can? Easy bone meal?
Blackstone and Gilded Blackstone barrels that speed up composting and liquid transformation?
Hammers with Fortune