Skip to content
Merged
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
230 changes: 168 additions & 62 deletions src/main/java/codechicken/nei/FavoriteRecipes.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,141 @@
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Objects;
import java.util.Set;

import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;

import org.apache.commons.io.IOUtils;

import com.google.common.io.Files;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;

import codechicken.core.CommonUtils;
import codechicken.nei.api.API;
import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.ICraftingHandler;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.RecipeHandlerRef;
import codechicken.nei.recipe.StackInfo;
import codechicken.nei.util.FavoriteStorage;
import codechicken.nei.util.NBTJson;

public class FavoriteRecipes {

private static final Set<NBTTagCompound> tools = new HashSet<>();
private static final Map<NBTTagCompound, RecipeId> items = new HashMap<>();
private static final Map<String, RecipeId> fluids = new HashMap<>();
protected static final class FastKey {

public final ItemStack stack;
private int hash;

public FastKey(ItemStack stack) {
this.stack = stack;

this.hash = 31 + Item.getIdFromItem(stack.getItem());
this.hash = 31 * this.hash + stack.getItemDamage();
}

@Override
public int hashCode() {
return this.hash;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;

if (o instanceof FastKey k && this.stack.getItem() == k.stack.getItem()
&& this.stack.getItemDamage() == k.stack.getItemDamage()) {
return this.stack == k.stack || Objects.equals(this.stack.stackTagCompound, k.stack.stackTagCompound);
}

return false;
}
}

private static final RestartableTask singleRecipeFavoritesTask = new RestartableTask(
"Generate Single-Recipe Favorites") {

@Override
public void execute() {
final List<ICraftingHandler> handlers = GuiCraftingRecipe.getCraftingHandlers("all");
final Map<FastKey, RecipeHandlerRef> autogeneratedRecipes = new HashMap<>();
final Set<FastKey> whitelistItems = new HashSet<>(ItemList.items.size());
final FavoriteStorage storage = new FavoriteStorage();

for (ItemStack stack : ItemList.items) {
whitelistItems.add(new FastKey(stack));
}

if (interrupted()) return;

for (ICraftingHandler handler : handlers) {
if (!GuiRecipeTab.getHandlerInfo(handler).getShowFavoritesButton()) continue;

for (int i = 0; i < handler.numRecipes(); i++) {
RecipeHandlerRef ref = null;

for (PositionedStack result : getOutputs(handler, i)) {
ItemStack stack = StackInfo.getItemStackWithMinimumDamage(result.items);
final FastKey key = new FastKey(stack);

if (!whitelistItems.contains(key)) continue;

if (autogeneratedRecipes.get(key) == null) {

if (ref == null) {
ref = RecipeHandlerRef.of(handler, i);
}

autogeneratedRecipes.put(key, ref);
} else {
autogeneratedRecipes.remove(key);
whitelistItems.remove(key);
}

}
}
if (interrupted()) return;
}

final Map<RecipeHandlerRef, RecipeId> recipesCache = new HashMap<>();

for (Map.Entry<FastKey, RecipeHandlerRef> entry : autogeneratedRecipes.entrySet()) {
final RecipeId recipeId = recipesCache
.computeIfAbsent(entry.getValue(), r -> RecipeId.of(r.handler, r.recipeIndex));
storage.add(entry.getKey().stack, recipeId);
}

FavoriteRecipes.autoFavorites = storage;
SubsetWidget.updateHiddenItems();
ItemList.updateFilter.restart();
}

protected List<PositionedStack> getOutputs(IRecipeHandler handler, int recipeIndex) {
final PositionedStack pStackResult = handler.getResultStack(recipeIndex);
return pStackResult != null ? Collections.singletonList(pStackResult) : handler.getOtherStacks(recipeIndex);
}

};

private static final FavoriteStorage manualFavorites = new FavoriteStorage();
private static FavoriteStorage autoFavorites = new FavoriteStorage();
private static File favoriteFile;

static {
API.addSubset("Favorites", FavoriteRecipes::contains);
API.addSubset("Favorites.Manual", manualFavorites::contains);
API.addSubset("Favorites.Generated", stack -> autoFavorites.contains(stack));
}

private FavoriteRecipes() {}
Expand All @@ -66,7 +168,6 @@ public static void load() {
final File defaultFavorites = configFavorites.exists() ? configFavorites : globalFavorites;

if (defaultFavorites.exists()) {

try {
if (favoriteFile.createNewFile()) {
InputStream src = new FileInputStream(defaultFavorites);
Expand Down Expand Up @@ -101,7 +202,9 @@ private static void loadData() {
}

final JsonParser parser = new JsonParser();
items.clear();
boolean hasErrors = false;

manualFavorites.clear();

for (String itemStr : itemStrings) {

Expand All @@ -121,107 +224,110 @@ private static void loadData() {
continue;
}

final String fluidKey = getFluidKey(stack);

if (fluidKey != null) {
fluids.put(fluidKey, recipeId);
if (RecipeHandlerRef.of(recipeId) != null) {
manualFavorites.add(stack, recipeId);
} else {
hasErrors = true;
}

items.put(itemStackNBT, recipeId);

if (NEIServerUtils.isItemTool(stack)) {
tools.add(itemStackNBT);
}
}

} catch (Exception e) {
NEIClientConfig.logger.error("Failed to load favorite from json string: {}", itemStr);
hasErrors = true;
}

}

if (hasErrors) {

try {
Files.copy(
favoriteFile,
new File(favoriteFile.getAbsolutePath() + ".backup-" + System.currentTimeMillis()));
NEIClientConfig.logger.info("Backed up invalid favorite file to {}", favoriteFile);
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to backup invalid favorite file to {}", favoriteFile, e);
}

save();
}

loadAutoGeneratedRecipes();

SubsetWidget.updateHiddenItems();
ItemList.updateFilter.restart();
}

public static RecipeId getFavorite(ItemStack stack) {

if (NEIClientConfig.favoritesEnabled() && stack != null) {
RecipeId recipeId = items.get(StackInfo.itemStackToNBT(stack, false));

if (recipeId == null) {
recipeId = fluids.get(getFluidKey(stack));
}

if (recipeId != null) {
return recipeId;
}
final RecipeId recipeId = manualFavorites.getRecipeId(stack);
return recipeId != null ? recipeId : autoFavorites.getRecipeId(stack);
}

if (NEIServerUtils.isItemTool(stack)
&& (stack.stackTagCompound == null || !stack.stackTagCompound.hasKey("GT.ToolStats"))) {
for (NBTTagCompound nbt : tools) {
if (NEIServerUtils.areStacksSameTypeCraftingWithNBT(stack, StackInfo.loadFromNBT(nbt))) {
return items.get(nbt);
}
}
}
return null;
}

public static ItemStack getManualFavorite(RecipeId recipeId) {
if (NEIClientConfig.favoritesEnabled() && recipeId != null) {
return manualFavorites.getItemStack(recipeId);
}

return null;
}

public static ItemStack getFavorite(RecipeId recipeId) {
if (NEIClientConfig.favoritesEnabled()) {
final Optional<Map.Entry<NBTTagCompound, RecipeId>> result = items.entrySet().stream()
.filter(entry -> entry.getValue().equals(recipeId)).findAny();

if (result.isPresent()) {
return StackInfo.loadFromNBT(result.get().getKey());
}
if (NEIClientConfig.favoritesEnabled() && recipeId != null) {
final ItemStack stack = manualFavorites.getItemStack(recipeId);
return stack != null ? stack : autoFavorites.getItemStack(recipeId);
}

return null;
}

public static int size() {
return items.size() + fluids.size();
return manualFavorites.size() + autoFavorites.size();
}

public static void reload() {
save();
load();
}

public static boolean contains(ItemStack stack) {
return getFavorite(stack) != null;
}

public static void setFavorite(ItemStack stack, RecipeId recipeId) {
final NBTTagCompound itemStackNBT = StackInfo.itemStackToNBT(stack, false);
final String fluidKey = getFluidKey(stack);
if (stack == null) return;

items.entrySet().removeIf(entry -> entry.getKey().equals(itemStackNBT) || entry.getValue().equals(recipeId));
fluids.entrySet().removeIf(entry -> entry.getKey().equals(fluidKey) || entry.getValue().equals(recipeId));
manualFavorites.removeItemStack(stack);

if (recipeId != null) {
items.put(itemStackNBT, recipeId);

if (NEIServerUtils.isItemTool(stack)) {
tools.add(itemStackNBT);
}

if (fluidKey != null) {
fluids.put(fluidKey, recipeId);
}
manualFavorites.removeRecipeId(recipeId);
manualFavorites.add(stack, recipeId);
}

SubsetWidget.updateHiddenItems();
ItemList.updateFilter.restart();
}

private static String getFluidKey(ItemStack stack) {
final FluidStack fluid = StackInfo.getFluid(stack);
public static void loadAutoGeneratedRecipes() {
if (ItemList.loadFinished
&& NEIClientConfig.getBooleanSetting("inventory.favorites.generateSingleRecipeFavorites")) {
singleRecipeFavoritesTask.restart();
} else {
singleRecipeFavoritesTask.stop();
autoFavorites.clear();

if (fluid != null) {
return FluidRegistry.getFluidName(fluid);
SubsetWidget.updateHiddenItems();
ItemList.updateFilter.restart();
}
}

return null;
public static boolean isAutogenerated(RecipeId recipeId) {
return autoFavorites.getItemStack(recipeId) != null;
}

public static void save() {
Expand All @@ -232,7 +338,7 @@ public static void save() {

final List<String> strings = new ArrayList<>();

for (Map.Entry<NBTTagCompound, RecipeId> entry : items.entrySet()) {
for (Map.Entry<NBTTagCompound, RecipeId> entry : manualFavorites.getAllFavorites()) {

try {
final JsonObject line = new JsonObject();
Expand Down
1 change: 1 addition & 0 deletions src/main/java/codechicken/nei/ItemList.java
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ public void execute() {

loadFinished = true;

FavoriteRecipes.loadAutoGeneratedRecipes();
SubsetWidget.updateHiddenItems();
ItemPanels.bookmarkPanel.load();
updateFilter.restart();
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/codechicken/nei/NEIClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -782,8 +782,7 @@ private static void setFavoriteDefaults(ConfigTagParent tag) {
@Override
public boolean onClick(int button) {
super.onClick(button);
FavoriteRecipes.save();
FavoriteRecipes.load();
FavoriteRecipes.reload();
return true;
}
});
Expand All @@ -796,6 +795,18 @@ public boolean onClick(int button) {
.getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.favorites.showRecipeTooltipInGui", true));

tag.getTag("inventory.favorites.generateSingleRecipeFavorites")
.setComment("Automatically generate favorites for items with only one recipe").getBooleanValue(true);
API.addOption(new OptionToggleButton("inventory.favorites.generateSingleRecipeFavorites", true) {

@Override
public boolean onClick(int button) {
super.onClick(button);
FavoriteRecipes.loadAutoGeneratedRecipes();
return true;
}
});

tag.getTag("inventory.favorites.depth").setComment("Bookmark creation depth").getIntValue(3);
API.addOption(new OptionIntegerField("inventory.favorites.depth", 0, 100));
}
Expand Down
Loading