2023-01-13 20:34:34 +01:00
|
|
|
package net.knarcraft.blacksmith.util;
|
|
|
|
|
|
|
|
import org.bukkit.Material;
|
2023-01-14 15:10:40 +01:00
|
|
|
import org.bukkit.Server;
|
2023-01-13 20:34:34 +01:00
|
|
|
import org.bukkit.inventory.ItemStack;
|
|
|
|
import org.bukkit.inventory.Recipe;
|
|
|
|
import org.bukkit.inventory.ShapedRecipe;
|
|
|
|
import org.bukkit.inventory.ShapelessRecipe;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Random;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A helper class for deciding the salvage returned if salvaging an item
|
|
|
|
*/
|
|
|
|
public final class SalvageHelper {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the items to return if salvaging the given item stack
|
|
|
|
*
|
|
|
|
* <p>Note: Only items craft-able in a crafting table are salvageable. Netherite gear is therefore not salvageable.</p>
|
|
|
|
*
|
2023-01-16 19:45:41 +01:00
|
|
|
* @param server <p>The server to get recipes from</p>
|
|
|
|
* @param salvagedItem <p>The item stack to salvage</p>
|
|
|
|
* @param ignoredSalvage <p>Any material which should not be returned as part of the salvage.</p>
|
2023-01-13 20:34:34 +01:00
|
|
|
* @return <p>The items to return to the user, or null if not salvageable</p>
|
|
|
|
*/
|
2023-01-16 19:45:41 +01:00
|
|
|
public static List<ItemStack> getSalvage(Server server, ItemStack salvagedItem, List<Material> ignoredSalvage) {
|
2023-01-13 20:34:34 +01:00
|
|
|
if (salvagedItem == null || salvagedItem.getAmount() < 1 ||
|
2023-01-16 19:45:41 +01:00
|
|
|
!ItemHelper.isRepairable(salvagedItem)) {
|
2023-01-13 20:34:34 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-01-14 15:10:40 +01:00
|
|
|
for (Recipe recipe : server.getRecipesFor(salvagedItem)) {
|
|
|
|
List<ItemStack> salvage = getRecipeSalvage(recipe, salvagedItem, ignoredSalvage);
|
2023-01-13 20:34:34 +01:00
|
|
|
if (salvage != null) {
|
|
|
|
return salvage;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the salvage resulting from the given recipe and the given item
|
|
|
|
*
|
2023-01-14 15:10:40 +01:00
|
|
|
* @param recipe <p>The recipe to get salvage for</p>
|
|
|
|
* @param salvagedItem <p>The item to be salvaged</p>
|
|
|
|
* @param ignoredSalvage <p>Any material which should not be returned as part of the salvage.</p>
|
2023-01-13 20:34:34 +01:00
|
|
|
* @return <p>A list of items, or null if not a valid type of recipe</p>
|
|
|
|
*/
|
2023-01-14 15:10:40 +01:00
|
|
|
private static List<ItemStack> getRecipeSalvage(Recipe recipe, ItemStack salvagedItem,
|
|
|
|
List<Material> ignoredSalvage) {
|
2023-01-13 20:34:34 +01:00
|
|
|
List<ItemStack> ingredients;
|
|
|
|
if (recipe instanceof ShapedRecipe shapedRecipe) {
|
|
|
|
ingredients = getIngredients(shapedRecipe);
|
|
|
|
} else if (recipe instanceof ShapelessRecipe shapelessRecipe) {
|
|
|
|
ingredients = shapelessRecipe.getIngredientList();
|
|
|
|
} else {
|
|
|
|
//Recipes other than crafting shouldn't be considered for salvaging
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
//Make things easier by eliminating identical stacks
|
|
|
|
ingredients = combineStacks(ingredients);
|
|
|
|
|
2023-01-14 15:10:40 +01:00
|
|
|
//Purge any ignored salvage to only calculate salvage using the remaining items
|
|
|
|
if (ignoredSalvage != null) {
|
|
|
|
ingredients.removeIf((item) -> ignoredSalvage.contains(item.getType()));
|
|
|
|
}
|
|
|
|
|
2023-01-16 17:42:54 +01:00
|
|
|
Random random = new Random();
|
|
|
|
|
2023-01-13 20:34:34 +01:00
|
|
|
//Make sure to give salvage for all items if a stack > 1 is provided
|
2023-01-14 17:46:29 +01:00
|
|
|
List<ItemStack> allSalvage = new ArrayList<>();
|
2023-01-13 20:34:34 +01:00
|
|
|
for (int i = 0; i < salvagedItem.getAmount(); i++) {
|
2023-01-16 17:42:54 +01:00
|
|
|
allSalvage.addAll(getSalvage(copyItems(ingredients), salvagedItem, random));
|
2023-01-13 20:34:34 +01:00
|
|
|
}
|
|
|
|
|
2023-01-14 17:46:29 +01:00
|
|
|
return combineStacks(allSalvage);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copies a list of items
|
|
|
|
*
|
|
|
|
* <p>Note: This does not copy any metadata. It only copies the item type and the amount.</p>
|
|
|
|
*
|
|
|
|
* @param itemsToCopy <p>The items to make a copy of</p>
|
|
|
|
* @return <p>A copy of the given items</p>
|
|
|
|
*/
|
|
|
|
private static List<ItemStack> copyItems(List<ItemStack> itemsToCopy) {
|
|
|
|
List<ItemStack> copies = new ArrayList<>(itemsToCopy.size());
|
|
|
|
for (ItemStack itemStack : itemsToCopy) {
|
|
|
|
copies.add(new ItemStack(itemStack.getType(), itemStack.getAmount()));
|
|
|
|
}
|
|
|
|
return copies;
|
2023-01-13 20:34:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the salvage resulting from the given item, and the given recipe items
|
|
|
|
*
|
|
|
|
* @param recipeItems <p>All items required for crafting the item to salvage</p>
|
|
|
|
* @param salvagedItem <p>The item to be salvaged</p>
|
2023-01-16 17:42:54 +01:00
|
|
|
* @param random <p>The randomness generator to use</p>
|
2023-01-13 20:34:34 +01:00
|
|
|
* @return <p>The items to be returned to the user as salvage</p>
|
|
|
|
*/
|
2023-01-16 17:42:54 +01:00
|
|
|
private static List<ItemStack> getSalvage(List<ItemStack> recipeItems, ItemStack salvagedItem, Random random) {
|
2023-01-13 20:34:34 +01:00
|
|
|
double percentageRemaining = (ItemHelper.getDurability(salvagedItem) /
|
|
|
|
(double) ItemHelper.getMaxDurability(salvagedItem));
|
|
|
|
int totalItems = totalItems(recipeItems);
|
|
|
|
//Get the amount of recipe items to be returned
|
|
|
|
int itemsToReturn = (int) Math.floor(percentageRemaining * totalItems);
|
|
|
|
int bound = recipeItems.size();
|
|
|
|
List<ItemStack> salvage = new ArrayList<>();
|
|
|
|
|
|
|
|
for (int i = 0; i < itemsToReturn; i++) {
|
|
|
|
int itemIndex = random.nextInt(bound);
|
|
|
|
ItemStack itemStack = recipeItems.get(itemIndex);
|
|
|
|
|
|
|
|
//Make sure to never give more of one item than the amount which exists in the recipe
|
|
|
|
if (itemStack.getAmount() <= 0) {
|
|
|
|
i--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
itemStack.setAmount(itemStack.getAmount() - 1);
|
|
|
|
|
|
|
|
salvage.add(new ItemStack(itemStack.getType(), 1));
|
|
|
|
}
|
|
|
|
return salvage;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the total sum of items in the given list of items
|
|
|
|
*
|
|
|
|
* @param items <p>The items to get the sum of</p>
|
|
|
|
* @return <p>The total number of items</p>
|
|
|
|
*/
|
|
|
|
private static int totalItems(List<ItemStack> items) {
|
|
|
|
int sum = 0;
|
|
|
|
for (ItemStack itemStack : items) {
|
|
|
|
sum += itemStack.getAmount();
|
|
|
|
}
|
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Combines all items of the same type in the given list
|
|
|
|
*
|
|
|
|
* <p>Basically, if the input is two item stacks containing one diamond each, the output will be an item stack with
|
|
|
|
* two diamonds instead.</p>
|
|
|
|
*
|
|
|
|
* @param items <p>The items to combine</p>
|
|
|
|
* @return <p>The given items, but combined</p>
|
|
|
|
*/
|
|
|
|
private static List<ItemStack> combineStacks(List<ItemStack> items) {
|
|
|
|
Map<Material, Integer> itemAmounts = new HashMap<>();
|
|
|
|
for (ItemStack item : items) {
|
|
|
|
Material itemType = item.getType();
|
|
|
|
itemAmounts.put(itemType, itemAmounts.getOrDefault(itemType, 0) + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<ItemStack> combined = new ArrayList<>();
|
|
|
|
for (Material material : itemAmounts.keySet()) {
|
|
|
|
combined.add(new ItemStack(material, itemAmounts.get(material)));
|
|
|
|
}
|
|
|
|
return combined;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets all ingredients contained in the given shaped recipe
|
|
|
|
*
|
|
|
|
* @param shapedRecipe <p>The shaped recipe to get ingredients for</p>
|
|
|
|
* @return <p>The items contained in the recipe</p>
|
|
|
|
*/
|
|
|
|
private static List<ItemStack> getIngredients(ShapedRecipe shapedRecipe) {
|
|
|
|
List<ItemStack> ingredients = new ArrayList<>();
|
|
|
|
Map<Character, ItemStack> ingredientMap = shapedRecipe.getIngredientMap();
|
|
|
|
//The shape is a list of the three rows' strings. Each string contains 3 characters.
|
|
|
|
String[] shape = shapedRecipe.getShape();
|
|
|
|
for (String row : shape) {
|
|
|
|
for (int column = 0; column < row.length(); column++) {
|
|
|
|
ItemStack item = ingredientMap.get(row.charAt(column));
|
|
|
|
if (item != null && item.getType() != Material.AIR && item.getAmount() > 0) {
|
|
|
|
ingredients.add(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ingredients;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|