All checks were successful
EpicKnarvik97/Blacksmith/pipeline/head This commit looks good
395 lines
16 KiB
Java
395 lines
16 KiB
Java
package net.knarcraft.blacksmith.util;
|
|
|
|
import net.knarcraft.blacksmith.BlacksmithPlugin;
|
|
import net.knarcraft.blacksmith.container.RecipeResult;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.Server;
|
|
import org.bukkit.enchantments.Enchantment;
|
|
import org.bukkit.inventory.ItemStack;
|
|
import org.bukkit.inventory.Recipe;
|
|
import org.bukkit.inventory.ShapedRecipe;
|
|
import org.bukkit.inventory.ShapelessRecipe;
|
|
import org.bukkit.inventory.meta.ArmorMeta;
|
|
import org.bukkit.inventory.meta.trim.ArmorTrim;
|
|
import org.bukkit.inventory.meta.trim.TrimMaterial;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
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 {
|
|
|
|
private static final Random random = new Random();
|
|
private static final Map<TrimMaterial, Material> trimMaterialToMaterial;
|
|
|
|
static {
|
|
trimMaterialToMaterial = new HashMap<>();
|
|
trimMaterialToMaterial.put(TrimMaterial.AMETHYST, Material.AMETHYST_SHARD);
|
|
trimMaterialToMaterial.put(TrimMaterial.COPPER, Material.COPPER_INGOT);
|
|
trimMaterialToMaterial.put(TrimMaterial.DIAMOND, Material.DIAMOND);
|
|
trimMaterialToMaterial.put(TrimMaterial.EMERALD, Material.EMERALD);
|
|
trimMaterialToMaterial.put(TrimMaterial.GOLD, Material.GOLD_INGOT);
|
|
trimMaterialToMaterial.put(TrimMaterial.IRON, Material.IRON_INGOT);
|
|
trimMaterialToMaterial.put(TrimMaterial.LAPIS, Material.LAPIS_LAZULI);
|
|
trimMaterialToMaterial.put(TrimMaterial.NETHERITE, Material.NETHERITE_INGOT);
|
|
trimMaterialToMaterial.put(TrimMaterial.QUARTZ, Material.QUARTZ);
|
|
trimMaterialToMaterial.put(TrimMaterial.REDSTONE, Material.REDSTONE);
|
|
}
|
|
|
|
/**
|
|
* Gets salvage for a given netherite item
|
|
*
|
|
* @param item <p>The netherite armor/tool to salvage</p>
|
|
* @return <p></p>
|
|
*/
|
|
@Nullable
|
|
public static List<ItemStack> getNetheriteSalvage(@NotNull ItemStack item) {
|
|
Material newMaterial = Material.matchMaterial(item.getType().name().replace("NETHERITE", "DIAMOND"));
|
|
if (newMaterial == null) {
|
|
return null;
|
|
}
|
|
ItemStack clone = item.clone();
|
|
clone.setType(newMaterial);
|
|
|
|
List<ItemStack> salvage = new ArrayList<>();
|
|
salvage.add(clone);
|
|
salvage.add(new ItemStack(Material.NETHERITE_INGOT, 1));
|
|
salvage.add(new ItemStack(Material.NETHERITE_UPGRADE_SMITHING_TEMPLATE, 1));
|
|
|
|
return salvage;
|
|
}
|
|
|
|
/**
|
|
* Gets salvage for the given armor trim
|
|
*
|
|
* @param item <p>The item to have its armor trim salvaged</p>
|
|
* @param armorMeta <p>The armor meta of the item to salvage</p>
|
|
* @return <p>The salvage, or null if salvage could not be calculated</p>
|
|
*/
|
|
@Nullable
|
|
public static List<ItemStack> getArmorTrimSalvage(@NotNull ItemStack item, @NotNull ArmorMeta armorMeta) {
|
|
ArmorTrim armorTrim = armorMeta.getTrim();
|
|
if (armorTrim == null) {
|
|
return null;
|
|
}
|
|
|
|
List<ItemStack> result = new ArrayList<>();
|
|
Material trimMaterial = trimMaterialToMaterial.get(armorTrim.getMaterial());
|
|
if (trimMaterial != null) {
|
|
result.add(new ItemStack(trimMaterial, 1));
|
|
} else {
|
|
return null;
|
|
}
|
|
Material patternMaterial = Material.matchMaterial(armorTrim.getPattern().getKey().getKey() +
|
|
"_ARMOR_TRIM_SMITHING_TEMPLATE");
|
|
if (patternMaterial != null) {
|
|
result.add(new ItemStack(patternMaterial, 1));
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
// Return a clone of the input item, with the armor trim removed
|
|
ItemStack strippedItem = item.clone();
|
|
armorMeta.setTrim(null);
|
|
strippedItem.setItemMeta(armorMeta);
|
|
result.add(strippedItem);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the given item can be salvaged, assuming no restrictions apply
|
|
*
|
|
* @param server <p>The server to get recipes from</p>
|
|
* @param item <p>The item to check</p>
|
|
* @return <p>True if the item can be salvaged</p>
|
|
*/
|
|
public static boolean isSalvageable(@NotNull Server server, @NotNull ItemStack item) {
|
|
List<Recipe> recipes = server.getRecipesFor(new ItemStack(item.getType(), item.getAmount()));
|
|
for (Recipe recipe : recipes) {
|
|
// Only crafting recipes are allowed.
|
|
if ((recipe instanceof ShapedRecipe || recipe instanceof ShapelessRecipe) &&
|
|
item.getAmount() >= recipe.getResult().getAmount()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets the sum of all enchantment levels for the given item
|
|
*
|
|
* @param item <p>The item to check</p>
|
|
* @return <p>The total amount of enchantment levels on the item</p>
|
|
*/
|
|
public static int getTotalEnchantmentLevels(@NotNull ItemStack item) {
|
|
int expLevels = 0;
|
|
for (Enchantment enchantment : item.getEnchantments().keySet()) {
|
|
expLevels += item.getEnchantmentLevel(enchantment);
|
|
}
|
|
return expLevels;
|
|
}
|
|
|
|
/**
|
|
* 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>
|
|
*
|
|
* @param server <p>The server to get recipes from</p>
|
|
* @param salvagedItem <p>The item stack to salvage</p>
|
|
* @param trashSalvage <p>Any material treated as trash salvage</p>
|
|
* @param extended <p>Whether to enable extended salvage, ignoring the repairable restriction</p>
|
|
* @return <p>The items to return to the user, or null if not salvageable</p>
|
|
*/
|
|
public static @Nullable RecipeResult getSalvage(@NotNull Server server, @Nullable ItemStack salvagedItem,
|
|
@NotNull Collection<Material> trashSalvage, boolean extended) {
|
|
if (salvagedItem == null || salvagedItem.getAmount() < 1 ||
|
|
(!extended && !ItemHelper.isRepairable(salvagedItem))) {
|
|
return null;
|
|
}
|
|
|
|
for (Recipe recipe : server.getRecipesFor(new ItemStack(salvagedItem.getType(), salvagedItem.getAmount()))) {
|
|
BlacksmithPlugin.debug("Considering recipe: " + recipe.getResult() + " -> " + getRawRecipeSalvage(recipe));
|
|
|
|
// Only consider crafting table recipes
|
|
if (!(recipe instanceof ShapedRecipe) && !(recipe instanceof ShapelessRecipe)) {
|
|
BlacksmithPlugin.debug("Recipe had invalid type");
|
|
continue;
|
|
}
|
|
|
|
// Make sure the player has enough items
|
|
if (salvagedItem.getAmount() < recipe.getResult().getAmount()) {
|
|
BlacksmithPlugin.debug("Too few items for recipe");
|
|
continue;
|
|
}
|
|
|
|
// Get actual salvage, as long as any can be produced
|
|
List<ItemStack> salvage = getRecipeSalvage(recipe, salvagedItem, trashSalvage);
|
|
if (salvage != null && !salvage.isEmpty()) {
|
|
BlacksmithPlugin.debug("Valid recipe: " + recipe.getResult() + " -> " + getRawRecipeSalvage(recipe));
|
|
BlacksmithPlugin.debug("Actual salvage: " + salvage);
|
|
return new RecipeResult(recipe, salvage);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gets the salvage resulting from the given recipe and the given item
|
|
*
|
|
* @param recipe <p>The recipe to get salvage for</p>
|
|
* @param salvagedItem <p>The item to be salvaged</p>
|
|
* @param trashSalvage <p>Any material treated as trash salvage</p>
|
|
* @return <p>A list of items, or null if not a valid type of recipe</p>
|
|
*/
|
|
private static @Nullable List<ItemStack> getRecipeSalvage(@NotNull Recipe recipe, @NotNull ItemStack salvagedItem,
|
|
@NotNull Collection<Material> trashSalvage) {
|
|
List<ItemStack> ingredients = getRawRecipeSalvage(recipe);
|
|
if (ingredients == null) {
|
|
return null;
|
|
}
|
|
List<ItemStack> copy = copyItems(ingredients);
|
|
BlacksmithPlugin.debug("Copied salvage: " + copy);
|
|
List<ItemStack> salvage = getSalvage(copy, salvagedItem, trashSalvage);
|
|
BlacksmithPlugin.debug("Combining salvage: " + salvage);
|
|
List<ItemStack> combined = combineStacks(salvage);
|
|
BlacksmithPlugin.debug("Combined : " + combined);
|
|
return combined;
|
|
}
|
|
|
|
/**
|
|
* 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 @NotNull List<ItemStack> copyItems(@NotNull List<ItemStack> itemsToCopy) {
|
|
List<ItemStack> copies = new ArrayList<>(itemsToCopy.size());
|
|
for (ItemStack itemStack : itemsToCopy) {
|
|
copies.add(new ItemStack(itemStack.getType(), itemStack.getAmount()));
|
|
}
|
|
return copies;
|
|
}
|
|
|
|
/**
|
|
* 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>
|
|
* @param trashSalvage <p>The types of materials considered trash for this recipe</p>
|
|
* @return <p>The items to be returned to the user as salvage</p>
|
|
*/
|
|
@NotNull
|
|
private static List<ItemStack> getSalvage(@NotNull List<ItemStack> recipeItems,
|
|
@NotNull ItemStack salvagedItem,
|
|
@NotNull Collection<Material> trashSalvage) {
|
|
int durability = ItemHelper.getDurability(salvagedItem);
|
|
int maxDurability = ItemHelper.getMaxDurability(salvagedItem);
|
|
|
|
// Prevent divide by zero for items that don't have a set max durability
|
|
if (maxDurability <= 0) {
|
|
maxDurability = 1;
|
|
durability = 1;
|
|
}
|
|
|
|
BlacksmithPlugin.debug("Durability: " + durability + "/" + maxDurability);
|
|
|
|
double percentageRemaining = (double) durability / maxDurability;
|
|
BlacksmithPlugin.debug("Remaining: " + percentageRemaining);
|
|
return pickRandomSalvage(recipeItems, trashSalvage, percentageRemaining);
|
|
}
|
|
|
|
/**
|
|
* Picks random salvage from the given items
|
|
*
|
|
* @param itemsToChooseFrom <p>The salvage to choose from</p>
|
|
* @param trashSalvage <p>The salvage considered trash</p>
|
|
* @param percentageRemaining <p>The percentage remaining for the salvaged item (0-1)</p>
|
|
* @return <p>Random salvage</p>
|
|
*/
|
|
@NotNull
|
|
public static List<ItemStack> pickRandomSalvage(@NotNull List<ItemStack> itemsToChooseFrom,
|
|
@NotNull Collection<Material> trashSalvage,
|
|
double percentageRemaining) {
|
|
// Avoid an infinite loop
|
|
if (percentageRemaining > 1 || percentageRemaining < 0) {
|
|
percentageRemaining = 1;
|
|
}
|
|
|
|
// If not damaged, just give everything
|
|
if (percentageRemaining == 1) {
|
|
BlacksmithPlugin.debug("100% Remaining. Copying " + itemsToChooseFrom);
|
|
return copyItems(itemsToChooseFrom);
|
|
}
|
|
|
|
// Split into good items and trash items
|
|
List<ItemStack> goodItems = copyItems(itemsToChooseFrom);
|
|
goodItems.removeIf((item) -> trashSalvage.contains(item.getType()));
|
|
List<ItemStack> trashItems = copyItems(itemsToChooseFrom);
|
|
trashItems.removeIf((item) -> !trashSalvage.contains(item.getType()));
|
|
|
|
int itemsToReturn = (int) Math.floor(totalItemAmount(itemsToChooseFrom) * percentageRemaining);
|
|
int goodItemAmount = totalItemAmount(goodItems);
|
|
int pickedItems = 0;
|
|
List<ItemStack> salvage = new ArrayList<>(itemsToReturn);
|
|
|
|
while (pickedItems < itemsToReturn && pickedItems < goodItemAmount) {
|
|
int index = SalvageHelper.random.nextInt(goodItems.size());
|
|
ItemStack itemStack = goodItems.get(index);
|
|
if (itemStack.getType().isAir() || itemStack.getAmount() == 0) {
|
|
continue;
|
|
}
|
|
|
|
salvage.add(new ItemStack(itemStack.getType(), 1));
|
|
itemStack.setAmount(itemStack.getAmount() - 1);
|
|
pickedItems++;
|
|
}
|
|
|
|
while (pickedItems < itemsToReturn) {
|
|
int index = SalvageHelper.random.nextInt(trashItems.size());
|
|
ItemStack itemStack = trashItems.get(index);
|
|
if (itemStack.getType().isAir() || itemStack.getAmount() == 0) {
|
|
continue;
|
|
}
|
|
|
|
salvage.add(new ItemStack(itemStack.getType(), 1));
|
|
itemStack.setAmount(itemStack.getAmount() - 1);
|
|
pickedItems++;
|
|
}
|
|
|
|
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>
|
|
*/
|
|
public static int totalItemAmount(@NotNull 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 @NotNull List<ItemStack> combineStacks(@NotNull List<ItemStack> items) {
|
|
Map<Material, Integer> itemAmounts = new HashMap<>();
|
|
for (ItemStack item : items) {
|
|
Material itemType = item.getType();
|
|
itemAmounts.put(itemType, itemAmounts.getOrDefault(itemType, 0) + item.getAmount());
|
|
}
|
|
|
|
List<ItemStack> combined = new ArrayList<>();
|
|
for (Material material : itemAmounts.keySet()) {
|
|
combined.add(new ItemStack(material, itemAmounts.get(material)));
|
|
}
|
|
return combined;
|
|
}
|
|
|
|
/**
|
|
* Gets the raw salvage of a recipe, assuming full salvage would be possible
|
|
*
|
|
* @param recipe <p>The recipe to get salvage for</p>
|
|
* @return <p>The salvage resulting from the recipe</p>
|
|
*/
|
|
private static List<ItemStack> getRawRecipeSalvage(@NotNull Recipe recipe) {
|
|
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
|
|
return combineStacks(ingredients);
|
|
}
|
|
|
|
/**
|
|
* 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>
|
|
*/
|
|
@NotNull
|
|
private static List<ItemStack> getIngredients(@NotNull 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;
|
|
}
|
|
|
|
}
|