Revision of items where gemstones are lost now supports giving those gems back to the player.

Suppose the latest item has less gem slots / different colored gem slots, or you set keep-gems to FALSE. In all these scenarios, gems are 'lost' so that this config option (**true** by default!) gives them back to the player.

Enable in your config:
```
# Options for the Item Revision System
item-revision:

    # If an item is updated, and the new version does not
    # keep its gems, this will give the gems back to the
    # player so that they don't get lost forever.
    drop-extra-gems: true
```
This commit is contained in:
Gunging 2021-04-15 21:40:44 -05:00
parent 53a3b8671d
commit 9b09ec1d10
11 changed files with 191 additions and 47 deletions

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.Indyuce</groupId>
<artifactId>MMOItems</artifactId>
<version>6.5.6-SNAPSHOT</version>
<version>6.5.7</version>
<name>MMOItems</name>
<description>A great item solution for your RPG server!</description>

View File

@ -4,6 +4,8 @@ import net.Indyuce.mmoitems.MMOItems;
import org.bukkit.configuration.ConfigurationSection;
public class ReforgeOptions {
public static boolean dropRestoredGems;
private final boolean
keepName;
private final boolean keepLore;

View File

@ -19,6 +19,7 @@ import net.Indyuce.mmoitems.stat.data.EnchantListData;
import net.Indyuce.mmoitems.stat.data.GemSocketsData;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction;
@ -155,6 +156,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
*/
eventTrigger.setCancelled(true);
if (!(eventTrigger.getWhoClicked() instanceof Player)) { return; }
Player player = (Player) eventTrigger.getWhoClicked();
// Get the two combinant items
ItemStack item = otherInventories.getMainInventory().getFirst();
@ -165,7 +167,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// Get the display
Ref<ArrayList<ItemStack>> droppedGemstones = new Ref<>();
MMOItem display = fromCombinationWith(itemMMO, ingotMMO, (Player) eventTrigger.getWhoClicked(), droppedGemstones);
MMOItem display = fromCombinationWith(itemMMO, ingotMMO, player, droppedGemstones);
// Result
MythicRecipeInventory result = otherInventories.getResultInventory().clone();
@ -259,7 +261,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// Build the result
ArrayList<ItemStack> outputItems = MRORecipe.toItemsList(result);
HashMap<Integer, ItemStack> modifiedInventory = null;
Inventory inven = eventTrigger.getWhoClicked().getInventory();
Inventory inven = player.getInventory();
int trueTimes = 0;
// For every time
@ -303,17 +305,17 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
}
// Drop?
if (isDropGemstones() && (droppedGemstones.getValue() != null) && (eventTrigger.getWhoClicked().getLocation().getWorld() != null)) {
Location l = eventTrigger.getWhoClicked().getLocation();
if (isDropGemstones() && (droppedGemstones.getValue() != null) && (player.getLocation().getWorld() != null)) {
// Drop each yea
for (ItemStack gem : droppedGemstones.getValue()) {
// Give the gems back
for (ItemStack drop : player.getInventory().addItem(
droppedGemstones.getValue().toArray(new ItemStack[0])).values()) {
if (SilentNumbers.isAir(gem)) { continue; }
// Not air right
if (SilentNumbers.isAir(drop)) { continue; }
// God damn drop yo
l.getWorld().dropItemNaturally(l, gem);
} }
// Drop to the world
player.getWorld().dropItem(player.getLocation(), drop); } }
// Consume ingredients
consumeIngredients(otherInventories, cache, eventTrigger.getInventory(), map, times);

View File

@ -141,16 +141,16 @@ public class ItemStackBuilder {
StatHistory s = builtMMOItem.getStatHistory(stat); int l = mmoitem.getUpgradeLevel();
// Found it?
if (s != null && (!s.isClear() || stat instanceof Enchants)) {
if (s != null) {
//GEM//MMOItems.log("\u00a7a -+- \u00a77Recording History");
// Add to NBT
addItemTag(new ItemTag(histroy_keyword + stat.getId(), s.toNBTString()));
// Recalculate
//HSY//MMOItems.log(" \u00a73-\u00a7a- \u00a77ItemStack Building Recalculation \u00a73-\u00a7a-\u00a73-\u00a7a-\u00a73-\u00a7a-\u00a73-\u00a7a-");
builtMMOItem.setData(stat, s.recalculate(l));
// Add to NBT, if the gemstones were not purged
if ((!s.isClear() || stat instanceof Enchants)) { addItemTag(new ItemTag(histroy_keyword + stat.getId(), s.toNBTString())); }
}
if (forDisplay && stat instanceof Previewable) {

View File

@ -9,6 +9,7 @@ import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.stat.Enchants;
import net.Indyuce.mmoitems.stat.data.GemSocketsData;
import net.Indyuce.mmoitems.stat.data.GemstoneData;
import net.Indyuce.mmoitems.stat.data.StringData;
import net.Indyuce.mmoitems.stat.data.UpgradeData;
import net.Indyuce.mmoitems.stat.data.type.Mergeable;
import net.Indyuce.mmoitems.stat.data.type.StatData;
@ -388,6 +389,61 @@ public class MMOItem implements ItemReference {
return new ArrayList<>(regeneratedGems.values());
}
/**
* Extracts a single gemstone. Note that this only builds the original Gemstone MMOItem, and if you
* wish to actually remove the GemStone, you must do so through {@link #removeGemStone(UUID, String)}
*
* @param gem Gemstone that you believe is in here
*
* @return The gemstone as it was when inserted, or <code>null</code>
* if such gemstone is not in here.
*
* @see #extractGemstones() More optimized method for extracting all gemstones at the same time.
*/
@Nullable public MMOItem extractGemstone(@NotNull GemstoneData gem) {
//XTC//MMOItems.log("\u00a7a *\u00a77 Extracting gem stone -\u00a7a " + gem.getMMOItemType() + " " + gem.getMMOItemID());
// Can we generate?
MMOItem restored = MMOItems.plugin.getMMOItem(MMOItems.plugin.getType(gem.getMMOItemType()), gem.getMMOItemID());
// Valid? neat-o
if (restored != null) {
//XTC//MMOItems.log("\u00a7a *\u00a73>\u00a77 Valid, regenerated \u00a7e" + restored.getData(ItemStats.NAME));
restored.asGemColor = gem.getSocketColor();
restored.asGemUUID = gem.getHistoricUUID();
//XTC//MMOItems.log("\u00a7a >\u00a77 Color \u00a7e" + restored.getAsGemColor());
//XTC//MMOItems.log("\u00a7a >\u00a77 UUID \u00a7e" + restored.getAsGemUUID().toString());
// Cannot be removed
} else {
//XTC//MMOItems.log("\u00a7a *\u00a7c Gem too old / MMOItem missing");
return null; }
// Identify actual attributes
for (ItemStat stat : getStats()) {
// Mergeable right
if (!(stat.getClearStatData() instanceof Mergeable)) { continue; }
// Any stat affected by gems is sure to have a Stat History
StatHistory hist = getStatHistory(stat);
if (hist == null) { continue; }
//XTC//MMOItems.log("\u00a7a *\u00a7c>\u00a7a Found Stat History \u00a79" + stat.getId());
// History got gem registered?
StatData historicGemData = hist.getGemstoneData(gem.getHistoricUUID());
if (historicGemData == null) { continue;}
//XTC//MMOItems.log("\u00a7a *\u00a77 Found data for gem \u00a7e" + gem.getHistoricUUID());
// This gemstone had this data... Override.
restored.setData(stat, historicGemData); }
// That's it
//XTC//MMOItems.log("\u00a7a *\u00a77 Restored \u00a7e" + gem.getName() + "\u00a7a Successfully");
return restored;
}
@Nullable String asGemColor;
@NotNull UUID asGemUUID = UUID.randomUUID();
@ -411,6 +467,7 @@ public class MMOItem implements ItemReference {
* @param gemUUID UUID of gem to remove
* @param color Color of the gem socket to restore. <code>null</code> to not restore socket.
*/
@SuppressWarnings("ConstantConditions")
public void removeGemStone(@NotNull UUID gemUUID, @Nullable String color) {
// Get gemstone data

View File

@ -3,7 +3,6 @@ package net.Indyuce.mmoitems.api.util;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.util.Ref;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
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;
@ -182,7 +181,7 @@ public class MMOItemReforger {
//UPDT//MMOItems.log("Determined Level: \u00a7e" + determinedItemLevel);
// Restore stats
restorePreRNGStats(temporalDataHistory, options, template, determinedItemLevel);
restorePreRNGStats(temporalDataHistory, template, determinedItemLevel);
return; }
/*
@ -227,7 +226,7 @@ public class MMOItemReforger {
//UPDT//MMOItems.log("Determined Level: \u00a7e" + determinedItemLevel);
// Restore stats
restorePreRNGStats(temporalDataHistory, options, template, determinedItemLevel);
restorePreRNGStats(temporalDataHistory, template, determinedItemLevel);
// Choose enchantments to keep
if (options.shouldKeepEnchantments() && ambiguouslyOriginalEnchantmentCache != null) { ambiguouslyOriginalEnchantmentCache.identifyTrueOriginalEnchantments(mmoItem, cachedEnchantments);}
@ -261,7 +260,8 @@ public class MMOItemReforger {
return ret;
}
void restorePreRNGStats(@NotNull HashMap<ItemStat, StatHistory> backup, @NotNull ReforgeOptions options, @NotNull MMOItemTemplate template, int determinedItemLevel) {
@SuppressWarnings("ConstantConditions")
void restorePreRNGStats(@NotNull HashMap<ItemStat, StatHistory> backup, @NotNull MMOItemTemplate template, int determinedItemLevel) {
/*
* Extra step: Check every stat history
@ -815,6 +815,7 @@ public class MMOItemReforger {
// Gem Stones
if (cachedGemStones != null) {
//UPDT//MMOItems.log(" \u00a7a@ \u00a77Applying Gemstones");
ArrayList<GemstoneData> lostGems = new ArrayList<>();
// If has a upgrade template defined, just remember the level
if (buildingMMOItem.hasData(ItemStats.GEM_SOCKETS)) {
@ -824,6 +825,7 @@ public class MMOItemReforger {
GemSocketsData current = ((GemSocketsData) buildingMMOItem.getData(ItemStats.GEM_SOCKETS));
// Get those damn empty sockets
ArrayList<GemstoneData> putGems = new ArrayList<>();
ArrayList<String> availableSockets = new ArrayList<>(current.getEmptySlots());
ArrayList<GemstoneData> oldSockets = new ArrayList<>(cachedGemStones.getGemstones());
@ -835,44 +837,46 @@ public class MMOItemReforger {
if (availableSockets.size() <= 0) {
//UPDT//MMOItems.log(" \u00a7a +\u00a7c+ \u00a77No More Sockets");
// They all will fit anyway
break;
// This gemstone could not be inserted, it is thus lost
lostGems.add(data);
//UPDT//MMOItems.log("\u00a7c *\u00a7e*\u00a77 Gemstone lost - \u00a7cno socket \u00a78" + data.getHistoricUUID());
// Still some sockets to fill hMMM
} else {
// Get colour
// Get colour, uncolored if Unknown
String colour = data.getSocketColor();
String remembrance;
if (colour == null) { colour = GemSocketsData.getUncoloredGemSlot(); }
String remembrance = null;
// Not null?
if (colour != null) {
// Does the gem data have an available socket?
for (String slot : availableSockets) { if (slot.equals(GemSocketsData.getUncoloredGemSlot()) || colour.equals(slot)) { remembrance = slot; } }
// Contained? Remove
remembrance = colour;
// No colour data, just remove a random slot ig
} else {
// Get and remove
remembrance = availableSockets.get(0);
}
// Existed?
if (remembrance != null) {
//UPDT//MMOItems.log("\u00a7c *\u00a7e*\u00a77 Gemstone fit - \u00a7e " + remembrance + " \u00a78" + data.getHistoricUUID());
// 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();
// Remember as a put gem
putGems.add(data);
// No space/valid socket hmm
} else {
//UPDT//MMOItems.log("\u00a7c *\u00a7e*\u00a77 Gemstone lost - \u00a7cno color \u00a78" + data.getHistoricUUID());
// Include as lost gem
lostGems.add(data); }
}
}
// Create with select socket slots and gems
GemSocketsData primeGems = new GemSocketsData(availableSockets);
for (GemstoneData gem : cachedGemStones.getGemstones()) { if (gem == null) { continue; } primeGems.add(gem); }
for (GemstoneData gem : putGems) { if (gem == null) { continue; } primeGems.add(gem); }
// That's the original data
StatHistory gemStory = StatHistory.from(buildingMMOItem, ItemStats.GEM_SOCKETS);
@ -880,9 +884,23 @@ public class MMOItemReforger {
//HSY//MMOItems.log(" \u00a73-\u00a7a- \u00a77Restore Gemstones Recalculation \u00a73-\u00a7a-\u00a73-\u00a7a-\u00a73-\u00a7a-\u00a73-\u00a7a-");
buildingMMOItem.setData(ItemStats.GEM_SOCKETS, gemStory.recalculate(l));
}
// Could not fit any gems: No gem sockets!
} else {
//UPDT//MMOItems.log("\u00a7c *\u00a7e*\u00a77 All gemstones were lost - \u00a7cno data");
// ALl were lost
lostGems.addAll(cachedGemStones.getGemstones()); }
// Config option enabled? Build the lost gem MMOItems!
if (ReforgeOptions.dropRestoredGems) {
for (GemstoneData lost : lostGems) {
// Get MMOItem
MMOItem restoredGem = buildingMMOItem.extractGemstone(lost);
// Success?
if (restoredGem != null) { destroyedGems.add(restoredGem); } } }
}
//GEM//MMOItems.log(" \u00a7a>\u00a7e2 \u00a77Regenerated Gem Sockets:\u00a7f " + buildingMMOItem.getData(ItemStats.GEM_SOCKETS));
//GEM//if (buildingMMOItem.getData(ItemStats.GEM_SOCKETS) instanceof GemSocketsData) for (String str : SilentNumbers.transcribeList(new ArrayList<>(((GemSocketsData) buildingMMOItem.getData(ItemStats.GEM_SOCKETS)).getGemstones()), (s) -> (s instanceof GemstoneData ? ((GemstoneData) s).getHistoricUUID() + "\u00a7f " + ((GemstoneData) s).getName() : "null"))) { MMOItems.log(" \u00a7a+>\u00a7e2 \u00a77Gem: \u00a7a" + str); }
@ -951,4 +969,16 @@ public class MMOItemReforger {
if (mmoItem != null) { return;}
mmoItem = new VolatileMMOItem(nbtItem);
}
@NotNull ArrayList<MMOItem> destroyedGems = new ArrayList<>();
/**
* @return List of gems that have been destroyed from an item, presumably due
* to updating it without keeping gemstones, or they simply could not
* fit.
* <br>
* Currently, it is only ever filled if the config option drop-gems
* for item revision is enabled.
*/
@NotNull public ArrayList<MMOItem> getDestroyedGems() { return destroyedGems; }
}

View File

@ -1,7 +1,10 @@
package net.Indyuce.mmoitems.listener;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ReforgeOptions;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.util.MMOItemReforger;
import io.lumine.mythic.lib.api.item.NBTItem;
import org.bukkit.entity.EntityType;
@ -18,6 +21,8 @@ import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
public class ItemListener implements Listener {
@EventHandler(ignoreCancelled = true)
private void itemPickup(EntityPickupItemEvent e) {
@ -93,8 +98,41 @@ public class ItemListener implements Listener {
// Should this item be soulbound?
if (shouldSoulbind(nbt, type)) { mod.applySoulbound(player); }
// Return either the changed one or null
return mod.hasChanges() ? mod.toStack() : null;
// L
if (!mod.hasChanges()) { return null; }
// Perform all operations (including extracting lost gems)
ItemStack ret = mod.toStack();
// Give the gems to the player
if (ReforgeOptions.dropRestoredGems) {
//XTC//MMOItems.log("\u00a7a *\u00a7e*\u00a77 Dropping lost gemstones (\u00a73" + mod.getDestroyedGems().size() + "\u00a78)");
// Get Items
ArrayList<ItemStack> items = new ArrayList<>();
// Build and drop every lost gemstone
for (MMOItem item : mod.getDestroyedGems()) {
// Build
ItemStack built = item.newBuilder().build();
//XTC//MMOItems.log("\u00a7e *\u00a77 Saved " + SilentNumbers.getItemName(built));
// Include
items.add(built); }
// Drop those gems
for (ItemStack drop : player.getInventory().addItem(
items.toArray(new ItemStack[0])).values()) {
// Not air right
if (SilentNumbers.isAir(drop)) { continue; }
// Drop to the world
player.getWorld().dropItem(player.getLocation(), drop); } }
// Return the modified version
return ret;
}
/* Checks whether or not an item should be automatically soulbound */

View File

@ -222,6 +222,7 @@ public class ConfigManager implements Reloadable {
ConfigurationSection keepData = MMOItems.plugin.getConfig().getConfigurationSection("item-revision.keep-data");
ConfigurationSection phatLoots = MMOItems.plugin.getConfig().getConfigurationSection("item-revision.phat-loots");
ReforgeOptions.dropRestoredGems = MMOItems.plugin.getConfig().getBoolean("item-revision.drop-extra-gems", true);
revisionOptions = keepData != null ? new ReforgeOptions(keepData) : new ReforgeOptions(false, false, false, false, false, false, false, true);
phatLootsOptions = phatLoots != null ? new ReforgeOptions(phatLoots) : new ReforgeOptions(false, false, false, false, false, false, false, true);

View File

@ -6,8 +6,12 @@ import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.item.mmoitem.LiveMMOItem;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.stat.GemUpgradeScaling;
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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -122,8 +122,13 @@ public class DoubleStat extends ItemStat implements Upgradable, Previewable {
} }
// Add NBT Path
item.addItemTag(getAppliedNBT(data));
/*
* Add NBT Data if it is not equal to ZERO, in which case it will just get removed.
*
* It is important that the tags are not excluded in getAppliedNBT() because the StatHistory does
* need that blanc tag information to remember when an Item did not initially have any of a stat.
*/
if (((DoubleData) data).getValue() != 0) { item.addItemTag(getAppliedNBT(data)); }
}
@NotNull public static String formatPath(@NotNull String format, boolean moreIsBetter, double value) {

View File

@ -301,6 +301,11 @@ item-revision:
# ´reroll-when-updated´ is set to true.
keep-tiers: true
# If an item is updated, and the new version does not
# keep its gems, this will give the gems back to the
# player so that they don't get lost forever.
drop-extra-gems: true
# Whether or not specific stats should be kept
# when an item is updated to latest revision.
keep-data: