Significantly polished the Item Updater, and the way StatHistory saves and loads data from Gem Stones.

Gemstones now removable (via this *Unsocket* stat available for consumables)
This commit is contained in:
Gunging 2021-04-10 19:07:05 -05:00
parent 5eeaba43c7
commit fde1cdce5e
25 changed files with 732 additions and 272 deletions

View File

@ -102,7 +102,6 @@ public class ItemStats {
DURABILITY = new ItemDamage(),
CUSTOM_MODEL_DATA = new CustomModelData(),
MAX_DURABILITY = new MaximumDurability(),
DURABILITY_BAR = new DurabilityBar(),
WILL_BREAK = new LostWhenBroken(),
NAME = new DisplayName(),
LORE = new Lore(),
@ -191,6 +190,7 @@ public class ItemStats {
EQUIP_PRIORITY = new DoubleStat("EQUIP_PRIORITY", VersionMaterial.DIAMOND_HORSE_ARMOR.toMaterial(), "Equip Priority", new String[]{"Sets the level of priority this item has for the", "right click to swap equipped armor feature."}),
REQUIRED_BIOMES = new RequiredBiomes(),
DROP_ON_DEATH = new DisableDeathDrop(),
DURABILITY_BAR = new DurabilityBar(),
// Permanent Effects
PERM_EFFECTS = new PermanentEffects(),
@ -218,8 +218,6 @@ public class ItemStats {
ITEM_TYPE_RESTRICTION = new ItemTypeRestriction(),
MAX_CONSUME = new DoubleStat("MAX_CONSUME", Material.BLAZE_POWDER, "Max Consume", new String[]{"Max amount of usage before", "item disappears."}, new String[]{"consumable"}),
SUCCESS_RATE = new SuccessRate(),
COMPATIBLE_TYPES = new CompatibleTypes(),
COMPATIBLE_IDS = new CompatibleIds(),
// Crafting Stats
CRAFTING = new Crafting(),
@ -238,7 +236,11 @@ public class ItemStats {
LUTE_ATTACK_EFFECT = new LuteAttackEffectStat(),
NOTE_WEIGHT = new DoubleStat("NOTE_WEIGHT", VersionMaterial.MUSIC_DISC_MALL.toMaterial(), "Note Weight", new String[]{"Defines how the projectile cast", "by your lute tilts downwards."}, new String[]{"lute"}),
REMOVE_ON_CRAFT = new BooleanStat("REMOVE_ON_CRAFT", Material.GLASS_BOTTLE, "Remove on Craft", new String[]{"If the item should be completely", "removed when used in a recipe,", "or if it should become an", "empty bottle or bucket."}, new String[]{"all"}, Material.POTION, Material.SPLASH_POTION, Material.LINGERING_POTION, Material.MILK_BUCKET, Material.LAVA_BUCKET, Material.WATER_BUCKET),
COMPATIBLE_TYPES = new CompatibleTypes(),
COMPATIBLE_IDS = new CompatibleIds(),
GEM_SOCKETS = new GemSockets(),
RANDOM_UNSOCKET = new RandomUnsocket(),
//todo CAN_UNSOCKET = new CanUnsocket(),
REPAIR = new RepairPower(),
REPAIR_TYPE = new RepairType(),
KNOCKBACK = new DoubleStat("KNOCKBACK", VersionMaterial.IRON_HORSE_ARMOR.toMaterial(), "Knockback", new String[]{"Using this musket will knock", "the user back if positive."}, new String[]{"musket"}),

View File

@ -19,6 +19,7 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
@ -116,8 +117,11 @@ public class MMOUtils {
return type.equals(PotionEffectType.NIGHT_VISION) || type.equals(PotionEffectType.CONFUSION) ? 260 : type.equals(PotionEffectType.BLINDNESS) ? 140 : 80;
}
public static String getDisplayName(ItemStack item) {
return item.hasItemMeta() && item.getItemMeta().hasDisplayName() ? item.getItemMeta().getDisplayName() : caseOnWords(item.getType().name().toLowerCase().replace("_", " "));
@NotNull public static String getDisplayName(@Nullable ItemStack item) {
if (item == null) { return "null"; }
return (item.hasItemMeta() && item.getItemMeta().hasDisplayName()) ?
item.getItemMeta().getDisplayName() :
caseOnWords(item.getType().name().toLowerCase().replace("_", " "));
}
/**

View File

@ -18,6 +18,7 @@ import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -39,7 +40,7 @@ public class Consumable extends UseItem {
* @param target The item on which the consumable is being applied
* @return If the consumable was successfully applied on the item
*/
public boolean useOnItem(InventoryClickEvent event, NBTItem target) {
public boolean useOnItem(@NotNull InventoryClickEvent event, @NotNull NBTItem target) {
if (event.getClickedInventory() != event.getWhoClicked().getInventory())
return false;

View File

@ -21,6 +21,7 @@ import net.Indyuce.mmoitems.stat.data.type.StatData;
import net.Indyuce.mmoitems.stat.type.GemStoneStat;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import io.lumine.mythic.lib.api.item.NBTItem;
import net.Indyuce.mmoitems.stat.type.StatHistory;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Sound;
@ -29,6 +30,8 @@ import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public class GemStone extends UseItem {
public GemStone(Player player, NBTItem item) {
@ -94,31 +97,75 @@ public class GemStone extends UseItem {
*/
LiveMMOItem gemMMOItem = new LiveMMOItem(getNBTItem());
GemstoneData gemData = new GemstoneData(gemMMOItem, foundSocketColor);
sockets.apply(gemType, gemData);
//UPGRD//MMOItems. Log("Applying Gemstone: \u00a73" + foundSocketColor);
/*
* Now must apply the gem sockets data to the Stat History and then recalculate.
*
* Gotta, however, find the correct StatData to which apply it to. Damn this can
* be pretty complicated!
*/
StatHistory gemStory = StatHistory.from(targetMMO, ItemStats.GEM_SOCKETS);
// Original?
if (((GemSocketsData) gemStory.getOriginalData()).getEmptySocket(gemType) != null) {
//UPGRD//MMOItems.log("\u00a77Applied Gemstone @\u00a76Original\u00a77: \u00a73" + foundSocketColor);
// Charmer
((GemSocketsData) gemStory.getOriginalData()).apply(gemType, gemData);
} else {
// Check gem gems lol
boolean success = false;
for (UUID registeredGem : gemStory.getAllGemstones()) {
// Get that gem
GemSocketsData registeredGemData = (GemSocketsData) gemStory.getGemstoneData(registeredGem);
if (registeredGemData == null) { continue; }
if (registeredGemData.getEmptySocket(gemType) != null) {
//UPGRD//MMOItems.log("\u00a77Applied Gemstone @\u00a76Gemstone\u00a77: \u00a73" + foundSocketColor);
// Charmer
success = true;
registeredGemData.apply(gemType, gemData); break; } }
if (!success) {
for (StatData extraneousGem : gemStory.getExternalData()) {
// Get that gem
GemSocketsData registeredGemData = (GemSocketsData) extraneousGem;
if (registeredGemData == null) { continue; }
if (registeredGemData.getEmptySocket(gemType) != null) {
//UPGRD//MMOItems.log("\u00a77Applied Gemstone @\u00a76External\u00a77: \u00a73" + foundSocketColor);
// Charmer
registeredGemData.apply(gemType, gemData); break; } } } }
// Recalculate
targetMMO.setData(ItemStats.GEM_SOCKETS, gemStory.recalculate(targetMMO.getUpgradeLevel()));
//UPGRD//MMOItems.log("Applied Gemstone: \u00a73" + foundSocketColor);
/*
* Get the item's level, important for the GemScalingStat
*/
Integer levelIdentified = null; String scaling = GemUpgradeScaling.SUBSEQUENT;
if (gemMMOItem.hasData(ItemStats.GEM_UPGRADE_SCALING)) { scaling = gemMMOItem.getData(ItemStats.GEM_UPGRADE_SCALING).toString(); }
//UPGRD//MMOItems. Log("Scaling Identified: \u00a73" + scaling);
//UPGRD//MMOItems.log("Scaling Identified: \u00a73" + scaling);
switch (scaling) {
case GemUpgradeScaling.HISTORIC:
levelIdentified = 0;
break;
case GemUpgradeScaling.SUBSEQUENT:
StatData upgradeLevel = targetMMO.getData(ItemStats.UPGRADE);
if (upgradeLevel != null) { levelIdentified = ((UpgradeData) upgradeLevel).getLevel(); }
levelIdentified = targetMMO.getUpgradeLevel();
break;
case GemUpgradeScaling.NEVER:
default:
levelIdentified = null;
break;
}
gemData.setLevel(levelIdentified);
//UPGRD//MMOItems. Log("Set Level: \u00a7b" + gemData.getLevel());
default: break; }
gemData.setLevel(levelIdentified);
//UPGRD//MMOItems.log("Set Level: \u00a7b" + gemData.getLevel());
/*
* Only applies NON PROPER and MERGEABLE item stats
*/
@ -132,7 +179,7 @@ public class GemStone extends UseItem {
// If the data is MERGEABLE
if (data instanceof Mergeable) {
//GEM////UPGRD//MMOItems. Log("\u00a79>>> \u00a77Gem-Merging \u00a7c" + stat.getNBTPath());
//UPGRD//MMOItems.log("\u00a79>>> \u00a77Gem-Merging \u00a7c" + stat.getNBTPath());
// Merge into it
targetMMO.mergeData(stat, data, gemData.getHistoricUUID());

View File

@ -7,7 +7,6 @@ import net.Indyuce.mmoitems.api.UpgradeTemplate;
import net.Indyuce.mmoitems.api.item.ItemReference;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.stat.Enchants;
import net.Indyuce.mmoitems.stat.GemSockets;
import net.Indyuce.mmoitems.stat.data.GemSocketsData;
import net.Indyuce.mmoitems.stat.data.GemstoneData;
import net.Indyuce.mmoitems.stat.data.UpgradeData;
@ -63,31 +62,32 @@ 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 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);
}
// Recalculate
//GEM//MMOItems.log(" \u00a76+++\u00a77 Recalculating...");
setData(stat, sHistory.recalculate(getUpgradeLevel()));
// Merging means replacing if it cannot be merged
@ -321,38 +321,51 @@ public class MMOItem implements ItemReference {
* from calling it much because it completely loads all the stats
* of every Gem Stone.
*
* @see #getColor()
* @see #getAsGemColor()
*
* @return The list of GemStones contained here.
*/
@NotNull public ArrayList<MMOItem> extractGemstones() {
//XTC//MMOItems.log("\u00a73 *\u00a77 Extracting gems from this\u00a7b " + getType() + " " + getId());
// Found?
GemSocketsData thisSocketsData = (GemSocketsData) getData(ItemStats.GEM_SOCKETS);
if (thisSocketsData == null) { return new ArrayList<>(); }
if (thisSocketsData == null) {
//XTC//MMOItems.log("\u00a7a *\u00a77 Clear array - no data");
return new ArrayList<>(); }
// All right, whats all yous data
HashMap<UUID, MMOItem> regeneratedGems = new HashMap<>();
for (GemstoneData gem : thisSocketsData.getGemstones()) {
//XTC//MMOItems.log("\u00a7a *\u00a77 Found 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) {
restored.color = gem.getSocketColor();
//XTC//MMOItems.log("\u00a7a *\u00a73>\u00a77 Valid, regenerated \u00a7e" + ((StringData) restored.getData(ItemStats.NAME)));
restored.asGemColor = gem.getSocketColor();
restored.asGemUUID = gem.getHistoricUUID();
regeneratedGems.put(gem.getHistoricUUID(), restored);
//XTC//MMOItems.log("\u00a7a >\u00a77 Color \u00a7e" + restored.getAsGemColor());
//XTC//MMOItems.log("\u00a7a >\u00a77 UUID \u00a7e" + restored.getAsGemUUID().toString());
} }
//XTC//MMOItems.log("\u00a7b *\u00a77 Regen Size:\u00a79 " + regeneratedGems.values().size());
// Identify actual attributes
for (ItemStat stat : getStats()) {
// Mergeable right
if (!(stat.getClearStatData() instanceof Mergeable)) { continue; }
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; }
if (hist == null) {
continue; }
//XTC//MMOItems.log("\u00a7a *\u00a7c>\u00a7a Found Stat History \u00a79" + stat.getId());
// Data associated with any of the gems?
for (Map.Entry<UUID, MMOItem> gem : regeneratedGems.entrySet()) {
@ -360,6 +373,7 @@ public class MMOItem implements ItemReference {
// History got gem registered?
StatData historicGemData = hist.getGemstoneData(gem.getKey());
if (historicGemData == null) { continue;}
//XTC//MMOItems.log("\u00a7a *\u00a77 Found data for gem \u00a7e" + gem.getKey());
// This gemstone had this data... Override.
gem.getValue().setData(stat, historicGemData);
@ -367,16 +381,54 @@ public class MMOItem implements ItemReference {
} }
// Thats the gemstones we was searching for
//XTC//MMOItems.log("\u00a7b *\u00a77 Result Size:\u00a79 " + regeneratedGems.values().size());
return new ArrayList<>(regeneratedGems.values());
}
@Nullable String color;
@Nullable String asGemColor;
@NotNull UUID asGemUUID = UUID.randomUUID();
/**
* @return Supposing this MMOItem is a Gem Stone within an item,
* obtained via {@link #extractGemstones()}, then this
* will be the color of the slot it occupies.
*/
@Nullable public String getColor() { return color; }
@Nullable public String getAsGemColor() { return asGemColor; }
/**
* @return Supposing this MMOItem is a Gem Stone within an item,
* obtained via {@link #extractGemstones()}, then what
* was its UUID?
*/
@NotNull public UUID getAsGemUUID() { return asGemUUID; }
/**
* Deletes this UUID from all Stat Histories.
*
* @param gemUUID UUID of gem to remove
* @param color Color of the gem socket to restore. <code>null</code> to not restore socket.
*/
public void removeGemStone(@NotNull UUID gemUUID, @Nullable String color) {
// Get gemstone data
if (!hasData(ItemStats.GEM_SOCKETS)) { return; }
//GEM//MMOItems.log("\u00a7b-\u00a78-\u00a79-\u00a77 Extracting \u00a7e" + gemUUID.toString());
StatHistory gemStory = StatHistory.from(this, ItemStats.GEM_SOCKETS);
/*
* We must only find the StatData where this gem resides,
* and eventually the StatHistories of the affected stats
* will purge themselves from extraneous gems (that are
* no longer registered onto the GEM_SOCKETS history).
*/
if (GemSocketsData.removeGemFrom(((GemSocketsData) gemStory.getOriginalData()), gemUUID, color)) { return; }
// Attempt gems
for (UUID gemDataUUID : gemStory.getAllGemstones()) { if (GemSocketsData.removeGemFrom(((GemSocketsData) gemStory.getGemstoneData(gemDataUUID)), gemUUID, color)) { return; } }
// Attempt externals
for (StatData externalData : gemStory.getExternalData()) { if (GemSocketsData.removeGemFrom(((GemSocketsData) externalData), gemUUID, color)) { return; } }
}
//endregion
}

View File

@ -14,7 +14,9 @@ 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.FFPMMOItems;
import net.Indyuce.mmoitems.stat.DisplayName;
import net.Indyuce.mmoitems.stat.Enchants;
import net.Indyuce.mmoitems.stat.Lore;
import net.Indyuce.mmoitems.stat.RevisionID;
import net.Indyuce.mmoitems.stat.data.*;
import net.Indyuce.mmoitems.stat.data.random.RandomStatData;
@ -163,7 +165,27 @@ public class MMOItemReforger {
*/
@SuppressWarnings("ConstantConditions")
public void update(@Nullable RPGPlayer player, @NotNull ReforgeOptions options) {
if (options.isRegenerate()) { regenerate(player); return; }
// Initialize as Volatile, find source template. GemStones require a Live MMOItem though (to correctly load all Stat Histories and sh)
loadLiveMMOItem();
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(mmoItem.getType(), mmoItem.getId()); ItemMeta meta = nbtItem.getItem().getItemMeta();
if (template == null) { MMOItems.print(null, "Could not find template for $r{0} {1}$b. ", "MMOItems Reforger", mmoItem.getType().toString(), mmoItem.getId()); mmoItem = null; return; }
Validate.isTrue(meta != null, FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Invalid item meta prevented $f{0}$b from updating.", template.getType().toString() + " " + template.getId()));
// Skip all this trash and just regenerate completely
if (options.isRegenerate()) {
// Store all the history of stat proceedings.
HashMap<ItemStat, StatHistory> temporalDataHistory = extratStatDataHistory(options);
/*
* Generate fresh MMOItem, with stats that will be set if the chance is too low
*/
int determinedItemLevel = regenerate(player, template);
//UPDT//MMOItems.log("Determined Level: \u00a7e" + determinedItemLevel);
// Restore stats
restorePreRNGStats(temporalDataHistory, options, template, determinedItemLevel);
return; }
/*
* Has to store every stat into itemData, then check each stat of
@ -178,13 +200,6 @@ public class MMOItemReforger {
* 3: If the stat is gone completely, its again a ZERO chance
* so it is removed (the updated value of 0 prevailing).
*/
// Initialize as Volatile, find source template. GemStones require a Live MMOItem though (to correctly load all Stat Histories and sh)
loadLiveMMOItem();
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(mmoItem.getType(), mmoItem.getId()); ItemMeta meta = nbtItem.getItem().getItemMeta();
//noinspection ConstantConditions
Validate.isTrue(meta != null, FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Invalid item meta prevented $f{0}$b from updating.", template.getType().toString() + " " + template.getId()));
// Keep name
if (options.shouldKeepName()) { keepName(meta); }
@ -205,22 +220,7 @@ public class MMOItemReforger {
if (options.shouldKeepSoulbind() && mmoItem.hasData(ItemStats.SOULBOUND)) { keepSoulbound(); }
// Store all the history of stat proceedings.
HashMap<ItemStat, StatHistory> temporalDataHistory = new HashMap<>();
//UPDT//MMOItems.log(" \u00a71 * \u00a77Remembering Stats");
for (ItemStat stat : mmoItem.getStats()) {
//UPDT//MMOItems.log(" \u00a79 * \u00a77Stat \u00a7f" + stat.getNBTPath());
// Skip if it cant merge
if (!(stat.getClearStatData() instanceof Mergeable)) { continue; }
StatHistory hist = StatHistory.from(mmoItem, stat);
//UPDT//MMOItems.log(" \u00a73 * \u00a77History of \u00a7f" + hist.getItemStat().getNBTPath());
// Clear externals
if (!options.shouldKeepExternalSH()) { hist.getExternalData().clear(); }
// Get and set
temporalDataHistory.put(hist.getItemStat(), hist); }
HashMap<ItemStat, StatHistory> temporalDataHistory = extratStatDataHistory(options);
/*
* Generate fresh MMOItem, with stats that will be set if the chance is too low
@ -228,15 +228,24 @@ public class MMOItemReforger {
int determinedItemLevel = regenerate(player, template);
//UPDT//MMOItems.log("Determined Level: \u00a7e" + determinedItemLevel);
// Restore stats
restorePreRNGStats(temporalDataHistory, options, template, determinedItemLevel);
// Choose enchantments to keep
if (options.shouldKeepEnchantments() && ambiguouslyOriginalEnchantmentCache != null) { ambiguouslyOriginalEnchantmentCache.identifyTrueOriginalEnchantments(mmoItem, cachedEnchantments);}
}
void restorePreRNGStats(@NotNull HashMap<ItemStat, StatHistory> backup, @NotNull ReforgeOptions options, @NotNull MMOItemTemplate template, int determinedItemLevel) {
/*
* Extra step: Check every stat history
*/
int l = mmoItem.getUpgradeLevel();
for (ItemStat stat : temporalDataHistory.keySet()) {
for (ItemStat stat : backup.keySet()) {
//UPDT//MMOItems.log("\u00a7e @\u00a77 " + stat.getId());
// Get history
StatHistory hist = temporalDataHistory.get(stat);
StatHistory hist = backup.get(stat);
if (hist == null) { continue; }
// Alr what the template say
@ -282,7 +291,7 @@ public class MMOItemReforger {
// Make a clear one
clear = new StatHistory(mmoItem, stat, finalData);
// Data arguably fine tbh, just use previous
// Data arguably fine tbh, just use previous
} else {
//UPDT//MMOItems.log("\u00a7a +\u00a77 Acceptable Range --- kept");
@ -293,8 +302,17 @@ public class MMOItemReforger {
} else {
//UPDT//MMOItems.log("\u00a7e +\u00a77 Not contained / unmerged --- reroll I suppose");
// Make a clear one
clear = new StatHistory(mmoItem, stat, hist.getOriginalData());
// Delete lore
if (ItemStats.LORE.equals(stat) || ItemStats.NAME.equals(stat)) {
// Keep regenerated one
clear = new StatHistory(mmoItem, stat, mmoItem.getData(stat));
} else {
// Make a clear one
clear = new StatHistory(mmoItem, stat, hist.getOriginalData());
}
}
// Keep Gemstone and Extraneous data
@ -306,9 +324,29 @@ public class MMOItemReforger {
mmoItem.setStatHistory(stat, clear);
mmoItem.setData(stat, clear.recalculate(false, l));
}
}
// Choose enchantments to keep
if (options.shouldKeepEnchantments() && ambiguouslyOriginalEnchantmentCache != null) { ambiguouslyOriginalEnchantmentCache.identifyTrueOriginalEnchantments(mmoItem, cachedEnchantments);}
@NotNull HashMap<ItemStat, StatHistory> extratStatDataHistory(@NotNull ReforgeOptions options) {
HashMap<ItemStat, StatHistory> ret = new HashMap<>();
//UPDT//MMOItems.log(" \u00a71 * \u00a77Remembering Stats");
for (ItemStat stat : mmoItem.getStats()) {
//UPDT//MMOItems.log(" \u00a79 * \u00a77Stat \u00a7f" + stat.getNBTPath());
// Skip if it cant merge
if (!(stat.getClearStatData() instanceof Mergeable)) { continue; }
StatHistory hist = StatHistory.from(mmoItem, stat);
//UPDT//MMOItems.log(" \u00a73 * \u00a77History of \u00a7f" + hist.getItemStat().getNBTPath());
// Clear externals
if (!options.shouldKeepExternalSH()) { hist.getExternalData().clear(); }
// Get and set
ret.put(hist.getItemStat(), hist); }
// Yes
return ret;
}
/**
@ -420,6 +458,7 @@ public class MMOItemReforger {
// Initialize as Volatile, find source template. GemStones require a Live MMOItem though (to correctly load all Stat Histories and sh)
if (!options.shouldKeepGemStones() && !options.shouldKeepExternalSH()) { loadVolatileMMOItem(); } else { loadLiveMMOItem(); }
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(mmoItem.getType(), mmoItem.getId()); ItemMeta meta = nbtItem.getItem().getItemMeta();
if (template == null) { MMOItems.print(null, "Could not find template for $r{0} {1}$b. ", "MMOItems Reforger", mmoItem.getType().toString(), mmoItem.getId()); mmoItem = null; return; }
//noinspection ConstantConditions
Validate.isTrue(meta != null, FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Invalid item meta prevented $f{0}$b from updating.", template.getType().toString() + " " + template.getId()));
@ -474,21 +513,30 @@ public class MMOItemReforger {
* lines are desirable to keep (Those that start with §7)
*/
void keepLore() {
if (!mmoItem.hasData(ItemStats.LORE)) { return; }
cachedLore = extractLore(((StringListData) mmoItem.getData(ItemStats.LORE)).getList());
}
@NotNull ArrayList<String> extractLore(@NotNull List<String> lore) {
//UPDT//MMOItems.log(" \u00a7d> \u00a77Keeping Lore");
ArrayList<String> ret = new ArrayList<>();
// Examine every element
for (String str : ((StringListData) mmoItem.getData(ItemStats.LORE)).getList()) {
for (String str : lore) {
//UPDT//MMOItems.log(" \u00a7d>\u00a7c-\u00a7e- \u00a77Line:\u00a7f " + str);
// Does it start with the promised...?
if (str.startsWith("\u00a77")) {
//UPDT//MMOItems.log(" \u00a72>\u00a7a-\u00a7e- \u00a77Kept");
cachedLore.add(str); }
ret.add(str); }
}
//UPDT//MMOItems.log(" \u00a7d> \u00a77Result");
//UPDT//for (String lr : cachedLore) { //UPDT//MMOItems.log(" \u00a7d + \u00a77" + lr); }
return ret;
}
/**
*
* Step #1: Identify the current (not-null) enchantment data (creates one if missing)

View File

@ -46,6 +46,8 @@ public enum Message {
SKIN_BROKE("Your skin broke while trying to apply it onto your &6#item#&c."),
SKIN_REJECTED("A skin has already been applied onto your &6#item#&c!"),
SKIN_INCOMPATIBLE("This skin is not compatible with your &6#item#&c!"),
RANDOM_UNSOCKET_GEM_TOO_OLD("The gems have bonded strongly with your item. Cannot remove."),
RANDOM_UNSOCKET_SUCCESS("&aYou removed &3#gem# &afrom your &6#item#&a!"),
// soulbound
CANT_BIND_ITEM("This item is currently linked to #player# by a Lvl #level# soulbound. You will have to break this soulbound first."),

View File

@ -183,8 +183,7 @@ public class RecipeManager implements Reloadable {
blueprint.deploy(MythicRecipeStation.SMITHING, nk);
// Remember it
if (nk.getValue() != null) { customRecipes.put(nk.getValue(), blueprint); } else { booklessRecipes.add(blueprint);
if (book) { MMOItems.print(null, "Cannot register custom smithing recipe for $e{0} {1}$b into crafting book", "Custom Crafting", type.getId(), id);} }
if (nk.getValue() != null) { customRecipes.put(nk.getValue(), blueprint); } else { booklessRecipes.add(blueprint); }
}
/**

View File

@ -21,6 +21,7 @@ import net.Indyuce.mmoitems.api.util.message.Message;
import net.Indyuce.mmoitems.stat.type.BooleanStat;
import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction;
import io.lumine.mythic.lib.api.item.NBTItem;
import org.jetbrains.annotations.NotNull;
public class CanDeconstruct extends BooleanStat implements ConsumableItemInteraction {
public CanDeconstruct() {
@ -30,7 +31,7 @@ public class CanDeconstruct extends BooleanStat implements ConsumableItemInterac
}
@Override
public boolean handleConsumableEffect(InventoryClickEvent event, PlayerData playerData, Consumable consumable, NBTItem target, Type targetType) {
public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) {
String itemTierTag = target.getString("MMOITEMS_TIER");
if (itemTierTag.equals("") || !consumable.getNBTItem().getBoolean("MMOITEMS_CAN_DECONSTRUCT"))
return false;

View File

@ -30,6 +30,7 @@ import io.lumine.mythic.lib.api.item.ItemTag;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.util.SmartGive;
import io.lumine.mythic.lib.version.VersionMaterial;
import org.jetbrains.annotations.NotNull;
public class CanDeskin extends BooleanStat implements ConsumableItemInteraction {
public CanDeskin() {
@ -39,7 +40,7 @@ public class CanDeskin extends BooleanStat implements ConsumableItemInteraction
// TODO needs some cleanup
@Override
public boolean handleConsumableEffect(InventoryClickEvent event, PlayerData playerData, Consumable consumable, NBTItem target, Type targetType) {
public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) {
String skinId = target.getString("MMOITEMS_SKIN_ID");
Player player = playerData.getPlayer();

View File

@ -17,6 +17,7 @@ import net.Indyuce.mmoitems.api.util.message.Message;
import net.Indyuce.mmoitems.stat.type.BooleanStat;
import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction;
import io.lumine.mythic.lib.api.item.NBTItem;
import org.jetbrains.annotations.NotNull;
public class CanIdentify extends BooleanStat implements ConsumableItemInteraction {
public CanIdentify() {
@ -25,7 +26,7 @@ public class CanIdentify extends BooleanStat implements ConsumableItemInteractio
}
@Override
public boolean handleConsumableEffect(InventoryClickEvent event, PlayerData playerData, Consumable consumable, NBTItem target, Type targetType) {
public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) {
if (targetType != null)
return false;

View File

@ -0,0 +1,74 @@
package net.Indyuce.mmoitems.stat;
import io.lumine.mythic.lib.api.item.NBTItem;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.event.item.DeconstructItemEvent;
import net.Indyuce.mmoitems.api.interaction.Consumable;
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.util.message.Message;
import net.Indyuce.mmoitems.stat.data.GemSocketsData;
import net.Indyuce.mmoitems.stat.type.BooleanStat;
import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* This item will be able to be used on other items, and
* pop up an unsocketing GUI if it would make sense.
*
* @author Gunging
*/
public class CanUnsocket extends BooleanStat implements ConsumableItemInteraction {
public CanUnsocket() {
super("CAN_UNSOCKET", Material.PAPER, "Can Unsocket?",
new String[] { "This item, when used on another item, if", "that other item has Gem Stones", "may be used to remove those Gems." },
new String[] { "consumable" });
}
@Override
public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) {
/*
* Cancel if the target is just not an MMOItem
*/
if (targetType == null) { return false; }
/*
* No Gemstones? No service
*/
MMOItem mmo = new VolatileMMOItem(target);
if (!mmo.hasData(ItemStats.GEM_SOCKETS)) { return false; }
GemSocketsData mmoGems = (GemSocketsData) mmo.getData(ItemStats.GEM_SOCKETS);
if (mmoGems == null || mmoGems.getGemstones().size() == 0) { return false; }
Player player = playerData.getPlayer();
/*
* All right do it correctly I guess.
*
* Cancel if no gem could be extracted.
*/
mmo = new LiveMMOItem(target);
ArrayList<MMOItem> mmoGemStones = mmo.extractGemstones();
if (mmoGemStones.size() == 0) {
Message.RANDOM_UNSOCKET_GEM_TOO_OLD.format(ChatColor.YELLOW, "#item#", MMOUtils.getDisplayName(event.getCurrentItem())).send(player);
return false; }
return true;
}
}

View File

@ -46,7 +46,7 @@ public class DisplayName extends StringStat {
String sB4 = suffix.substring(0, lvlOFFSET);
String aFt = suffix.substring(lvlOFFSET + "#lvl#".length());
String sB4_alt = sB4.replace("+", "-");
String aFt_alt = sB4.replace("+", "-");
String aFt_alt = aFt.replace("+", "-");
// Remove it
if (format.contains(sB4)) {

View File

@ -35,7 +35,7 @@ import org.jetbrains.annotations.Nullable;
public class GemSockets extends ItemStat {
public GemSockets() {
super("GEM_SOCKETS", Material.EMERALD, "Gem Sockets", new String[] { "The amount of gem", "sockets your weapon has." },
new String[] { "piercing", "slashing", "blunt", "offhand", "range", "tool", "armor", "accessory" });
new String[] { "piercing", "slashing", "blunt", "offhand", "range", "tool", "armor", "accessory", "!gem_stone" });
}
@Override

View File

@ -0,0 +1,141 @@
package net.Indyuce.mmoitems.stat;
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.MMOUtils;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.event.item.DeconstructItemEvent;
import net.Indyuce.mmoitems.api.interaction.Consumable;
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.util.message.Message;
import net.Indyuce.mmoitems.stat.data.DoubleData;
import net.Indyuce.mmoitems.stat.data.GemSocketsData;
import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction;
import net.Indyuce.mmoitems.stat.type.DoubleStat;
import net.Indyuce.mmoitems.stat.type.StatHistory;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
/**
* When used on an item with Gem Sockets Data, it will
* cause a random gem to pop out.
*
* @author Gunging
*/
public class RandomUnsocket extends DoubleStat implements ConsumableItemInteraction {
public RandomUnsocket() {
super("RANDOM_UNSOCKET", Material.BOWL, "Random Unsocket",
new String[] { "Number of gems (rounded down) that", "will pop out of an item when", "this is applied." },
new String[] { "consumable" });
}
@Override
public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) {
/*
* Cancel if the target is just not an MMOItem
*/
if (targetType == null) { return false; }
/*
* No Gemstones? No service
*/
MMOItem mmoVol = new VolatileMMOItem(target);
if (!mmoVol.hasData(ItemStats.GEM_SOCKETS)) { return false; }
GemSocketsData mmoGems = (GemSocketsData) mmoVol.getData(ItemStats.GEM_SOCKETS);
if (mmoGems == null || mmoGems.getGemstones().size() == 0) { return false; }
Player player = playerData.getPlayer();
/*
* All right do it correctly I guess.
*
* Cancel if no gem could be extracted.
*/
MMOItem mmo = new LiveMMOItem(target);
ArrayList<MMOItem> mmoGemStones = mmo.extractGemstones();
if (mmoGemStones.size() == 0) {
Message.RANDOM_UNSOCKET_GEM_TOO_OLD.format(ChatColor.YELLOW, "#item#", MMOUtils.getDisplayName(event.getCurrentItem())).send(player);
return false; }
// Get removed gems amount
int s = 1;
if (consumable.getMMOItem().hasData(ItemStats.RANDOM_UNSOCKET)) {
// Get
DoubleData unsocket = (DoubleData) consumable.getMMOItem().getData(ItemStats.RANDOM_UNSOCKET);
if (unsocket != null) { s = SilentNumbers.floor(unsocket.getValue()); } }
//GEM//for (String str : SilentNumbers.transcribeList(mmoGemStones, (lam) -> "\u00a73Found \u00a77 " + ((MMOItem) lam).getType().getId() + " " + ((MMOItem) lam).getId() )) { MMOItems.log(str); };
// Drop gemstones to the ground :0
ArrayList<ItemStack> items2Drop = new ArrayList<>();
while (s > 0 && mmoGemStones.size() > 0) {
/*
* Choose a gem to drop :)
*/
int randomGem = SilentNumbers.floor(SilentNumbers.randomRange(0, mmoGemStones.size()));
if (randomGem >= mmoGemStones.size()) { randomGem = mmoGemStones.size() - 1;}
// Choose gem
MMOItem gem = mmoGemStones.get(randomGem);
mmoGemStones.remove(randomGem);
//GEM//MMOItems.log("\u00a73 *\u00a77 Chose to remove\u00a7b " + gem.getType() + " " + gem.getId());
try {
// Drop?
ItemStack builtGem = gem.newBuilder().build();
//GEM//MMOItems.log("\u00a73 *\u00a77 Built " + SilentNumbers.getItemName(builtGem));
// Valid?
if (!SilentNumbers.isAir(builtGem)) {
// Drop
items2Drop.add(builtGem);
String chosenColor;
if (gem.getAsGemColor() != null) {
//GEM//MMOItems.log("\u00a7b *\u00a77 Restored slot\u00a7e " + gem.getAsGemColor());
chosenColor = gem.getAsGemColor(); } else {
//GEM//MMOItems.log("\u00a7b *\u00a77 Restored slot\u00a76 " + GemSocketsData.getUncoloredGemSlot() + " \u00a78(Uncolored Def)");
chosenColor = GemSocketsData.getUncoloredGemSlot(); }
// Unregister
mmo.removeGemStone(gem.getAsGemUUID(), chosenColor);
// Gem Removal Count Decreased
s--;
// Message
Message.RANDOM_UNSOCKET_SUCCESS.format(ChatColor.YELLOW, "#item#", MMOUtils.getDisplayName(event.getCurrentItem()), "#gem#", MMOUtils.getDisplayName(builtGem)).send(player);
}
} catch (Throwable e) { MMOItems.print(Level.WARNING, "Could not unsocket gem from item $u{0}$b: $f{1}", "Stat \u00a7eRandom Unsocket", SilentNumbers.getItemName(event.getCurrentItem()), e.getMessage()); }
}
// Replace
mmo.setData(ItemStats.GEM_SOCKETS, StatHistory.from(mmo, ItemStats.GEM_SOCKETS).recalculate(mmo.getUpgradeLevel()));
//GEM//MMOItems.log("\u00a7b*\u00a77 Final at \u00a7b" + ((GemSocketsData) mmo.getData(ItemStats.GEM_SOCKETS)).getEmptySlots().size() + " Empty\u00a77 and \u00a7e" + ((GemSocketsData) mmo.getData(ItemStats.GEM_SOCKETS)).getGemstones().size() + " Gems");
event.setCurrentItem(mmo.newBuilder().build());
// Give the gems back
for (ItemStack drop : player.getInventory().addItem(items2Drop.toArray(new ItemStack[0])).values()) player.getWorld().dropItem(player.getLocation(), drop);
player.playSound(player.getLocation(), Sound.BLOCK_IRON_DOOR_OPEN, 1, 2);
return true;
}
}

View File

@ -21,6 +21,7 @@ import net.Indyuce.mmoitems.api.util.message.Message;
import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction;
import net.Indyuce.mmoitems.stat.type.DoubleStat;
import io.lumine.mythic.lib.api.item.NBTItem;
import org.jetbrains.annotations.NotNull;
public class RepairPower extends DoubleStat implements ConsumableItemInteraction {
public RepairPower() {
@ -29,7 +30,7 @@ public class RepairPower extends DoubleStat implements ConsumableItemInteraction
}
@Override
public boolean handleConsumableEffect(InventoryClickEvent event, PlayerData playerData, Consumable consumable, NBTItem target, Type targetType) {
public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) {
int repairPower = (int) consumable.getNBTItem().getStat(ItemStats.REPAIR.getId());
if (repairPower <= 0)
return false;

View File

@ -23,6 +23,7 @@ import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction;
import net.Indyuce.mmoitems.stat.type.DoubleStat;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.version.VersionMaterial;
import org.jetbrains.annotations.NotNull;
public class SoulbindingBreakChance extends DoubleStat implements ConsumableItemInteraction {
private static final Random random = new Random();
@ -35,7 +36,7 @@ public class SoulbindingBreakChance extends DoubleStat implements ConsumableItem
}
@Override
public boolean handleConsumableEffect(InventoryClickEvent event, PlayerData playerData, Consumable consumable, NBTItem target, Type targetType) {
public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) {
Player player = playerData.getPlayer();
double soulboundBreakChance = consumable.getNBTItem().getStat("SOULBOUND_BREAK_CHANCE");

View File

@ -23,6 +23,7 @@ import net.Indyuce.mmoitems.stat.type.ConsumableItemInteraction;
import net.Indyuce.mmoitems.stat.type.DoubleStat;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.version.VersionMaterial;
import org.jetbrains.annotations.NotNull;
public class SoulbindingChance extends DoubleStat implements ConsumableItemInteraction {
private static final Random random = new Random();
@ -34,7 +35,7 @@ public class SoulbindingChance extends DoubleStat implements ConsumableItemInter
}
@Override
public boolean handleConsumableEffect(InventoryClickEvent event, PlayerData playerData, Consumable consumable, NBTItem target, Type targetType) {
public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) {
Player player = playerData.getPlayer();
double soulbindingChance = consumable.getNBTItem().getStat("SOULBINDING_CHANCE");

View File

@ -170,7 +170,7 @@ public class UpgradeStat extends ItemStat implements ConsumableItemInteraction {
public StatData getClearStatData() { return new UpgradeData(null, null, false, false, 0, 0D); }
@Override
public boolean handleConsumableEffect(InventoryClickEvent event, PlayerData playerData, Consumable consumable, NBTItem target, Type targetType) {
public boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, Type targetType) {
VolatileMMOItem mmoitem = consumable.getMMOItem();
Player player = playerData.getPlayer();

View File

@ -1,9 +1,6 @@
package net.Indyuce.mmoitems.stat.data;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import io.lumine.mythic.lib.api.util.Ref;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
@ -72,21 +69,20 @@ public class GemSocketsData implements StatData, Mergeable, RandomStatData {
*/
@Nullable public String getEmptySocket(@NotNull String gem) {
for (String slot : emptySlots)
if (gem.equals("") || slot.equals(MMOItems.plugin.getConfig().getString("gem-sockets.uncolored")) || gem.equals(slot))
if (gem.equals("") || slot.equals(getUncoloredGemSlot()) || gem.equals(slot))
return slot;
return null;
}
@NotNull public static String getUncoloredGemSlot() { String s = MMOItems.plugin.getConfig().getString("gem-sockets.uncolored"); return s == null ? "Uncolored" : s; }
public void add(GemstoneData gem) {
gems.add(gem);
}
public void apply(String gem, GemstoneData gemstone) {
emptySlots.remove(getEmptySocket(gem));
gems.add(gemstone);
}
public void apply(String gem, GemstoneData gemstone) { emptySlots.remove(getEmptySocket(gem)); gems.add(gemstone); }
public void addEmptySlot(String slot) {
public void addEmptySlot(@NotNull String slot) {
emptySlots.add(slot);
}
@ -98,6 +94,47 @@ public class GemSocketsData implements StatData, Mergeable, RandomStatData {
return gems;
}
public void removeGem(@NotNull UUID gem) {
// Find
GemstoneData d = null;
for (GemstoneData data : getGemstones()) {
if (data.getHistoricUUID().equals(gem)) {
//GEM//MMOItems.log("\u00a7b*\u00a77 Found gem to unregister: \u00a7a" + data.getName());
d = data; break; }}
// Remove
gems.remove(d);
}
/**
* Removes such gem from this GemSocketsData, if it exists.
*
* @param data The Data from which to remove the gem
* @param gemUUID The Gem to remove
* @param socket The socket color to replace the gem with, <code>null</code> for no socket.
* @return Whether a gem was removed from the data.
*/
public static boolean removeGemFrom(@NotNull GemSocketsData data, @NotNull UUID gemUUID, @Nullable String socket) {
boolean removal = false;
for (GemstoneData gem : data.getGemstones()) {
// Is it the one we are searching for?
if (gem.getHistoricUUID().equals(gemUUID)) {
// Found it, restore the socket and we're done.
if (socket != null) { data.addEmptySlot(socket); }
// Remove
removal = true; break; } }
// Its time.
if (removal) { data.removeGem(gemUUID); }
return removal;
}
public JsonObject toJson() {
JsonObject object = new JsonObject();
@ -115,17 +152,29 @@ public class GemSocketsData implements StatData, Mergeable, RandomStatData {
@Override
public void merge(StatData data) {
Validate.isTrue(data instanceof GemSocketsData, "Cannot merge two different stat data types");
//MRG//MMOItems.log("\u00a73||| \u00a77Merging slots; Original:");
//MRG//for (String str : SilentNumbers.transcribeList(emptySlots, (s) -> "\u00a73|+ \u00a77" + ((String) s))) { MMOItems.log(str); }
//MRG//MMOItems.log("\u00a73||| \u00a77Including");
//MRG//for (String str : SilentNumbers.transcribeList(((GemSocketsData) data).emptySlots, (s) -> "\u00a73|+ \u00a77" + ((String) s))) { MMOItems.log(str); }
// Combine both actual gems, and empty slots
emptySlots.addAll(((GemSocketsData) data).emptySlots);
gems.addAll(((GemSocketsData) data).getGemstones());
//MRG//MMOItems.log("\u00a73||| \u00a7aResult");
//MRG//for (String str : SilentNumbers.transcribeList(emptySlots, (s) -> "\u00a73|+ \u00a77" + ((String) s))) { MMOItems.log(str); }
//MRG//MMOItems.log("\u00a73||| \u00a7a---------------------------------------");
}
@Override
public @NotNull StatData cloneData() {
// Start Fresh
GemSocketsData ret = new GemSocketsData(emptySlots);
GemSocketsData ret = new GemSocketsData(new ArrayList<>(emptySlots));
// Add Gems
for (GemstoneData g : getGemstones()) { ret.add(g); }
for (GemstoneData g : getGemstones()) { ret.add(g.cloneGem()); }
return ret;
}

View File

@ -20,6 +20,20 @@ public class GemstoneData {
@NotNull private final String name;
@Nullable Integer levelPut = 0;
@NotNull final UUID historicUUID;
@Nullable final String mmoitemType;
@Nullable final String mmoitemID;
@Nullable String socketColor;
public GemstoneData cloneGem() {
GemstoneData ret = new GemstoneData(getName(), getMMOItemType(), getMMOItemID(), getSocketColor(), getHistoricUUID());
for (AbilityData d : abilities) { ret.addAbility(d); }
for (PotionEffectData d : effects) { ret.addPermanentEffect(d); }
for (ItemStat d : stats.keySet()) { ret.setStat(d, stats.get(d)); }
ret.setLevel(getLevel());
return ret;
}
/**
* Gemstone equals method is for practical purposes and only checks that
@ -53,11 +67,6 @@ public class GemstoneData {
return socketColor;
}
@Nullable final String mmoitemType;
@Nullable final String mmoitemID;
@Nullable
String socketColor;
/**
* This constructor is not really performance friendly. It should only be
* used when applying gem stones to keep max performance.
@ -171,11 +180,24 @@ public class GemstoneData {
*/
public GemstoneData(@NotNull String name, @Nullable String type, @Nullable String id, @Nullable String color) {
this.name = name;
mmoitemID = type;
mmoitemType = id;
mmoitemID = id;
mmoitemType = type;
socketColor = color;
historicUUID = UUID.randomUUID();
}
historicUUID = UUID.randomUUID(); }
/**
* This is a completely empty builder.
* <p></p>
* You may add whatever you want with <code>addAbility()</code>,<code>addPermamentEffect</code>, or most widely usedly, <code>setStat()</code>.
* @param name Name to display in the lore of the item when you put the gemstone into it.
* @param color Color of the socket this gem is inserted onto
*/
public GemstoneData(@NotNull String name, @Nullable String type, @Nullable String id, @Nullable String color, @NotNull UUID uiid) {
this.name = name;
mmoitemID = id;
mmoitemType = type;
socketColor = color;
historicUUID = uiid; }
/**
* Add an ability to this Gem Stone
@ -227,6 +249,7 @@ public class GemstoneData {
if (mmoitemID != null) { object.addProperty("Id", mmoitemID); }
if (mmoitemType != null) { object.addProperty("Type", mmoitemType); }
object.addProperty("Level", levelPut);
object.addProperty("Color", socketColor);
/*
* These seem obsolete. Abilities, effects, and stats, are merged into the

View File

@ -6,6 +6,8 @@ import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.interaction.Consumable;
import net.Indyuce.mmoitems.api.player.PlayerData;
import io.lumine.mythic.lib.api.item.NBTItem;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Stats which implement a consumable action like deconstructing, identifying,
@ -29,5 +31,5 @@ public interface ConsumableItemInteraction {
* (basically return true if it should be the only
* consumable effect applied).
*/
public boolean handleConsumableEffect(InventoryClickEvent event, PlayerData playerData, Consumable consumable, NBTItem target, Type targetType);
boolean handleConsumableEffect(@NotNull InventoryClickEvent event, @NotNull PlayerData playerData, @NotNull Consumable consumable, @NotNull NBTItem target, @Nullable Type targetType);
}

View File

@ -14,6 +14,7 @@ import net.Indyuce.mmoitems.stat.data.type.Mergeable;
import net.Indyuce.mmoitems.stat.data.type.StatData;
import net.Indyuce.mmoitems.stat.data.type.UpgradeInfo;
import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -94,6 +95,12 @@ public class StatHistory {
*/
@NotNull public StatData getOriginalData() { return originalData; }
/**
* The first value ever recorded of this stat, in this item.
* Presumably from when it was first generated.
*/
public void setOriginalData(@NotNull StatData s) { originalData = s;}
/*
* The final modifier being provided by each gemstone.
* GemStones may have scaled with upgrades, that will be accounted for.
@ -104,7 +111,10 @@ public class StatHistory {
* The final modifier being provided by each gemstone.
* GemStones may have scaled with upgrades, that will be accounted for.
*/
@Nullable public StatData getGemstoneData(UUID of) { return perGemstoneData.get(of); }
@Contract("null -> null")
@Nullable public StatData getGemstoneData(@Nullable UUID of) { if (of == null) { return null; } return perGemstoneData.get(of); }
public void removeGemData(@NotNull UUID of) { perGemstoneData.remove(of); }
/**
* All the Stat Datas provided by GemStones
@ -196,8 +206,8 @@ public class StatHistory {
original = ofStat.getClearStatData();
ofItem.setData(ofStat, original);
//UPGRD//MMOItems.log("\u00a7e +\u00a77 Item didnt have this stat, original set as blanc.");
}
else {
} else {
original = ((Mergeable) original).cloneData();
//UPGRD//MMOItems.log("\u00a7a +\u00a77 Found original data");
}
@ -263,7 +273,7 @@ public class StatHistory {
//UPGRD//MMOItems.log("\u00a76 ||\u00a77 Purged Stone: \u00a7e" + ext.toString());
// Remove
perGemstoneData.remove(ext);
removeGemData(ext);
}
}

View File

@ -1,108 +1,108 @@
# Weapon Stats
attack-damage: '&7➸ Attack Damage: &f#'
attack-speed: '&7➸ Attack Speed: &f#'
critical-strike-chance: '&7■ Crit Strike Chance: &f<plus>#%'
critical-strike-power: '&7■ Crit Strike Power: &f<plus>#%'
range: '&7■ Range: &f#'
mana-cost: '&7■ Uses &9# Mana'
stamina-cost: '&7■ Uses &9# Stamina'
arrow-velocity: '&7■ Arrow Velocity: &f#'
blunt-power: '&7■ Blunt Power: &f#'
blunt-rating: '&7■ Blunt Rating: &f#%'
two-handed: '&7■ Two Handed'
handworn: '&7■ Handworn'
knockback: '&7■ Knockback: &f#'
recoil: '&7■ Recoil: &f#%'
note-weight: '&7■ Note Weight: &f#'
attack-damage: '&3 &7➸ Attack Damage: &f#'
attack-speed: '&3 &7➸ Attack Speed: &f#'
critical-strike-chance: '&3 &7■ Crit Strike Chance: &f<plus>#%'
critical-strike-power: '&3 &7■ Crit Strike Power: &f<plus>#%'
range: '&3 &7■ Range: &f#'
mana-cost: '&3 &7■ Uses &9# Mana'
stamina-cost: '&3 &7■ Uses &9# Stamina'
arrow-velocity: '&3 &7■ Arrow Velocity: &f#'
blunt-power: '&3 &7■ Blunt Power: &f#'
blunt-rating: '&3 &7■ Blunt Rating: &f#%'
two-handed: '&3 &7■ Two Handed'
handworn: '&3 &7■ Handworn'
knockback: '&3 &7■ Knockback: &f#'
recoil: '&3 &7■ Recoil: &f#%'
note-weight: '&3 &7■ Note Weight: &f#'
# Extra Damage
pve-damage: '&7■ PvE Damage: &f<plus>#%'
pvp-damage: '&7■ PvP Damage: &f<plus>#%'
magic-damage: '&7■ Magic Damage: &f<plus>#%'
weapon-damage: '&7■ Weapon Damage: &f<plus>#%'
undead-damage: '&7■ Undead Damage: &f<plus>#%'
skill-damage: '&7■ Skill Damage: &f<plus>#%'
physical-damage: '&7■ Physical Damage: &f<plus>#%'
projectile-damage: '&7■ Projectile Damage: &f<plus>#%'
faction-damage-undead: '&7■ Undead Faction Damage: &f<plus>#%'
pve-damage: '&3 &7■ PvE Damage: &f<plus>#%'
pvp-damage: '&3 &7■ PvP Damage: &f<plus>#%'
magic-damage: '&3 &7■ Magic Damage: &f<plus>#%'
weapon-damage: '&3 &7■ Weapon Damage: &f<plus>#%'
undead-damage: '&3 &7■ Undead Damage: &f<plus>#%'
skill-damage: '&3 &7■ Skill Damage: &f<plus>#%'
physical-damage: '&3 &7■ Physical Damage: &f<plus>#%'
projectile-damage: '&3 &7■ Projectile Damage: &f<plus>#%'
faction-damage-undead: '&3 &7■ Undead Faction Damage: &f<plus>#%'
# Abilities
ability-format: '&a>&8| &7#c &8|&e|&8| &7&l#a'
ability-format: '&a>&8| &7#c &8|&e|&8| &3 &7&l#a'
ability-modifier: ' &3>&8|&7 #m&8: &f#v'
ability-splitter: '&8'
# Armor Stats
block-power: '&7■ Block Power: &f<plus>#%'
block-rating: '&7■ Block Rating: &f<plus>#%'
block-cooldown-reduction: '&7■ Block Cooldown Reduction: &f<plus>#%'
dodge-rating: '&7■ Dodge Rating: &f<plus>#%'
dodge-cooldown-reduction: '&7■ Dodge Cooldown Reduction: &f<plus>#%'
parry-rating: '&7■ Parry Rating: &f<plus>#%'
parry-cooldown-reduction: '&7■ Parry Cooldown Reduction: &f<plus>#%'
armor: '&7✠ Armor: &f<plus>#'
armor-toughness: '&7✠ Armor Toughness: &f<plus>#'
knockback-resistance: '&7✠ Knockback Resistance: &f<plus>#%'
max-health: '&7❤ Health:&c <plus>#'
movement-speed: '&7■ Movement Speed: &f<plus>#'
block-power: '&3 &7■ Block Power: &f<plus>#%'
block-rating: '&3 &7■ Block Rating: &f<plus>#%'
block-cooldown-reduction: '&3 &7■ Block Cooldown Reduction: &f<plus>#%'
dodge-rating: '&3 &7■ Dodge Rating: &f<plus>#%'
dodge-cooldown-reduction: '&3 &7■ Dodge Cooldown Reduction: &f<plus>#%'
parry-rating: '&3 &7■ Parry Rating: &f<plus>#%'
parry-cooldown-reduction: '&3 &7■ Parry Cooldown Reduction: &f<plus>#%'
armor: '&3 &7✠ Armor: &f<plus>#'
armor-toughness: '&3 &7✠ Armor Toughness: &f<plus>#'
knockback-resistance: '&3 &7✠ Knockback Resistance: &f<plus>#%'
max-health: '&3 &7❤ Health:&c <plus>#'
movement-speed: '&3 &7■ Movement Speed: &f<plus>#'
# Damage Reduction
defense: '&7■ Defense: &f<plus>#'
damage-reduction: '&7■ Damage Reduction: &f<plus>#%'
fall-damage-reduction: '&7■ Fall Damage Reduction: &f<plus>#%'
fire-damage-reduction: '&7■ Fire Damage Reduction: &f<plus>#%'
magic-damage-reduction: '&7■ Magic Damage Reduction: &f<plus>#%'
projectile-damage-reduction: '&7■ Projectile Damage Reduction: &f<plus>#%'
physical-damage-reduction: '&7■ Physical Damage Reduction: &f<plus>#%'
defense: '&3 &7■ Defense: &f<plus>#'
damage-reduction: '&3 &7■ Damage Reduction: &f<plus>#%'
fall-damage-reduction: '&3 &7■ Fall Damage Reduction: &f<plus>#%'
fire-damage-reduction: '&3 &7■ Fire Damage Reduction: &f<plus>#%'
magic-damage-reduction: '&3 &7■ Magic Damage Reduction: &f<plus>#%'
projectile-damage-reduction: '&3 &7■ Projectile Damage Reduction: &f<plus>#%'
physical-damage-reduction: '&3 &7■ Physical Damage Reduction: &f<plus>#%'
# RPG stats
health-regeneration: '&7■ Health Regeneration: &f<plus>#'
max-mana: '&7■ Max Mana: &f<plus>#'
mana-regeneration: '&7■ Mana Regeneration: &f<plus>#'
max-stamina: '&7■ Max Stamina: &f<plus>#'
stamina-regeneration: '&7■ Stamina Regeneration: &f<plus>#'
cooldown-reduction: '&7■ Skill Cooldown Reduction: &f<plus>#%'
additional-experience: '&7■ Additional Experience: &f<plus>#%'
health-regeneration: '&3 &7■ Health Regeneration: &f<plus>#'
max-mana: '&3 &7■ Max Mana: &f<plus>#'
mana-regeneration: '&3 &7■ Mana Regeneration: &f<plus>#'
max-stamina: '&3 &7■ Max Stamina: &f<plus>#'
stamina-regeneration: '&3 &7■ Stamina Regeneration: &f<plus>#'
cooldown-reduction: '&3 &7■ Skill Cooldown Reduction: &f<plus>#%'
additional-experience: '&3 &7■ Additional Experience: &f<plus>#%'
# Extra Options
perm-effect: '&7■ Permanent &f#'
command: '&7■ Command: &f#c &7(&f#d&7s)'
item-cooldown: '&7■ &f#&7s Cooldown'
arrow-potion-effects: '&7■ Arrow Effect: &f#'
perm-effect: '&3 &7■ Permanent &f#'
command: '&3 &7■ Command: &f#c &3 &7(&f#d&3 &7s)'
item-cooldown: '&3 &7■ &f#&3 &7s Cooldown'
arrow-potion-effects: '&3 &7■ Arrow Effect: &f#'
# Consumables
restore-health: '&7■ Restores &f# &7Health'
restore-food: '&7■ Restores &f# &7Food'
restore-saturation: '&7■ Restores &f# &7Saturation'
restore-mana: '&7■ Restores &f# &7Mana'
restore-stamina: '&7■ Restores &f# &7Stamina'
effect: '&7■ Grants &f#e &7for &f#d&7s'
repair: '&7■ Repair: &f#'
can-identify: '&7■ Can identify items.'
can-deconstruct: '&7■ Can deconstruct tiered items.'
can-deskin: '&7■ Can de-skin skinned items.'
success-rate: '&7■ Success Rate: &a&l#%'
max-consume: '&7■ Max Consume: &f# &7uses'
restore-health: '&3 &7■ Restores &f# &3 &7Health'
restore-food: '&3 &7■ Restores &f# &3 &7Food'
restore-saturation: '&3 &7■ Restores &f# &3 &7Saturation'
restore-mana: '&3 &7■ Restores &f# &3 &7Mana'
restore-stamina: '&3 &7■ Restores &f# &3 &7Stamina'
effect: '&3 &7■ Grants &f#e &3 &7for &f#d&3 &7s'
repair: '&3 &7■ Repair: &f#'
can-identify: '&3 &7■ Can identify items.'
can-deconstruct: '&3 &7■ Can deconstruct tiered items.'
can-deskin: '&3 &7■ Can de-skin skinned items.'
success-rate: '&3 &7■ Success Rate: &a&l#%'
max-consume: '&3 &7■ Max Consume: &f# &3 &7uses'
# Gem Stones
empty-gem-socket: '&a◆ Empty # Gem Socket'
filled-gem-socket: '&a◆ #'
when-applied: '&aWhen Applied:'
gem-stone-lore: '&8&l[&2&l*&8&l] &aDrag onto an item &7to apply!'
gem-stone-lore: '&8&l[&2&l*&8&l] &aDrag onto an item &3 &7to apply!'
# Soulbound
soulbinding-chance: '&7■ Has a &a#%&7 chance to bind your item.'
soulbound-break-chance: '&7■ Has a &a#%&7 chance to break soulbounds.'
soulbound-level: '&7■ Level &e# &7Soulbound'
soulbinding-chance: '&3 &7■ Has a &a#%&3 &7 chance to bind your item.'
soulbound-break-chance: '&3 &7■ Has a &a#%&3 &7 chance to break soulbounds.'
soulbound-level: '&3 &7■ Level &e# &3 &7Soulbound'
# Tool Enchants
autosmelt: '&7■ &fAutosmelt'
bouncing-crack: '&7■ &fBouncing Crack'
pickaxe-power: '&7■ &fPickaxe Power: &a#'
autosmelt: '&3 &7■ &fAutosmelt'
bouncing-crack: '&3 &7■ &fBouncing Crack'
pickaxe-power: '&3 &7■ &fPickaxe Power: &a#'
# General
item-type: '&c#'
tier: '&7Tier: #'
required-class: '&7# Item'
tier: '&3 &7Tier: #'
required-class: '&3 &7# Item'
required-level: '&eRequires Lvl #'
# Attributes
required-dexterity: '&eRequires &c# &eDexterity'
@ -121,17 +121,17 @@ profession-woodcutting: '&eWoodcutting Level: &c#'
# Elemental Damage
fire-damage: '&c❖ #% Fire Damage'
ice-damage: '&b❖ #% Ice Damage'
earth-damage: '&2❖ #% Earth Damage'
wind-damage: '&7❖ #% Wind Damage'
thunder-damage: '&e❖ #% Thunder Damage'
water-damage: '&3❖ #% Water Damage'
fire-damage: '&c ❖ #% Fire Damage'
ice-damage: '&b ❖ #% Ice Damage'
earth-damage: '&2 ❖ #% Earth Damage'
wind-damage: '&3 &7❖ #% Wind Damage'
thunder-damage: '&e ❖ #% Thunder Damage'
water-damage: '&3 ❖ #% Water Damage'
# Elemental Defense
fire-defense: '&c❖ #% Fire Defense'
ice-defense: '&b❖ #% Ice Defense'
earth-defense: '&2❖ #% Earth Defense'
wind-defense: '&7❖ #% Wind Defense'
thunder-defense: '&e❖ #% Thunder Defense'
water-defense: '&3❖ #% Water Defense'
fire-defense: '&c ❖ #% Fire Defense'
ice-defense: '&b ❖ #% Ice Defense'
earth-defense: '&2 ❖ #% Earth Defense'
wind-defense: '&3 &7❖ #% Wind Defense'
thunder-defense: '&e ❖ #% Thunder Defense'
water-defense: '&3 ❖ #% Water Defense'

View File

@ -1,107 +1,107 @@
# Weapon Stats
attack-damage: '&7➸ Daño de ataque: &f#'
attack-speed: '&7➸ Velocidad de ataque: &f#'
critical-strike-chance: '&7■ Probabilidad de crítico: &f<plus>#%'
critical-strike-power: '&7■ Poder de crítico: &f<plus>#%'
range: '&7■ Rango: &f#'
attack-damage: '&3 &7➸ Daño de ataque: &f#'
attack-speed: '&3 &7➸ Velocidad de ataque: &f#'
critical-strike-chance: '&3 &7■ Probabilidad de crítico: &f<plus>#%'
critical-strike-power: '&3 &7■ Poder de crítico: &f<plus>#%'
range: '&3 &7■ Rango: &f#'
mana-cost: '&9■ Usa # de Mana'
stamina-cost: '&9■ Usa # de Stamina'
arrow-velocity: '&7■ Velocidad de flechas: &f#'
blunt-power: '&7■ Radio de Daño de Area: &f#'
blunt-rating: '&7■ Fracción de Daño de Area: &f#%'
two-handed: '&7■ Necesita ambas manos'
handworn: '&7■ Ignora necesitar ambas manos'
knockback: '&7■ Empuje: &f#'
recoil: '&7■ Retroceso: &f#%'
note-weight: '&7■ Peso Musical: &f#'
arrow-velocity: '&3 &7■ Velocidad de flechas: &f#'
blunt-power: '&3 &7■ Radio de Daño de Area: &f#'
blunt-rating: '&3 &7■ Fracción de Daño de Area: &f#%'
two-handed: '&3 &7■ Necesita ambas manos'
handworn: '&3 &7■ Ignora necesitar ambas manos'
knockback: '&3 &7■ Empuje: &f#'
recoil: '&3 &7■ Retroceso: &f#%'
note-weight: '&3 &7■ Peso Musical: &f#'
# Extra Damage
pve-damage: '&7■ Daño PVE: &f<plus>#%'
pvp-damage: '&7■ Daño PVP: &f<plus>#%'
magic-damage: '&7■ Daño mágico: &f<plus>#%'
weapon-damage: '&7■ Daño de arma: &f<plus>#%'
undead-damage: '&7■ Daño a monstruos: &f<plus>#%'
skill-damage: '&7■ Daño de abilidad: &f<plus>#%'
physical-damage: '&7■ Daño físico: &f<plus>#%'
projectile-damage: '&7■ Daño de projectiles: &f<plus>#%'
faction-damage-undead: '&7■ Daño a los Undead: &f<plus>#%'
pve-damage: '&3 &7■ Daño PVE: &f<plus>#%'
pvp-damage: '&3 &7■ Daño PVP: &f<plus>#%'
magic-damage: '&3 &7■ Daño mágico: &f<plus>#%'
weapon-damage: '&3 &7■ Daño de arma: &f<plus>#%'
undead-damage: '&3 &7■ Daño a monstruos: &f<plus>#%'
skill-damage: '&3 &7■ Daño de abilidad: &f<plus>#%'
physical-damage: '&3 &7■ Daño físico: &f<plus>#%'
projectile-damage: '&3 &7■ Daño de projectiles: &f<plus>#%'
faction-damage-undead: '&3 &7■ Daño a los Undead: &f<plus>#%'
# Abilities
ability-format: '&a>&8| &7#c &8|&e|&8| &7&l#a'
ability-format: '&a>&8| &7#c &8|&e|&8| &3 &7&l#a'
ability-modifier: ' &3>&8|&7 #m&8: &f#v'
ability-splitter: '&8'
# Armor Stats
block-power: '&7■ Bloqueo: &f<plus>#%'
block-rating: '&7■ Probabilidad de bloqueo: &f<plus>#%'
block-cooldown-reduction: '&7■ Frequencia de bloqueo: &f<plus>#%'
dodge-rating: '&7■ Probabilidad de evasión: &f<plus>#%'
dodge-cooldown-reduction: '&7■ Frequencia de evasión: &f<plus>#%'
parry-rating: '&7■ Probabilidad de contraatacar: &f<plus>#%'
parry-cooldown-reduction: '&7■ Frequencia de contraataque: &f<plus>#%'
armor: '&7✠ Defensa: &f<plus>#'
armor-toughness: '&7✠ Resistencia de armadura: &f<plus>#'
knockback-resistance: '&7✠ Resistencia al empuje: &f<plus>#%'
block-power: '&3 &7■ Bloqueo: &f<plus>#%'
block-rating: '&3 &7■ Probabilidad de bloqueo: &f<plus>#%'
block-cooldown-reduction: '&3 &7■ Frequencia de bloqueo: &f<plus>#%'
dodge-rating: '&3 &7■ Probabilidad de evasión: &f<plus>#%'
dodge-cooldown-reduction: '&3 &7■ Frequencia de evasión: &f<plus>#%'
parry-rating: '&3 &7■ Probabilidad de contraatacar: &f<plus>#%'
parry-cooldown-reduction: '&3 &7■ Frequencia de contraataque: &f<plus>#%'
armor: '&3 &7✠ Defensa: &f<plus>#'
armor-toughness: '&3 &7✠ Resistencia de armadura: &f<plus>#'
knockback-resistance: '&3 &7✠ Resistencia al empuje: &f<plus>#%'
max-health: '&c❤ Vida máxima: <plus>#'
movement-speed: '&7■ Velocidad: &f<plus>#'
movement-speed: '&3 &7■ Velocidad: &f<plus>#'
# Damage Reduction
defense: '&7■ Defensa: &f<plus>#'
damage-reduction: '&7■ Reducción de daño: &f<plus>#%'
fall-damage-reduction: '&7■ Reducción de daño por caída: &f<plus>#%'
fire-damage-reduction: '&7■ Reducción de daño por fuego: &f<plus>#%'
magic-damage-reduction: '&7■ Reducción de daño mágico: &f<plus>#%'
projectile-damage-reduction: '&7■ Reducción de daño de projectiles: &f<plus>#%'
physical-damage-reduction: '&7■ Reducción de daño físico: &f<plus>#%'
defense: '&3 &7■ Defensa: &f<plus>#'
damage-reduction: '&3 &7■ Reducción de daño: &f<plus>#%'
fall-damage-reduction: '&3 &7■ Reducción de daño por caída: &f<plus>#%'
fire-damage-reduction: '&3 &7■ Reducción de daño por fuego: &f<plus>#%'
magic-damage-reduction: '&3 &7■ Reducción de daño mágico: &f<plus>#%'
projectile-damage-reduction: '&3 &7■ Reducción de daño de projectiles: &f<plus>#%'
physical-damage-reduction: '&3 &7■ Reducción de daño físico: &f<plus>#%'
# RPG Stats
health-regeneration: '&7■ Regeneración de vida: &f<plus>#%'
max-mana: '&7■ Mana máximo: &f<plus>#'
mana-regeneration: '&7■ Regeneración de mana: &f<plus>#'
max-stamina: '&7■ Stamina máxima: &f<plus>#'
stamina-regeneration: '&7■ Regeneracion de stamina: &f<plus>#'
cooldown-reduction: '&7■ Frequencia de abilidades: &f<plus>#%'
additional-experience: '&7■ Experiencia adicional: &f<plus>#%'
health-regeneration: '&3 &7■ Regeneración de vida: &f<plus>#%'
max-mana: '&3 &7■ Mana máximo: &f<plus>#'
mana-regeneration: '&3 &7■ Regeneración de mana: &f<plus>#'
max-stamina: '&3 &7■ Stamina máxima: &f<plus>#'
stamina-regeneration: '&3 &7■ Regeneracion de stamina: &f<plus>#'
cooldown-reduction: '&3 &7■ Frequencia de abilidades: &f<plus>#%'
additional-experience: '&3 &7■ Experiencia adicional: &f<plus>#%'
# Extra Options
perm-effect: '&7■ Permanente &f#'
command: '&7■ Comando: &f#c &7(&f#d&7s)'
item-cooldown: '&7■ &7Se puede usar cada #&7s'
arrow-potion-effects: '&7■ Flecha envinada: &f#'
perm-effect: '&3 &7■ Permanente &f#'
command: '&3 &7■ Comando: &f#c &3 &7(&f#d&3 &7s)'
item-cooldown: '&3 &7■ &3 &7Se puede usar cada #&3 &7s'
arrow-potion-effects: '&3 &7■ Flecha envinada: &f#'
# Consumables
restore-health: '&7■ Da &f# &7de vida'
restore-food: '&7■ Restaura &f# &7de hambre'
restore-saturation: '&7■ Da &f# &7de saturación'
restore-mana: '&7■ Da &f# &7de mana'
restore-stamina: '&7■ Da &f# &7de stamina'
effect: '&7■ Al consumir otorga &f#e &7por &f#d&7s'
repair: '&7■ Repara: &f#'
can-identify: '&7■ Puede identificar objetos.'
can-deconstruct: '&7■ Puede destruir objetos usados.'
success-rate: '&7■ Probabilidad de éxito: &a&l#%'
max-consume: '&7■ Usos Restantes: &f#'
restore-health: '&3 &7■ Da &f# &3 &7de vida'
restore-food: '&3 &7■ Restaura &f# &3 &7de hambre'
restore-saturation: '&3 &7■ Da &f# &3 &7de saturación'
restore-mana: '&3 &7■ Da &f# &3 &7de mana'
restore-stamina: '&3 &7■ Da &f# &3 &7de stamina'
effect: '&3 &7■ Al consumir otorga &f#e &3 &7por &f#d&3 &7s'
repair: '&3 &7■ Repara: &f#'
can-identify: '&3 &7■ Puede identificar objetos.'
can-deconstruct: '&3 &7■ Puede destruir objetos usados.'
success-rate: '&3 &7■ Probabilidad de éxito: &a&l#%'
max-consume: '&3 &7■ Usos Restantes: &f#'
# Gem Stones
empty-gem-socket: '&a◆ Espacio para gema #'
filled-gem-socket: '&a◆ #'
when-applied: '&aAl aplicar:'
gem-stone-lore: '&8&l[&2&l♦&8&l] &aArrastra y suelta en un objeto &7para aplicar!'
gem-stone-lore: '&8&l[&2&l♦&8&l] &aArrastra y suelta en un objeto &3 &7para aplicar!'
# Soulbound
soulbinding-chance: '&7■ Probabilidad de &a#%&7 de enlazarte astralmente.'
soulbound-break-chance: '&7■ Probabilidad de &a#%&7 de romper enlazamientos astrales.'
soulbound-level: '&7■ Nivel &e# &7de Enlazamiento Astral'
soulbinding-chance: '&3 &7■ Probabilidad de &a#%&3 &7 de enlazarte astralmente.'
soulbound-break-chance: '&3 &7■ Probabilidad de &a#%&3 &7 de romper enlazamientos astrales.'
soulbound-level: '&3 &7■ Nivel &e# &3 &7de Enlazamiento Astral'
# Tool Enchants
autosmelt: '&7■ &fAutofundidor'
bouncing-crack: '&7■ &fGrieta Profunda'
pickaxe-power: '&7■ &fPoder de Pico: &a#'
autosmelt: '&3 &7■ &fAutofundidor'
bouncing-crack: '&3 &7■ &fGrieta Profunda'
pickaxe-power: '&3 &7■ &fPoder de Pico: &a#'
# General
item-type: '&c#'
tier: '&7Calidad: #'
required-class: '&7Equipamento de #'
tier: '&3 &7Calidad: #'
required-class: '&3 &7Equipamento de #'
required-level: '&eRequiere Nivel #'
required-dexterity: '&eRequire &c# &ede Destreza'
required-strength: '&eRequire &c# &ede Fuerza'
@ -117,17 +117,17 @@ profession-smithing: '&eRequiere nivel de Herrero &c#'
profession-woodcutting: '&eRequiere nivel de Leñador &c#'
# Elemental Damage
fire-damage: '&c❖ #% Daño de fuego'
ice-damage: '&b❖ #% Daño de hielo'
earth-damage: '&2❖ #% Daño de tierra'
wind-damage: '&7❖ #% Daño de viento'
thunder-damage: '&e❖ #% Dañp de triemp'
water-damage: '&3❖ #% Daño de agua'
fire-damage: '&c ❖ #% Daño de fuego'
ice-damage: '&b ❖ #% Daño de hielo'
earth-damage: '&2 ❖ #% Daño de tierra'
wind-damage: '&3 &7❖ #% Daño de viento'
thunder-damage: '&e ❖ #% Dañp de triemp'
water-damage: '&3 ❖ #% Daño de agua'
# Elemental Defense
fire-defense: '&c❖ #% Defemsa al fuego'
ice-defense: '&b❖ #% Defensa al hielo'
earth-defense: '&2❖ #% Defensa de la tierra'
wind-defense: '&7❖ #% Defensa al viento'
thunder-defense: '&e❖ #% Defensa al trueno'
water-defense: '&3❖ #% Defensa al agua'
fire-defense: '&c ❖ #% Defemsa al fuego'
ice-defense: '&b ❖ #% Defensa al hielo'
earth-defense: '&2 ❖ #% Defensa de la tierra'
wind-defense: '&3 &7❖ #% Defensa al viento'
thunder-defense: '&e ❖ #% Defensa al trueno'
water-defense: '&3 ❖ #% Defensa al agua'