Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ changelog.txt

.cache
config

# ClientDevBridge session state (golden images are intentionally kept)
.clientdevbridge/*
!.clientdevbridge/golden/
10 changes: 7 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'net.neoforged.moddev' version '2.0.141'
id 'net.neoforged.moddev' version '2.0.147'
id 'net.darkhax.curseforgegradle' version '1.1.28'
id 'com.github.kt3k.coveralls' version '2.12.0'
id 'com.diffplug.spotless' version '7.2.1'
Expand Down Expand Up @@ -134,8 +134,12 @@ dependencies {
implementation "org.cyclops.cyclopscore:cyclopscore-${project.minecraft_version}-neoforge:${project.cyclopscore_version}"
}

implementation("curse.maven:visual-workbench-500273:${visualworkbench_version}")
implementation("curse.maven:puzzles-lib-495476:${puzzleslib_version}")
// compileOnly until Visual Workbench (and its Puzzles Lib dependency) release for 26.3:
// their 26.2 builds refuse to load on 26.3, which fails the test JVM's mod loading.
// The mod compat itself only activates when visualworkbench is actually present.
Comment thread
rubensworks marked this conversation as resolved.
Outdated
// TODO: put these back on implementation once 26.3 builds exist.
compileOnly("curse.maven:visual-workbench-500273:${visualworkbench_version}")
compileOnly("curse.maven:puzzles-lib-495476:${puzzleslib_version}")
}

test {
Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ group=org.cyclops.commoncapabilities
java_version=25

# Common
minecraft_version=26.2
minecraft_version=26.3
mod_name=CommonCapabilities
mod_author=rubensworks (aka kroeser)
mod_id=commoncapabilities
Expand All @@ -19,7 +19,7 @@ curseforge_project_id=247007
modrinth_project_id=oFXrCkDI

# NeoForge
neoforge_version=26.2.0.6-beta
neoforge_version=26.3.0.7-beta
neoforge_loader_version_range=[4,)
neoforge_update_json_url=https://raw.githubusercontent.com/CyclopsMC/Versions/master/neoforge_update/common-capabilities.json

Expand All @@ -31,7 +31,7 @@ org.gradle.configuration-cache=true
org.gradle.configureondemand=true

# Dependencies
cyclopscore_version=1.30.0-1038
cyclopscore_version=1.30.0-1159
rfapi_version=1.12-2.1.0.7
enderio_version=3.0.0.40_alpha
endercore_version=0.4.0.24-alpha
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
107 changes: 55 additions & 52 deletions src/main/java/net/minecraft/potion/PotionHelperCommonCapabilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,31 @@
import com.google.common.collect.Lists;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.Item;
import net.minecraft.core.component.predicates.PotionsPredicate;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.Potion;
import net.minecraft.world.item.alchemy.PotionBrewing;
import net.minecraft.world.item.alchemy.PotionContents;
import net.minecraft.world.item.alchemy.Potions;
import net.minecraft.world.item.crafting.BrewingInput;
import net.minecraft.world.item.crafting.BrewingRecipe;
import net.minecraft.world.item.crafting.PotionIngredient;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.server.ServerLifecycleHooks;
import org.cyclops.commoncapabilities.api.capability.itemhandler.ItemMatch;
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
import org.cyclops.commoncapabilities.api.capability.recipehandler.RecipeDefinition;
import org.cyclops.commoncapabilities.api.capability.recipehandler.RecipeHandlerHelpers;
import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
import org.cyclops.commoncapabilities.api.ingredient.MixedIngredients;
import org.cyclops.commoncapabilities.api.ingredient.PrototypedIngredient;

import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
* Brewing became datapack-driven in Minecraft 26.3, so the recipes are read straight from
* {@link BrewingRecipe} instead of being derived by walking {@code PotionBrewing}'s mixes.
Comment thread
rubensworks marked this conversation as resolved.
Outdated
* @author rubensworks
*/
public class PotionHelperCommonCapabilities {
Expand All @@ -34,66 +37,66 @@ public class PotionHelperCommonCapabilities {
public static List<IRecipeDefinition> getVanillaRecipes() {
if (VANILLA_RECIPES == null) {
VANILLA_RECIPES = Lists.newArrayList();
List<ItemStack> inputItems = Lists.newArrayList(PotionContents.createItemStack(
Items.POTION, Potions.WATER));
List<IPrototypedIngredient<ItemStack, Integer>> ingredients = Lists.newArrayList();
PotionBrewing potionBrewing = ServerLifecycleHooks.getCurrentServer().potionBrewing();
for (PotionBrewing.Mix<Item> mixPredicate : potionBrewing.containerMixes) {
ingredients.addAll(RecipeHandlerHelpers.getPrototypesFromIngredient(mixPredicate.ingredient(), null).getAlternatives());
}
for (PotionBrewing.Mix<Potion> mixPredicate : potionBrewing.potionMixes) {
ingredients.addAll(RecipeHandlerHelpers.getPrototypesFromIngredient(mixPredicate.ingredient(), null).getAlternatives());
}

List<ItemStack> checkInputItems = Lists.newArrayList(inputItems);
while (!checkInputItems.isEmpty()) {
List<ItemStack> newItems = Lists.newArrayList();
for (ItemStack inputItem : checkInputItems) {
IPrototypedIngredient<ItemStack, Integer> item =
new PrototypedIngredient<>(IngredientComponent.ITEMSTACK, inputItem, ItemMatch.ITEM | ItemMatch.DATA);
for (IPrototypedIngredient<ItemStack, Integer> ingredient : ingredients) {
ItemStack output = potionBrewing.mix(ingredient.getPrototype().copy(), inputItem.copy());
if (isPotionOutputValid(inputItem, output)) {
addRecipeIfNew(ingredient, item, output, newItems);
}
}

// Throw error if infinite loop detected
if (VANILLA_RECIPES.size() > 7500) {
throw new RuntimeException("Infinite loop detected! Please report this to the Common " +
"Capabilities issue tracker with a list of (potion-changing) mods installed");
Level level = ServerLifecycleHooks.getCurrentServer().overworld();
if (level.recipeAccess() instanceof RecipeManager recipeManager) {
for (RecipeHolder<?> holder : recipeManager.getRecipes()) {
if (holder.value() instanceof BrewingRecipe recipe) {
addRecipes(recipe);
}
}
checkInputItems = newItems;
}

}
return VANILLA_RECIPES;
}

protected static boolean isPotionOutputValid(ItemStack input, ItemStack output) {
return !input.isEmpty() && !output.isEmpty() && (input.getItem() != output.getItem()
|| (getPotion(output) != Potions.WATER
&& !Objects.equals(BuiltInRegistries.POTION.getKey(getPotion(output).value()),
BuiltInRegistries.POTION.getKey(getPotion(input).value()))));
protected static void addRecipes(BrewingRecipe recipe) {
for (ItemStack reagent : getMatchingStacks(recipe.getReagent())) {
for (ItemStack input : getMatchingStacks(recipe.getInput())) {
// Let the recipe assemble the output itself, so subtypes that derive the output
// from the input container (as the vanilla mixes do) are handled correctly
ItemStack output = recipe.assemble(new BrewingInput(input, reagent));
if (!output.isEmpty()) {
addRecipe(
new PrototypedIngredient<>(IngredientComponent.ITEMSTACK, reagent, ItemMatch.ITEM | ItemMatch.DATA),
new PrototypedIngredient<>(IngredientComponent.ITEMSTACK, input, ItemMatch.ITEM | ItemMatch.DATA),
output);
}
}
}
}

protected static Holder<Potion> getPotion(ItemStack itemStack) {
return itemStack.get(DataComponents.POTION_CONTENTS)
.potion()
.orElse(Potions.WATER);
/**
* Expand a potion ingredient into the concrete stacks it accepts:
* its container items, each carrying one of the potions its predicate allows.
*/
protected static List<ItemStack> getMatchingStacks(PotionIngredient ingredient) {
List<ItemStack> containers = ingredient.ingredient().items()
.map(ItemStack::new)
.toList();
Optional<PotionsPredicate> potionsPredicate = ingredient.potions();
if (potionsPredicate.isEmpty() || potionsPredicate.get().potions().isEmpty()) {
return containers;
}

List<ItemStack> stacks = Lists.newArrayList();
for (ItemStack container : containers) {
for (Holder<Potion> potion : potionsPredicate.get().potions().get()) {
ItemStack stack = container.copy();
stack.set(DataComponents.POTION_CONTENTS, new PotionContents(potion));
stacks.add(stack);
}
}
return stacks;
}

protected static void addRecipeIfNew(IPrototypedIngredient<ItemStack, Integer> ingredient,
IPrototypedIngredient<ItemStack, Integer> item,
ItemStack output,
List<ItemStack> newItems) {
protected static void addRecipe(IPrototypedIngredient<ItemStack, Integer> reagent,
IPrototypedIngredient<ItemStack, Integer> input,
ItemStack output) {
IRecipeDefinition recipe = RecipeDefinition.ofIngredients(IngredientComponent.ITEMSTACK,
Lists.newArrayList(Lists.newArrayList(ingredient), Lists.newArrayList(item), Lists.newArrayList(item), Lists.newArrayList(item)),
Lists.newArrayList(Lists.newArrayList(reagent), Lists.newArrayList(input), Lists.newArrayList(input), Lists.newArrayList(input)),
MixedIngredients.ofInstances(IngredientComponent.ITEMSTACK, Lists.newArrayList(ItemStack.EMPTY, output, output, output)));
if (!VANILLA_RECIPES.contains(recipe)) {
VANILLA_RECIPES.add(recipe);
newItems.add(output);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
import net.minecraft.resources.Identifier;
import org.cyclops.commoncapabilities.CommonCapabilities;
import org.cyclops.commoncapabilities.GeneralConfig;
import org.cyclops.commoncapabilities.api.ingredient.IDataComparator;

import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Set;
import java.util.stream.Collectors;

/**
* A comparator implementation for Data Components.
* @author rubensworks
*/
public class DataComparator implements Comparator<DataComponentMap> {
public class DataComparator implements IDataComparator {

/**
* A comparator for Data Components. (This is set in GeneralConfig)
Expand All @@ -32,6 +32,7 @@ public DataComparator(@Nullable Set<Identifier> ignoreDataComponentTypes) {
this.ignoreDataComponentTypes = ignoreDataComponentTypes;
}

@Override
public boolean hasIgnoreDataComponentTypes() {
return ignoreDataComponentTypes != null && !ignoreDataComponentTypes.isEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ protected NonNullList<ItemStack> getItemList() {
BundleContents container = getItemStack().get(DataComponents.BUNDLE_CONTENTS);
if (container != null) {
NonNullList<ItemStack> list = NonNullList.create();
container.itemCopyStream().forEach(list::add);
container.itemCopies().forEach(list::add);
list.add(ItemStack.EMPTY);
return list;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import net.minecraft.core.NonNullList;
import net.minecraft.potion.PotionHelperCommonCapabilities;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.alchemy.PotionBrewing;
import net.minecraft.world.item.crafting.BrewingInput;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.server.ServerLifecycleHooks;
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeHandler;
Expand Down Expand Up @@ -64,14 +67,28 @@ public IMixedIngredients simulate(IMixedIngredients input) {
return null;
}

// Brewing is recipe-driven since Minecraft 26.3: slot 0 holds the reagent, slots 1-3 the potions
Comment thread
rubensworks marked this conversation as resolved.
Outdated
Level level = ServerLifecycleHooks.getCurrentServer().overworld();
if (!(level.recipeAccess() instanceof RecipeManager recipeManager)) {
return null;
}

ItemStack reagent = recipeIngredients.get(0);
NonNullList<ItemStack> brewingItemStacks = NonNullList.withSize(4, ItemStack.EMPTY);
PotionBrewing potionbrewing = ServerLifecycleHooks.getCurrentServer().potionBrewing();
for (int i = 0; i < recipeIngredients.size(); i++) {
brewingItemStacks.set(i, potionbrewing.mix(brewingItemStacks.get(0), recipeIngredients.get(i).copy()));
boolean brewed = false;
for (int i = 1; i < recipeIngredients.size(); i++) {
ItemStack potion = recipeIngredients.get(i).copy();
BrewingInput brewingInput = new BrewingInput(potion, reagent);
ItemStack output = recipeManager.getRecipeFor(RecipeType.BREWING, brewingInput, level)
.map(recipe -> recipe.value().assemble(brewingInput))
.orElse(ItemStack.EMPTY);
if (!output.isEmpty()) {
brewingItemStacks.set(i, output);
brewed = true;
}
}
brewingItemStacks.set(0, ItemStack.EMPTY);

return MixedIngredients.ofInstances(IngredientComponent.ITEMSTACK, brewingItemStacks);
return brewed ? MixedIngredients.ofInstances(IngredientComponent.ITEMSTACK, brewingItemStacks) : null;
}

}
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
package org.cyclops.commoncapabilities.modcompat.vanilla.capability.work;

import net.minecraft.core.component.DataComponents;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.SingleRecipeInput;
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
import org.cyclops.commoncapabilities.api.capability.work.IWorker;

import java.lang.reflect.Field;

/**
* Worker capability for the vanilla furnace tile entities.
* @author rubensworks
*/
public class VanillaAbstractFurnaceWorker implements IWorker {

private static Field FIELD_RECIPE_TYPE;
static {
try {
FIELD_RECIPE_TYPE = AbstractFurnaceBlockEntity.class.getDeclaredField("recipeType");
FIELD_RECIPE_TYPE.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}

private final AbstractFurnaceBlockEntity furnace;

public VanillaAbstractFurnaceWorker(AbstractFurnaceBlockEntity furnace) {
Expand All @@ -32,21 +24,20 @@ public VanillaAbstractFurnaceWorker(AbstractFurnaceBlockEntity furnace) {
@Override
public boolean hasWork() {
ItemStack toMelt = furnace.getItem(0);
Recipe<?> recipe = null;
if (furnace.getLevel().recipeAccess() instanceof RecipeManager recipeManager) {
try {
recipe = recipeManager.getRecipeFor(((RecipeType<? extends AbstractCookingRecipe>) FIELD_RECIPE_TYPE.get(furnace)), new SingleRecipeInput(furnace.getItem(0)), furnace.getLevel())
.map(RecipeHolder::value)
.orElse(null);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
if (toMelt.isEmpty() || !(furnace.getLevel() instanceof ServerLevel serverLevel)) {
return false;
}
return !toMelt.isEmpty() && recipe != null && !((Recipe<SingleRecipeInput>) recipe).assemble(new SingleRecipeInput(furnace.getItem(0))).isEmpty();
// The recipe type is no longer a field, the furnace holds a cached check for it instead
Comment thread
rubensworks marked this conversation as resolved.
Outdated
SingleRecipeInput input = new SingleRecipeInput(toMelt);
return furnace.quickCheck.getRecipeFor(input, serverLevel)
.map(RecipeHolder::value)
.map(recipe -> !((AbstractCookingRecipe) recipe).assemble(input).isEmpty())
.orElse(false);
}

@Override
public boolean canWork() {
return furnace.litTimeRemaining > 0 || furnace.getBurnDuration(furnace.getLevel().fuelValues(), furnace.getItem(1)) > 0;
// FuelValues was replaced by the COOKING_FUEL data component
Comment thread
rubensworks marked this conversation as resolved.
Outdated
return furnace.litTimeRemaining > 0 || furnace.getItem(1).has(DataComponents.COOKING_FUEL);
}
}
Loading
Loading