Made Item Updating work **much** better, and much more transparent on what it is actually doing.

+ Upgrade level of items may be kept, with its bonuses
+ Gem Stones are now perfectly kept, with all their stats
+ Enchantments may be kept (more details in-game)
+ Soulbound (was already working) may be kept.
+ Item Name may be kept (already kinda working)
+ Lore may be kept (more detail in-game)

Add to your config:
```yml
item-revision:
  keep-data:
    display-name: true
    lore: true
    enchantments: true
    upgrades: true
    gemstones: true
    soulbound: true
 ```

 *Requires MMOLib update*
This commit is contained in:
Gunging 2021-02-24 23:23:57 -06:00
parent 12f9e30ea7
commit ae5d45b04a
23 changed files with 1150 additions and 273 deletions

View File

@ -96,7 +96,7 @@
<dependency>
<groupId>io.lumine</groupId>
<artifactId>MythicLib</artifactId>
<version>1.0.10</version>
<version>1.0.12-SNAPSHOT</version>
<scope>provided</scope>
</dependency>

View File

@ -1,10 +1,12 @@
package net.Indyuce.mmoitems;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.lib.version.SpigotPlugin;
import io.lumine.mythic.utils.plugin.LuminePlugin;
import net.Indyuce.mmoitems.api.*;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.util.message.FriendlyFeedbackPalette_MMOItems;
import net.Indyuce.mmoitems.command.MMOItemsCommandTreeRoot;
import net.Indyuce.mmoitems.command.UpdateItemCommand;
import net.Indyuce.mmoitems.command.completion.UpdateItemCompletion;
@ -525,7 +527,7 @@ public class MMOItems extends LuminePlugin {
setRPG(plugin.load());
// Mention it
Log("Using \u00a76" + plugin.getName() + "\u00a77 as RPG Player provider.");
Print(Level.INFO, "Using $s{0}$b as RPGPlayer provider", plugin.getName());
return;
}
}
@ -601,6 +603,13 @@ public class MMOItems extends LuminePlugin {
plugin.getServer().getConsoleSender().sendMessage("\u00a78[" + ChatColor.YELLOW + "MMOItems\u00a78] \u00a77" + message);
}
/**
* Easily log something using the FriendlyFeedbackProvider, nice!
*/
public static void Print(@NotNull Level level, @NotNull String message, String... replaces) {
MMOItems.plugin.getLogger().log(level, FriendlyFeedbackProvider.QuickForConsole(FriendlyFeedbackPalette_MMOItems.get(), message, replaces));
}
/**
* @return The server's console sender.
*/

View File

@ -3,32 +3,66 @@ package net.Indyuce.mmoitems.api;
import org.bukkit.configuration.ConfigurationSection;
public class ReforgeOptions {
private final boolean keepName, keepLore, keepEnchantments, keepModifications, keepSoulbind;
private final boolean
keepName,
keepLore,
keepEnchantments,
keepUpgrades,
keepGemStones,
keepSoulbind;
public ReforgeOptions(ConfigurationSection config) {
this.keepName = config.getBoolean("display-name");
this.keepLore = config.getBoolean("lore");
this.keepEnchantments = config.getBoolean("enchantments");
this.keepModifications = config.getBoolean("modifications");
this.keepUpgrades = config.getBoolean("upgrades");
this.keepGemStones = config.getBoolean("gemstones");
this.keepSoulbind = config.getBoolean("soulbound");
}
/**
* Keeps the display name of the item.
*/
public boolean shouldKeepName() {
return keepName;
}
/**
* Keeps all lore lines that begin with {@link org.bukkit.ChatColor#GRAY}
*/
public boolean shouldKeepLore() {
return keepLore;
}
/**
* 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 shouldKeepModifications() {
return keepModifications;
/**
* Retains the upgrade level of the item.
*/
public boolean shouldKeepUpgrades() {
return keepUpgrades;
}
/**
* Retains all gem stones if there are any, removing
* one gem socket for every gemstone kept.
* <p></p>
* Gemstones remember at what upgrade level they were inserted.
*/
public boolean shouldKeepGemStones() {
return keepGemStones;
}
/**
* Retains the soulbind if it has any.
*/
public boolean shouldKeepSoulbind() {
return keepSoulbind;
}

View File

@ -10,6 +10,7 @@ import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.api.util.message.FriendlyFeedbackPalette_MMOItems;
import net.Indyuce.mmoitems.stat.Enchants;
import net.Indyuce.mmoitems.stat.data.UpgradeData;
import net.Indyuce.mmoitems.stat.data.type.Mergeable;
import net.Indyuce.mmoitems.stat.data.type.StatData;
@ -112,6 +113,9 @@ public class UpgradeTemplate {
*/
public void upgradeTo(@NotNull MMOItem mmoitem, int level) {
// Make sure to not overwrite player's enchantments when upgrading.
Enchants.separateEnchantments(mmoitem);
// Set the items level
UpgradeData dat;
if (mmoitem.hasData(ItemStats.UPGRADE)) { dat = (UpgradeData) mmoitem.getData(ItemStats.UPGRADE); } else { dat = new UpgradeData(null, null, false, false, 0, 100); }

View File

@ -20,6 +20,8 @@ import net.Indyuce.mmoitems.api.util.message.Message;
import net.Indyuce.mmoitems.comp.flags.FlagPlugin.CustomFlag;
import io.lumine.mythic.lib.api.item.NBTItem;
import java.util.logging.Level;
public class Weapon extends UseItem {
public Weapon(Player player, NBTItem item) {
this(PlayerData.get(player), item);
@ -38,7 +40,7 @@ public class Weapon extends UseItem {
boolean asCanUse = playerData.getRPG().canUse(getNBTItem(), true);
boolean asFlagAllowed = true; FlagPlugin fg = MMOItems.plugin.getFlags(); if (fg != null) { asFlagAllowed = fg.isFlagAllowed(getPlayer(), CustomFlag.MI_WEAPONS); }
else { MMOItems.Log("Flag Plugin Not Found");}
else { MMOItems.Print(Level.WARNING, "$fFlag plugin not found"); }
return asCanUse || asFlagAllowed;
}

View File

@ -105,7 +105,7 @@ public class ItemStackBuilder {
public NBTItem buildNBT() {
// Clone as to not conflict in any way
MMOItem builtMMOItem = mmoitem.clone();
//GEM//MMOItems. Log("\u00a7e+ \u00a77Building \u00a7c" + mmoitem.getType().getName() + " " + mmoitem.getId() + "\u00a77 (Size \u00a7e" + mmoitem.mergeableStatHistory.size() + "\u00a77 Historic)");
//GEM//MMOItems.Log("\u00a7e+ \u00a77Building \u00a7c" + mmoitem.getType().getName() + " " + mmoitem.getId() + "\u00a77 (Size \u00a7e" + mmoitem.getStatHistories().size() + "\u00a77 Historic)");
// For every stat within this item
for (ItemStat stat : builtMMOItem.getStats())
@ -113,7 +113,7 @@ public class ItemStackBuilder {
// Attempt to add
try {
//GEM//MMOItems. Log("\u00a7e -+- \u00a77Applying \u00a76" + stat.getNBTPath());
//GEM//MMOItems.Log("\u00a7e -+- \u00a77Applying \u00a76" + stat.getNBTPath());
// Make necessary lore changes
stat.whenApplied(this, builtMMOItem.getData(stat));
@ -124,7 +124,7 @@ public class ItemStackBuilder {
// Found it?
if (s != null) {
//GEM//MMOItems. Log("\u00a7a -+- \u00a77Found History");
//GEM//MMOItems.Log("\u00a7a -+- \u00a77Found History");
// Add to NBT
addItemTag(new ItemTag(histroy_keyword + stat.getId(), s.toNBTString()));

View File

@ -60,25 +60,25 @@ public class MMOItem implements ItemReference {
* stored as a GemStone in the history, allowing to be removed from the item with that same UUID.
*/
public void mergeData(@NotNull ItemStat stat, @NotNull StatData data, @Nullable UUID associatedGemStone) {
//GEM//MMOItems.Log("Merging stone stat \u00a76" + stat.getNBTPath() + "\u00a77 into \u00a7c" + getType().getName() + " " + getId());
//GEM//MMOItems. Log("Merging stone stat \u00a76" + stat.getNBTPath() + "\u00a77 into \u00a7c" + getType().getName() + " " + getId());
// Do we already have the data?
if (data instanceof Mergeable) {
//GEM//MMOItems.Log("\u00a7a + \u00a77Mergeable");
//GEM//MMOItems. Log("\u00a7a + \u00a77Mergeable");
// Prepare to merge: Gather History (Also initializes the ORIGINAL stats)
StatHistory<StatData> sHistory = StatHistory.From(this, stat);
// As GemStone or as External?
if (associatedGemStone != null) {
//GEM//MMOItems.Log(" \u00a79++\u00a77 As Gemstone \u00a7b" + associatedGemStone.toString());
//GEM//MMOItems. Log(" \u00a79++\u00a77 As Gemstone \u00a7b" + associatedGemStone.toString());
// As GemStone
sHistory.registerGemstoneData(associatedGemStone, data);
// As External
} else {
//GEM//MMOItems.Log(" \u00a7c++\u00a77 As External");
//GEM//MMOItems. Log(" \u00a7c++\u00a77 As External");
// As External, UUIDless modifier
sHistory.registerExternalData(data);
@ -111,9 +111,7 @@ public class MMOItem implements ItemReference {
return stats.get(stat);
}
public boolean hasData(@NotNull ItemStat stat) {
return (stats.get(stat) != null);
}
public boolean hasData(@NotNull ItemStat stat) { return (stats.get(stat) != null); }
/**
* @return Collection of all item stats which have some data on this mmoitem
@ -163,15 +161,14 @@ public class MMOItem implements ItemReference {
* its original stats, and from which gem stone came each stat, in order to allow
* removal of gem stones in the future. This is where that is remembered.
*/
@NotNull public final Map<String, StatHistory<StatData>> mergeableStatHistory = new HashMap<>();
@NotNull final Map<String, StatHistory<StatData>> mergeableStatHistory = new HashMap<>();
/**
* Gets the history associated to this stat, if there is any
* <p></p>
* A stat history is basically the memmory of its original stats, from when it was created, its gem stone stats, those added by which gem, and its upgrade bonuses.
*/
@Nullable
public StatHistory<StatData> getStatHistory(@NotNull ItemStat stat) {
@Nullable public StatHistory<StatData> getStatHistory(@NotNull ItemStat stat) {
try {
// Well that REALLY should work
@ -181,6 +178,11 @@ public class MMOItem implements ItemReference {
return null;
}
}
@NotNull public ArrayList<StatHistory<StatData>> getStatHistories() {
// Those
return new ArrayList<>(mergeableStatHistory.values());
}
/**
* Sets the history associated to this stat.

View File

@ -1,8 +1,13 @@
package net.Indyuce.mmoitems.api.item.mmoitem;
import io.lumine.mythic.lib.api.item.ItemTag;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.item.SupportedNBTTagValues;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.stat.data.type.StatData;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import net.Indyuce.mmoitems.stat.type.StatHistory;
import org.bukkit.ChatColor;
import org.jetbrains.annotations.NotNull;
@ -42,6 +47,15 @@ public class VolatileMMOItem extends ReadMMOItem {
try {
stat.whenLoaded(this);
// Also laod history :think ing:
ItemTag hisTag = ItemTag.getTagAtPath(ItemStackBuilder.histroy_keyword + stat.getId(), getNBT(), SupportedNBTTagValues.STRING);
if (hisTag != null) {
// Aye
StatHistory<StatData> hist = StatHistory.fromNBTString(this, (String) hisTag.getValue());
if (hist != null) { this.setStatHistory(stat, hist); }
}
// Nope
} catch (IllegalArgumentException exception) {

View File

@ -144,7 +144,8 @@ public class MMOItemTemplate extends PostLoadObject implements ItemReference {
* template has the 'tiered' recipe option, a random tier will be picked. If
* the template has the 'level-item' option, a random level will be picked
*
* @param player The player for whom you are generating the itme
* @param player The player for whom you are generating the item. Seems to only
* matter when rolling for the 'item level'
* @return Item builder with random level and tier?
*/
public MMOItemBuilder newBuilder(RPGPlayer player) {

View File

@ -1,158 +0,0 @@
package net.Indyuce.mmoitems.api.util;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.utils.adventure.text.Component;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.ReforgeOptions;
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.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.player.RPGPlayer;
import net.Indyuce.mmoitems.stat.data.DoubleData;
import net.Indyuce.mmoitems.stat.data.SoulboundData;
import net.Indyuce.mmoitems.stat.data.type.Mergeable;
import net.Indyuce.mmoitems.stat.data.type.StatData;
import net.Indyuce.mmoitems.stat.type.GemStoneStat;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import net.Indyuce.mmoitems.stat.type.Upgradable;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ItemModInstance {
private final NBTItem nbtItem;
private final int amount;
private final Map<ItemStat, StatData> itemData = new HashMap<>();
// Not initialized at first for performance reasons
private MMOItem mmoItem;
private Component cachedName;
private List<Component> cachedLore;
private Map<Enchantment, Integer> cachedEnchants;
private StatData cachedSoulbound;
public ItemModInstance(NBTItem nbt) {
this.nbtItem = nbt;
this.amount = nbt.getItem().getAmount();
}
public void applySoulbound(Player p) {
applySoulbound(p, MMOItems.plugin.getConfig().getInt("soulbound.auto-bind.level", 1));
}
public void applySoulbound(Player p, int level) {
loadLiveMMOItem();
mmoItem.setData(ItemStats.SOULBOUND, new SoulboundData(p.getUniqueId(), p.getName(), level));
}
public void reforge(Player p, ReforgeOptions options) {
reforge(p == null ? null : PlayerData.get(p).getRPG(), options);
}
/**
* @param player The player to generate the new item from. If empty, it will
* use the old items level and tier, or default values if
* needed.
*/
public void reforge(RPGPlayer player, ReforgeOptions options) {
loadVolatileMMOItem();
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(mmoItem.getType(), mmoItem.getId());
ItemMeta meta = nbtItem.getItem().getItemMeta();
if (options.shouldKeepName() && meta.hasDisplayName())
cachedName = nbtItem.getDisplayNameComponent();
if (options.shouldKeepLore() && meta.hasLore())
cachedLore = nbtItem.getLoreComponents();
if (options.shouldKeepEnchantments())
cachedEnchants = nbtItem.getItem().getEnchantments();
if (options.shouldKeepModifications())
for (ItemStat stat : mmoItem.getStats()) {
if (stat instanceof Upgradable)
itemData.put(stat, mmoItem.getData(stat));
if (!(stat instanceof GemStoneStat)) {
StatData data = mmoItem.getData(stat);
if (data instanceof Mergeable) {
if (itemData.containsKey(stat))
((Mergeable) itemData.get(stat)).merge(data);
else
itemData.put(stat, data);
}
}
}
if (options.shouldKeepSoulbind() && mmoItem.hasData(ItemStats.SOULBOUND))
cachedSoulbound = mmoItem.getData(ItemStats.SOULBOUND);
if (player == null) {
final int iLevel = MMOItems.plugin.getConfig().getInt("item-revision.default-item-level", -1);
int level = iLevel == -1
? (mmoItem.hasData(ItemStats.ITEM_LEVEL) ? (int) ((DoubleData) mmoItem.getData(ItemStats.ITEM_LEVEL)).getValue() : 0)
: iLevel;
ItemTier tier = (mmoItem.hasData(ItemStats.TIER) && MMOItems.plugin.getConfig().getBoolean("item-revision.keep-tiers"))
? MMOItems.plugin.getTiers().get(mmoItem.getData(ItemStats.TIER).toString())
: null;
mmoItem = template.newBuilder(level, tier).build();
} else
mmoItem = template.newBuilder(player).build();
}
public boolean hasChanges() {
return mmoItem != null;
}
public ItemStack toStack() {
for (Map.Entry<ItemStat, StatData> data : itemData.entrySet())
if (mmoItem.hasData(data.getKey()))
((Mergeable) mmoItem.getData(data.getKey())).merge(data.getValue());
else
mmoItem.setData(data.getKey(), data.getValue());
if (cachedSoulbound != null)
mmoItem.setData(ItemStats.SOULBOUND, cachedSoulbound);
ItemStack stack = mmoItem.newBuilder().build();
stack.setAmount(amount);
ItemMeta meta = stack.getItemMeta();
if (cachedEnchants != null)
stack.addUnsafeEnchantments(cachedEnchants);
stack.setItemMeta(meta);
NBTItem nbtItem = NBTItem.get(stack);
if (cachedName != null)
nbtItem.setDisplayNameComponent(cachedName);
if (cachedLore != null) {
nbtItem.setLoreComponents(cachedLore);
}
return nbtItem.toItem();
}
/*
* Initialize the MMOItem as a LiveMMOItem if it's null or not already a
* LiveMMOItem
*/
private void loadLiveMMOItem() {
if (mmoItem != null && mmoItem instanceof LiveMMOItem)
return;
mmoItem = new LiveMMOItem(nbtItem);
}
/* Initialize the MMOItem as a VolatileMMOItem if it's null */
private void loadVolatileMMOItem() {
if (mmoItem != null)
return;
mmoItem = new VolatileMMOItem(nbtItem);
}
}

View File

@ -0,0 +1,576 @@
package net.Indyuce.mmoitems.api.util;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.utils.adventure.text.Component;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.ReforgeOptions;
import net.Indyuce.mmoitems.api.UpgradeTemplate;
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.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.player.RPGPlayer;
import net.Indyuce.mmoitems.api.util.message.FriendlyFeedbackPalette_MMOItems;
import net.Indyuce.mmoitems.stat.Enchants;
import net.Indyuce.mmoitems.stat.GemSockets;
import net.Indyuce.mmoitems.stat.RevisionID;
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.GemStoneStat;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import net.Indyuce.mmoitems.stat.type.StatHistory;
import net.Indyuce.mmoitems.stat.type.Upgradable;
import org.apache.commons.lang.Validate;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A class to manage modification of items with reference to what they used to be
* (and apparently also used to automatically apply SoulBounds):
*
* <p><code><b>updating</b></code> refers to changing the base stats
* of a MMOItem instance to what the template currently has, usually
* keeping gem stones and upgrade level. This wont reroll RNG stats.</p>
*
* <p><code><b>reforging</b></code> same thing as updating, but rerolling
* the RNG stats - basically transferring the data specified by the
* {@link ReforgeOptions} into a new item of the same Type-ID</p>
*
* @author Gunging, ? (Indyuce is my guess)
*/
public class MMOItemReforger {
// region Item Data
// Raw NBT Item
@NotNull private final NBTItem nbtItem;
// ItemStack size
private final int amount;
// Not initialized at first for performance reasons
private MMOItem mmoItem;
// Data
private final Map<ItemStat, StatData> itemData = new HashMap<>();
private final Map<ItemStat, StatHistory<StatData>> itemDataHistory = new HashMap<>();
//endregion
//region Cached stuff
// Stripped Name
@Nullable String cachedName;
// Grayish Lore
@NotNull ArrayList<String> cachedLore = new ArrayList<>();
// Extraneous Enchantments
@Nullable EnchantListData cachedEnchantments;
// Gem Stones
@Nullable GemSocketsData cachedGemStones;
// Upgrade Level
@Nullable UpgradeData cachedUpgradeLevel;
// Soulbound
private StatData cachedSoulbound;
//endregion
/**
* Prepare to reforge this MMOItem (starts out as NBTItem due to backend reasons).
* @param nbt <b>Make sure {@link NBTItem#hasType()} returns true for this</b>.
*/
public MMOItemReforger(@NotNull NBTItem nbt) {
this.nbtItem = nbt;
this.amount = nbt.getItem().getAmount();
}
/**
* Apply a quick soulbound based on the config value <code>soulbound.auto-bind.level</code> (default = 1)
*/
public void applySoulbound(@NotNull Player p) {
applySoulbound(p, MMOItems.plugin.getConfig().getInt("soulbound.auto-bind.level", 1));
}
/**
* Apply a quick soulbound of this level
*/
public void applySoulbound(@NotNull Player p, int level) {
// Initialize as Live MMOItem
loadLiveMMOItem();
// Override Soulbound Data
mmoItem.setData(ItemStats.SOULBOUND, new SoulboundData(p.getUniqueId(), p.getName(), level));
}
/**
* This method updates the base stats of this item to what
* the template currently has, only rerolling RNGs where the
* probability of getting the same roll is less than 5%
*
* @param options Which data to 'keep'
* @param p There is an option where an item's base stats will be better
* if the player who first generates the item is a higher level.
* This player will be used to fulfill that operation.
* <p></p>
* If <code>null</code>, the default modifiers
* specified in the config is used.
*/
public void update(@Nullable Player p, @NotNull ReforgeOptions options) {
update(p == null ? null : PlayerData.get(p).getRPG(), options);
}
/**
* This method updates the base stats of this item to what
* the template currently has, only rerolling RNGs where the
* probability of getting the same roll is less than 5%
* <p></p>
* Used when updating items with the updater.
*
* @param player There is an option where an item's base stats will be better
* if the player who first generates the item is a higher level.
* This player will be used to fulfill that operation.
* <p></p>
* If empty, it will use the old items level and tier,
* or default values if needed.
*
* @see RevisionID
*/
public void update(@Nullable RPGPlayer player, @NotNull ReforgeOptions options) {
/*
* todo Has to store every stat into itemData, then check each stat of
* the new item to see if they are RNG rolls in order to:
*
* 1: If they arent, the probability of getting the old number
* is straight up ZERO and must be replaced by the updated value.
*
* 2: If they are RNG rolls, if the probability of getting the
* current roll is at least 5%, the old roll is kept.
*
* 3: If the stat is gone completely, its again a ZERO chance
* so it is removed (the updated value of 0 prevailing).
*/
reforge(player, options);
}
/**
* Generates a new item of the same Type-ID and transfers the data
* from the old one following the options.
*
* @param options Which data to 'keep'
* @param p There is an option where an item's base stats will be better
* if the player who first generates the item is a higher level.
* This player will be used to fulfill that operation.
* <p></p>
* If <code>null</code>, the default modifiers
* specified in the config is used.
*/
public void reforge(@Nullable Player p, @NotNull ReforgeOptions options) {
reforge(p == null ? null : PlayerData.get(p).getRPG(), options);
}
/**
* Generates a new item of the same Type-ID and transfers the data
* from the old one following the options.
*
* @param player There is an option where an item's base stats will be better
* if the player who first generates the item is a higher level.
* This player will be used to fulfill that operation.
* <p></p>
* If empty, it will use the old items level and tier,
* or default values if needed.
*/
public void reforge(@Nullable RPGPlayer player, @NotNull ReforgeOptions options) {
// Initialize as Volatile, find source template. GemStones require a Live MMOItem though (to correctly load all Stat Histories and sh)
if (!options.shouldKeepGemStones()) { loadVolatileMMOItem(); } else { loadLiveMMOItem(); }
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(mmoItem.getType(), mmoItem.getId());
ItemMeta meta = nbtItem.getItem().getItemMeta();
Validate.isTrue(meta != null, FriendlyFeedbackProvider.QuickForConsole(FriendlyFeedbackPalette_MMOItems.get(), "Invalid item meta prevented $f{0}$b from updating.", template.getType().toString() + " " + template.getId()));
// Keep name
if (options.shouldKeepName()) {
//UPDT//MMOItems.Log(" \u00a73> \u00a77Keeping Name");
// Does it have a name?
if (mmoItem.hasData(ItemStats.NAME)) {
// Cache it
cachedName = mmoItem.getData(ItemStats.NAME).toString();
// No name defined, use display name I guess (pretty unusual btw)
} else if (meta.hasDisplayName()) {
cachedName = meta.getDisplayName();
}
//UPDT//MMOItems.Log(" \u00a73 + \u00a77" + cachedName);
}
// Keep specific lore components
if (options.shouldKeepLore() && mmoItem.hasData(ItemStats.LORE)) {
//UPDT//MMOItems.Log(" \u00a7d> \u00a77Keeping Lore");
// Examine every element
for (String str : ((StringListData) mmoItem.getData(ItemStats.LORE)).getList()) {
// Does it start with the promised...?
if (str.startsWith("\u00a77")) { cachedLore.add(str); }
}
//UPDT//for (String lr : cachedLore) { //UPDT//MMOItems.Log(" \u00a7d + \u00a77" + lr); }
}
// Choose enchantments to keep
if (options.shouldKeepEnchantments()) {
//UPDT//MMOItems.Log(" \u00a7b> \u00a77Keeping Enchantments");
// Enchant list data
cachedEnchantments = new EnchantListData();
// Does it have MMOItems enchantment data?
if (mmoItem.hasData(ItemStats.ENCHANTS)) {
//UPDT//MMOItems.Log(" \u00a7b* \u00a77Found Data");
// Nope
} else {
//UPDT//MMOItems.Log(" \u00a7b* \u00a77No Data");
mmoItem.setData(ItemStats.ENCHANTS, new EnchantListData());
}
// Make sure they are consolidated
Enchants.separateEnchantments(mmoItem);
// Gather
StatHistory<StatData> hist = StatHistory.From(mmoItem, ItemStats.ENCHANTS);
// Reap
for (StatData pEnchants : hist.getExternalData()) {
// It really should be but whatever
if (pEnchants instanceof EnchantListData) {
// For every stat
for (Enchantment e : ((EnchantListData) pEnchants).getEnchants()) {
// Get Base/Current
int established = cachedEnchantments.getLevel(e);
// Add
int calculated = established + ((EnchantListData) pEnchants).getLevel(e);
// Put
cachedEnchantments.addEnchant(e, calculated);
//UPDT//MMOItems.Log(" \u00a7b + \u00a77" + e.getName() + " " + calculated);
}
}
}
// The cache now stores the full extent of extraneous data. Separate from thy history. (As to not include it in this in the cached data later)
hist.getExternalData().clear();
}
// Acquire old upgrade level
if (options.shouldKeepUpgrades() && mmoItem.hasData(ItemStats.UPGRADE)) {
//UPDT//MMOItems.Log(" \u00a7e> \u00a77Keeping Upgrade Data");
// Get Level
cachedUpgradeLevel = ((UpgradeData) mmoItem.getData(ItemStats.UPGRADE));
}
// Gather Gemstones
if (options.shouldKeepGemStones() && mmoItem.hasData(ItemStats.GEM_SOCKETS)) {
//UPDT//MMOItems.Log(" \u00a7a> \u00a77Keeping Gem Sockets");
// Cache that gemstone data
cachedGemStones = (GemSocketsData) mmoItem.getData(ItemStats.GEM_SOCKETS);
// Store all the history of stat proceedings.
for (StatHistory<StatData> hist : mmoItem.getStatHistories()) {
//UPDT//MMOItems.Log(" \u00a7a + \u00a77History of \u00a7f" + hist.getItemStat().getNBTPath());
// Get and set
itemDataHistory.put(hist.getItemStat(), hist);
}
}
// Soulbound transfer
if (options.shouldKeepSoulbind() && mmoItem.hasData(ItemStats.SOULBOUND)) {
//UPDT//MMOItems.Log(" \u00a7c> \u00a77Keeping Soulbind");
// Find data
cachedSoulbound = mmoItem.getData(ItemStats.SOULBOUND);
}
if (player == null) {
// Get default Item Level
final int iLevel = MMOItems.plugin.getConfig().getInt("item-revision.default-item-level", -32767);
// What level with the regenerated item will be hmmmm.....
int level =
// No default level specified?
(iLevel == -32767) ?
// Does the item have level?
(mmoItem.hasData(ItemStats.ITEM_LEVEL) ? (int) ((DoubleData) mmoItem.getData(ItemStats.ITEM_LEVEL)).getValue() : 0 )
// Default level was specified, use that.
: iLevel;
// Identify tier.
ItemTier tier =
// Does the item have a tier, and it should keep it?
(mmoItem.hasData(ItemStats.TIER) && MMOItems.plugin.getConfig().getBoolean("item-revision.keep-tiers")) ?
// The tier will be the current tier
MMOItems.plugin.getTiers().get(mmoItem.getData(ItemStats.TIER).toString())
// The item either has no tier, or shouldn't keep it. Null
: null;
// Build it again (Reroll RNG)
mmoItem = template.newBuilder(level, tier).build();
// No player provided, use defaults.
} else {
// Build it again (Reroll RNG)
mmoItem = template.newBuilder(player).build();
}
}
/**
* Was any reforge actually performed on this item?
*/
public boolean hasChanges() { return mmoItem != null; }
public ItemStack toStack() {
// For every cached stat (presumably due to its importance)
for (ItemStat stat : itemData.keySet()) {
//UPDT//MMOItems.Log(" \u00a72@\u00a78@ \u00a77Cached Stat");
// Replace into incoming MMOItem
StatData data = itemData.get(stat);
// Include if nonull
if (data != null) {
//UPDT//MMOItems.Log(" \u00a72@\u00a78@ \u00a77Old stat \u00a7f" + stat.getNBTPath());
// Set, replace, begone the previous!
mmoItem.setData(stat, data);
}
}
// Apply histories
for (ItemStat stat : itemDataHistory.keySet()) {
//UPDT//MMOItems.Log(" \u00a72@\u00a76@ \u00a77Cached Stat History");
// Does it have history too?
StatHistory<StatData> histOld = itemDataHistory.get(stat);
if (histOld != null) {
//UPDT//MMOItems.Log(" \u00a72 *\u00a76* \u00a77Of old stat \u00a7f" + histOld.getItemStat().getNBTPath());
// Regenerate the original data
StatHistory<StatData> hist = StatHistory.From(mmoItem, stat);
// Remember...
hist.Assimilate(histOld);
// Recalculate
mmoItem.setData(hist.getItemStat(), hist.Recalculate(false));
}
}
// Apply soulbound
if (cachedSoulbound != null) {
//UPDT//MMOItems.Log(" \u00a7c@ \u00a77Applying Soulbind");
// Apply
mmoItem.setData(ItemStats.SOULBOUND, cachedSoulbound);
}
// Contained enchantments huh
if (cachedEnchantments != null) {
//UPDT//MMOItems.Log(" \u00a7b@ \u00a77Applying Enchantments");
// Register as extraneous obviously
StatHistory<StatData> hist = StatHistory.From(mmoItem, ItemStats.ENCHANTS);
hist.registerExternalData(cachedEnchantments);
//UPDT//for (Enchantment lr : cachedEnchantments.getEnchants()) { //UPDT//MMOItems. Log(" \u00a7b + \u00a77" + lr.getName() + " \u00a7f" + cachedEnchantments.getLevel(lr)); }
}
// Upgrade Information
if (cachedUpgradeLevel != null) {
//UPDT//MMOItems.Log(" \u00a7e@ \u00a77Applying Upgrade");
// If has a upgrade template defined, just remember the level
if (mmoItem.hasData(ItemStats.UPGRADE)) {
//UPDT//MMOItems.Log(" \u00a7e* \u00a77Existing Upgrade Detected");
// Get current ig
UpgradeData current = ((UpgradeData) mmoItem.getData(ItemStats.UPGRADE));
// Edit level
current.setLevel(Math.min(cachedUpgradeLevel.getLevel(), current.getMaxUpgrades()));
//UPDT//MMOItems.Log(" \u00a7e + \u00a77Set to level \u00a7f" + current.getLevel());
// Re-set cuz why not
mmoItem.setData(ItemStats.UPGRADE, current);
// Otherwise, the level AND template shall prevail
} else {
//UPDT//MMOItems.Log(" \u00a7e* \u00a77Using Cached");
// Set from the cached
mmoItem.setData(ItemStats.UPGRADE, cachedUpgradeLevel);
//UPDT//MMOItems.Log(" \u00a7e + \u00a77Set to level \u00a7f" + cachedUpgradeLevel.getLevel());
}
}
// Gem Stones
if (cachedGemStones != null) {
//UPDT//MMOItems.Log(" \u00a7a@ \u00a77Applying Gemstones");
// If has a upgrade template defined, just remember the level
if (mmoItem.hasData(ItemStats.GEM_SOCKETS)) {
//UPDT//MMOItems.Log(" \u00a7a* \u00a77Existing Data Detected");
// Get current ig
GemSocketsData current = ((GemSocketsData) mmoItem.getData(ItemStats.GEM_SOCKETS));
// Get those damn empty sockets
ArrayList<String> availableSockets = new ArrayList<>(current.getEmptySlots());
ArrayList<GemstoneData> oldSockets = new ArrayList<>(cachedGemStones.getGemstones());
// Remaining
for (GemstoneData data : oldSockets) {
//UPDT//MMOItems.Log(" \u00a7a*\u00a7e* \u00a77Fitting \u00a7f" + data.getHistoricUUID().toString());
// No more if no more sockets left
if (availableSockets.size() <= 0) {
//UPDT//MMOItems.Log(" \u00a7a +\u00a7c+ \u00a77No More Sockets");
// They all will fit anyway
break;
// Still some sockets to fill hMMM
} else {
// Get colour
String colour = data.getColour();
String remembrance;
// Not null?
if (colour != null) {
// Contained? Remove
remembrance = colour;
// No colour data, just remove a random slot ig
} else {
// Get and remove
remembrance = availableSockets.get(0);
}
// Remove
availableSockets.remove(remembrance);
// And guess what... THAT is the colour of this gem! Fabulous huh?
data.setColour(remembrance);
//UPDT//MMOItems.Log(" \u00a7a + \u00a77Fit into color \u00a7f" + remembrance);
}
}
// Update list of empty sockets
cachedGemStones.getEmptySlots().clear();
cachedGemStones.getEmptySlots().addAll(availableSockets);
}
// Set the data, as changed as it may be
mmoItem.setData(ItemStats.GEM_SOCKETS, cachedGemStones);
}
// Lore
if (!cachedLore.isEmpty()) {
//UPDT//MMOItems.Log(" \u00a7d@ \u00a77Applying Lore");
// If it has lore, add I guess
if (mmoItem.hasData(ItemStats.LORE)) {
//UPDT//MMOItems.Log(" \u00a7d* \u00a77Inserting first");
// Get current ig
StringListData current = ((StringListData) mmoItem.getData(ItemStats.LORE));
// Get those damn empty sockets
ArrayList<String> listYes = new ArrayList<>(current.getList());
// Append to the end of the cached >:]
cachedLore.addAll(listYes);
}
// Create stat
StringListData sData = new StringListData(cachedLore);
//UPDT//for (String lr : cachedLore) { //UPDT//MMOItems.Log(" \u00a7d + \u00a77" + lr); }
// Set that as the lore
mmoItem.setData(ItemStats.LORE, sData);
}
// Name
if (cachedName != null) {
//UPDT//MMOItems.Log(" \u00a73@ \u00a77Applying Name \u00a7f" + cachedName);
// Replace name completely
mmoItem.setData(ItemStats.NAME, new StringData(cachedName));
}
// Apply upgrades
if (mmoItem.hasUpgradeTemplate()) { mmoItem.getUpgradeTemplate().upgradeTo(mmoItem, mmoItem.getUpgradeLevel()); }
// Build and set amount
ItemStack stack = mmoItem.newBuilder().build();
stack.setAmount(amount);
return stack;
}
/*
* Initialize the MMOItem as a LiveMMOItem if it's null or not already a
* LiveMMOItem
*/
private void loadLiveMMOItem() {
if (mmoItem != null && mmoItem instanceof LiveMMOItem) { return; }
mmoItem = new LiveMMOItem(nbtItem);
}
/* Initialize the MMOItem as a VolatileMMOItem if it's null */
private void loadVolatileMMOItem() {
if (mmoItem != null) { return;}
mmoItem = new VolatileMMOItem(nbtItem);
}
}

View File

@ -0,0 +1,215 @@
package net.Indyuce.mmoitems.gui.edition;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.lib.version.VersionMaterial;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.util.MMOItemReforger;
import net.Indyuce.mmoitems.stat.RevisionID;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Inventory displayed when enabling the item updater.
* @see RevisionID
* @see MMOItemReforger
*
* @author Gunging
*/
public class RevisionInventory extends EditionInventory {
static ItemStack name;
static ItemStack lore;
static ItemStack enchantments;
static ItemStack upgrades;
static ItemStack gemstones;
static ItemStack soulbind;
static ItemStack revisionID;
static final String REVISION = "\u00a76Revision ID";
public RevisionInventory(@NotNull Player player, @NotNull MMOItemTemplate template) {
super(player, template);
// If null
if (revisionID == null) {
name = ItemFactory.of(Material.NAME_TAG).name("\u00a73Name").lore(SilentNumbers.Chop(
"The display name of the old item will be transferred to the new one"
, 40, "\u00a77")).build();
lore = ItemFactory.of(VersionMaterial.WRITABLE_BOOK.toMaterial()).name("\u00a7dLore").lore(SilentNumbers.Chop(
"Specifically keeps lore lines that begin with the color code \u00a7n&7"
, 40, "\u00a77")).build();
enchantments = ItemFactory.of(Material.EXPERIENCE_BOTTLE).name("\u00a7bEnchantments").lore(SilentNumbers.Chop(
"This keeps specifically enchantments that are not accounted for in upgrades nor gem stones (presumably added by the player)."
, 40, "\u00a77")).build();
upgrades = ItemFactory.of(Material.NETHER_STAR).name("\u00a7aUpgrades").lore(SilentNumbers.Chop(
"Will this item retain the upgrade level after updating?"
, 40, "\u00a77")).build();
gemstones = ItemFactory.of(Material.EMERALD).name("\u00a7eGem Stones").lore(SilentNumbers.Chop(
"Will the item retain its gem stones when updating? (Note that this allows gemstone overflow - will keep ALL old gemstones even if you reduced the gem sockets)"
, 40, "\u00a77")).build();
soulbind = ItemFactory.of(Material.ENDER_EYE).name("\u00a7cSoulbind").lore(SilentNumbers.Chop(
"If the old item is soulbound, updating will transfer the soulbind to the new item."
, 40, "\u00a77")).build();
// Fill stack
revisionID = ItemFactory.of(Material.ITEM_FRAME).name(REVISION).lore(SilentNumbers.Chop(
"The updater is always active, increasing this number will update all instances of this MMOItem without further action."
, 40, "\u00a77")).build();
}
}
@NotNull @Override public Inventory getInventory() {
Inventory inv = Bukkit.createInventory(this, 54, "Revision Manager");
// Place corresponding item stacks in there
for (int i = 0; i < inv.getSize(); i++) {
// What item to even put here
ItemStack which = null;
Boolean enable = null;
Integer id = null;
switch (i) {
case 19:
which = name.clone();
enable = MMOItems.plugin.getLanguage().revisionOptions.shouldKeepName();
break;
case 20:
which = lore.clone();
enable = MMOItems.plugin.getLanguage().revisionOptions.shouldKeepLore();
break;
case 21:
which = enchantments.clone();
enable = MMOItems.plugin.getLanguage().revisionOptions.shouldKeepEnchantments();
break;
case 28:
which = upgrades.clone();
enable = MMOItems.plugin.getLanguage().revisionOptions.shouldKeepUpgrades();
break;
case 29:
which = gemstones.clone();
enable = MMOItems.plugin.getLanguage().revisionOptions.shouldKeepGemStones();
break;
case 30:
which = soulbind.clone();
enable = MMOItems.plugin.getLanguage().revisionOptions.shouldKeepSoulbind();
break;
case 33:
id = getEditedSection().getInt(ItemStats.REVISION_ID.getPath(), 1);
which = revisionID.clone();
break;
default:
break;
}
// If an item corresponds to this slot
if (which != null) {
// If it is defined to be enabled
if (enable != null) {
// Add mentioning if enabled
inv.setItem(i, addLore(which, "", "\u00a78Enabled (in config)? \u00a76" + enable.toString()));
// If ID is enabled
} else if (id != null) {
// Add mentioning if enabled
inv.setItem(i, addLore(which, "", "\u00a78Current Value: \u00a76" + id));
// Neither enable nor ID are defined
} else {
// Add
inv.setItem(i, which);
}
}
}
// Add the top items, including the {Back} button
addEditionInventoryItems(inv, true);
return inv;
}
@Override
public void whenClicked(InventoryClickEvent event) {
// Get the clicked item
ItemStack item = event.getCurrentItem();
event.setCancelled(true);
// If the click did not happen in the correct inventory, or the item is not clickable
if (event.getInventory() != event.getClickedInventory() || !MMOUtils.isMetaItem(item, false)) { return; }
// Is the player clicking the revision ID thing?
if (item.getItemMeta().getDisplayName().equals(REVISION)) {
// Get the current ID
int id = getEditedSection().getInt(ItemStats.REVISION_ID.getPath(), 1);
// If right click
if (event.getAction() == InventoryAction.PICKUP_HALF) {
// Decrease by 1, but never before 1
id = Math.max(id - 1, 1);
// Any other click
} else {
// Increase by 1 until the ultimate maximum
id = Math.min(id + 1, Integer.MAX_VALUE);
}
// Register edition
getEditedSection().set(ItemStats.REVISION_ID.getPath(), id);
registerTemplateEdition();
// Update ig
event.setCurrentItem(addLore(revisionID.clone(), "", "\u00a78Current Value: \u00a76" + id));
}
}
@NotNull ItemStack addLore(@NotNull ItemStack iSource, String... extraLines){
// Get its lore
ArrayList<String> iLore = new ArrayList<>();
ItemMeta iMeta = iSource.getItemMeta();
if (iMeta != null && iMeta.getLore() != null) {iLore = new ArrayList<>(iMeta.getLore()); }
// Add lines
iLore.addAll(Arrays.asList(extraLines));
// Put lore
iMeta.setLore(iLore);
// Yes
iSource.setItemMeta(iMeta);
// Yes
return iSource;
}
}

View File

@ -1,7 +1,8 @@
package net.Indyuce.mmoitems.listener;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.util.ItemModInstance;
import net.Indyuce.mmoitems.api.util.MMOItemReforger;
import io.lumine.mythic.lib.api.item.NBTItem;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
@ -14,6 +15,8 @@ import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ItemListener implements Listener {
@EventHandler(ignoreCancelled = true)
@ -67,13 +70,26 @@ public class ItemListener implements Listener {
if(newItem != null) player.getEquipment().setItemInOffHand(newItem);
}
private ItemStack modifyItem(ItemStack stack, Player player, String type) {
@Nullable private ItemStack modifyItem(@NotNull ItemStack stack, @NotNull Player player, @NotNull String type) {
// Get the item
NBTItem nbt = NBTItem.get(stack);
if (!nbt.hasType()) return null;
ItemModInstance mod = new ItemModInstance(nbt);
if (shouldUpdate(nbt, type))
// Not a MMOItem? not our problem.
if (!nbt.hasType()) { return null; }
// ??
MMOItemReforger mod = new MMOItemReforger(nbt);
// Should this item be updated (via the updater)
if (shouldUpdate(nbt, type)) {
mod.reforge(MMOItems.plugin.getLanguage().rerollOnItemUpdate ? player : null, MMOItems.plugin.getLanguage().revisionOptions);
if (shouldSoulbind(nbt, type)) mod.applySoulbound(player);
}
// Should this item be soulbount?
if (shouldSoulbind(nbt, type)) { mod.applySoulbound(player); }
// Return either the changed one or null
return mod.hasChanges() ? mod.toStack() : null;
}
@ -87,9 +103,24 @@ public class ItemListener implements Listener {
private boolean shouldUpdate(NBTItem nbt, String type) {
if(!MMOItems.plugin.getTemplates().hasTemplate(nbt)) return false;
return !MMOItems.plugin.getConfig().getBoolean("item-revision.disable-on." + type) &&
((MMOItems.plugin.getTemplates().getTemplate(nbt).getRevisionId() > (nbt.hasTag("MMOITEMS_REVISION_ID")
? nbt.getInteger("MMOITEMS_REVISION_ID") : 1)) || (MMOItems.INTERNAL_REVISION_ID >
(nbt.hasTag("MMOITEMS_INTERNAL_REVISION_ID") ? nbt.getInteger("MMOITEMS_INTERNAL_REVISION_ID") : 1)) );
return
// It must not be disabled for this item
!MMOItems.plugin.getConfig().getBoolean("item-revision.disable-on." + type) &&
// Either the normal revision ID or the internal revision IDs are out of date.
(
// Is the template's revision ID greater than the one currently in the item?
(MMOItems.plugin.getTemplates().getTemplate(nbt).getRevisionId() >
// The one 'currently in the item' is the value of the stat, or 1 if missing.
(nbt.hasTag(ItemStats.REVISION_ID.getNBTPath()) ? nbt.getInteger(ItemStats.REVISION_ID.getNBTPath()) : 1)) ||
// Or the MMOItems internal revision ID itself
(MMOItems.INTERNAL_REVISION_ID >
// Same thing: either the value of the stat or 1 if missing.
(nbt.hasTag(ItemStats.INTERNAL_REVISION_ID.getNBTPath()) ? nbt.getInteger(ItemStats.INTERNAL_REVISION_ID.getNBTPath()) : 1))
);
}
}

View File

@ -23,6 +23,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
/**
* @author Gunging
*/
public class Amphibian extends ChooseStat implements ItemRestriction, GemStoneStat {
public static final String
NORMAL = "UNRESTRICTED",

View File

@ -30,6 +30,15 @@ public class DisplayName extends StringStat {
format = format.replace("<tier-name>", tier != null ? ChatColor.stripColor(tier.getName()) : "");
format = format.replace("<tier-color>", tier != null ? ChatColor.getLastColors(tier.getName()) : "&f");
int upgradeLevel = item.getMMOItem().getUpgradeLevel();
String suffix = MythicLib.plugin.parseColors(MMOItems.plugin.getConfig().getString("item-upgrading.name-suffix"));
String oldSuffix = suffix.replace("#lvl#", String.valueOf(upgradeLevel - 1));
String actSuffix = suffix.replace("#lvl#", String.valueOf(upgradeLevel));
String oldSuffix2 = suffix.replace("#lvl#", String.valueOf(upgradeLevel + 1));
format = format.replace(oldSuffix, "").replace(oldSuffix2, "").replace(actSuffix, "");
if (upgradeLevel != 0) { format = format + actSuffix; }
item.getMeta().setDisplayName(MythicLib.inst().parseColors(format));
}

View File

@ -9,10 +9,12 @@ import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.lib.api.util.ui.PlusMinusPercent;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.util.message.FriendlyFeedbackPalette_MMOItems;
import net.Indyuce.mmoitems.stat.data.DoubleData;
import net.Indyuce.mmoitems.stat.data.type.UpgradeInfo;
import net.Indyuce.mmoitems.stat.type.DoubleStat;
import net.Indyuce.mmoitems.stat.type.StatHistory;
import net.Indyuce.mmoitems.stat.type.Upgradable;
import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor;
@ -192,7 +194,16 @@ public class Enchants extends ItemStat implements Upgradable {
// Enchant Item
EnchantListData enchants = (EnchantListData) data;
for (Enchantment enchant : enchants.getEnchants()) { item.getMeta().addEnchant(enchant, enchants.getLevel(enchant), true); }
for (Enchantment enchant : enchants.getEnchants()) {
// Get level
int lvl = enchants.getLevel(enchant);
// Not ZERO bruh
if (lvl != 0) {
item.getMeta().addEnchant(enchant, enchants.getLevel(enchant), true);
}
}
// Apply tags
item.addItemTag(getAppliedNBT(data));
@ -296,6 +307,57 @@ public class Enchants extends ItemStat implements Upgradable {
return original;
}
/**
* Players may enchant an item via 'vanilla' means (Not Gemstones, not Upgrades).
* If they do so, the enchantments of their item will be wiped if they try to put
* a gemstone in or upgrade the item, which is not nice.
* <p></p>
* This operation classifies these discrepancies in the enchantment levels as
* 'externals' in the Stat History of enchantments. From then on, they will be
* accounted for.
*/
public static void separateEnchantments(@NotNull MMOItem mmoitem) {
// Cancellation because the player could not have done so HMMM
if (mmoitem.hasData(ItemStats.DISABLE_REPAIRING) && mmoitem.hasData(ItemStats.DISABLE_ENCHANTING)) { return; }
// Does it have enchantment data?
if (mmoitem.hasData(ItemStats.ENCHANTS)) {
// Get that data
EnchantListData data = (EnchantListData) mmoitem.getData(ItemStats.ENCHANTS);
StatHistory<StatData> hist = StatHistory.From(mmoitem, ItemStats.ENCHANTS);
// All right, whats the expected enchantment levels?
EnchantListData expected = (EnchantListData) hist.Recalculate();
// Gather a list of extraneous enchantments
HashMap<Enchantment, Integer> discrepancies = new HashMap<>();
// For every enchantment
for (Enchantment e : Enchantment.values()) {
// Get both
int actual = data.getLevel(e);
int ideal = expected.getLevel(e);
// Get difference, and register.
int offset = actual - ideal;
if (offset != 0) { discrepancies.put(e, offset); }
}
// It has been extracted
if (discrepancies.size() > 0) {
// Generate enchantment list with offsets
EnchantListData extraneous = new EnchantListData();
for (Enchantment e : discrepancies.keySet()) { extraneous.addEnchant(e, discrepancies.get(e));}
// Register extraneous
hist.registerExternalData(extraneous);
}
}
}
public static class EnchantUpgradeInfo implements UpgradeInfo {
@NotNull HashMap<Enchantment, PlusMinusPercent> perEnchantmentOperations = new HashMap<>();

View File

@ -14,6 +14,9 @@ import org.bukkit.Material;
import java.util.ArrayList;
/**
* @author Gunging
*/
public class RequiredBiomes extends StringListStat implements ItemRestriction, GemStoneStat {
public RequiredBiomes() {
super("REQUIRED_BIOMES", Material.JUNGLE_SAPLING, "Required Biomes", new String[] { "The biome the player must be within", "for this item to activate." }, new String[] { "!block", "all" });

View File

@ -5,6 +5,9 @@ import java.util.List;
import java.util.Optional;
import io.lumine.mythic.lib.api.item.SupportedNBTTagValues;
import net.Indyuce.mmoitems.api.util.MMOItemReforger;
import net.Indyuce.mmoitems.gui.edition.AbilityListEdition;
import net.Indyuce.mmoitems.gui.edition.RevisionInventory;
import net.Indyuce.mmoitems.stat.type.GemStoneStat;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@ -24,10 +27,15 @@ import io.lumine.mythic.lib.api.util.AltChar;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Regarding the auto updating of items
* @see RevisionInventory
* @see MMOItemReforger
*/
public class RevisionID extends ItemStat implements GemStoneStat {
public RevisionID() {
super("REVISION_ID", Material.ITEM_FRAME, "Revision ID", new String[] { "The Revision ID is used to determine",
"if an item is outdated or not. You", "should increase this whenever", "you make changes to your item!"},
"if an item is outdated or not. You", "should increase this whenever", "you make changes to your item!", "", "\u00a76The updater is smart and will apply", "\u00a76changes to the base stats of the item,", "\u00a76keeping gemstones intact (for example)."},
new String[] { "all" });
}
@ -54,19 +62,11 @@ public class RevisionID extends ItemStat implements GemStoneStat {
@Override
public void whenClicked(@NotNull EditionInventory inv, @NotNull InventoryClickEvent event) {
int id = inv.getEditedSection().getInt(getPath(), 1);
if (event.getAction() == InventoryAction.PICKUP_HALF) {
inv.getEditedSection().set(getPath(), Math.max(id - 1, 1));
inv.registerTemplateEdition();
return;
}
inv.getEditedSection().set(getPath(), Math.min(id + 1, Integer.MAX_VALUE));
inv.registerTemplateEdition();
new RevisionInventory(inv.getPlayer(), inv.getEdited()).open(inv.getPage());
}
@Override
public void whenInput(@NotNull EditionInventory inv, @NotNull String message, Object... info) {}
public void whenInput(@NotNull EditionInventory inv, @NotNull String message, Object... info) { }
@Override
public void whenLoaded(@NotNull ReadMMOItem mmoitem) {

View File

@ -4,6 +4,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import net.Indyuce.mmoitems.MMOItems;
import org.apache.commons.lang.Validate;
import org.bukkit.enchantments.Enchantment;
@ -32,8 +33,19 @@ public class EnchantListData implements StatData, Mergeable {
public void merge(StatData data) {
Validate.isTrue(data instanceof EnchantListData, "Cannot merge two different stat data types");
Map<Enchantment, Integer> extra = ((EnchantListData) data).enchants;
for (Enchantment enchant : extra.keySet())
enchants.put(enchant, enchants.containsKey(enchant) ? Math.max(extra.get(enchant), enchants.get(enchant)) : extra.get(enchant));
boolean additiveMerge = MMOItems.plugin.getConfig().getBoolean("stat-merging.additive-enchantments", false);
for (Enchantment enchant : extra.keySet()) {
if (additiveMerge) {
// Additive
enchants.put(enchant, extra.get(enchant) + enchants.get(enchant));
} else {
// Max Enchantment
enchants.put(enchant, enchants.containsKey(enchant) ? extra.get(enchant) + enchants.get(enchant) : extra.get(enchant));
}
}
}
@Override

View File

@ -21,7 +21,10 @@ public class GemstoneData {
@Nullable Integer levelPut = 0;
@NotNull final UUID historicUUID;
@Nullable final String mmoitemType, mmoitemID, socketColor;
@Nullable final String mmoitemType;
@Nullable final String mmoitemID;
@Nullable
String socketColor;
/**
* This constructor is not really performance friendly. It should only be
@ -168,6 +171,16 @@ public class GemstoneData {
return name;
}
/**
* If known, the socket colour this gem was put into
*/
@Nullable public String getColour() { return socketColor; }
/**
* If known, the socket colour this gem was put into
*/
public void setColour(@Nullable String color) { socketColor = color; }
/**
* Want to know which stats were given to the item by this gemstone (after applying upgrade scaling and such)? Use this!
*/

View File

@ -106,28 +106,6 @@ public class UpgradeData implements StatData, RandomStatData {
return;
}
/*
* Display Upgrade Level
*/
String suffix = MythicLib.plugin.parseColors(MMOItems.plugin.getConfig().getString("item-upgrading.name-suffix"));
if (MMOItems.plugin.getConfig().getBoolean("item-upgrading.display-in-name"))
if (mmoitem.hasData(ItemStats.NAME)) {
StringData nameData = (StringData) mmoitem.getData(ItemStats.NAME);
nameData.setString(level == 0 ? nameData.toString() + suffix.replace("#lvl#", "" + (level + 1))
: nameData.toString().replace(suffix.replace("#lvl#", "" + level), suffix.replace("#lvl#", "" + (level + 1))));
}
/*TODO: implement this as a new dynamic lore type
else if (mmoitem.hasData(ItemStats.LORE)) {
StringListData loreData = (StringListData) mmoitem.getData(ItemStats.LORE);
loreData.getList().forEach(line -> {
if (line.contains("%upgrade_level%") || line.contains(suffix.replace("#lvl#", "" + level))) {
line.replace("%upgrade_level%", suffix.replace("#lvl#", "" + level + 1));
line.replace(suffix.replace("#lvl#", "" + level), suffix.replace("#lvl#", "" + level + 1));
}
});
}*/
/*
* Go through every stat that must be ugpraded and apply
*/

View File

@ -3,6 +3,7 @@ package net.Indyuce.mmoitems.stat.type;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.item.ItemTag;
import io.lumine.mythic.lib.api.util.AltChar;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.api.item.mmoitem.ReadMMOItem;
@ -96,7 +97,7 @@ public abstract class ChooseStat extends StringStat {
}
// Get Definition
if (chooseableDefs.containsKey(def)) { for (String deff : Chop(chooseableDefs.get(def), 50)) { lore.add(ChatColor.GRAY + " " + deff); } }
if (chooseableDefs.containsKey(def)) { for (String definition : SilentNumbers.Chop(chooseableDefs.get(def), 50, "")) { lore.add(ChatColor.GRAY + " " + definition); } }
lore.add("");
lore.add(ChatColor.YELLOW + AltChar.listDash + " Right click to return to default value.");
@ -123,39 +124,4 @@ public abstract class ChooseStat extends StringStat {
*/
@NotNull HashMap<String, String> chooseableDefs = new HashMap<>();
public void HintChooseableDefs(@NotNull HashMap<String, String> list) { chooseableDefs = list; }
/**
* Chops a long description into several parts.
*/
ArrayList<String> Chop(String longString, int paragraphWide) {
// Ret
ArrayList<String> ret = new ArrayList<>();
boolean skip = false;
// While longer
while (longString.length() > paragraphWide) {
// Skip
skip = true;
// Get the wide
int idx = longString.lastIndexOf(" ", paragraphWide + 1);
// Chop
ret.add(longString.substring(0, idx));
// Update
longString = longString.substring(idx + 1);
// Add final.
if (longString.length() <= paragraphWide) { ret.add(longString); }
}
// Wasnt long at all
if (!skip) { ret.add(longString); }
// Thats it
return ret;
}
}

View File

@ -4,6 +4,7 @@ import com.google.gson.*;
import io.lumine.mythic.lib.api.item.ItemTag;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.util.message.FriendlyFeedbackPalette_MMOItems;
@ -125,9 +126,9 @@ public class StatHistory<S extends StatData> {
// Found? Thats it
if (hist != null) {
//UPGRD//MMOItems. Log("Found Stat History of \u00a76" + ofStat.getNBTPath() + "\u00a77 in this \u00a7c" + ofItem.getType().getName() + " " + ofItem.getId());
//UPGRD//MMOItems.Log("Found Stat History of \u00a76" + ofStat.getNBTPath() + "\u00a77 in this \u00a7c" + ofItem.getType().getName() + " " + ofItem.getId());
return hist; }
//UPGRD//MMOItems. Log("\u00a7aCreated Hisotry of \u00a76" + ofStat.getNBTPath() + "\u00a7a of this \u00a7c" + ofItem.getType().getName() + " " + ofItem.getId());
//UPGRD//MMOItems.Log("\u00a7aCreated Hisotry of \u00a76" + ofStat.getNBTPath() + "\u00a7a of this \u00a7c" + ofItem.getType().getName() + " " + ofItem.getId());
// That is Mergeable right...
Validate.isTrue(ofStat.getClearStatData() instanceof Mergeable, "Non-Mergeable stat data wont have a Stat History; they cannot be modified dynamically in the first place.");
@ -136,11 +137,12 @@ public class StatHistory<S extends StatData> {
StatData original = ofItem.getData(ofStat);
if (original == null) {
original = ofStat.getClearStatData();
//UPGRD//MMOItems. Log("\u00a7e +\u00a77 Item didnt have this stat, original set as blanc.");
ofItem.setData(ofStat, original);
//UPGRD//MMOItems.Log("\u00a7e +\u00a77 Item didnt have this stat, original set as blanc.");
}
else {
original = ((Mergeable) original).cloneData();
//UPGRD//MMOItems. Log("\u00a7a +\u00a77 Found original data");
//UPGRD//MMOItems.Log("\u00a7a +\u00a77 Found original data");
}
// Create new
@ -160,13 +162,72 @@ public class StatHistory<S extends StatData> {
*/
StatHistory(@NotNull MMOItem ofItem, @NotNull ItemStat ofStat, @NotNull S ogData) { itemStat = ofStat; originalData = ogData; parent = ofItem; }
/**
* Checks the item and makes sure that the UUIDs
* attributed to gemstones link to existing gem
* stones. Removes them if no such gemstone exists.
*/
public void PurgeGemstones() {
// Which will get purged...
ArrayList<UUID> extraneous = new ArrayList<>();
GemSocketsData data = (GemSocketsData) getMMOItem().getData(ItemStats.GEM_SOCKETS);
if (data == null) { data = new GemSocketsData(new ArrayList<>()); }
// For each UUID
for (UUID gem : perGemstoneData.keySet()) {
// Check Gemstones
boolean success = false;
for (GemstoneData indiv : data.getGemstones()) {
// Not null
if (indiv != null) {
// Equal in UUID
if (gem.equals(indiv.getHistoricUUID())) {
success = true;
break;
}
}
}
// No success?
if (!success) {
// No gemstone matched
extraneous.add(gem);
}
}
// Unregister
for (UUID ext : extraneous) {
//UPGRD//MMOItems.Log("\u00a76 ||\u00a77 Purged Stone: \u00a7e" + ext.toString());
// Remove
perGemstoneData.remove(ext);
}
}
/**
* This recalculates final value of the stats of the item.
* <p></p>
* This will not apply the changes, it will just give you the final
* <code>StatData</code> that shall be applied (used when upgrading).
*/
@NotNull public S Recalculate() {
@NotNull public S Recalculate() { return Recalculate(true); }
/**
* This recalculates final value of the stats of the item.
* <p></p>
* This will not apply the changes, it will just give you the final
* <code>StatData</code> that shall be applied (used when upgrading).
* @param withPurge Check if the gemstones UUIDs are valid.
* Leave <code>true</code> unless you know
* what you're doing.
*/
@NotNull public S Recalculate(boolean withPurge) {
if (withPurge) { PurgeGemstones(); }
// If its upgradeable and not level ZERO, it must apply upgrades
if ((getMMOItem().getUpgradeLevel() != 0) &&
@ -181,12 +242,26 @@ public class StatHistory<S extends StatData> {
// Merge Normally
return Recalculate_ThroughClone();
}
/**
* This recalculates values accounting only for gemstones and external data.
* <p></p>
* In case someone was wondered the contribution of upgrading the item.
* In case someone was wondered the contribution of upgrading the item, just
* substract it from {@link #Recalculate()}
*/
@NotNull public S Recalculate_Unupgraded() {
@NotNull public S Recalculate_Unupgraded() { return Recalculate_Unupgraded(true); }
/**
* This recalculates values accounting only for gemstones and external data.
* <p></p>
* In case someone was wondered the contribution of upgrading the item, just
* substract it from {@link #Recalculate()}
* @param withPurge Check if the gemstones UUIDs are valid.
* Leave <code>true</code> unless you know
* what you're doing.
*/
@NotNull public S Recalculate_Unupgraded(boolean withPurge) {
if (withPurge) { PurgeGemstones(); }
// Merge Normally
return Recalculate_ThroughClone();
@ -203,7 +278,7 @@ public class StatHistory<S extends StatData> {
* <p>5: Sums external data (modifiers that are not linked to an ID, I suppose by external plugins).
*/
private S Recalculate_AsUpgradeable() {
//UPGRD//MMOItems. Log("\u00a76|||\u00a77 Calculating as Upgradeable");
//UPGRD//MMOItems.Log("\u00a76|||\u00a77 Calculating \u00a7f" + getItemStat().getNBTPath() + "\u00a77 as Upgradeable");
// Get Upgrade Info?
UpgradeInfo inf = getMMOItem().getUpgradeTemplate().getUpgradeInfo(getItemStat());
@ -216,7 +291,7 @@ public class StatHistory<S extends StatData> {
// Level up
int lvl = getMMOItem().getUpgradeLevel();
//UPGRD//MMOItems. Log("\u00a76 ||\u00a77 Item Level: \u00a7e" + lvl);
//UPGRD//MMOItems.Log("\u00a76 ||\u00a77 Item Level: \u00a7e" + lvl);
//UPGRD//MMOItems. Log("\u00a76 >\u00a77 Original Base: \u00a7e" + ((DoubleData) ogCloned).getValue());
S ret = (S) ((Upgradable) getItemStat()).apply(ogCloned, inf, lvl);
//UPGRD//MMOItems. Log("\u00a76 >\u00a77 Leveled Base: \u00a7e" + ((DoubleData) ret).getValue());
@ -248,7 +323,7 @@ public class StatHistory<S extends StatData> {
// Calculate level difference
int gLevel = lvl - level;
//UPGRD//MMOItems. Log("\u00a76 |\u00a7b|\u00a76>\u00a77 Gemstone Level: \u00a7e" + gLevel + "\u00a77 (Put at \u00a7b" + level + "\u00a77)");
//UPGRD//MMOItems.Log("\u00a76 |\u00a7b|\u00a76>\u00a77 Gemstone Level: \u00a7e" + gLevel + "\u00a77 (Put at \u00a7b" + level + "\u00a77)");
//UPGRD//MMOItems. Log("\u00a76 \u00a7b|>\u00a77 Gemstone Base: \u00a7e" + ((DoubleData) getGemstoneData(d)).getValue());
// Apply upgrades
@ -282,7 +357,7 @@ public class StatHistory<S extends StatData> {
* <p>4: Sums external data (modifiers that are not linked to an ID, I suppose by external plugins).
*/
private S Recalculate_ThroughClone() {
//UPGRD//MMOItems. Log("\u00a73|||\u00a77 Calculating as Clonium");
//UPGRD//MMOItems.Log("\u00a73|||\u00a77 Calculating \u00a7f" + getItemStat().getNBTPath() + "\u00a77 as Clonium");
// Just clone bro
S ret = (S) ((Mergeable) getOriginalData()).cloneData();
@ -528,6 +603,32 @@ public class StatHistory<S extends StatData> {
}
}
/**
* Get all gemstone and extraneous data from this other, while
* keeping the current ones as well as <u>these</u> original bases.
* <p></p>
* Fails if the stats are not the same one.
*/
public void Assimilate(@NotNull StatHistory<StatData> other) {
// Stat must be the same
if (other.getItemStat().getNBTPath().equals(getItemStat().getNBTPath())) {
//UPDT//MMOItems.Log(" \u00a72>\u00a76> \u00a77History Stat Matches");
//UPDT//MMOItems.Log(" \u00a76:\u00a72: \u00a77Original Gemstones \u00a7f" + perGemstoneData.size());
//UPDT//MMOItems.Log(" \u00a76:\u00a72: \u00a77Original Externals \u00a7f" + perExternalData.size());
// Register gemstones
for (UUID exUID : other.perGemstoneData.keySet()) { registerGemstoneData(exUID, (S) other.getGemstoneData(exUID)); }
// Register externals
for (StatData ex : other.perExternalData) { registerExternalData((S) ex); }
//UPDT//MMOItems.Log(" \u00a76:\u00a72: \u00a77Final Gemstones \u00a7f" + perGemstoneData.size());
//UPDT//MMOItems.Log(" \u00a76:\u00a72: \u00a77Final Externals \u00a7f" + perExternalData.size());
}
}
static final String enc_Stat = "Stat";
static final String enc_OGS = "OGStory";
static final String enc_GSS = "Gemstory";