Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master-1.20-lts' into master-1.21
Browse files Browse the repository at this point in the history
  • Loading branch information
rubensworks committed Oct 23, 2024
2 parents 48e136b + 9dbe24f commit a30fe1d
Show file tree
Hide file tree
Showing 21 changed files with 1,264 additions and 203 deletions.
16 changes: 16 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ repositories {
name 'jared'
url 'https://maven.blamejared.com/'
}
maven {
name "EMI"
url "https://maven.terraformersmc.com/"
}
maven {
name "REI"
url "https://maven.shedaniel.me/"
}
}

dependencies {
Expand Down Expand Up @@ -139,6 +147,14 @@ dependencies {

//runtimeOnly fg.deobf("top.theillusivec4.curios:curios-forge:${project.curios_version}") // https://maven.theillusivec4.top/top/theillusivec4/curios/curios-forge/
compileOnly("top.theillusivec4.curios:curios-neoforge:${project.curios_version}:api")

compileOnly("top.theillusivec4.curios:curios-forge:${project.curios_version}:api")

compileOnly("dev.emi:emi-neoforge:${emi_version}") // https://maven.terraformersmc.com/releases/dev/emi/emi-neoforge

compileOnly("me.shedaniel:RoughlyEnoughItems-neoforge:${rei_version}") // https://maven.shedaniel.me/me/shedaniel/RoughlyEnoughItems-neoforge/ or just look on CurseForge by modloader and MC version...
implementation("me.shedaniel.cloth:cloth-config-neoforge:$cloth_config_version");
implementation("dev.architectury:architectury-neoforge:$architectury_version");
}

subsystems {
Expand Down
6 changes: 5 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ release_type=release
fingerprint=bd0353b3e8a2810d60dd584e256e364bc3bedd44

integrateddynamics_version=1.23.2-832
integratedterminals_version=1.5.0-368
integratedterminals_version=1.5.2-388
commoncapabilities_version=2.9.3-147
curios_version=7.3.4+1.20.4
jei_version=1.21-neoforge:19.5.0.31
emi_version=1.1.16+1.21.1
rei_version=16.0.783
cloth_config_version=15.0.140
architectury_version=13.0.6

# Workaround for Spotless bug
# https://github.com/diffplug/spotless/issues/834
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.cyclops.integratedterminalscompat.modcompat.common;

import net.minecraft.world.item.ItemStack;

/**
* @author rubensworks
*/
public interface RecipeInputSlot extends Iterable<ItemStack> {

public boolean isEmpty();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package org.cyclops.integratedterminalscompat.modcompat.common;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.items.wrapper.InvWrapper;
import org.apache.commons.lang3.tuple.Pair;
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
import org.cyclops.commoncapabilities.ingredient.storage.IngredientComponentStorageWrapperHandlerItemStack;
import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollectionMutable;
import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionHelpers;
import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionPrototypeMap;
import org.cyclops.integratedterminals.api.terminalstorage.ITerminalStorageTabCommon;
import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentClient;
import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentItemStackCrafting;
import org.cyclops.integratedterminals.core.terminalstorage.TerminalStorageTabIngredientComponentItemStackCraftingCommon;
import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStorageBase;
import org.cyclops.integratedterminalscompat.IntegratedTerminalsCompat;
import org.cyclops.integratedterminalscompat.network.packet.TerminalStorageIngredientItemStackCraftingGridSetRecipe;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
* Common helpers for JEI/EMI/REI
* @author rubensworks
*/
public class RecipeTransferHelpers {

// The amount of seconds recipeErrors will be cached for transferRecipe
private static final long RECIPE_ERROR_CACHE_TIME = 60;
private static final Cache<Object, Optional<RecipeTransferResult<?>>> recipeErrorCache = CacheBuilder.newBuilder()
.expireAfterAccess(RECIPE_ERROR_CACHE_TIME, TimeUnit.SECONDS)
.build();

public static Optional<Pair<TerminalStorageTabIngredientComponentItemStackCraftingCommon, TerminalStorageTabIngredientComponentClient<ItemStack, Integer>>> getTabs(ContainerTerminalStorageBase<?> container) {
if (Objects.equals(container.getSelectedTab(), TerminalStorageTabIngredientComponentItemStackCrafting.NAME.toString())) {
ITerminalStorageTabCommon tabCommon = container.getTabCommon(container.getSelectedTab());
TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting =
(TerminalStorageTabIngredientComponentItemStackCraftingCommon) tabCommon;
TerminalStorageTabIngredientComponentClient tabClient = (TerminalStorageTabIngredientComponentClient)
container.getTabClient(container.getSelectedTab());
return Optional.of(Pair.of(tabCommonCrafting, tabClient));
}
return Optional.empty();
}

public static <T extends RecipeInputSlot> Optional<RecipeTransferResult<T>> getMissingItems(Object cacheKey, ContainerTerminalStorageBase<?> container, Iterable<T> recipeInputSlots, Player player, TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting, TerminalStorageTabIngredientComponentClient<?, ?> tabClient, Function<ItemStack, Integer> itemStackToMatchCondition, Supplier<Integer> getId, Consumer<Integer> onChangeId) {
Callable<Optional<RecipeTransferResult<T>>> missingItemsSupplier =
() -> getMissingItemsUncached(container, recipeInputSlots, player, tabCommonCrafting, itemStackToMatchCondition, onChangeId);
if (getId.get() != tabClient.getLastChangeId()) {
// Clear cache when storage contents changed
recipeErrorCache.invalidateAll();
}
try {
return recipeErrorCache.get(cacheKey, (Callable) missingItemsSupplier);
} catch (ExecutionException e) {
// Throw exceptions from missingItemsSupplier
throw new RuntimeException(e);
}
}

public static <T extends RecipeInputSlot> Optional<RecipeTransferResult<T>> getMissingItemsUncached(ContainerTerminalStorageBase<?> container, Iterable<T> recipeInputSlots, Player player, TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting, Function<ItemStack, Integer> itemStackToMatchCondition, Consumer<Integer> onChangeId) {
TerminalStorageTabIngredientComponentClient tabClient = (TerminalStorageTabIngredientComponentClient)
container.getTabClient(container.getSelectedTab());

// Check in the player inventory and local client view if the required recipe ingredients are available

// Build crafting grid index
IIngredientCollectionMutable<ItemStack, Integer> hayStackCraftingGrid = new IngredientCollectionPrototypeMap<>(IngredientComponent.ITEMSTACK);
for (int slot = 0; slot < tabCommonCrafting.getInventoryCrafting().getContainerSize(); slot++) {
hayStackCraftingGrid.add(tabCommonCrafting.getInventoryCrafting().getItem(slot));
}

// Build player inventory index
IIngredientCollectionMutable<ItemStack, Integer> hayStackPlayer = new IngredientCollectionPrototypeMap<>(IngredientComponent.ITEMSTACK);
hayStackPlayer.addAll(player.getInventory().items);

// Build local client view of storage
List<TerminalStorageTabIngredientComponentClient.InstanceWithMetadata<ItemStack>> unfilteredIngredients = tabClient
.getUnfilteredIngredientsView(container.getSelectedChannel());
IIngredientCollectionMutable<ItemStack, Integer> hayStack = IngredientCollectionHelpers.createCollapsedCollection(IngredientComponent.ITEMSTACK);
IIngredientCollectionMutable<ItemStack, Integer> hayStackCraftable = IngredientCollectionHelpers.createCollapsedCollection(IngredientComponent.ITEMSTACK);
hayStack.addAll(unfilteredIngredients
.stream()
.filter(i -> i.getCraftingOption() == null)
.map(TerminalStorageTabIngredientComponentClient.InstanceWithMetadata::getInstance)
.collect(Collectors.toList()));
hayStackCraftable.addAll(unfilteredIngredients
.stream()
.filter(i -> i.getCraftingOption() != null)
.map(TerminalStorageTabIngredientComponentClient.InstanceWithMetadata::getInstance)
.collect(Collectors.toList()));

List<T> slotsMissingItems = Lists.newArrayList();
List<T> slotsMissingCraftableItems = Lists.newArrayList();
for (T slot : recipeInputSlots) {
if (!slot.isEmpty()) {
boolean found = false;
boolean craftable = false;
for (ItemStack itemStack : slot) {
int matchCondition = itemStackToMatchCondition.apply(itemStack);

// First check in the crafting grid
if (hayStackCraftingGrid.contains(itemStack, matchCondition)) {
hayStackPlayer.remove(itemStack);
found = true;
break;
}

// Then check in player inventory
if (hayStackPlayer.contains(itemStack, matchCondition)) {
hayStackPlayer.remove(itemStack);
found = true;
break;
}

// Then check the storage
if (hayStack.contains(itemStack, matchCondition)) {
hayStack.remove(itemStack);
found = true;
break;
}

// Also check if this item could be craftable
if (hayStackCraftable.contains(itemStack, matchCondition)) {
craftable = true;
}
}

if (!found) {
if (craftable) {
slotsMissingCraftableItems.add(slot);
} else {
slotsMissingItems.add(slot);
}
}
}
}

onChangeId.accept(tabClient.getLastChangeId());
if (!slotsMissingItems.isEmpty() || !slotsMissingCraftableItems.isEmpty()) {
return Optional.of(new RecipeTransferResult<T>(slotsMissingItems, slotsMissingCraftableItems));
}

return Optional.empty();
}

public static <T extends RecipeInputSlot> void transferRecipe(ContainerTerminalStorageBase<?> container, Iterable<T> recipeInputSlots, Player player, TerminalStorageTabIngredientComponentItemStackCraftingCommon tabCommonCrafting, Function<ItemStack, Integer> itemStackToMatchCondition, boolean maxTransfer) {
IngredientComponentStorageWrapperHandlerItemStack.ComponentStorageWrapper playerInventory =
new IngredientComponentStorageWrapperHandlerItemStack.ComponentStorageWrapper(IngredientComponent.ITEMSTACK, new InvWrapper(player.getInventory()));

// Send a packet to the server if the recipe effectively needs to be applied to the grid
Map<Integer, Pair<ItemStack, Integer>> slottedIngredientsFromPlayer = Maps.newHashMap();
Map<Integer, List<Pair<ItemStack, Integer>>> slottedIngredientsFromStorage = Maps.newHashMap();
int slotOffset = tabCommonCrafting.getSlotCrafting().index;
int slotId = 0;
for (T recipeSlot : recipeInputSlots) {
if (!recipeSlot.isEmpty()) {
boolean found = false;

// First check if we can transfer from the player inventory
// No need to check the crafting grid, as the server will first clear the grid into the storage in TerminalStorageIngredientItemStackCraftingGridSetRecipe
for (ItemStack itemStack : recipeSlot) {
int matchCondition = itemStackToMatchCondition.apply(itemStack);

if (!playerInventory.extract(itemStack, matchCondition, true).isEmpty()) {
found = true;

// Move from player to crafting grid
ItemStack extracted = playerInventory.extract(itemStack, matchCondition, false);
Slot slot = container.getSlot(slotId + slotOffset);
slot.set(extracted);

// Do the exact same thing server-side
slottedIngredientsFromPlayer.put(slotId, Pair.of(itemStack, itemStackToMatchCondition.apply(itemStack)));

break;
}
}

if (!found) {
// Otherwise, request them from the storage
slottedIngredientsFromStorage.put(slotId, Streams.stream(recipeSlot)
.map(itemStack -> Pair.of(itemStack, itemStackToMatchCondition.apply(itemStack)))
.collect(Collectors.toList()));
}
}
slotId++;
}

IntegratedTerminalsCompat._instance.getPacketHandler().sendToServer(
new TerminalStorageIngredientItemStackCraftingGridSetRecipe(container.getSelectedTab(),
container.getSelectedChannel(), maxTransfer, slottedIngredientsFromPlayer, slottedIngredientsFromStorage, AbstractContainerScreen.hasControlDown()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.cyclops.integratedterminalscompat.modcompat.common;

import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import org.cyclops.cyclopscore.helper.Helpers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
* @author rubensworks
*/
public class RecipeTransferResult<T extends RecipeInputSlot> {

public static final boolean HAS_CMD = System.getProperty("os.name").equals("Mac OS X");

public static final int SLOT_COLOR_MISSING = Helpers.RGBAToInt(255, 0, 0, 100);
public static final int SLOT_COLOR_CRAFTABLE = Helpers.RGBAToInt(0, 0, 255, 100);

public static final int HIGHLIGHT_COLOR_FAIL = Helpers.RGBAToInt(255, 0, 0, 100);
public static final int HIGHLIGHT_COLOR_CRAFTABLE = Helpers.RGBAToInt(0, 0, 255, 100);
public static final int HIGHLIGHT_COLOR_CRAFTABLE_PARTIAL = Helpers.RGBAToInt(255, 125, 0, 100);

private final List<Component> message = new ArrayList<>();
private final Collection<T> slotsMissing;
private final Collection<T> slotsCraftable;
private final int color;

public RecipeTransferResult(Collection<T> slotsMissing, Collection<T> slotsCraftable) {
this.slotsMissing = slotsMissing;
this.slotsCraftable = slotsCraftable;
if (slotsCraftable.isEmpty() && slotsMissing.isEmpty()) {
this.color = HIGHLIGHT_COLOR_CRAFTABLE;
} else if (slotsMissing.isEmpty()) {
// Missing items, but they are all craftable
this.message.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.jei.transfer.craftable").withStyle(ChatFormatting.RED));
this.message.add(Component.translatable(HAS_CMD ? "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info_cmd" : "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info").withStyle(ChatFormatting.ITALIC));
this.color = HIGHLIGHT_COLOR_CRAFTABLE;
} else if (!slotsCraftable.isEmpty()) {
// Missing items, but only some of them are craftable
this.message.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.jei.transfer.craftable_partial").withStyle(ChatFormatting.RED));
this.message.add(Component.translatable(HAS_CMD ? "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info_cmd" : "gui.integratedterminalscompat.terminal_storage.jei.transfer.craft.info").withStyle(ChatFormatting.ITALIC));
this.color = HIGHLIGHT_COLOR_CRAFTABLE_PARTIAL;
} else {
// Missing items, and none are craftable
this.message.add(Component.translatable("gui.integratedterminalscompat.terminal_storage.jei.transfer.missing").withStyle(ChatFormatting.ITALIC));
this.color = HIGHLIGHT_COLOR_FAIL;
}
}

public int getButtonHighlightColor() {
return this.color;
}

public List<Component> getMessage() {
return message;
}

public Collection<T> getSlotsCraftable() {
return slotsCraftable;
}

public Collection<T> getSlotsMissing() {
return slotsMissing;
}

}
Loading

0 comments on commit a30fe1d

Please sign in to comment.