Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import me.shedaniel.clothconfig2.gui.entries.BooleanListEntry;
import me.shedaniel.clothconfig2.gui.entries.IntegerListEntry;
import me.shedaniel.clothconfig2.gui.entries.StringListListEntry;
import me.shedaniel.clothconfig2.gui.entries.TextListEntry;
import me.shedaniel.clothconfig2.impl.builders.SubCategoryBuilder;
import net.minecraft.ChatFormatting;
Expand All @@ -20,6 +21,8 @@
import satisfyu.vinery.Vinery;
import satisfyu.vinery.config.VineryConfig;

import java.util.List;

public class ClothConfigScreen {

private static Screen lastScreen;
Expand All @@ -46,6 +49,7 @@ private static class ConfigEntries {
private final ConfigCategory category;
private final BooleanListEntry enableWineMakerSetBonus, enableNetherLattices;
private final IntegerListEntry wineTraderChance, yearLengthInDays, yearsPerEffectLevel, fermentationBarrelTime, damagePerUse, probabilityForDamage, probabilityToKeepBoneMeal, grapeGrowthSpeed;
private final StringListListEntry disabledWines;



Expand All @@ -60,6 +64,12 @@ public ConfigEntries(ConfigEntryBuilder builder, VineryConfig config, ConfigCate
grapeGrowthSpeed = createIntField("grapeGrowthSpeed", config.grapeGrowthSpeed(), VineryConfig.DEFAULT.grapeGrowthSpeed(), null, 1, 100);
enableNetherLattices = createBooleanField("enableNetherLattices", config.enableNetherLattices(), VineryConfig.DEFAULT.enableNetherLattices(), null);

disabledWines = builder.startStrList(Component.translatable("vinery.config.entry.disabledWines"), config.disabledWines())
.setDefaultValue(VineryConfig.DEFAULT.disabledWines())
.setTooltip(Component.translatable("vinery.config.entry.disabledWines.tooltip"))
.build();
category.addEntry(disabledWines);

SubCategoryBuilder wineMaker = new SubCategoryBuilder(Component.empty(), Component.translatable("vinery.config.subCategory.wineMaker"));

enableWineMakerSetBonus = createBooleanField("enableWineMakerSetBonus", config.enableWineMakerSetBonus(), VineryConfig.DEFAULT.enableWineMakerSetBonus(), wineMaker);
Expand All @@ -73,7 +83,7 @@ public ConfigEntries(ConfigEntryBuilder builder, VineryConfig config, ConfigCate


public VineryConfig createConfig() {
return new VineryConfig(wineTraderChance.getValue(), yearLengthInDays.getValue(), yearsPerEffectLevel.getValue(), enableWineMakerSetBonus.getValue(), damagePerUse.getValue(), probabilityForDamage.getValue(), probabilityToKeepBoneMeal.getValue(), fermentationBarrelTime.getValue(), grapeGrowthSpeed.getValue(), enableNetherLattices.getValue());
return new VineryConfig(wineTraderChance.getValue(), yearLengthInDays.getValue(), yearsPerEffectLevel.getValue(), enableWineMakerSetBonus.getValue(), damagePerUse.getValue(), probabilityForDamage.getValue(), probabilityToKeepBoneMeal.getValue(), fermentationBarrelTime.getValue(), grapeGrowthSpeed.getValue(), enableNetherLattices.getValue(), disabledWines.getValue());
}


Expand Down
24 changes: 21 additions & 3 deletions common/src/main/java/satisfyu/vinery/config/VineryConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
import com.mojang.serialization.codecs.RecordCodecBuilder;
import de.cristelknight.doapi.config.jankson.config.CommentedConfig;
import net.minecraft.Util;
import net.minecraft.resources.ResourceLocation;

import java.util.HashMap;
import java.util.List;


public record VineryConfig(int wineTraderChance, int yearLengthInDays, int yearsPerEffectLevel,
boolean enableWineMakerSetBonus, int damagePerUse, int probabilityForDamage, int probabilityToKeepBoneMeal, int fermentationBarrelTime, int grapeGrowthSpeed, boolean enableNetherLattices)
boolean enableWineMakerSetBonus, int damagePerUse, int probabilityForDamage, int probabilityToKeepBoneMeal, int fermentationBarrelTime, int grapeGrowthSpeed, boolean enableNetherLattices,
List<String> disabledWines)
implements CommentedConfig<VineryConfig> {

private static VineryConfig INSTANCE = null;

public static final VineryConfig DEFAULT = new VineryConfig(50, 16, 4, true, 1, 30, 100, 50, 100, false);
public static final VineryConfig DEFAULT = new VineryConfig(50, 16, 4, true, 1, 30, 100, 50, 100, false, List.of());

public static final Codec<VineryConfig> CODEC = RecordCodecBuilder.create(builder ->
builder.group(
Expand All @@ -27,7 +30,8 @@ public record VineryConfig(int wineTraderChance, int yearLengthInDays, int years
Codec.intRange(1, 100).fieldOf("probability_to_keep_bone_meal").orElse(DEFAULT.probabilityToKeepBoneMeal).forGetter(c -> c.probabilityToKeepBoneMeal),
Codec.intRange(1, 10000).fieldOf("fermentation_barrel_time").orElse(DEFAULT.fermentationBarrelTime).forGetter(c -> c.fermentationBarrelTime),
Codec.intRange(0, 100).fieldOf("grape_growth_speed").orElse(DEFAULT.grapeGrowthSpeed).forGetter(c -> c.grapeGrowthSpeed),
Codec.BOOL.fieldOf("enable_nether_lattices").orElse(DEFAULT.enableNetherLattices).forGetter(c -> c.enableNetherLattices)
Codec.BOOL.fieldOf("enable_nether_lattices").orElse(DEFAULT.enableNetherLattices).forGetter(c -> c.enableNetherLattices),
Codec.STRING.listOf().fieldOf("disabled_wines").orElse(DEFAULT.disabledWines).forGetter(c -> c.disabledWines)
).apply(builder, VineryConfig::new)
);

Expand Down Expand Up @@ -55,6 +59,9 @@ public HashMap<String, String> getComments() {
Ticks it takes to ferment a bottle""");
map.put("enable_nether_lattices", """
(It is recommended to download NetherVinery instead)""");
map.put("disabled_wines", """
List of wine item IDs to completely disable (e.g. vinery:eiswein).
Disabled wines can't be crafted, are hidden from the creative menu / JEI / REI / villager trades, and existing bottles can't be drunk.""");
});
}

Expand Down Expand Up @@ -98,4 +105,15 @@ public boolean isSorted() {
public void setInstance(VineryConfig instance) {
INSTANCE = instance;
}

/** True if the given item id is listed in {@code disabled_wines}. */
public boolean isWineDisabled(ResourceLocation id) {
return id != null && this.disabledWines.contains(id.toString());
}

/** Convenience accessor against the currently-loaded config; safe to call before the config is loaded. */
public static boolean isDisabled(ResourceLocation id) {
VineryConfig config = DEFAULT.getConfig();
return config != null && config.isWineDisabled(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
import net.mehvahdjukaar.moonlight.api.resources.pack.DynamicTexturePack;
import net.mehvahdjukaar.moonlight.api.set.BlockSetAPI;
import net.mehvahdjukaar.moonlight.api.set.wood.WoodType;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.GsonHelper;
import org.apache.logging.log4j.Logger;
import satisfyu.vinery.Vinery;
import satisfyu.vinery.VineryIdentifier;
import satisfyu.vinery.config.VineryConfig;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class VineryServerDataProvider {
Expand Down Expand Up @@ -55,6 +60,43 @@ public void regenerateDynamicAssets(ResourceManager resourceManager) {
latticeCount.getAndIncrement();
});
this.getLogger().debug("Generated {} lattice recipes", latticeCount);

// Keep the "wine_collector" advancement completable when wines are disabled: its single
// criterion requires holding every wine at once, so drop any disabled wine's requirement.
List<String> disabledWines = VineryConfig.DEFAULT.getConfig().disabledWines();
if (disabledWines != null && !disabledWines.isEmpty()) {
try {
StaticResource wineCollector = StaticResource.getOrFail(resourceManager, new VineryIdentifier("advancements/main/wine_collector.json"));
this.addSimilarJsonResource(resourceManager, wineCollector,
content -> filterWineCollector(content, disabledWines),
path -> path);
this.getLogger().info("Patched wine_collector advancement to skip {} disabled wine(s)", disabledWines.size());
} catch (Exception e) {
this.getLogger().error("Failed to patch wine_collector advancement for disabled wines", e);
}
}
}

/** Removes any {@code inventory_changed} item predicate that references a disabled wine. */
private static String filterWineCollector(String json, List<String> disabledWines) {
JsonObject obj = GsonHelper.parse(json);
JsonObject conditions = obj.getAsJsonObject("criteria").getAsJsonObject("get_wines").getAsJsonObject("conditions");
JsonArray items = conditions.getAsJsonArray("items");
JsonArray kept = new JsonArray();
for (JsonElement entry : items) {
boolean disabled = false;
for (JsonElement id : entry.getAsJsonObject().getAsJsonArray("items")) {
if (disabledWines.contains(id.getAsString())) {
disabled = true;
break;
}
}
if (!disabled) {
kept.add(entry);
}
}
conditions.add("items", kept);
return obj.toString();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import net.minecraft.world.entity.npc.WanderingTrader;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.Level;
import net.minecraft.core.registries.BuiltInRegistries;
import satisfyu.vinery.config.VineryConfig;
import satisfyu.vinery.registry.ObjectRegistry;

import java.util.HashMap;
Expand Down Expand Up @@ -49,6 +51,7 @@ protected void updateTrades() {
this.offers = new MerchantOffers();
}
this.addOffersFromItemListings(this.offers, TRADES.get(1), 8);
this.offers.removeIf(offer -> VineryConfig.isDisabled(BuiltInRegistries.ITEM.getKey(offer.getResult().getItem())));
}

}
6 changes: 6 additions & 0 deletions common/src/main/java/satisfyu/vinery/item/DrinkBlockItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import de.cristelknight.doapi.common.block.entity.StorageBlockEntity;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.InteractionHand;
Expand All @@ -22,6 +23,7 @@
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import satisfyu.vinery.config.VineryConfig;
import satisfyu.vinery.registry.ObjectRegistry;
import satisfyu.vinery.util.GeneralUtil;
import satisfyu.vinery.util.WineYears;
Expand Down Expand Up @@ -144,6 +146,10 @@ public ItemStack finishUsingItem(ItemStack itemStack, Level level, LivingEntity

@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand interactionHand) {
if (VineryConfig.isDisabled(BuiltInRegistries.ITEM.getKey(this))) {
// Disabled wines are inert: any existing bottle can't be drunk.
return InteractionResultHolder.fail(player.getItemInHand(interactionHand));
}
return ItemUtils.startUsingInstantly(level, player, interactionHand);
}
}
105 changes: 105 additions & 0 deletions common/src/main/java/satisfyu/vinery/mixin/RecipeManagerMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package satisfyu.vinery.mixin;

import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.item.crafting.RecipeType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 satisfyu.vinery.config.VineryConfig;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Drops any recipe whose result item is a disabled wine (see {@code disabled_wines} in the Vinery config),
* regardless of recipe type. This single hook covers the fermentation barrel, the apple press, and Create
* mixing/pressing recipes (Create's {@code ProcessingRecipe#getResultItem} returns its first rollable output,
* and every Vinery wine recipe is single-output). JEI, REI and the vanilla recipe book inherit the filtered
* set for free because they all read from the (client-synced) RecipeManager.
* <p>
* The only theoretical gap is a disabled wine appearing as a non-first output of a multi-output recipe (no
* Vinery recipe does this); recipes whose {@link Recipe#getResultItem} throws are skipped defensively.
* <p>
* {@code apply} only runs on the logical server (datapack load); the client receives the already-filtered set
* via the recipe sync packet, so this does not need to run client-side.
*/
@Mixin(RecipeManager.class)
public abstract class RecipeManagerMixin {

@Shadow private Map<RecipeType<?>, Map<ResourceLocation, Recipe<?>>> recipes;

@Shadow private Map<ResourceLocation, Recipe<?>> byName;

private static final Logger VINERY$LOGGER = LoggerFactory.getLogger("Vinery/DisabledWines");

@Inject(
method = "apply(Ljava/util/Map;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V",
at = @At("TAIL")
)
private void vinery$filterDisabledWines(Map<ResourceLocation, JsonElement> object, ResourceManager resourceManager, ProfilerFiller profiler, CallbackInfo ci) {
VineryConfig config = VineryConfig.DEFAULT.getConfig();
if (config == null) {
return;
}
List<String> disabled = config.disabledWines();
if (disabled == null || disabled.isEmpty()) {
return;
}

int[] removed = {0};

Map<ResourceLocation, Recipe<?>> newByName = new HashMap<>();
this.byName.forEach((id, recipe) -> {
if (vinery$isDisabledResult(recipe)) {
removed[0]++;
} else {
newByName.put(id, recipe);
}
});

Map<RecipeType<?>, Map<ResourceLocation, Recipe<?>>> newByType = new HashMap<>();
this.recipes.forEach((type, byId) -> {
Map<ResourceLocation, Recipe<?>> kept = new HashMap<>();
byId.forEach((id, recipe) -> {
if (!vinery$isDisabledResult(recipe)) {
kept.put(id, recipe);
}
});
newByType.put(type, ImmutableMap.copyOf(kept));
});

this.byName = ImmutableMap.copyOf(newByName);
this.recipes = ImmutableMap.copyOf(newByType);

if (removed[0] > 0) {
VINERY$LOGGER.info("Removed {} recipe(s) producing disabled wines: {}", removed[0], disabled);
}
}

private boolean vinery$isDisabledResult(Recipe<?> recipe) {
try {
ItemStack result = recipe.getResultItem(RegistryAccess.EMPTY);
if (result == null || result.isEmpty()) {
return false;
}
return VineryConfig.isDisabled(BuiltInRegistries.ITEM.getKey(result.getItem()));
} catch (Throwable t) {
// A misbehaving third-party recipe must never break datapack loading.
return false;
}
}
}
Loading