Răsfoiți Sursa

Convert Salvage to a child skill.

GJ 12 ani în urmă
părinte
comite
91bf54019e
25 a modificat fișierele cu 710 adăugiri și 194 ștergeri
  1. 0 6
      src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java
  2. 79 0
      src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java
  3. 61 5
      src/main/java/com/gmail/nossr50/config/AdvancedConfig.java
  4. 6 3
      src/main/java/com/gmail/nossr50/config/Config.java
  5. 2 2
      src/main/java/com/gmail/nossr50/config/mods/CustomArmorConfig.java
  6. 2 2
      src/main/java/com/gmail/nossr50/config/mods/CustomToolConfig.java
  7. 2 1
      src/main/java/com/gmail/nossr50/config/skills/repair/RepairConfig.java
  8. 6 0
      src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java
  9. 4 1
      src/main/java/com/gmail/nossr50/datatypes/skills/SecondaryAbility.java
  10. 4 1
      src/main/java/com/gmail/nossr50/datatypes/skills/SkillType.java
  11. 3 1
      src/main/java/com/gmail/nossr50/listeners/BlockListener.java
  12. 11 10
      src/main/java/com/gmail/nossr50/listeners/PlayerListener.java
  13. 1 93
      src/main/java/com/gmail/nossr50/skills/repair/Repair.java
  14. 9 58
      src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java
  15. 111 0
      src/main/java/com/gmail/nossr50/skills/salvage/Salvage.java
  16. 218 0
      src/main/java/com/gmail/nossr50/skills/salvage/SalvageManager.java
  17. 2 1
      src/main/java/com/gmail/nossr50/util/BlockUtils.java
  18. 9 0
      src/main/java/com/gmail/nossr50/util/Permissions.java
  19. 5 0
      src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java
  20. 62 0
      src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java
  21. 49 0
      src/main/resources/advanced.yml
  22. 3 0
      src/main/resources/child.yml
  23. 6 3
      src/main/resources/config.yml
  24. 21 7
      src/main/resources/locale/locale_en_US.properties
  25. 34 0
      src/main/resources/plugin.yml

+ 0 - 6
src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java

@@ -26,7 +26,6 @@ public class RepairCommand extends SkillCommand {
     private boolean canSuperRepair;
     private boolean canMasterRepair;
     private boolean canArcaneForge;
-    private boolean canSalvage;
     private boolean canRepairStone;
     private boolean canRepairIron;
     private boolean canRepairGold;
@@ -77,7 +76,6 @@ public class RepairCommand extends SkillCommand {
         canSuperRepair = Permissions.secondaryAbilityEnabled(player, SecondaryAbility.SUPER_REPAIR);
         canMasterRepair = Permissions.secondaryAbilityEnabled(player, SecondaryAbility.REPAIR_MASTERY);
         canArcaneForge = Permissions.secondaryAbilityEnabled(player, SecondaryAbility.ARCANE_FORGING);
-        canSalvage = Permissions.secondaryAbilityEnabled(player, SecondaryAbility.SALVAGE);
         canRepairDiamond = Permissions.repairDiamond(player);
         canRepairGold = Permissions.repairGold(player);
         canRepairIron = Permissions.repairIron(player);
@@ -122,10 +120,6 @@ public class RepairCommand extends SkillCommand {
             messages.add(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.6", diamondLevel), LocaleLoader.getString("Repair.Effect.7")));
         }
 
-        if (canSalvage && Repair.salvageUnlockLevel > 0) {
-            messages.add(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.16", Repair.salvageUnlockLevel), LocaleLoader.getString("Repair.Effect.17")));
-        }
-
         if (canArcaneForge) {
             messages.add(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Repair.Effect.8"), LocaleLoader.getString("Repair.Effect.9")));
         }

+ 79 - 0
src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java

@@ -0,0 +1,79 @@
+package com.gmail.nossr50.commands.skills;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.datatypes.skills.SecondaryAbility;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.salvage.Salvage;
+import com.gmail.nossr50.skills.salvage.SalvageManager;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class SalvageCommand extends SkillCommand {
+    private boolean canAdvancedSalvage;
+    private boolean canArcaneSalvage;
+
+    public SalvageCommand() {
+        super(SkillType.SALVAGE);
+    }
+
+    @Override
+    protected void dataCalculations(Player player, float skillValue, boolean isLucky) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    protected void permissionsCheck(Player player) {
+        canAdvancedSalvage = Permissions.secondaryAbilityEnabled(player, SecondaryAbility.ADVANCED_SALVAGE);
+        canArcaneSalvage = Permissions.secondaryAbilityEnabled(player, SecondaryAbility.ARCANE_SALVAGE);
+    }
+
+    @Override
+    protected List<String> effectsDisplay() {
+        List<String> messages = new ArrayList<String>();
+
+        if (canAdvancedSalvage) {
+            messages.add(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Salvage.Effect.0"), LocaleLoader.getString("Salvage.Effect.1")));
+        }
+
+        if (canArcaneSalvage) {
+            messages.add(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Salvage.Effect.2"), LocaleLoader.getString("Salvage.Effect.3")));
+        }
+
+        return messages;
+    }
+
+    @Override
+    protected List<String> statsDisplay(Player player, float skillValue, boolean hasEndurance, boolean isLucky) {
+        List<String> messages = new ArrayList<String>();
+        if (canAdvancedSalvage) {
+            if (skillValue < Salvage.advancedSalvageUnlockLevel) {
+                messages.add(LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Salvage.Ability.Locked.0", Salvage.advancedSalvageUnlockLevel)));
+            }
+            else {
+                messages.add(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Salvage.Ability.Bonus.0"), LocaleLoader.getString("Salvage.Ability.Bonus.1")));
+            }
+        }
+
+        if (canArcaneSalvage) {
+            SalvageManager salvageManager = UserManager.getPlayer(player).getSalvageManager();
+
+            messages.add(LocaleLoader.getString("Salvage.Arcane.Rank", salvageManager.getArcaneSalvageRank(), Salvage.Tier.EIGHT.toNumerical()));
+
+            if (Salvage.arcaneSalvageEnchantLoss) {
+                messages.add(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Salvage.Arcane.ExtractFull"), salvageManager.getExtractFullEnchantChance()));
+            }
+
+            if (Salvage.arcaneSalvageDowngrades) {
+                messages.add(LocaleLoader.getString("Ability.Generic.Template", LocaleLoader.getString("Salvage.Arcane.ExtractPartial"), salvageManager.getExtractPartialEnchantChance()));
+            }
+        }
+
+        return messages;
+    }
+}

+ 61 - 5
src/main/java/com/gmail/nossr50/config/AdvancedConfig.java

@@ -10,6 +10,7 @@ import com.gmail.nossr50.skills.alchemy.Alchemy;
 import com.gmail.nossr50.skills.fishing.Fishing;
 import com.gmail.nossr50.skills.mining.BlastMining;
 import com.gmail.nossr50.skills.repair.ArcaneForging;
+import com.gmail.nossr50.skills.salvage.Salvage;
 import com.gmail.nossr50.skills.smelting.Smelting;
 import com.gmail.nossr50.util.StringUtils;
 
@@ -378,10 +379,6 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Repair.SuperRepair.MaxBonusLevel should be at least 1!");
         }
 
-        if (getSalvageUnlockLevel() < 0) {
-            reason.add("Skills.Repair.Salvage.UnlockLevel should be at least 0!");
-        }
-
         List<ArcaneForging.Tier> arcaneForgingTierList = Arrays.asList(ArcaneForging.Tier.values());
 
         for (ArcaneForging.Tier tier : arcaneForgingTierList) {
@@ -414,6 +411,51 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             }
         }
 
+        /* SALVAGE */
+        if (getSalvageMaxPercentage() < 1) {
+            reason.add("Skills.Salvage.MaxPercentage should be at least 1!");
+        }
+
+        if (getSalvageMaxPercentageLevel() < 1) {
+            reason.add("Skills.Salvage.MaxPercentageLevel should be at least 1!");
+        }
+
+        if (getAdvancedSalvageUnlockLevel() < 1) {
+            reason.add("Skills.Salvage.AdvancedSalvage.UnlockLevel should be at least 1!");
+        }
+
+        List<Salvage.Tier> salvageTierList = Arrays.asList(Salvage.Tier.values());
+
+        for (Salvage.Tier tier : salvageTierList) {
+            if (getArcaneSalvageRankLevel(tier) < 0) {
+                reason.add("Skills.Salvage.ArcaneSalvage.Rank_Levels.Rank_" + tier.toNumerical() + " should be at least 0!");
+            }
+
+            if (getArcaneSalvageExtractFullEnchantsChance(tier) < 0 || getArcaneSalvageExtractFullEnchantsChance(tier) > 100) {
+                reason.add("Skills.Salvage.ArcaneSalvage.ExtractFullEnchant.Rank_" + tier.toNumerical() + " only accepts values from 0 to 100!");
+            }
+
+            if (getArcaneSalvageExtractPartialEnchantsChance(tier) < 0 || getArcaneSalvageExtractPartialEnchantsChance(tier) > 100) {
+                reason.add("Skills.Salvage.ArcaneSalvage.ExtractPartialEnchant.Rank_" + tier.toNumerical() + " only accepts values from 0 to 100!");
+            }
+
+            if (tier != Salvage.Tier.EIGHT) {
+                Salvage.Tier nextTier = salvageTierList.get(salvageTierList.indexOf(tier) - 1);
+
+                if (getArcaneSalvageRankLevel(tier) >= getArcaneSalvageRankLevel(nextTier)) {
+                    reason.add("Skills.Salvage.ArcaneSalvage.Rank_Levels.Rank_" + tier.toNumerical() + " should be less than Skills.Salvage.ArcaneSalvage.Rank_Levels.Rank_" + nextTier.toNumerical() + "!");
+                }
+
+                if (getArcaneSalvageExtractFullEnchantsChance(tier) > getArcaneSalvageExtractFullEnchantsChance(nextTier)) {
+                    reason.add("Skills.Salvage.ArcaneSalvage.ExtractFullEnchant.Rank_" + tier.toNumerical() + " should be less than or equal to Skills.Salvage.ArcaneSalvage.ExtractFullEnchant.Rank_" + nextTier.toNumerical() + "!");
+                }
+
+                if (getArcaneSalvageExtractPartialEnchantsChance(tier) > getArcaneSalvageExtractPartialEnchantsChance(nextTier)) {
+                    reason.add("Skills.Salvage.ArcaneSalvage.ExtractPartialEnchant.Rank_" + tier.toNumerical() + " should be less than or equal to Skills.Salvage.ArcaneSalvage.ExtractPartialEnchant.Rank_" + nextTier.toNumerical() + "!");
+                }
+            }
+        }
+
         /* SMELTING */
         if (getBurnModifierMaxLevel() < 1) {
             reason.add("Skills.Smelting.FuelEfficiency.MaxBonusLevel should be at least 1!");
@@ -725,7 +767,8 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
     /* REPAIR */
     public double getRepairMasteryMaxBonus() { return config.getDouble("Skills.Repair.RepairMastery.MaxBonusPercentage", 200.0D); }
     public int getRepairMasteryMaxLevel() { return config.getInt("Skills.Repair.RepairMastery.MaxBonusLevel", 1000); }
-    public int getSalvageUnlockLevel() { return config.getInt("Skills.Repair.Salvage.UnlockLevel", 600); }
+    public double getSuperRepairChanceMax() { return config.getDouble("Skills.Repair.SuperRepair.ChanceMax", 100.0D); }
+    public int getSuperRepairMaxLevel() { return config.getInt("Skills.Repair.SuperRepair.MaxBonusLevel", 1000); }
 
     /* Arcane Forging */
     public int getArcaneForgingRankLevel(ArcaneForging.Tier tier) { return config.getInt("Skills.Repair.ArcaneForging.Rank_Levels.Rank_" + tier.toNumerical()); }
@@ -736,6 +779,19 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
     public boolean getArcaneForgingDowngradeEnabled() { return config.getBoolean("Skills.Repair.ArcaneForging.Downgrades_Enabled", true); }
     public double getArcaneForgingDowngradeChance(ArcaneForging.Tier tier) { return config.getDouble("Skills.Repair.ArcaneForging.Downgrades_Chance.Rank_" + tier.toNumerical()); }
 
+    /* SALVAGE */
+    public double getSalvageMaxPercentage() { return config.getDouble("Skills.Salvage.MaxPercentage", 100.0D); }
+    public int getSalvageMaxPercentageLevel() { return config.getInt("Skills.Salvage.MaxPercentageLevel", 1000); }
+
+    public int getAdvancedSalvageUnlockLevel() { return config.getInt("Skills.Salvage.AdvancedSalvage.UnlockLevel", 350); }
+
+    public boolean getArcaneSalvageEnchantDowngradeEnabled() { return config.getBoolean("Skills.Salvage.ArcaneSalvage.EnchantDowngradeEnabled", true); }
+    public boolean getArcaneSalvageEnchantLossEnabled() { return config.getBoolean("Skills.Salvage.ArcaneSalvage.EnchantLossEnabled", true); }
+
+    public int getArcaneSalvageRankLevel(Salvage.Tier tier) { return config.getInt("Skills.Salvage.ArcaneSalvage.Rank_Levels.Rank_" + tier.toNumerical()); }
+    public int getArcaneSalvageExtractFullEnchantsChance(Salvage.Tier tier) { return config.getInt("Skills.Salvage.ArcaneSalvage.ExtractFullEnchant.Rank_" + tier.toNumerical()); }
+    public int getArcaneSalvageExtractPartialEnchantsChance(Salvage.Tier tier) { return config.getInt("Skills.Salvage.ArcaneSalvage.ExtractPartialEnchant.Rank_" + tier.toNumerical()); }
+
     /* SMELTING */
     public int getBurnModifierMaxLevel() { return config.getInt("Skills.Smelting.FuelEfficiency.MaxBonusLevel", 1000); }
     public double getBurnTimeMultiplier() { return config.getDouble("Skills.Smelting.FuelEfficiency.Multiplier", 3.0D); }

+ 6 - 3
src/main/java/com/gmail/nossr50/config/Config.java

@@ -464,11 +464,14 @@ public class Config extends AutoUpdateConfigLoader {
     public boolean getRepairAnvilPlaceSoundsEnabled() { return config.getBoolean("Skills.Repair.Anvil_Placed_Sounds", true); }
     public boolean getRepairAnvilUseSoundsEnabled() { return config.getBoolean("Skills.Repair.Anvil_Use_Sounds", true); }
     public Material getRepairAnvilMaterial() { return Material.matchMaterial(config.getString("Skills.Repair.Anvil_Material", "IRON_BLOCK")); }
-    public Material getSalvageAnvilMaterial() { return Material.matchMaterial(config.getString("Skills.Repair.Salvage_Anvil_Material", "GOLD_BLOCK")); }
-    public boolean getSalvageTools() { return config.getBoolean("Skills.Repair.Salvage_tools", true); }
-    public boolean getSalvageArmor() { return config.getBoolean("Skills.Repair.Salvage_armor", true); }
     public boolean getRepairConfirmRequired() { return config.getBoolean("Skills.Repair.Confirm_Required", true); }
 
+    /* Salvage */
+    public boolean getSalvageAnvilMessagesEnabled() { return config.getBoolean("Skills.Salvage.Anvil_Messages", true); }
+    public Material getSalvageAnvilMaterial() { return Material.matchMaterial(config.getString("Skills.Repair.Salvage_Anvil_ID", "GOLD_BLOCK")); }
+    public boolean getSalvageTools() { return config.getBoolean("Skills.Salvage.Salvage_tools", true); }
+    public boolean getSalvageArmor() { return config.getBoolean("Skills.Salvage.Salvage_armor", true); }
+
     /* Unarmed */
     public boolean getUnarmedBlockCrackerSmoothbrickToCracked() { return config.getBoolean("Skills.Unarmed.Block_Cracker.SmoothBrick_To_CrackedBrick", true); }
 

+ 2 - 2
src/main/java/com/gmail/nossr50/config/mods/CustomArmorConfig.java

@@ -9,11 +9,11 @@ import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.inventory.ItemStack;
 
 import com.gmail.nossr50.config.ConfigLoader;
-import com.gmail.nossr50.skills.repair.Repair;
 import com.gmail.nossr50.skills.repair.repairables.RepairItemType;
 import com.gmail.nossr50.skills.repair.repairables.RepairMaterialType;
 import com.gmail.nossr50.skills.repair.repairables.Repairable;
 import com.gmail.nossr50.skills.repair.repairables.RepairableFactory;
+import com.gmail.nossr50.util.skills.SkillUtils;
 
 public class CustomArmorConfig extends ConfigLoader {
     private boolean needsUpdate = false;
@@ -79,7 +79,7 @@ public class CustomArmorConfig extends ConfigLoader {
 
             if (repairable) {
                 byte repairData = (byte) config.getInt(armorType + "." + armorName + ".Repair_Material_Data_Value", -1);
-                int repairQuantity = Repair.getRepairAndSalvageQuantities(new ItemStack(armorMaterial), repairMaterial, repairData);
+                int repairQuantity = SkillUtils.getRepairAndSalvageQuantities(new ItemStack(armorMaterial), repairMaterial, repairData);
 
                 if (repairQuantity == 0) {
                     repairQuantity = config.getInt(armorType + "." + armorName + ".Repair_Material_Data_Quantity", 2);

+ 2 - 2
src/main/java/com/gmail/nossr50/config/mods/CustomToolConfig.java

@@ -11,11 +11,11 @@ import org.bukkit.inventory.ItemStack;
 
 import com.gmail.nossr50.config.ConfigLoader;
 import com.gmail.nossr50.datatypes.mods.CustomTool;
-import com.gmail.nossr50.skills.repair.Repair;
 import com.gmail.nossr50.skills.repair.repairables.RepairItemType;
 import com.gmail.nossr50.skills.repair.repairables.RepairMaterialType;
 import com.gmail.nossr50.skills.repair.repairables.Repairable;
 import com.gmail.nossr50.skills.repair.repairables.RepairableFactory;
+import com.gmail.nossr50.util.skills.SkillUtils;
 
 public class CustomToolConfig extends ConfigLoader {
     private boolean needsUpdate = false;
@@ -87,7 +87,7 @@ public class CustomToolConfig extends ConfigLoader {
 
             if (repairable) {
                 byte repairData = (byte) config.getInt(toolType + "." + toolName + ".Repair_Material_Data_Value", -1);
-                int repairQuantity = Repair.getRepairAndSalvageQuantities(new ItemStack(toolMaterial), repairMaterial, repairData);
+                int repairQuantity = SkillUtils.getRepairAndSalvageQuantities(new ItemStack(toolMaterial), repairMaterial, repairData);
 
                 if (repairQuantity == 0) {
                     repairQuantity = config.getInt(toolType + "." + toolName + ".Repair_Material_Data_Quantity", 2);

+ 2 - 1
src/main/java/com/gmail/nossr50/config/skills/repair/RepairConfig.java

@@ -15,6 +15,7 @@ import com.gmail.nossr50.skills.repair.repairables.RepairMaterialType;
 import com.gmail.nossr50.skills.repair.repairables.Repairable;
 import com.gmail.nossr50.skills.repair.repairables.RepairableFactory;
 import com.gmail.nossr50.util.ItemUtils;
+import com.gmail.nossr50.util.skills.SkillUtils;
 
 public class RepairConfig extends ConfigLoader {
     private List<Repairable> repairables;
@@ -136,7 +137,7 @@ public class RepairConfig extends ConfigLoader {
             }
 
             // Minimum Quantity
-            int minimumQuantity = (itemMaterial != null ? Repair.getRepairAndSalvageQuantities(new ItemStack(itemMaterial), repairMaterial, repairMetadata) : config.getInt("Repairables." + key + ".MinimumQuantity", 2));
+            int minimumQuantity = (itemMaterial != null ? SkillUtils.getRepairAndSalvageQuantities(new ItemStack(itemMaterial), repairMaterial, repairMetadata) : config.getInt("Repairables." + key + ".MinimumQuantity", 2));
 
             if (minimumQuantity <= 0 && itemMaterial != null) {
                 minimumQuantity = config.getInt("Repairables." + key + ".MinimumQuantity", 2);

+ 6 - 0
src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java

@@ -41,6 +41,8 @@ import com.gmail.nossr50.skills.fishing.FishingManager;
 import com.gmail.nossr50.skills.herbalism.HerbalismManager;
 import com.gmail.nossr50.skills.mining.MiningManager;
 import com.gmail.nossr50.skills.repair.RepairManager;
+import com.gmail.nossr50.skills.salvage.Salvage;
+import com.gmail.nossr50.skills.salvage.SalvageManager;
 import com.gmail.nossr50.skills.smelting.SmeltingManager;
 import com.gmail.nossr50.skills.swords.SwordsManager;
 import com.gmail.nossr50.skills.taming.TamingManager;
@@ -224,6 +226,10 @@ public class McMMOPlayer {
         return (RepairManager) skillManagers.get(SkillType.REPAIR);
     }
 
+    public SalvageManager getSalvageManager() {
+        return (SalvageManager) skillManagers.get(SkillType.SALVAGE);
+    }
+
     public SmeltingManager getSmeltingManager() {
         return (SmeltingManager) skillManagers.get(SkillType.SMELTING);
     }

+ 4 - 1
src/main/java/com/gmail/nossr50/datatypes/skills/SecondaryAbility.java

@@ -46,9 +46,12 @@ public enum SecondaryAbility {
     /* Repair */
     ARCANE_FORGING,
     REPAIR_MASTERY,
-    SALVAGE,
     SUPER_REPAIR,
 
+    /* Salvage */
+    ADVANCED_SALVAGE,
+    ARCANE_SALVAGE,
+
     /* Smelting */
     FLUX_MINING,
     FUEL_EFFICIENCY,

+ 4 - 1
src/main/java/com/gmail/nossr50/datatypes/skills/SkillType.java

@@ -23,6 +23,7 @@ import com.gmail.nossr50.skills.fishing.FishingManager;
 import com.gmail.nossr50.skills.herbalism.HerbalismManager;
 import com.gmail.nossr50.skills.mining.MiningManager;
 import com.gmail.nossr50.skills.repair.RepairManager;
+import com.gmail.nossr50.skills.salvage.SalvageManager;
 import com.gmail.nossr50.skills.smelting.SmeltingManager;
 import com.gmail.nossr50.skills.swords.SwordsManager;
 import com.gmail.nossr50.skills.taming.TamingManager;
@@ -43,7 +44,8 @@ public enum SkillType {
     FISHING(FishingManager.class, Color.NAVY, ImmutableList.of(SecondaryAbility.FISHERMANS_DIET, SecondaryAbility.FISHING_TREASURE_HUNTER, SecondaryAbility.ICE_FISHING, SecondaryAbility.MAGIC_HUNTER, SecondaryAbility.MASTER_ANGLER, SecondaryAbility.SHAKE)),
     HERBALISM(HerbalismManager.class, Color.GREEN, AbilityType.GREEN_TERRA, ToolType.HOE, ImmutableList.of(SecondaryAbility.FARMERS_DIET, SecondaryAbility.GREEN_THUMB_PLANT, SecondaryAbility.GREEN_THUMB_BLOCK, SecondaryAbility.HERBALISM_DOUBLE_DROPS, SecondaryAbility.HYLIAN_LUCK, SecondaryAbility.SHROOM_THUMB)),
     MINING(MiningManager.class, Color.GRAY, AbilityType.SUPER_BREAKER, ToolType.PICKAXE, ImmutableList.of(SecondaryAbility.MINING_DOUBLE_DROPS)),
-    REPAIR(RepairManager.class, Color.SILVER, ImmutableList.of(SecondaryAbility.ARCANE_FORGING, SecondaryAbility.REPAIR_MASTERY, SecondaryAbility.SALVAGE, SecondaryAbility.SUPER_REPAIR)),
+    REPAIR(RepairManager.class, Color.SILVER, ImmutableList.of(SecondaryAbility.ARCANE_FORGING, SecondaryAbility.REPAIR_MASTERY, SecondaryAbility.SUPER_REPAIR)),
+    SALVAGE(SalvageManager.class, Color.ORANGE, ImmutableList.of(SecondaryAbility.ADVANCED_SALVAGE, SecondaryAbility.ARCANE_SALVAGE)),
     SMELTING(SmeltingManager.class, Color.YELLOW, ImmutableList.of(SecondaryAbility.FLUX_MINING, SecondaryAbility.FUEL_EFFICIENCY, SecondaryAbility.SECOND_SMELT)),
     SWORDS(SwordsManager.class, Color.fromRGB(178, 34, 34), AbilityType.SERRATED_STRIKES, ToolType.SWORD, ImmutableList.of(SecondaryAbility.BLEED, SecondaryAbility.COUNTER)),
     TAMING(TamingManager.class, Color.PURPLE, ImmutableList.of(SecondaryAbility.BEAST_LORE, SecondaryAbility.CALL_OF_THE_WILD, SecondaryAbility.ENVIROMENTALLY_AWARE, SecondaryAbility.FAST_FOOD, SecondaryAbility.GORE, SecondaryAbility.HOLY_HOUND, SecondaryAbility.SHARPENED_CLAWS, SecondaryAbility.SHOCK_PROOF, SecondaryAbility.THICK_FUR)),
@@ -182,6 +184,7 @@ public enum SkillType {
     // TODO: This is a little "hacky", we probably need to add something to distinguish child skills in the enum, or to use another enum for them
     public boolean isChildSkill() {
         switch (this) {
+            case SALVAGE:
             case SMELTING:
                 return true;
 

+ 3 - 1
src/main/java/com/gmail/nossr50/listeners/BlockListener.java

@@ -122,7 +122,9 @@ public class BlockListener implements Listener {
         }
 
         if (BlockUtils.isMcMMOAnvil(blockState)) {
-            UserManager.getPlayer(player).getRepairManager().placedAnvilCheck(blockState.getType());
+            McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
+            mcMMOPlayer.getRepairManager().placedAnvilCheck(blockState.getType());
+            mcMMOPlayer.getSalvageManager().placedAnvilCheck(blockState.getType());
         }
     }
 

+ 11 - 10
src/main/java/com/gmail/nossr50/listeners/PlayerListener.java

@@ -38,7 +38,6 @@ import com.gmail.nossr50.datatypes.chat.ChatMode;
 import com.gmail.nossr50.datatypes.party.Party;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.AbilityType;
-import com.gmail.nossr50.datatypes.skills.SecondaryAbility;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.party.ShareHandler;
@@ -49,6 +48,8 @@ import com.gmail.nossr50.skills.herbalism.HerbalismManager;
 import com.gmail.nossr50.skills.mining.MiningManager;
 import com.gmail.nossr50.skills.repair.Repair;
 import com.gmail.nossr50.skills.repair.RepairManager;
+import com.gmail.nossr50.skills.salvage.Salvage;
+import com.gmail.nossr50.skills.salvage.SalvageManager;
 import com.gmail.nossr50.skills.taming.TamingManager;
 import com.gmail.nossr50.skills.unarmed.Unarmed;
 import com.gmail.nossr50.util.BlockUtils;
@@ -451,7 +452,7 @@ public class PlayerListener implements Listener {
 
                 if (!Config.getInstance().getAbilitiesOnlyActivateWhenSneaking() || player.isSneaking()) {
                     /* REPAIR CHECKS */
-                    if (type == Repair.repairAnvilMaterial && SkillType.REPAIR.getPermissions(player) && mcMMO.getRepairableManager().isRepairable(heldItem)) {
+                    if (type == Repair.anvilMaterial && SkillType.REPAIR.getPermissions(player) && mcMMO.getRepairableManager().isRepairable(heldItem)) {
                         RepairManager repairManager = mcMMOPlayer.getRepairManager();
                         event.setCancelled(true);
 
@@ -462,13 +463,13 @@ public class PlayerListener implements Listener {
                         }
                     }
                     /* SALVAGE CHECKS */
-                    else if (type == Repair.salvageAnvilMaterial && Permissions.secondaryAbilityEnabled(player, SecondaryAbility.SALVAGE) && Repair.isSalvageable(heldItem)) {
-                        RepairManager repairManager = mcMMOPlayer.getRepairManager();
+                    else if (type == Salvage.anvilMaterial && SkillType.SALVAGE.getPermissions(player) && Salvage.isSalvageable(heldItem)) {
+                        SalvageManager salvageManager = UserManager.getPlayer(player).getSalvageManager();
                         event.setCancelled(true);
 
                         // Make sure the player knows what he's doing when trying to salvage an enchanted item
-                        if (!(heldItem.getEnchantments().size() > 0) || repairManager.checkConfirmation(type, true)) {
-                            repairManager.handleSalvage(block.getLocation(), heldItem);
+                        if (!(heldItem.getEnchantments().size() > 0) || mcMMOPlayer.getRepairManager().checkConfirmation(type, true)) {
+                            salvageManager.handleSalvage(block.getLocation(), heldItem);
                             player.updateInventory();
                         }
                     }
@@ -490,22 +491,22 @@ public class PlayerListener implements Listener {
 
                 if ((Config.getInstance().getAbilitiesOnlyActivateWhenSneaking() && player.isSneaking()) || !Config.getInstance().getAbilitiesOnlyActivateWhenSneaking()) {
                     /* REPAIR CHECKS */
-                    if (type == Repair.repairAnvilMaterial && SkillType.REPAIR.getPermissions(player) && mcMMO.getRepairableManager().isRepairable(heldItem)) {
+                    if (type == Repair.anvilMaterial && SkillType.REPAIR.getPermissions(player) && mcMMO.getRepairableManager().isRepairable(heldItem)) {
                         RepairManager repairManager = mcMMOPlayer.getRepairManager();
 
                         // Cancel repairing an enchanted item
                         if (repairManager.checkConfirmation(type, false) && Config.getInstance().getRepairConfirmRequired()) {
-                            repairManager.setLastAnvilUse(Repair.repairAnvilMaterial, 0);
+                            repairManager.setLastAnvilUse(Repair.anvilMaterial, 0);
                             player.sendMessage(LocaleLoader.getString("Skills.Cancelled", LocaleLoader.getString("Repair.Pretty.Name")));
                         }
                     }
                     /* SALVAGE CHECKS */
-                    else if (type == Repair.salvageAnvilMaterial && Permissions.secondaryAbilityEnabled(player, SecondaryAbility.SALVAGE) && Repair.isSalvageable(heldItem)) {
+                    else if (type == Salvage.anvilMaterial && SkillType.SALVAGE.getPermissions(player) && Salvage.isSalvageable(heldItem)) {
                         RepairManager repairManager = mcMMOPlayer.getRepairManager();
 
                         // Cancel salvaging an enchanted item
                         if (repairManager.checkConfirmation(type, false) && Config.getInstance().getRepairConfirmRequired()) {
-                            repairManager.setLastAnvilUse(Repair.salvageAnvilMaterial, 0);
+                            mcMMOPlayer.getSalvageManager().setLastAnvilUse(Repair.anvilMaterial, 0);
                             player.sendMessage(LocaleLoader.getString("Skills.Cancelled", LocaleLoader.getString("Salvage.Pretty.Name")));
                         }
                     }

+ 1 - 93
src/main/java/com/gmail/nossr50/skills/repair/Repair.java

@@ -3,106 +3,14 @@ package com.gmail.nossr50.skills.repair;
 import java.util.List;
 
 import org.bukkit.Material;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.inventory.Recipe;
-import org.bukkit.inventory.ShapedRecipe;
-import org.bukkit.inventory.ShapelessRecipe;
-import org.bukkit.material.MaterialData;
 
-import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.ItemUtils;
 
 public class Repair {
     public static int    repairMasteryMaxBonusLevel = AdvancedConfig.getInstance().getRepairMasteryMaxLevel();
     public static double repairMasteryMaxBonus      = AdvancedConfig.getInstance().getRepairMasteryMaxBonus();
 
-    public static int salvageUnlockLevel = AdvancedConfig.getInstance().getSalvageUnlockLevel();
-
-    public static Material salvageAnvilMaterial = Config.getInstance().getSalvageAnvilMaterial();
-    public static Material repairAnvilMaterial  = Config.getInstance().getRepairAnvilMaterial();
+    public static Material anvilMaterial  = Config.getInstance().getRepairAnvilMaterial();
     public static boolean  anvilMessagesEnabled = Config.getInstance().getRepairAnvilMessagesEnabled();
-
-    /**
-     * Checks if the item is salvageable.
-     *
-     * @param item Item to check
-     *
-     * @return true if the item is salvageable, false otherwise
-     */
-    public static boolean isSalvageable(ItemStack item) {
-        return (Config.getInstance().getSalvageTools() && ItemUtils.isMinecraftTool(item)) || (Config.getInstance().getSalvageArmor() && !ItemUtils.isChainmailArmor(item) && ItemUtils.isMinecraftArmor(item));
-    }
-
-    public static String getAnvilMessage(Material type) {
-        if (type == repairAnvilMaterial) {
-            return LocaleLoader.getString("Repair.Listener.Anvil");
-        }
-
-        if (type == salvageAnvilMaterial) {
-            return LocaleLoader.getString("Repair.Listener.Anvil2");
-        }
-
-        return "";
-    }
-
-    protected static Material getRepairAndSalvageItem(ItemStack inHand) {
-        if (ItemUtils.isDiamondTool(inHand) || ItemUtils.isDiamondArmor(inHand)) {
-            return Material.DIAMOND;
-        }
-        else if (ItemUtils.isGoldTool(inHand) || ItemUtils.isGoldArmor(inHand)) {
-            return Material.GOLD_INGOT;
-        }
-        else if (ItemUtils.isIronTool(inHand) || ItemUtils.isIronArmor(inHand)) {
-            return Material.IRON_INGOT;
-        }
-        else if (ItemUtils.isStoneTool(inHand)) {
-            return Material.COBBLESTONE;
-        }
-        else if (ItemUtils.isWoodTool(inHand)) {
-            return Material.WOOD;
-        }
-        else if (ItemUtils.isLeatherArmor(inHand)) {
-            return Material.LEATHER;
-        }
-        else if (ItemUtils.isStringTool(inHand)) {
-            return Material.STRING;
-        }
-        else {
-            return null;
-        }
-    }
-
-    public static int getRepairAndSalvageQuantities(ItemStack item) {
-        return getRepairAndSalvageQuantities(item, getRepairAndSalvageItem(item), (byte) -1);
-    }
-
-    public static int getRepairAndSalvageQuantities(ItemStack item, Material repairMaterial, byte repairMetadata) {
-        int quantity = 0;
-        MaterialData repairData = repairMaterial != null ? new MaterialData(repairMaterial, repairMetadata) : null;
-        List<Recipe> recipes = mcMMO.p.getServer().getRecipesFor(item);
-
-        if (!recipes.isEmpty()) {
-            Recipe recipe = recipes.get(0);
-
-            if (recipe instanceof ShapelessRecipe) {
-                for (ItemStack ingredient : ((ShapelessRecipe) recipe).getIngredientList()) {
-                    if (ingredient != null && (repairMaterial == null || ingredient.getType() == repairMaterial) && (repairMetadata == -1 || ingredient.getData().equals(repairData))) {
-                        quantity += ingredient.getAmount();
-                    }
-                }
-            }
-            else if (recipe instanceof ShapedRecipe) {
-                for (ItemStack ingredient : ((ShapedRecipe) recipe).getIngredientMap().values()) {
-                    if (ingredient != null && (repairMaterial == null || ingredient.getType() == repairMaterial) && (repairMetadata == -1 || ingredient.getData().equals(repairData))) {
-                        quantity += ingredient.getAmount();
-                    }
-                }
-            }
-        }
-
-        return quantity;
-    }
 }

+ 9 - 58
src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java

@@ -3,7 +3,6 @@ package com.gmail.nossr50.skills.repair;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import org.bukkit.Location;
 import org.bukkit.Material;
 import org.bukkit.Sound;
 import org.bukkit.enchantments.Enchantment;
@@ -23,6 +22,7 @@ import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.SkillManager;
 import com.gmail.nossr50.skills.repair.ArcaneForging.Tier;
 import com.gmail.nossr50.skills.repair.repairables.Repairable;
+import com.gmail.nossr50.skills.salvage.Salvage;
 import com.gmail.nossr50.util.EventUtils;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
@@ -32,8 +32,6 @@ import com.gmail.nossr50.util.skills.SkillUtils;
 public class RepairManager extends SkillManager {
     private boolean placedRepairAnvil;
     private int     lastRepairClick;
-    private boolean placedSalvageAnvil;
-    private int     lastSalvageClick;
 
     public RepairManager(McMMOPlayer mcMMOPlayer) {
         super(mcMMOPlayer, SkillType.REPAIR);
@@ -51,9 +49,7 @@ public class RepairManager extends SkillManager {
             return;
         }
 
-        if (Repair.anvilMessagesEnabled) {
-            player.sendMessage(Repair.getAnvilMessage(anvilType));
-        }
+        player.sendMessage(LocaleLoader.getString("Repair.Listener.Anvil"));
 
         if (Config.getInstance().getRepairAnvilPlaceSoundsEnabled()) {
             player.playSound(player.getLocation(), Sound.ANVIL_LAND, Misc.ANVIL_USE_VOLUME, Misc.ANVIL_USE_PITCH);
@@ -160,31 +156,6 @@ public class RepairManager extends SkillManager {
         return ((startDurability - newDurability) / (float) totalDurability);
     }
 
-    public void handleSalvage(Location location, ItemStack item) {
-        Player player = getPlayer();
-
-        if (getSkillLevel() < Repair.salvageUnlockLevel) {
-            player.sendMessage(LocaleLoader.getString("Repair.Skills.AdeptSalvage"));
-            return;
-        }
-
-        if (item.getDurability() == 0) {
-            player.setItemInHand(new ItemStack(Material.AIR));
-            location.setY(location.getY() + 1);
-
-            Misc.dropItems(location, new ItemStack(Repair.getRepairAndSalvageItem(item)), Repair.getRepairAndSalvageQuantities(item) * item.getAmount());
-
-            if (Config.getInstance().getRepairAnvilUseSoundsEnabled()) {
-                player.playSound(player.getLocation(), Sound.ANVIL_USE, Misc.ANVIL_USE_VOLUME, Misc.ANVIL_USE_PITCH);
-            }
-
-            player.sendMessage(LocaleLoader.getString("Repair.Skills.SalvageSuccess"));
-        }
-        else {
-            player.sendMessage(LocaleLoader.getString("Repair.Skills.NotFullDurability"));
-        }
-    }
-
     /**
      * Check if the player has tried to use an Anvil before.
      *
@@ -204,10 +175,10 @@ public class RepairManager extends SkillManager {
 
         actualizeLastAnvilUse(anvilType);
 
-        if (anvilType == Repair.repairAnvilMaterial) {
+        if (anvilType == Repair.anvilMaterial) {
             player.sendMessage(LocaleLoader.getString("Skills.ConfirmOrCancel", LocaleLoader.getString("Repair.Pretty.Name")));
         }
-        else if (anvilType == Repair.salvageAnvilMaterial) {
+        else if (anvilType == Salvage.anvilMaterial) {
             player.sendMessage(LocaleLoader.getString("Skills.ConfirmOrCancel", LocaleLoader.getString("Salvage.Pretty.Name")));
         }
 
@@ -369,25 +340,17 @@ public class RepairManager extends SkillManager {
      */
 
     public boolean getPlacedAnvil(Material anvilType) {
-        if (anvilType == Repair.repairAnvilMaterial) {
+        if (anvilType == Repair.anvilMaterial) {
             return placedRepairAnvil;
         }
 
-        if (anvilType == Repair.salvageAnvilMaterial) {
-            return placedSalvageAnvil;
-        }
-
         return true;
     }
 
     public void togglePlacedAnvil(Material anvilType) {
-        if (anvilType == Repair.repairAnvilMaterial) {
+        if (anvilType == Repair.anvilMaterial) {
             placedRepairAnvil = !placedRepairAnvil;
         }
-
-        if (anvilType == Repair.salvageAnvilMaterial) {
-            placedSalvageAnvil = !placedSalvageAnvil;
-        }
     }
 
     /*
@@ -395,34 +358,22 @@ public class RepairManager extends SkillManager {
      */
 
     public int getLastAnvilUse(Material anvilType) {
-        if (anvilType == Repair.repairAnvilMaterial) {
+        if (anvilType == Repair.anvilMaterial) {
             return lastRepairClick;
         }
 
-        if (anvilType == Repair.salvageAnvilMaterial) {
-            return lastSalvageClick;
-        }
-
         return 0;
     }
 
     public void setLastAnvilUse(Material anvilType, int value) {
-        if (anvilType == Repair.repairAnvilMaterial) {
+        if (anvilType == Repair.anvilMaterial) {
             lastRepairClick = value;
         }
-
-        if (anvilType == Repair.salvageAnvilMaterial) {
-            lastSalvageClick = value;
-        }
     }
 
     public void actualizeLastAnvilUse(Material anvilType) {
-        if (anvilType == Repair.repairAnvilMaterial) {
+        if (anvilType == Repair.anvilMaterial) {
             lastRepairClick = (int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR);
         }
-
-        if (anvilType == Repair.salvageAnvilMaterial) {
-            lastSalvageClick = (int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR);
-        }
     }
 }

+ 111 - 0
src/main/java/com/gmail/nossr50/skills/salvage/Salvage.java

@@ -0,0 +1,111 @@
+package com.gmail.nossr50.skills.salvage;
+
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.util.ItemUtils;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public class Salvage {
+    // The order of the values is extremely important, a few methods depend on it to work properly
+    public enum Tier {
+        EIGHT(8),
+        SEVEN(7),
+        SIX(6),
+        FIVE(5),
+        FOUR(4),
+        THREE(3),
+        TWO(2),
+        ONE(1);
+
+        int numerical;
+
+        private Tier(int numerical) {
+            this.numerical = numerical;
+        }
+
+        public int toNumerical() {
+            return numerical;
+        }
+
+        protected int getLevel() {
+            return AdvancedConfig.getInstance().getArcaneSalvageRankLevel(this);
+        }
+
+        protected double getExtractFullEnchantChance() {
+            return AdvancedConfig.getInstance().getArcaneSalvageExtractFullEnchantsChance(this);
+        }
+
+        protected double getExtractPartialEnchantChance() {
+            return AdvancedConfig.getInstance().getArcaneSalvageExtractPartialEnchantsChance(this);
+        }
+    }
+
+    public static Material anvilMaterial = Config.getInstance().getSalvageAnvilMaterial();
+
+    public static int    salvageMaxPercentageLevel = AdvancedConfig.getInstance().getSalvageMaxPercentageLevel();
+    public static double salvageMaxPercentage      = AdvancedConfig.getInstance().getSalvageMaxPercentage();
+
+    public static int advancedSalvageUnlockLevel = AdvancedConfig.getInstance().getAdvancedSalvageUnlockLevel();
+
+    public static boolean arcaneSalvageDowngrades  = AdvancedConfig.getInstance().getArcaneSalvageEnchantDowngradeEnabled();
+    public static boolean arcaneSalvageEnchantLoss = AdvancedConfig.getInstance().getArcaneSalvageEnchantLossEnabled();
+
+    /**
+     * Checks if the item is salvageable.
+     *
+     * @param item Item to check
+     *
+     * @return true if the item is salvageable, false otherwise
+     */
+    public static boolean isSalvageable(ItemStack item) {
+        if (Config.getInstance().getSalvageTools() && ItemUtils.isMinecraftTool(item)) {
+            return true;
+        }
+
+        if (Config.getInstance().getSalvageArmor() && !ItemUtils.isChainmailArmor(item) && ItemUtils.isMinecraftArmor(item)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    protected static Material getSalvagedItem(ItemStack inHand) {
+        if (ItemUtils.isDiamondTool(inHand) || ItemUtils.isDiamondArmor(inHand)) {
+            return Material.DIAMOND;
+        }
+        else if (ItemUtils.isGoldTool(inHand) || ItemUtils.isGoldArmor(inHand)) {
+            return Material.GOLD_INGOT;
+        }
+        else if (ItemUtils.isIronTool(inHand) || ItemUtils.isIronArmor(inHand)) {
+            return Material.IRON_INGOT;
+        }
+        else if (ItemUtils.isStoneTool(inHand)) {
+            return Material.COBBLESTONE;
+        }
+        else if (ItemUtils.isWoodTool(inHand)) {
+            return Material.WOOD;
+        }
+        else if (ItemUtils.isLeatherArmor(inHand)) {
+            return Material.LEATHER;
+        }
+        else if (ItemUtils.isStringTool(inHand)) {
+            return Material.STRING;
+        }
+        else {
+            return null;
+        }
+    }
+
+    protected static int getSalvagedAmount(ItemStack inHand) {
+        return SkillUtils.getRepairAndSalvageQuantities(inHand, getSalvagedItem(inHand), (byte) -1);
+    }
+
+    protected static int calculateSalvageableAmount(short currentDurability, short maxDurability, int baseAmount) {
+        double percentDamaged = (double) (maxDurability - currentDurability) / maxDurability;
+
+        return (int) Math.floor(baseAmount * percentDamaged);
+    }
+}

+ 218 - 0
src/main/java/com/gmail/nossr50/skills/salvage/SalvageManager.java

@@ -0,0 +1,218 @@
+package com.gmail.nossr50.skills.salvage;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.bukkit.GameMode;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.EnchantmentStorageMeta;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.SkillManager;
+import com.gmail.nossr50.skills.salvage.Salvage.Tier;
+import com.gmail.nossr50.util.Misc;
+import com.gmail.nossr50.util.Permissions;
+
+public class SalvageManager extends SkillManager {
+    private boolean placedAnvil;
+    private int     lastClick;
+
+    public SalvageManager(McMMOPlayer mcMMOPlayer) {
+        super(mcMMOPlayer, SkillType.SALVAGE);
+    }
+
+    /**
+     * Handles notifications for placing an anvil.
+     *
+     * @param anvilType The {@link Material} of the anvil block
+     */
+    public void placedAnvilCheck(Material anvilType) {
+        Player player = getPlayer();
+
+        if (getPlacedAnvil(anvilType)) {
+            return;
+        }
+
+        player.sendMessage(LocaleLoader.getString("Salvage.Listener.Anvil"));
+
+        player.playSound(player.getLocation(), Sound.ANVIL_LAND, Misc.ANVIL_USE_VOLUME, Misc.ANVIL_USE_PITCH);
+        togglePlacedAnvil(anvilType);
+    }
+
+    public void handleSalvage(Location location, ItemStack item) {
+        Player player = getPlayer();
+
+        if (player.getGameMode() != GameMode.SURVIVAL) {
+            return;
+        }
+
+        if (item.getDurability() != 0 && (getSkillLevel() < Salvage.advancedSalvageUnlockLevel || !Permissions.advancedSalvage(player))) {
+            player.sendMessage(LocaleLoader.getString("Salvage.Skills.AdeptDamaged"));
+            return;
+        }
+
+        int salvageableAmount = Salvage.calculateSalvageableAmount(item.getDurability(), item.getType().getMaxDurability(), Salvage.getSalvagedAmount(item));
+
+        if (salvageableAmount == 0) {
+            player.sendMessage(LocaleLoader.getString("Salvage.Skills.TooDamaged"));
+            return;
+        }
+
+        double salvagePercentage = Math.min((((Salvage.salvageMaxPercentage / Salvage.salvageMaxPercentageLevel) * getSkillLevel()) / 100.0D), Salvage.salvageMaxPercentage / 100.0D);
+        salvageableAmount = Math.max((int) (salvageableAmount * salvagePercentage), 1); // Always get at least something back, if you're capable of repairing it.
+
+        player.setItemInHand(new ItemStack(Material.AIR));
+        location.add(0, 1, 0);
+
+        Map<Enchantment, Integer> enchants = item.getEnchantments();
+
+        if (!enchants.isEmpty()) {
+            ItemStack enchantBook = arcaneSalvageCheck(enchants);
+
+            if (enchantBook != null) {
+                Misc.dropItem(location, enchantBook);
+            }
+        }
+
+        Misc.dropItems(location, new ItemStack(Salvage.getSalvagedItem(item)), salvageableAmount);
+
+        player.playSound(player.getLocation(), Sound.ANVIL_USE, Misc.ANVIL_USE_VOLUME, Misc.ANVIL_USE_PITCH);
+        player.sendMessage(LocaleLoader.getString("Repair.Skills.SalvageSuccess"));
+    }
+
+    /**
+     * Gets the Arcane Salvage rank
+     *
+     * @return the current Arcane Salvage rank
+     */
+    public int getArcaneSalvageRank() {
+        int skillLevel = getSkillLevel();
+
+        for (Tier tier : Tier.values()) {
+            if (skillLevel >= tier.getLevel()) {
+                return tier.toNumerical();
+            }
+        }
+
+        return 0;
+    }
+
+    public double getExtractFullEnchantChance() {
+        int skillLevel = getSkillLevel();
+
+        for (Tier tier : Tier.values()) {
+            if (skillLevel >= tier.getLevel()) {
+                return tier.getExtractFullEnchantChance();
+            }
+        }
+
+        return 0;
+    }
+
+    public double getExtractPartialEnchantChance() {
+        int skillLevel = getSkillLevel();
+
+        for (Tier tier : Tier.values()) {
+            if (skillLevel >= tier.getLevel()) {
+                return tier.getExtractPartialEnchantChance();
+            }
+        }
+
+        return 0;
+    }
+
+    private ItemStack arcaneSalvageCheck(Map<Enchantment, Integer> enchants) {
+        Player player = getPlayer();
+
+        if (getArcaneSalvageRank() == 0 || !Permissions.arcaneSalvage(player)) {
+            player.sendMessage(LocaleLoader.getString("Salvage.Skills.ArcaneFailed"));
+            return null;
+        }
+
+        ItemStack book = new ItemStack(Material.ENCHANTED_BOOK);
+        EnchantmentStorageMeta enchantMeta = (EnchantmentStorageMeta) book.getItemMeta();
+
+        boolean downgraded = false;
+
+        for (Entry<Enchantment, Integer> enchant : enchants.entrySet()) {
+            int successChance = Misc.getRandom().nextInt(activationChance);
+
+            if (!Salvage.arcaneSalvageEnchantLoss || getExtractFullEnchantChance() > successChance) {
+                enchantMeta.addStoredEnchant(enchant.getKey(), enchant.getValue(), true);
+            }
+            else if (enchant.getValue() > 1 && Salvage.arcaneSalvageDowngrades && getExtractPartialEnchantChance() > successChance) {
+                enchantMeta.addStoredEnchant(enchant.getKey(), enchant.getValue() - 1, true);
+                downgraded = true;
+            }
+            else {
+                downgraded = true;
+            }
+        }
+
+        Map<Enchantment, Integer> newEnchants = enchantMeta.getStoredEnchants();
+
+        if (newEnchants.isEmpty()) {
+            player.sendMessage(LocaleLoader.getString("Salvage.Skills.ArcaneFailed"));
+            return null;
+        }
+
+        if (downgraded || newEnchants.size() < enchants.size()) {
+            player.sendMessage(LocaleLoader.getString("Salvage.Skills.ArcanePartial"));
+        }
+        else {
+            player.sendMessage(LocaleLoader.getString("Salvage.Skills.ArcaneSuccess"));
+        }
+
+        book.setItemMeta(enchantMeta);
+        return book;
+    }
+
+    /*
+     * Salvage Anvil Placement
+     */
+
+    public boolean getPlacedAnvil(Material anvilType) {
+        if (anvilType == Salvage.anvilMaterial) {
+            return placedAnvil;
+        }
+
+        return true;
+    }
+
+    public void togglePlacedAnvil(Material anvilType) {
+        if (anvilType == Salvage.anvilMaterial) {
+            placedAnvil = !placedAnvil;
+        }
+    }
+
+    /*
+     * Salvage Anvil Usage
+     */
+
+    public int getLastAnvilUse(Material anvilType) {
+        if (anvilType == Salvage.anvilMaterial) {
+            return lastClick;
+        }
+
+        return 0;
+    }
+
+    public void setLastAnvilUse(Material anvilType, int value) {
+        if (anvilType == Salvage.anvilMaterial) {
+            lastClick = value;
+        }
+    }
+
+    public void actualizeLastAnvilUse(Material anvilType) {
+        if (anvilType == Salvage.anvilMaterial) {
+            lastClick = (int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR);
+        }
+    }
+}

+ 2 - 1
src/main/java/com/gmail/nossr50/util/BlockUtils.java

@@ -14,6 +14,7 @@ import org.bukkit.material.SmoothBrick;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.skills.repair.Repair;
+import com.gmail.nossr50.skills.salvage.Salvage;
 
 public final class BlockUtils {
     private BlockUtils() {}
@@ -304,7 +305,7 @@ public final class BlockUtils {
     public static boolean isMcMMOAnvil(BlockState blockState) {
         Material type = blockState.getType();
 
-        return type == Repair.repairAnvilMaterial || type == Repair.salvageAnvilMaterial;
+        return type == Repair.anvilMaterial || type == Salvage.anvilMaterial;
     }
 
     /**

+ 9 - 0
src/main/java/com/gmail/nossr50/util/Permissions.java

@@ -162,6 +162,7 @@ public final class Permissions {
     public static boolean superBreaker(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.mining.superbreaker"); }
 
     /* REPAIR */
+
     public static boolean repairArmor(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.repair.armorrepair"); }
     public static boolean repairTools(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.repair.toolrepair"); }
     public static boolean repairOtherItems(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.repair.otherrepair"); }
@@ -175,6 +176,14 @@ public final class Permissions {
     public static boolean repairStone(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.repair.stonerepair"); }
     public static boolean repairWood(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.repair.woodrepair"); }
 
+    /* SALVAGE */
+    public static boolean advancedSalvage(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.salvage.advancedsalvage"); }
+    public static boolean arcaneSalvage(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.salvage.arcanesalvage"); }
+
+    /* SMELTING */
+    public static boolean fluxMining(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.smelting.fluxmining"); }
+    public static boolean fuelEfficiency(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.smelting.fuelefficiency"); }
+
     /* SWORDS */
     public static boolean serratedStrikes(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.swords.serratedstrikes"); }
 

+ 5 - 0
src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java

@@ -43,6 +43,7 @@ import com.gmail.nossr50.commands.skills.FishingCommand;
 import com.gmail.nossr50.commands.skills.HerbalismCommand;
 import com.gmail.nossr50.commands.skills.MiningCommand;
 import com.gmail.nossr50.commands.skills.RepairCommand;
+import com.gmail.nossr50.commands.skills.SalvageCommand;
 import com.gmail.nossr50.commands.skills.SmeltingCommand;
 import com.gmail.nossr50.commands.skills.SwordsCommand;
 import com.gmail.nossr50.commands.skills.TamingCommand;
@@ -109,6 +110,10 @@ public final class CommandRegistrationManager {
                     command.setExecutor(new RepairCommand());
                     break;
 
+                case SALVAGE:
+                    command.setExecutor(new SalvageCommand());
+                    break;
+
                 case SMELTING:
                     command.setExecutor(new SmeltingCommand());
                     break;

+ 62 - 0
src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java

@@ -8,7 +8,11 @@ import org.bukkit.Material;
 import org.bukkit.enchantments.Enchantment;
 import org.bukkit.entity.Player;
 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.ItemMeta;
+import org.bukkit.material.MaterialData;
 import org.bukkit.potion.PotionEffect;
 import org.bukkit.potion.PotionEffectType;
 
@@ -219,4 +223,62 @@ public class SkillUtils {
 
         return false;
     }
+
+    protected static Material getRepairAndSalvageItem(ItemStack inHand) {
+        if (ItemUtils.isDiamondTool(inHand) || ItemUtils.isDiamondArmor(inHand)) {
+            return Material.DIAMOND;
+        }
+        else if (ItemUtils.isGoldTool(inHand) || ItemUtils.isGoldArmor(inHand)) {
+            return Material.GOLD_INGOT;
+        }
+        else if (ItemUtils.isIronTool(inHand) || ItemUtils.isIronArmor(inHand)) {
+            return Material.IRON_INGOT;
+        }
+        else if (ItemUtils.isStoneTool(inHand)) {
+            return Material.COBBLESTONE;
+        }
+        else if (ItemUtils.isWoodTool(inHand)) {
+            return Material.WOOD;
+        }
+        else if (ItemUtils.isLeatherArmor(inHand)) {
+            return Material.LEATHER;
+        }
+        else if (ItemUtils.isStringTool(inHand)) {
+            return Material.STRING;
+        }
+        else {
+            return null;
+        }
+    }
+
+    public static int getRepairAndSalvageQuantities(ItemStack item) {
+        return getRepairAndSalvageQuantities(item, getRepairAndSalvageItem(item), (byte) -1);
+    }
+
+    public static int getRepairAndSalvageQuantities(ItemStack item, Material repairMaterial, byte repairMetadata) {
+        int quantity = 0;
+        MaterialData repairData = repairMaterial != null ? new MaterialData(repairMaterial, repairMetadata) : null;
+        List<Recipe> recipes = mcMMO.p.getServer().getRecipesFor(item);
+
+        if (!recipes.isEmpty()) {
+            Recipe recipe = recipes.get(0);
+
+            if (recipe instanceof ShapelessRecipe) {
+                for (ItemStack ingredient : ((ShapelessRecipe) recipe).getIngredientList()) {
+                    if (ingredient != null && (repairMaterial == null || ingredient.getType() == repairMaterial) && (repairMetadata == -1 || ingredient.getData().equals(repairData))) {
+                        quantity += ingredient.getAmount();
+                    }
+                }
+            }
+            else if (recipe instanceof ShapedRecipe) {
+                for (ItemStack ingredient : ((ShapedRecipe) recipe).getIngredientMap().values()) {
+                    if (ingredient != null && (repairMaterial == null || ingredient.getType() == repairMaterial) && (repairMetadata == -1 || ingredient.getData().equals(repairData))) {
+                        quantity += ingredient.getAmount();
+                    }
+                }
+            }
+        }
+
+        return quantity;
+    }
 }

+ 49 - 0
src/main/resources/advanced.yml

@@ -349,6 +349,55 @@ Skills:
                 Rank_7: 15.0
                 Rank_8: 10.0
     #
+    #  Settings for Salvage
+    ###
+    Salvage:
+        # MaxPercentage: Maximum percentage of materials to be returned when Salvaging
+        # MaxPercentageLevel: On this level, the Salvage percentage will be <MaxPercentage>
+        MaxPercentage: 100.0
+        MaxPercentageLevel: 1000
+
+        # AdvancedSalvage_UnlockLevel: The level at which Advance Salvage become available
+        AdvancedSalvage:
+            UnlockLevel: 350
+
+        ArcaneSalvage:
+            # EnchantLossEnabled: When salvaging enchanted items, the enchants may be lost
+            # EnchantDowngradeEnabled: When salvaging enchanted items, the enchants may be downgraded
+            EnchantLossEnabled: true
+            EnchantDowngradeEnabled: true
+            Rank_Levels:
+                Rank_1: 125
+                Rank_2: 250
+                Rank_3: 375
+                Rank_4: 500
+                Rank_5: 625
+                Rank_6: 750
+                Rank_7: 875
+                Rank_8: 1000
+
+            # ExtractFullEnchant: Chance to extract the full enchant at each ArcaneSalvage rank
+            ExtractFullEnchant:
+                Rank_1: 2.5
+                Rank_2: 5.0
+                Rank_3: 7.5
+                Rank_4: 10.0
+                Rank_5: 12.5
+                Rank_6: 17.5
+                Rank_7: 25.0
+                Rank_8: 32.5
+
+            # ExtractPartialEnchant: Chance to extract the partial enchant at each ArcaneSalvage rank
+            ExtractPartialEnchant:
+                Rank_1: 2.0
+                Rank_2: 2.5
+                Rank_3: 5.0
+                Rank_4: 7.5
+                Rank_5: 10.0
+                Rank_6: 12.5
+                Rank_7: 15.0
+                Rank_8: 17.5
+    #
     #  Settings for Smelting
     ###
     Smelting:

+ 3 - 0
src/main/resources/child.yml

@@ -8,6 +8,9 @@
 #  WARNING: THIS IS NOT SUPPORTED, IF YOU DO SO YOU ARE RESPONSIBLE FOR THE ISSUES THAT MAY ARISE.  That said, watch out for circular dependencies, those are bad.
 #
 #####
+Salvage:
+    - Fishing
+    - Repair
 Smelting:
     - Mining
     - Repair

+ 6 - 3
src/main/resources/config.yml

@@ -309,11 +309,14 @@ Skills:
         Anvil_Placed_Sounds: true
         Anvil_Use_Sounds: true
         Anvil_Material: IRON_BLOCK
+        # Ask for a confirmation when a player tries to repair an enchanted item
+        Confirm_Required: true
+    Salvage:
+        Level_Cap: 0
+        Anvil_Messages: true
         Salvage_Anvil_Material: GOLD_BLOCK
         Salvage_tools: true
         Salvage_armor: true
-        # Ask for a confirmation when a player tries to repair an enchanted item
-        Confirm_Required: true
     Smelting:
         Level_Cap: 0
     Swords:
@@ -449,4 +452,4 @@ Particles:
     # These settings determine if fireworks should get launched when a player levels-up,
     # this will happen by default for every 100 levels.
     LevelUp_Enabled: true
-    LevelUp_Tier: 100
+    LevelUp_Tier: 100

+ 21 - 7
src/main/resources/locale/locale_en_US.properties

@@ -234,14 +234,10 @@ Repair.Effect.6=Diamond Repair ({0}+ SKILL)
 Repair.Effect.7=Repair Diamond Tools & Armor
 Repair.Effect.8=Arcane Forging
 Repair.Effect.9=Repair magic items
-Repair.Effect.16=Salvage ({0}+ SKILL)
-Repair.Effect.17=Salvage Tools & Armor
 Repair.Error=[[DARK_RED]]mcMMO encountered an error attempting to repair this item!
 Repair.Listener.Anvil=[[DARK_RED]]You have placed an anvil, anvils can repair tools and armor.
-Repair.Listener.Anvil2=[[DARK_RED]]You have placed a Salvage anvil, use this to Salvage tools and armor.
 Repair.Listener=Repair:
 Repair.SkillName=REPAIR
-Repair.Skills.AdeptSalvage=[[DARK_RED]]You're not skilled enough to Salvage items.
 Repair.Skills.AdeptDiamond=[[DARK_RED]]You're not skilled enough to repair Diamond.
 Repair.Skills.AdeptGold=[[DARK_RED]]You're not skilled enough to repair Gold.
 Repair.Skills.AdeptIron=[[DARK_RED]]You're not skilled enough to repair Iron.
@@ -249,14 +245,11 @@ Repair.Skills.AdeptStone=[[DARK_RED]]You're not skilled enough to repair Stone.
 Repair.Skills.Adept=[[RED]]You must be level [[YELLOW]]{0}[[RED]] to repair [[YELLOW]]{1}
 Repair.Skills.FeltEasy=[[GRAY]]That felt easy.
 Repair.Skills.FullDurability=[[GRAY]]That is at full durability.
-Repair.Skills.SalvageSuccess=[[GRAY]]Item salvaged!
-Repair.Skills.NotFullDurability=[[DARK_RED]]You can't salvage damaged items.
 Repair.Skills.Mastery=[[RED]]Repair Mastery: [[YELLOW]]Extra {0} durability restored
 Repair.Skills.StackedItems=[[DARK_RED]]You can't repair stacked items.
 Repair.Skills.Super.Chance=[[RED]]Super Repair Chance: [[YELLOW]]{0}
 Repair.Skillup=[[YELLOW]]Repair skill increased by {0}. Total ({1})
 Repair.Pretty.Name=Repair
-Salvage.Pretty.Name=Salvage
 
 #Arcane Forging
 Repair.Arcane.Chance.Downgrade=[[GRAY]]AF Downgrade Chance: [[YELLOW]]{0}%
@@ -267,6 +260,27 @@ Repair.Arcane.Lost=[[RED]]You were not skilled enough to keep any enchantments.
 Repair.Arcane.Perfect=[[GREEN]]You have sustained the arcane energies in this item.
 Repair.Arcane.Rank=[[RED]]Arcane Forging: [[YELLOW]]Rank {0}/{1}
 
+#SALVAGE
+Salvage.Pretty.Name=Salvage
+Salvage.Effect.0=Advanced Salvage
+Salvage.Effect.1=Salvage damaged items
+Salvage.Effect.2=Arcane Salvaging
+Salvage.Effect.3=Extract enchantments from items
+Salvage.Ability.Locked.0=LOCKED UNTIL {0}+ SKILL (ADVANCED SALVAGE)
+Salvage.Ability.Bonus.0=Advanced Salvage
+Salvage.Ability.Bonus.1=Salvaging damaged items will grant you items accordingly
+Salvage.Arcane.Rank=[[RED]]Arcane Salvaging: [[YELLOW]]Rank {0}/{1}
+Salvage.Arcane.ExtractFull=[[GRAY]]AS Full-Enchant Chance
+Salvage.Arcane.ExtractPartial=[[GRAY]]AS Partial-Enchant Chance
+Salvage.Skills.AdeptDamaged=You aren't skilled enough to salvage damaged items.
+Salvage.Skills.TooDamaged=This item is too damaged to be salvaged.
+Salvage.Skills.ArcaneFailed=You were unable to extract the knowledge contained within this item.
+Salvage.Skills.ArcanePartial=You were only able to extract some of the knowledge contained within this item.
+Salvage.Skills.ArcaneSuccess=You able to extract all of the knowledge contained within this item!
+Salvage.Listener.Anvil=[[DARK_RED]]You have placed a Salvage anvil, use this to Salvage tools and armor.
+Salvage.Listener=Salvage:
+Salvage.SkillName=SALVAGE
+
 #SWORDS
 Swords.Ability.Lower=[[GRAY]]**YOU LOWER YOUR SWORD**
 Swords.Ability.Ready=[[GREEN]]**YOU READY YOUR SWORD**

+ 34 - 0
src/main/resources/plugin.yml

@@ -91,6 +91,8 @@ commands:
         description: Detailed mcMMO skill info
     alchemy:
         description: Detailed mcMMO skill info
+    salvage:
+        description: Detailed mcMMO skill info
     adminchat:
         aliases: [ac, a]
         description: Toggle Admin chat or send admin chat messages
@@ -152,6 +154,7 @@ permissions:
             mcmmo.ability.herbalism.all: true
             mcmmo.ability.mining.all: true
             mcmmo.ability.repair.all: true
+            mcmmo.ability.salvage.all: true
             mcmmo.ability.smelting.all: true
             mcmmo.ability.swords.all: true
             mcmmo.ability.taming.all: true
@@ -433,6 +436,20 @@ permissions:
         description: Allows ability to repair tools
     mcmmo.ability.repair.woodrepair:
         description: Allows ability to repair Wood tools
+    mcmmo.ability.salvage.*:
+        default: false
+        description: Allows access to all Salvage abilities
+        children:
+            mcmmo.ability.salvage.all: true
+    mcmmo.ability.salvage.all:
+        description: Allows access to all Smelting abilities
+        children:
+            mcmmo.ability.salvage.advancedsalvage: true
+            mcmmo.ability.salvage.arcanesalvage: true
+    mcmmo.ability.salvage.advancedsalvage:
+        description: Allows access to the Advanced Salvage ability
+    mcmmo.ability.salvage.arcanesalvage:
+        description: Allows access to the Arcane Salvage ability
     mcmmo.ability.smelting.*:
         default: false
         description: Allows access to all Smelting abilities
@@ -656,6 +673,7 @@ permissions:
             mcmmo.commands.party.all: true
             mcmmo.commands.ptp.all: true
             mcmmo.commands.repair: true
+            mcmmo.commands.salvage: true
             mcmmo.commands.smelting: true
             mcmmo.commands.swords: true
             mcmmo.commands.taming: true
@@ -844,6 +862,7 @@ permissions:
             mcmmo.commands.mctop.herbalism: true
             mcmmo.commands.mctop.mining: true
             mcmmo.commands.mctop.repair: true
+            mcmmo.commands.mctop.salvage: true
             mcmmo.commands.mctop.smelting: true
             mcmmo.commands.mctop.swords: true
             mcmmo.commands.mctop.taming: true
@@ -869,6 +888,8 @@ permissions:
         description: Allows access to the mctop command for mining
     mcmmo.commands.mctop.repair:
         description: Allows access to the mctop command for repair
+    mcmmo.commands.mctop.salvage:
+        description: Allows access to the mctop command for salvage
     mcmmo.commands.mctop.smelting:
         description: Allows access to the mctop command for smelting
     mcmmo.commands.mctop.swords:
@@ -1014,6 +1035,7 @@ permissions:
             mcmmo.commands.skillreset.mining: true
             mcmmo.commands.skillreset.others.all: true
             mcmmo.commands.skillreset.repair: true
+            mcmmo.commands.skillreset.salvage: true
             mcmmo.commands.skillreset.smelting: true
             mcmmo.commands.skillreset.swords: true
             mcmmo.commands.skillreset.taming: true
@@ -1055,6 +1077,7 @@ permissions:
             mcmmo.commands.skillreset.others.herbalism: true
             mcmmo.commands.skillreset.others.mining: true
             mcmmo.commands.skillreset.others.repair: true
+            mcmmo.commands.skillreset.others.salvage: true
             mcmmo.commands.skillreset.others.smelting: true
             mcmmo.commands.skillreset.others.swords: true
             mcmmo.commands.skillreset.others.taming: true
@@ -1080,6 +1103,8 @@ permissions:
         description: Allows access to the skillreset command for mining for other players
     mcmmo.commands.skillreset.others.repair:
         description: Allows access to the skillreset command for repair for other players
+    mcmmo.commands.skillreset.others.salvage:
+        description: Allows access to the skillreset command for salvage for other players
     mcmmo.commands.skillreset.others.smelting:
         description: Allows access to the skillreset command for smelting for other players
     mcmmo.commands.skillreset.others.swords:
@@ -1092,6 +1117,8 @@ permissions:
         description: Allows access to the skillreset command for woodcutting for other players
     mcmmo.commands.skillreset.repair:
         description: Allows access to the skillreset command for repair
+    mcmmo.commands.skillreset.salvage:
+        description: Allows access to the skillreset command for smelting
     mcmmo.commands.skillreset.smelting:
         description: Allows access to the skillreset command for smelting
     mcmmo.commands.skillreset.swords:
@@ -1102,6 +1129,8 @@ permissions:
         description: Allows access to the skillreset command for unarmed
     mcmmo.commands.skillreset.woodcutting:
         description: Allows access to the skillreset command for woodcutting
+    mcmmo.commands.salvage:
+        description: Allows access to the salvage command
     mcmmo.commands.smelting:
         description: Allows access to the smelting command
     mcmmo.commands.swords:
@@ -1264,6 +1293,7 @@ permissions:
             mcmmo.perks.lucky.herbalism: true
             mcmmo.perks.lucky.mining: true
             mcmmo.perks.lucky.repair: true
+            mcmmo.perks.lucky.salvage: true
             mcmmo.perks.lucky.smelting: true
             mcmmo.perks.lucky.swords: true
             mcmmo.perks.lucky.taming: true
@@ -1296,6 +1326,9 @@ permissions:
     mcmmo.perks.lucky.repair:
         default: false
         description: Gives Repair abilities & skills a 33.3% better chance to activate.
+    mcmmo.perks.lucky.salvage:
+        default: false
+        description: Gives Salvage abilities & skills a 33.3% better chance to activate.
     mcmmo.perks.lucky.smelting:
         default: false
         description: Gives Smelting abilities & skills a 33.3% better chance to activate.
@@ -1840,6 +1873,7 @@ permissions:
             mcmmo.skills.herbalism: true
             mcmmo.skills.mining: true
             mcmmo.skills.repair: true
+            mcmmo.skills.salvage: true
             mcmmo.skills.swords: true
             mcmmo.skills.smelting: true
             mcmmo.skills.taming: true