From 2e204a8fc6f34a1b2b98ff2e7b45f2fb584c7ccf Mon Sep 17 00:00:00 2001 From: Gunging <48371009+Gunging@users.noreply.github.com> Date: Tue, 10 May 2022 15:31:38 -0500 Subject: [PATCH] - RevID increase no longer repairs items forcibly + Config option to automatically regenerate gems when they are popped out from their socket by a RevID update + Allows multiple set bonuses to be equipped at the same time + Allows granted-permissions stat in set bonuses + keepTiers config option for RevID updates + API for death downgrade stat --- .../net/Indyuce/mmoitems/api/ItemSet.java | 24 +- .../Indyuce/mmoitems/api/ReforgeOptions.java | 53 ++- .../mmoitems/api/item/mmoitem/MMOItem.java | 61 +++- .../mmoitems/api/player/PlayerData.java | 40 ++- .../mmoitems/api/util/DeathDowngrading.java | 340 ++++++++++++++++++ .../mmoitems/api/util/MMOItemReforger.java | 6 +- .../mmoitems/listener/PlayerListener.java | 157 ++------ .../listener/reforging/RFGKeepDurability.java | 1 - .../listener/reforging/RFGKeepGems.java | 5 +- src/main/resources/config.yml | 17 +- 10 files changed, 511 insertions(+), 193 deletions(-) create mode 100644 src/main/java/net/Indyuce/mmoitems/api/util/DeathDowngrading.java diff --git a/src/main/java/net/Indyuce/mmoitems/api/ItemSet.java b/src/main/java/net/Indyuce/mmoitems/api/ItemSet.java index 932e3127..56f0ce5f 100644 --- a/src/main/java/net/Indyuce/mmoitems/api/ItemSet.java +++ b/src/main/java/net/Indyuce/mmoitems/api/ItemSet.java @@ -1,11 +1,6 @@ package net.Indyuce.mmoitems.api; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import org.apache.commons.lang.Validate; import org.bukkit.configuration.ConfigurationSection; @@ -17,6 +12,7 @@ import net.Indyuce.mmoitems.MMOUtils; import net.Indyuce.mmoitems.stat.data.AbilityData; import net.Indyuce.mmoitems.stat.data.ParticleData; import net.Indyuce.mmoitems.stat.type.ItemStat; +import org.jetbrains.annotations.NotNull; public class ItemSet { private final Map bonuses = new HashMap<>(); @@ -39,10 +35,14 @@ public class ItemSet { Validate.isTrue(config.contains("bonuses"), "Could not find item set bonuses"); for (int j = 2; j <= itemLimit; j++) - if (config.getConfigurationSection("bonuses").contains("" + j)) { + if (config.getConfigurationSection("bonuses").contains(String.valueOf(j))) { SetBonuses bonuses = new SetBonuses(); - for (String key : config.getConfigurationSection("bonuses." + j).getKeys(false)) + // Add permissions + for (String perm : config.getConfigurationSection("bonuses." + j).getStringList("granted-permissions")) { bonuses.addPermission(perm); } + + for (String key : config.getConfigurationSection("bonuses." + j).getKeys(false)) { + try { String format = key.toUpperCase().replace("-", "_").replace(" ", "_"); @@ -75,6 +75,7 @@ public class ItemSet { } catch (IllegalArgumentException exception) { throw new IllegalArgumentException("Could not load set bonus '" + key + "': " + exception.getMessage()); } + } this.bonuses.put(j, bonuses); } @@ -105,6 +106,7 @@ public class ItemSet { private final Map permEffects = new HashMap<>(); private final Set abilities = new HashSet<>(); private final Set particles = new HashSet<>(); + private final ArrayList permissions = new ArrayList<>(); public void addStat(ItemStat stat, double value) { stats.put(stat, value); @@ -122,6 +124,8 @@ public class ItemSet { particles.add(particle); } + public void addPermission(@NotNull String permission) { permissions.add(permission); } + public boolean hasStat(ItemStat stat) { return stats.containsKey(stat); } @@ -146,6 +150,8 @@ public class ItemSet { return abilities; } + @NotNull public ArrayList getPermissions() { return permissions; } + public void merge(SetBonuses bonuses) { bonuses.getStats().forEach((stat, value) -> stats.put(stat, (stats.containsKey(stat) ? stats.get(stat) : 0) + value)); @@ -154,6 +160,8 @@ public class ItemSet { permEffects.put(effect.getType(), effect); abilities.addAll(bonuses.getAbilities()); + + permissions.addAll(bonuses.getPermissions()); } } } diff --git a/src/main/java/net/Indyuce/mmoitems/api/ReforgeOptions.java b/src/main/java/net/Indyuce/mmoitems/api/ReforgeOptions.java index f4c8bd03..e17ee411 100644 --- a/src/main/java/net/Indyuce/mmoitems/api/ReforgeOptions.java +++ b/src/main/java/net/Indyuce/mmoitems/api/ReforgeOptions.java @@ -1,8 +1,10 @@ package net.Indyuce.mmoitems.api; +import net.Indyuce.mmoitems.api.util.MMOItemReforger; import org.bukkit.ChatColor; import org.bukkit.configuration.ConfigurationSection; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -19,6 +21,7 @@ public class ReforgeOptions { private final boolean keepSoulbind; private final boolean keepExternalSH; private final boolean keepModifications; + @Nullable private final Boolean keepTier; private final boolean reroll; @@ -78,6 +81,7 @@ public class ReforgeOptions { keepModifications = config.getBoolean("modifications"); reroll = config.getBoolean("reroll"); keepAdvancedEnchantments = config.getBoolean("advanced-enchantments"); + keepTier = config.contains("tier") ? config.getBoolean("tier", true) : null; } public ReforgeOptions(boolean... values) { @@ -92,6 +96,7 @@ public class ReforgeOptions { keepModifications = arr(values, 8); keepAdvancedEnchantments = arr(values, 9); keepSkins = arr(values, 10); + keepTier = arr(values, 11); } boolean arr(@NotNull boolean[] booleans, int idx) { @@ -104,70 +109,56 @@ public class ReforgeOptions { /** * Keeps the display name of the item. */ - public boolean shouldReroll() { - return reroll; - } + public boolean shouldReroll() { return reroll; } /** * Keeps the display name of the item. */ - public boolean shouldKeepName() { - return keepName; - } + public boolean shouldKeepName() { return keepName; } /** * Keeps the modifiers of the item. */ - public boolean shouldKeepMods() { - return keepModifications; - } + public boolean shouldKeepMods() { return keepModifications; } /** * Keeps all lore lines that begin with {@link org.bukkit.ChatColor#GRAY} */ - public boolean shouldKeepLore() { - return keepLore; - } + public boolean shouldKeepLore() { return keepLore; } /** * Keeps skins */ - public boolean shouldKeepSkins() { - return keepSkins; + public boolean shouldKeepSkins() { return keepSkins; } - } + /** + * Should keep the tier? defaults to {@link MMOItemReforger#keepTiersWhenReroll} + */ + public boolean shouldKeepTier() { return keepTier == null ? MMOItemReforger.keepTiersWhenReroll : keepTier; } /** * Should this keep the enchantments the player * manually cast onto this item? (Not from gem * stones nor upgrades). */ - public boolean shouldKeepEnchantments() { - return keepEnchantments; - } + public boolean shouldKeepEnchantments() { return keepEnchantments; } /** * Should this keep the enchantments the player * manually cast onto this item? (Not from gem * stones nor upgrades). */ - public boolean shouldKeepAdvancedEnchants() { - return keepAdvancedEnchantments; - } + public boolean shouldKeepAdvancedEnchants() { return keepAdvancedEnchantments; } /** * Keep 'extraneous' data registered onto the Stat History */ - public boolean shouldKeepExternalSH() { - return keepExternalSH; - } + public boolean shouldKeepExternalSH() { return keepExternalSH; } /** * Retains the upgrade level of the item. */ - public boolean shouldKeepUpgrades() { - return keepUpgrades; - } + public boolean shouldKeepUpgrades() { return keepUpgrades; } /** * Retains all gem stones if there are any, removing @@ -175,14 +166,10 @@ public class ReforgeOptions { *

* Gemstones remember at what upgrade level they were inserted. */ - public boolean shouldKeepGemStones() { - return keepGemStones; - } + public boolean shouldKeepGemStones() { return keepGemStones; } /** * Retains the soulbind if it has any. */ - public boolean shouldKeepSoulbind() { - return keepSoulbind; - } + public boolean shouldKeepSoulbind() { return keepSoulbind; } } diff --git a/src/main/java/net/Indyuce/mmoitems/api/item/mmoitem/MMOItem.java b/src/main/java/net/Indyuce/mmoitems/api/item/mmoitem/MMOItem.java index 305dff30..15464388 100644 --- a/src/main/java/net/Indyuce/mmoitems/api/item/mmoitem/MMOItem.java +++ b/src/main/java/net/Indyuce/mmoitems/api/item/mmoitem/MMOItem.java @@ -1,23 +1,25 @@ package net.Indyuce.mmoitems.api.item.mmoitem; +import io.lumine.mythic.lib.api.item.NBTItem; import io.lumine.mythic.lib.api.util.ui.SilentNumbers; import net.Indyuce.mmoitems.ItemStats; import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.api.ItemTier; import net.Indyuce.mmoitems.api.Type; import net.Indyuce.mmoitems.api.UpgradeTemplate; +import net.Indyuce.mmoitems.api.interaction.util.DurabilityItem; import net.Indyuce.mmoitems.api.item.ItemReference; import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder; +import net.Indyuce.mmoitems.api.util.MMOItemReforger; import net.Indyuce.mmoitems.stat.Enchants; -import net.Indyuce.mmoitems.stat.data.DoubleData; -import net.Indyuce.mmoitems.stat.data.GemSocketsData; -import net.Indyuce.mmoitems.stat.data.GemstoneData; -import net.Indyuce.mmoitems.stat.data.UpgradeData; +import net.Indyuce.mmoitems.stat.data.*; import net.Indyuce.mmoitems.stat.data.type.Mergeable; import net.Indyuce.mmoitems.stat.data.type.StatData; import net.Indyuce.mmoitems.stat.type.ItemStat; import net.Indyuce.mmoitems.stat.type.StatHistory; import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -225,11 +227,25 @@ public class MMOItem implements ItemReference { */ public int getDamage() { - if (!hasData(ItemStats.ITEM_DAMAGE)) { return 0; } + // Does it use MMO Durability? + int maxDurability = hasData(ItemStats.MAX_DURABILITY) ? SilentNumbers.round(((DoubleData) getData(ItemStats.MAX_DURABILITY)).getValue()) : -1; - DoubleData durData = (DoubleData) getData(ItemStats.ITEM_DAMAGE); + if (maxDurability > 0) { - return SilentNumbers.round(durData.getValue()); + // Apparently we must do this + NBTItem nbtItem = newBuilder().buildNBT(); + + // Durability + int durability = hasData(ItemStats.CUSTOM_DURABILITY) ? SilentNumbers.round(((DoubleData) getData(ItemStats.CUSTOM_DURABILITY)).getValue()) : maxDurability; + + // Damage is the difference between max and current durability + return maxDurability - durability; + + } else { + + // Use vanilla durability + return hasData(ItemStats.ITEM_DAMAGE) ? SilentNumbers.round(((DoubleData) getData(ItemStats.ITEM_DAMAGE)).getValue()) : 0; + } } /** @@ -240,9 +256,33 @@ public class MMOItem implements ItemReference { */ public void setDamage(int damage) { + // Too powerful if (hasData(ItemStats.UNBREAKABLE)) { return; } - setData(ItemStats.ITEM_DAMAGE, new DoubleData(damage)); + // Does it use MMO Durability? + int maxDurability = hasData(ItemStats.MAX_DURABILITY) ? SilentNumbers.round(((DoubleData) getData(ItemStats.MAX_DURABILITY)).getValue()) : -1; + + if (maxDurability > 0) { + + // Apparently we must do this + NBTItem nbtItem = newBuilder().buildNBT(); + + // Durability + setData(ItemStats.CUSTOM_DURABILITY, new DoubleData(maxDurability - damage)); + + // Scale damage + Material mat = hasData(ItemStats.MATERIAL) ? ((MaterialData) getData(ItemStats.MATERIAL)).getMaterial() : Material.GOLD_INGOT; + double multiplier = ((double) damage) * ((double) mat.getMaxDurability()) / ((double) maxDurability); + if (multiplier == 0) { return; } + + // Set to some decent amount of damage + setData(ItemStats.ITEM_DAMAGE, new DoubleData(multiplier)); + + } else { + + // Use vanilla durability + setData(ItemStats.ITEM_DAMAGE, new DoubleData(damage)); + } } //endregion @@ -373,6 +413,9 @@ public class MMOItem implements ItemReference { } //XTC//MMOItems.log("\u00a7b *\u00a77 Regen Size:\u00a79 " + regeneratedGems.values().size()); + // If RevID updating, it basically just regenerates + if (MMOItemReforger.gemstonesRevIDWhenUnsocket) { return new ArrayList<>(regeneratedGems.values()); } + // Identify actual attributes for (ItemStat stat : getStats()) { @@ -421,6 +464,8 @@ public class MMOItem implements ItemReference { // Can we generate? MMOItem restored = MMOItems.plugin.getMMOItem(MMOItems.plugin.getTypes().get(gem.getMMOItemType()), gem.getMMOItemID()); + if (MMOItemReforger.gemstonesRevIDWhenUnsocket) { return restored; } + // Valid? neat-o if (restored != null) { //XTC//MMOItems.log("\u00a7a *\u00a73>\u00a77 Valid, regenerated \u00a7e" + restored.getData(ItemStats.NAME)); diff --git a/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java b/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java index b763a23e..dc02796f 100644 --- a/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java +++ b/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java @@ -4,6 +4,7 @@ import io.lumine.mythic.lib.MythicLib; import io.lumine.mythic.lib.api.item.NBTItem; import io.lumine.mythic.lib.api.player.EquipmentSlot; import io.lumine.mythic.lib.api.player.MMOPlayerData; +import io.lumine.mythic.lib.api.util.ui.SilentNumbers; import io.lumine.mythic.lib.damage.AttackMetadata; import io.lumine.mythic.lib.player.PlayerMetadata; import io.lumine.mythic.lib.player.modifier.ModifierSource; @@ -19,6 +20,7 @@ import net.Indyuce.mmoitems.api.crafting.CraftingStatus; import net.Indyuce.mmoitems.api.event.RefreshInventoryEvent; import net.Indyuce.mmoitems.api.interaction.Tool; import net.Indyuce.mmoitems.api.item.ItemReference; +import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem; import net.Indyuce.mmoitems.api.item.mmoitem.VolatileMMOItem; import net.Indyuce.mmoitems.api.player.inventory.EquippedItem; import net.Indyuce.mmoitems.api.player.inventory.EquippedPlayerItem; @@ -61,6 +63,7 @@ public class PlayerData { private final Set itemParticles = new HashSet<>(); private ParticleRunnable overridingItemParticles = null; private boolean fullHands = false; + @Nullable private SetBonuses setBonuses = null; private final PlayerStats stats; @@ -174,10 +177,7 @@ public class PlayerData { overridingItemParticles = null; if (MMOItems.plugin.hasPermissions()) { Permission perms = MMOItems.plugin.getVault().getPermissions(); - permissions.forEach(perm -> { - if (perms.has(getPlayer(), perm)) - perms.playerRemove(getPlayer(), perm); - }); + permissions.forEach(perm -> { if (perms.has(getPlayer(), perm)) { perms.playerRemove(getPlayer(), perm); } }); } permissions.clear(); @@ -248,12 +248,10 @@ public class PlayerData { * Apply permissions if vault exists */ if (MMOItems.plugin.hasPermissions() && item.hasData(ItemStats.GRANTED_PERMISSIONS)) { + permissions.addAll(((StringListData) item.getData(ItemStats.GRANTED_PERMISSIONS)).getList()); Permission perms = MMOItems.plugin.getVault().getPermissions(); - permissions.forEach(perm -> { - if (!perms.has(getPlayer(), perm)) - perms.playerAdd(getPlayer(), perm); - }); + permissions.forEach(perm -> { if (!perms.has(getPlayer(), perm)) { perms.playerAdd(getPlayer(), perm); } }); } } @@ -261,8 +259,6 @@ public class PlayerData { * calculate the player's item set and add the bonus permanent effects / * bonus abilities to the playerdata maps */ - int max = 0; - ItemSet set = null; Map sets = new HashMap<>(); for (EquippedPlayerItem equipped : inventory.getEquipped()) { VolatileMMOItem item = equipped.getItem(); @@ -273,14 +269,28 @@ public class PlayerData { int nextInt = (sets.getOrDefault(itemSet, 0)) + 1; sets.put(itemSet, nextInt); - if (nextInt >= max) { - max = nextInt; - set = itemSet; + } + + // Reset + setBonuses = null; + for (Map.Entry equippedSetBonus : sets.entrySet()) { + + if (setBonuses == null) { + + // Set set bonuses + setBonuses = equippedSetBonus.getKey().getBonuses(equippedSetBonus.getValue()); + + } else { + + // Merge bonuses + setBonuses.merge(equippedSetBonus.getKey().getBonuses(equippedSetBonus.getValue())); } } - setBonuses = set == null ? null : set.getBonuses(max); - if (hasSetBonuses()) { + if (setBonuses != null) { + Permission perms = MMOItems.plugin.getVault().getPermissions(); + for (String perm : setBonuses.getPermissions()) + if (!perms.has(getPlayer(), perm)) { perms.playerAdd(getPlayer(), perm); } for (AbilityData ability : setBonuses.getAbilities()) mmoData.getPassiveSkillMap().addModifier(new PassiveSkill("MMOItemsItem", ability, EquipmentSlot.OTHER, ModifierSource.OTHER)); for (ParticleData particle : setBonuses.getParticles()) diff --git a/src/main/java/net/Indyuce/mmoitems/api/util/DeathDowngrading.java b/src/main/java/net/Indyuce/mmoitems/api/util/DeathDowngrading.java new file mode 100644 index 00000000..1942c7b8 --- /dev/null +++ b/src/main/java/net/Indyuce/mmoitems/api/util/DeathDowngrading.java @@ -0,0 +1,340 @@ +package net.Indyuce.mmoitems.api.util; + +import io.lumine.mythic.lib.api.item.NBTItem; +import io.lumine.mythic.lib.api.util.ui.SilentNumbers; +import net.Indyuce.mmoitems.ItemStats; +import net.Indyuce.mmoitems.api.interaction.util.DurabilityItem; +import net.Indyuce.mmoitems.api.item.mmoitem.LiveMMOItem; +import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem; +import net.Indyuce.mmoitems.api.item.mmoitem.VolatileMMOItem; +import net.Indyuce.mmoitems.api.player.PlayerData; +import net.Indyuce.mmoitems.api.player.inventory.EditableEquippedItem; +import net.Indyuce.mmoitems.api.player.inventory.EquippedPlayerItem; +import net.Indyuce.mmoitems.api.player.inventory.InventoryUpdateHandler; +import net.Indyuce.mmoitems.api.util.message.Message; +import net.Indyuce.mmoitems.stat.data.UpgradeData; +import net.Indyuce.mmoitems.stat.type.NameData; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; + +public class DeathDowngrading { + + /** + * This will go through the following steps: + * + * #1 Evaluate the list of equipped items {@link InventoryUpdateHandler#getEquipped()} to + * find those that can be death-downgraded. + * + * + * #2 Roll for death downgrade chances, downgrading the items + * + * @param player Player whose inventory is to be death-downgraded. + */ + public static void playerDeathDowngrade(@NotNull Player player) { + + // Get Player + PlayerData data = PlayerData.get(player); + + // Get total downgrade chance, anything less than zero is invalid + double deathChance = data.getStats().getStat(ItemStats.DOWNGRADE_ON_DEATH_CHANCE); + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Current chance:\u00a7b " + deathChance); + if (deathChance <= 0) { return; } + + // Make sure the equipped items list is up to date and retrieve it + data.updateInventory(); + List items = data.getInventory().getEquipped(); + ArrayList equipped = new ArrayList<>(); + + // Equipped Player Items yeah... + for (EquippedPlayerItem playerItem : items) { + + // Cannot downgrade? skip + if (!canDeathDowngrade(playerItem)) { continue; } + + // Okay explore stat + equipped.add((EditableEquippedItem) playerItem.getEquipped()); + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Yes. \u00a7aAccepted"); + } + + // Nothing to perform operations? Snooze + if (equipped.size() == 0) { + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 No items to downgrade. "); + return; } + + // Create random + Random random = new Random(); + + // Degrade those items! + while (deathChance >= 100 && equipped.size() > 0) { + + // Decrease + deathChance -= 100; + + // Downgrade random item + int deathChosen = random.nextInt(equipped.size()); + + /* + * The item was chosen, we must downgrade it by one level. + */ + EditableEquippedItem equip = equipped.get(deathChosen); + + // Downgrade and remove from list + equip.setItem(downgrade(new LiveMMOItem(equip.getItem()), player)); + equipped.remove(deathChosen); + + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Autodegrading\u00a7a " + mmo.getData(ItemStats.NAME)); + } + + // If there is chance, and there is size, and there is chance success + if (deathChance > 0 && equipped.size() > 0 && random.nextInt(100) < deathChance) { + + // Downgrade random item + int d = random.nextInt(equipped.size()); + + /* + * The item was chosen, we must downgrade it by one level. + */ + EditableEquippedItem equip = equipped.get(d); + + // Downgrade and remove from list + equip.setItem(downgrade(new LiveMMOItem(equip.getItem()), player)); + equipped.remove(d); + + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Chancedegrade\u00a7a " + mmo.getData(ItemStats.NAME)); + } + } + + /** + * @param allItems Absolutely all the items equipped by the player. This method will only affect + * those that (A) can be downgraded, and (B) RNG roll to be downgraded. + * + * @param player Player whose items are being downgraded. + * + * @param deathChance Chance of downgrading one item of the list. Overrollable. + * + * @return The same list of items... but some of them possibly downgraded. + */ + @NotNull ArrayList downgradeItems(@NotNull List allItems, @NotNull Player player, double deathChance) { + + // List of result and downgrade + ArrayList result = new ArrayList<>(); + ArrayList downgrade = new ArrayList<>(); + + // Choose + for (ItemStack item : allItems) { + + // ?? Not that + if (SilentNumbers.isAir(item)) { continue; } + + // Downgrade yay or nay + if (canDeathDowngrade(item)) { + + // On to downgrading + downgrade.add(item); + + } else { + + // On to result + result.add(item); + } + } + + // Create random + Random random = new Random(); + + // Degrade those items! + while (deathChance >= 100 && downgrade.size() > 0) { + + // Decrease + deathChance -= 100; + + // Downgrade random item + int deathChosen = random.nextInt(downgrade.size()); + + /* + * The item was chosen, we must downgrade it by one level. + */ + ItemStack equip = downgrade.get(deathChosen); + + // Downgrade and remove from list + result.add(downgrade(new LiveMMOItem(equip), player)); + + // Remove this one item + downgrade.remove(deathChosen); + } + + // If there is chance, and there is size, and there is chance success + if (deathChance > 0 && downgrade.size() > 0 && random.nextInt(100) < deathChance) { + + // Downgrade random item + int deathChosen = random.nextInt(downgrade.size()); + + /* + * The item was chosen, we must downgrade it by one level. + */ + ItemStack equip = downgrade.get(deathChosen); + + // Downgrade and remove from list + result.add(downgrade(new LiveMMOItem(equip), player)); + + // Remove this one item + downgrade.remove(deathChosen); + } + + // Those that survived are rejoined + result.addAll(downgrade); + + // That's it + return result; + } + + /** + * @param player For some reason I myself dont understand, {@link DurabilityItem} wants + * a non-null player to be able to perform durability operations. + * + * @param item Item to downgrade, make sure to have checked + * {@link #canDeathDowngrade(ItemStack)} before! + * + * @return This item but downgraded if it was possible. + */ + @NotNull public static ItemStack downgrade(@NotNull ItemStack item, @NotNull Player player) { + + // No Item Meta I sleep + if (SilentNumbers.isAir(item) || !item.getType().isItem()) { return item; } + + // Must be a MMOItem + NBTItem asNBT = NBTItem.get(item); + if (!asNBT.hasType()) { return item; } + + // Delegate to MMOItem Method + return downgrade(new LiveMMOItem(asNBT), player); + } + + /** + * @param player For some reason I myself dont understand, {@link DurabilityItem} wants + * a non-null player to be able to perform durability operations. + * + * @param mmo Item to downgrade, make sure to have checked + * {@link #canDeathDowngrade(MMOItem)} before! + * + * @return This item but downgraded if it was possible. + */ + @NotNull public static ItemStack downgrade(@NotNull LiveMMOItem mmo, @NotNull Player player) { + + mmo.getUpgradeTemplate().upgradeTo(mmo, mmo.getUpgradeLevel() - 1); + + // Build NBT + ItemStack bakedItem = mmo.newBuilder().build(); + + // Set durability to zero (full repair) + DurabilityItem dur = new DurabilityItem(player, mmo.newBuilder().buildNBT()); + + // Perform durability operations + if (dur.getDurability() != dur.getMaxDurability()) { + dur.addDurability(dur.getMaxDurability()); + bakedItem.setItemMeta(dur.toItem().getItemMeta());} + + // Send downgrading message + Message.DEATH_DOWNGRADING.format(ChatColor.RED, "#item#", (mmo.getData(ItemStats.NAME) instanceof NameData) ? mmo.getData(ItemStats.NAME).toString() : SilentNumbers.getItemName(bakedItem, false)) + .send(player); + + // Uuuuh + return bakedItem; + } + + /** + * @param player Player to check their death downgrade chance + * + * @return The death downgrade chance of the player + */ + public double getDeathDowngradeChance(@NotNull Player player) { + + // Get Player + PlayerData data = PlayerData.get(player); + + // Get total downgrade chance, anything less than zero is invalid + return data.getStats().getStat(ItemStats.DOWNGRADE_ON_DEATH_CHANCE); + } + + /** + * @param playerItem Equipped Item you want to know if it can be death downgraded + * + * @return If this is an instance of {@link EditableEquippedItem} and meets {@link #canDeathDowngrade(MMOItem)} + */ + @Contract("null->false") + public static boolean canDeathDowngrade(@Nullable EquippedPlayerItem playerItem) { + + // Null + if (playerItem == null) { return false; } + //DET//playerItem.getItem().hasData(ItemStats.NAME); + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Item:\u00a7b " + playerItem.getItem().getData(ItemStats.NAME)); + + // Cannot perform operations of items that are uneditable + if (!(playerItem.getEquipped() instanceof EditableEquippedItem)) { + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Not equippable. \u00a7cCancel"); + return false; } + + // Delegate to MMOItem Method + return canDeathDowngrade(playerItem.getItem()); + } + + /** + * @param playerItem Item you want to know if it can be death downgraded + * + * @return If this item is an MMOItem and meets {@link #canDeathDowngrade(MMOItem)} + */ + @Contract("null->false") + public static boolean canDeathDowngrade(@Nullable ItemStack playerItem) { + + // Null + if (SilentNumbers.isAir(playerItem) || !playerItem.getType().isItem()) { return false; } + //DET//playerItem.getItem().hasData(ItemStats.NAME); + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Item:\u00a7b " + playerItem.getItem().getData(ItemStats.NAME)); + + // Get NBT + NBTItem asNBT = NBTItem.get(playerItem); + if (!asNBT.hasType()) { return false; } + + // Delegate to MMOItem Method + return canDeathDowngrade(new VolatileMMOItem(asNBT)); + } + + /** + * @param playerItem MMOItem you want to know if it can be death downgraded + * + * @return If this item has {@link ItemStats#DOWNGRADE_ON_DEATH} enabled, and + * has an upgrade template, and hasnt reached its minimum upgrades. + */ + @Contract("null->false") + public static boolean canDeathDowngrade(@Nullable MMOItem playerItem) { + if (playerItem == null) { return false; } + + // Not downgradeable on death? Snooze + if (!playerItem.hasData(ItemStats.DOWNGRADE_ON_DEATH)) { + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Not Downgradeable. \u00a7cCancel"); + return false; } + + // No upgrade template no snooze + if(!playerItem.hasData(ItemStats.UPGRADE)) { + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Not Upgradeable. \u00a7cCancel"); + return false; } + if (!playerItem.hasUpgradeTemplate()) { + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Null Template. \u00a7cCancel"); + return false; } + + // If it can be downgraded by one level... + UpgradeData upgradeData = (UpgradeData) playerItem.getData(ItemStats.UPGRADE); + //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Too downgraded? \u00a7c" + (upgradeData.getLevel() <= upgradeData.getMin())); + + return upgradeData.getLevel() > upgradeData.getMin(); + } +} diff --git a/src/main/java/net/Indyuce/mmoitems/api/util/MMOItemReforger.java b/src/main/java/net/Indyuce/mmoitems/api/util/MMOItemReforger.java index ad69b54c..40f66195 100644 --- a/src/main/java/net/Indyuce/mmoitems/api/util/MMOItemReforger.java +++ b/src/main/java/net/Indyuce/mmoitems/api/util/MMOItemReforger.java @@ -412,11 +412,13 @@ public class MMOItemReforger { : iLevel; + + // Identify tier. ItemTier tier = // Does the item have a tier, and it should keep it? - (MMOItemReforger.keepTiersWhenReroll && getOldMMOItem().hasData(ItemStats.TIER)) ? + (options.shouldKeepTier() && getOldMMOItem().hasData(ItemStats.TIER)) ? // The tier will be the current tier MMOItems.plugin.getTiers().get(getOldMMOItem().getData(ItemStats.TIER).toString()) @@ -478,11 +480,13 @@ public class MMOItemReforger { public static int autoSoulbindLevel = 1; public static int defaultItemLevel = -32767; public static boolean keepTiersWhenReroll = true; + public static boolean gemstonesRevIDWhenUnsocket = false; public static void reload() { autoSoulbindLevel = MMOItems.plugin.getConfig().getInt("soulbound.auto-bind.level", 1); defaultItemLevel = MMOItems.plugin.getConfig().getInt("item-revision.default-item-level", -32767); keepTiersWhenReroll = MMOItems.plugin.getConfig().getBoolean("item-revision.keep-tiers"); + gemstonesRevIDWhenUnsocket = MMOItems.plugin.getConfig().getBoolean("item-revision.regenerate-gems-when-unsocketed", false); } //endregion diff --git a/src/main/java/net/Indyuce/mmoitems/listener/PlayerListener.java b/src/main/java/net/Indyuce/mmoitems/listener/PlayerListener.java index b1acdd0b..9dfdbb76 100644 --- a/src/main/java/net/Indyuce/mmoitems/listener/PlayerListener.java +++ b/src/main/java/net/Indyuce/mmoitems/listener/PlayerListener.java @@ -21,6 +21,8 @@ import net.Indyuce.mmoitems.api.item.mmoitem.LiveMMOItem; import net.Indyuce.mmoitems.api.player.PlayerData; import net.Indyuce.mmoitems.api.player.inventory.EditableEquippedItem; import net.Indyuce.mmoitems.api.player.inventory.EquippedPlayerItem; +import net.Indyuce.mmoitems.api.player.inventory.InventoryUpdateHandler; +import net.Indyuce.mmoitems.api.util.DeathDowngrading; import net.Indyuce.mmoitems.api.util.message.Message; import net.Indyuce.mmoitems.skill.RegisteredSkill; import net.Indyuce.mmoitems.stat.data.AbilityData; @@ -39,6 +41,8 @@ import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.event.player.*; import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.NotNull; import java.util.*; @@ -57,8 +61,9 @@ public class PlayerListener implements Listener { /** * If the player dies, its time to roll the death-downgrade stat! */ + @SuppressWarnings("InstanceofIncompatibleInterface") @EventHandler(priority = EventPriority.MONITOR) - public void onDeathForUpgradeLoss(PlayerDeathEvent event) { + public void onDeathForUpgradeLoss(@NotNull PlayerDeathEvent event) { // No if (event instanceof Cancellable) { if (((Cancellable) event).isCancelled()) { return; } } @@ -66,126 +71,8 @@ public class PlayerListener implements Listener { // Supports NPCs if (!PlayerData.has(event.getEntity())) return; - // Get Player - PlayerData data = PlayerData.get(event.getEntity()); - - // Get total downgrade chance, anything less than zero is invalid - double deathChance = data.getStats().getStat(ItemStats.DOWNGRADE_ON_DEATH_CHANCE); - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Current chance:\u00a7b " + deathChance); - if (deathChance <= 0) { return; } - - List items = data.getInventory().getEquipped(); - ArrayList equipped = new ArrayList<>(); - - // Equipped Player Items yeah... - for (EquippedPlayerItem playerItem : items) { - - // Null - if (playerItem == null) { continue; } - //DET//playerItem.getItem().hasData(ItemStats.NAME); - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Item:\u00a7b " + playerItem.getItem().getData(ItemStats.NAME)); - - // Cannot perform operations of items that are uneditable - if (!(playerItem.getEquipped() instanceof EditableEquippedItem)) { - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Not equippable. \u00a7cCancel"); - continue; } - - // Not downgradeable on death? Snooze - if (!playerItem.getItem().hasData(ItemStats.DOWNGRADE_ON_DEATH)) { - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Not Downgradeable. \u00a7cCancel"); - continue; } - - // No upgrade template no snooze - if(!playerItem.getItem().hasData(ItemStats.UPGRADE)) { - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Not Upgradeable. \u00a7cCancel"); - continue; } - if (!playerItem.getItem().hasUpgradeTemplate()) { - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Null Template. \u00a7cCancel"); - continue; } - - // If it can be downgraded by one level... - UpgradeData upgradeData = (UpgradeData) playerItem.getItem().getData(ItemStats.UPGRADE); - if (upgradeData.getLevel() <= upgradeData.getMin()) { - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Too downgraded. \u00a7cCancel"); - continue; } - - // Okay explore stat - equipped.add((EditableEquippedItem) playerItem.getEquipped()); - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Yes. \u00a7aAccepted"); - } - - // Nothing to perform operations? Snooze - if (equipped.size() == 0) { - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 No items to downgrade. "); - return; } - Random random = new Random(); - - // Degrade those items! - while (deathChance >= 100 && equipped.size() > 0) { - - // Decrease - deathChance -= 100; - - // Downgrade random item - int d = random.nextInt(equipped.size()); - - /* - * The item was chosen, we must downgrade it by one level. - */ - EditableEquippedItem equip = equipped.get(d); - LiveMMOItem mmo = new LiveMMOItem(equip.getItem()); - mmo.getUpgradeTemplate().upgradeTo(mmo, mmo.getUpgradeLevel() - 1); - - // Build NBT - ItemStack bakedItem = mmo.newBuilder().build(); - - // Set durability to zero (full repair) - DurabilityItem dur = new DurabilityItem(event.getEntity(), mmo.newBuilder().buildNBT()); - - if (dur.getDurability() != dur.getMaxDurability()) { - dur.addDurability(dur.getMaxDurability()); - bakedItem.setItemMeta(dur.toItem().getItemMeta());} - - // AH - equip.setItem(bakedItem); - equipped.remove(d); - - Message.DEATH_DOWNGRADING.format(ChatColor.RED, "#item#", SilentNumbers.getItemName(equip.getItem().getItem(), false)) - .send(event.getEntity()); - - //DET//MMOItems.log("\u00a78DETH \u00a7cDG\u00a77 Autodegrading\u00a7a " + mmo.getData(ItemStats.NAME)); - } - - // If there is chance, and there is size, and there is chance success - if (deathChance > 0 && equipped.size() > 0 && random.nextInt(100) < deathChance) { - - // Downgrade random item - int d = random.nextInt(equipped.size()); - - /* - * The item was chosen, we must downgrade it by one level. - */ - EditableEquippedItem equip = equipped.get(d); - LiveMMOItem mmo = new LiveMMOItem(equip.getItem()); - mmo.getUpgradeTemplate().upgradeTo(mmo, mmo.getUpgradeLevel() - 1); - - // Build NBT - ItemStack bakedItem = mmo.newBuilder().build(); - - // Set durability to zero (full repair) - DurabilityItem dur = new DurabilityItem(event.getEntity(), mmo.newBuilder().buildNBT()); - - if (dur.getDurability() != dur.getMaxDurability()) { - dur.addDurability(dur.getMaxDurability()); - bakedItem.setItemMeta(dur.toItem().getItemMeta());} - - // AH - equip.setItem(bakedItem); - equipped.remove(d); - - Message.DEATH_DOWNGRADING.format(ChatColor.RED, "#item#", SilentNumbers.getItemName(equip.getItem().getItem(), false)) - .send(event.getEntity()); - } + // See description of DelayedDeathDowngrade child class for full explanation + (new DelayedDeathDowngrade(event)).runTaskLater(MMOItems.plugin, 3L); } /** @@ -339,4 +226,32 @@ public class PlayerListener implements Listener { // Call event for compatibility Bukkit.getPluginManager().callEvent(new AbilityUseEvent(playerData, abilityData, target)); } + + /** + * Some plugins like to interfere with dropping items when the + * player dies, or whatever of that sort. + * + * MMOItems would hate to dupe items because of this, as such, we wait + * 3 ticks for those plugins to reasonably complete their operations and + * then downgrade the items the player still has equipped. + * + * If a plugin removes items in this time, they will be completely excluded + * and no dupes will be caused, and if a plugin adds items, they will be + * included and downgraded. I think that's reasonable behaviour. + * + * @author Gunging + */ + private static class DelayedDeathDowngrade extends BukkitRunnable { + + @NotNull final PlayerDeathEvent event; + + DelayedDeathDowngrade(@NotNull PlayerDeathEvent event) {this.event = event;} + + @Override + public void run() { + + // Downgrade player's inventory + DeathDowngrading.playerDeathDowngrade(event.getEntity()); + } + } } diff --git a/src/main/java/net/Indyuce/mmoitems/listener/reforging/RFGKeepDurability.java b/src/main/java/net/Indyuce/mmoitems/listener/reforging/RFGKeepDurability.java index 4a264b8c..2b42b26a 100644 --- a/src/main/java/net/Indyuce/mmoitems/listener/reforging/RFGKeepDurability.java +++ b/src/main/java/net/Indyuce/mmoitems/listener/reforging/RFGKeepDurability.java @@ -15,7 +15,6 @@ public class RFGKeepDurability implements Listener { @EventHandler public void onReforge(MMOItemReforgeEvent event) { - //RFG// MMOItems.log("§8Reforge §4EFG§7 Keeping Durability"); // What was its durability? Transfer it event.getNewMMOItem().setDamage(event.getOldMMOItem().getDamage()); diff --git a/src/main/java/net/Indyuce/mmoitems/listener/reforging/RFGKeepGems.java b/src/main/java/net/Indyuce/mmoitems/listener/reforging/RFGKeepGems.java index 06ee0cd7..dd3e960b 100644 --- a/src/main/java/net/Indyuce/mmoitems/listener/reforging/RFGKeepGems.java +++ b/src/main/java/net/Indyuce/mmoitems/listener/reforging/RFGKeepGems.java @@ -5,6 +5,7 @@ import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.api.ReforgeOptions; import net.Indyuce.mmoitems.api.event.MMOItemReforgeEvent; import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem; +import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate; import net.Indyuce.mmoitems.stat.data.GemSocketsData; import net.Indyuce.mmoitems.stat.data.GemstoneData; import net.Indyuce.mmoitems.stat.data.type.Mergeable; @@ -148,8 +149,10 @@ public class RFGKeepGems implements Listener { // Get MMOItem MMOItem restoredGem = event.getOldMMOItem().extractGemstone(lost); + if (restoredGem == null) { continue; } // Success? Add that gem there - if (restoredGem != null) { event.getReforger().addReforgingOutput(restoredGem.newBuilder().build()); } } } + event.getReforger().addReforgingOutput(restoredGem.newBuilder().build()); + } } } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 648971a7..5d3085f2 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -256,10 +256,13 @@ item-revision: # ´reroll-when-updated´ is set to true. default-item-level: -1 - # Whether or not the current tier of the item should - # be carried over. - # Please note, that this value has no effect if - # ´reroll-when-updated´ is set to true. + # Unsocketing gems picks them up with the stats they had + # when first put into the item, disable this option to + # force them to be regenerated. + regenerate-gems-when-unsocketed: false + + # Legacy option to carry tiers over, will take precedence + # If not specified next to the other keep- flags. keep-tiers: true # If an item is updated, and the new version does not @@ -303,9 +306,12 @@ item-revision: # Modifiers of items ~ Sharp, Light, Heavy, Arcane modifications: true - # Third party plugin compatibility + # Skins applied by MMOItems skins: true + # Tier of the item + tier: true + # Third party plugin compatibility advanced-enchantments: true @@ -317,6 +323,7 @@ item-revision: upgrades: false lore: false exsh: false + tier: true skins: false reroll: true modifications: false