!Item updater refactor

This commit is contained in:
Indyuce 2020-04-17 18:50:29 +02:00
parent 7e4565f896
commit 324880f8e9
8 changed files with 141 additions and 191 deletions

View File

@ -256,7 +256,10 @@ public class MMOItems extends JavaPlugin {
// save item updater data
ConfigFile updater = new ConfigFile("/dynamic", "updater");
updater.getConfig().getKeys(false).forEach(key -> updater.getConfig().set(key, null));
dynamicUpdater.getDatas().forEach(data -> data.save(updater.getConfig()));
dynamicUpdater.getActive().forEach(data -> {
updater.getConfig().createSection(data.getPath());
data.save(updater.getConfig().getConfigurationSection(data.getPath()));
});
updater.save();
// drop abandonned soulbound items

View File

@ -25,7 +25,8 @@ public class MMOItemIngredient extends Ingredient {
super("mmoitem", config);
config.validate("type", "id");
type = MMOItems.plugin.getTypes().get(config.getString("type").toUpperCase().replace("-", "_").replace(" ", "_"));
type = MMOItems.plugin.getTypes().getOrThrow(config.getString("type").toUpperCase().replace("-", "_").replace(" ", "_"));
id = config.getString("id").toUpperCase().replace("-", "_").replace(" ", "_");
Validate.isTrue(MMOItems.plugin.getItems().hasMMOItem(type, id), "Could not find MMOItem with ID '" + id + "'");

View File

@ -45,9 +45,8 @@ public class MMOItemBuilder {
tags.add(new ItemTag("MMOITEMS_ITEM_TYPE", mmoitem.getType().getId()));
tags.add(new ItemTag("MMOITEMS_ITEM_ID", mmoitem.getId()));
String path = mmoitem.getType().getId() + "." + mmoitem.getId();
if (MMOItems.plugin.getUpdater().hasData(path))
tags.add(new ItemTag("MMOITEMS_ITEM_UUID", MMOItems.plugin.getUpdater().getData(path).getUniqueId().toString()));
if (MMOItems.plugin.getUpdater().hasData(mmoitem))
tags.add(new ItemTag("MMOITEMS_ITEM_UUID", MMOItems.plugin.getUpdater().getData(mmoitem).getUniqueId().toString()));
}
public MMOItemLore getLore() {

View File

@ -29,6 +29,7 @@ import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.PluginUpdate;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.UpdaterData;
import net.Indyuce.mmoitems.api.ability.Ability;
import net.Indyuce.mmoitems.api.crafting.CraftingStation;
import net.Indyuce.mmoitems.api.droptable.item.MMOItemDropItem;
@ -47,7 +48,7 @@ import net.Indyuce.mmoitems.gui.BlockBrowser;
import net.Indyuce.mmoitems.gui.CraftingStationView;
import net.Indyuce.mmoitems.gui.ItemBrowser;
import net.Indyuce.mmoitems.gui.edition.ItemEdition;
import net.Indyuce.mmoitems.manager.UpdaterManager.UpdaterData;
import net.Indyuce.mmoitems.manager.UpdaterManager.KeepOption;
import net.Indyuce.mmoitems.stat.LuteAttackEffectStat.LuteAttackEffect;
import net.Indyuce.mmoitems.stat.StaffSpiritStat.StaffSpirit;
import net.Indyuce.mmoitems.stat.data.AbilityData;
@ -315,19 +316,18 @@ public class MMOItemsCommand implements CommandExecutor {
else if (args[0].equalsIgnoreCase("checkupdater")) {
if (args.length < 2) {
sender.sendMessage(ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "--------------------------------------------------");
for (String s : MMOItems.plugin.getUpdater().getItemPaths())
sender.sendMessage(ChatColor.RED + s + ChatColor.WHITE + " - " + ChatColor.RED
+ MMOItems.plugin.getUpdater().getData(s).getUniqueId().toString());
for (UpdaterData data : MMOItems.plugin.getUpdater().getActive())
sender.sendMessage(ChatColor.RED + data.getPath() + ChatColor.WHITE + " - " + ChatColor.RED + data.getUniqueId().toString());
return true;
}
try {
@SuppressWarnings("deprecation")
UpdaterData data = MMOItems.plugin.getUpdater().getData(args[1].toUpperCase().replace("-", "_"));
sender.sendMessage(ChatColor.DARK_GRAY + "" + ChatColor.STRIKETHROUGH + "--------------------------------------------------");
sender.sendMessage(ChatColor.AQUA + "UUID = " + ChatColor.RESET + data.getUniqueId().toString());
sender.sendMessage(ChatColor.AQUA + "Keep Enchants = " + ChatColor.RESET + data.keepEnchants());
sender.sendMessage(ChatColor.AQUA + "Keep StringListStat = " + ChatColor.RESET + data.keepLore());
sender.sendMessage(ChatColor.AQUA + "Keep DefaultDurability = " + ChatColor.RESET + data.keepDurability());
sender.sendMessage(ChatColor.AQUA + "Keep Name = " + ChatColor.RESET + data.keepName());
for (KeepOption option : KeepOption.values())
sender.sendMessage(ChatColor.AQUA + MMOUtils.caseOnWords(option.name().replace("_", " ").toLowerCase()) + " = " + ChatColor.RESET
+ data.hasOption(option));
} catch (Exception e) {
sender.sendMessage("Couldn't find updater data.");
}
@ -910,8 +910,7 @@ public class MMOItemsCommand implements CommandExecutor {
* prevent other severe issues from happening that could potentially
* spam your console
*/
String path = type.getId() + "." + id;
MMOItems.plugin.getUpdater().disable(path);
MMOItems.plugin.getUpdater().disable(type, id);
sender.sendMessage(MMOItems.plugin.getPrefix() + ChatColor.GREEN + "You successfully deleted " + id + ".");
}

View File

@ -14,11 +14,14 @@ import org.bukkit.inventory.meta.ItemMeta;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.UpdaterData;
import net.Indyuce.mmoitems.api.util.AltChar;
import net.Indyuce.mmoitems.manager.UpdaterManager.UpdaterData;
import net.Indyuce.mmoitems.manager.UpdaterManager.KeepOption;
import net.mmogroup.mmolib.version.VersionMaterial;
public class ItemUpdaterEdition extends EditionInventory {
private static final int[] slots = { 19, 20, 21, 28, 29, 30, 37, 38, 39 };
public ItemUpdaterEdition(Player player, Type type, String id) {
super(player, type, id);
}
@ -28,44 +31,40 @@ public class ItemUpdaterEdition extends EditionInventory {
Inventory inv = Bukkit.createInventory(this, 54, ChatColor.UNDERLINE + "Item Updater: " + id);
// setup if not in map
String itemPath = type.getId() + "." + id;
if (!MMOItems.plugin.getUpdater().hasData(itemPath)) {
MMOItems.plugin.getUpdater().enable(itemPath);
if (!MMOItems.plugin.getUpdater().hasData(type, id)) {
MMOItems.plugin.getUpdater().enable(type, id);
player.sendMessage(ChatColor.YELLOW + "Successfully enabled the item updater for " + id + ".");
}
UpdaterData did = MMOItems.plugin.getUpdater().getData(itemPath);
UpdaterData did = MMOItems.plugin.getUpdater().getData(type, id);
ItemStack disable = VersionMaterial.RED_STAINED_GLASS_PANE.toItem();
ItemMeta disableMeta = disable.getItemMeta();
disableMeta.setDisplayName(ChatColor.GREEN + "Disable");
List<String> disableLore = new ArrayList<String>();
disableLore.add(ChatColor.GRAY + "Your item won't update anymore.");
disableLore.add(ChatColor.GRAY + "Your item will not be dynamically updated.");
disableLore.add("");
disableLore.add(ChatColor.YELLOW + AltChar.listDash + " Click to disable the item updater.");
disableMeta.setLore(disableLore);
disable.setItemMeta(disableMeta);
inv.setItem(20, getBooleanItem("Name", did.keepName(), "Your item will keep its", "old display name when updated."));
inv.setItem(21, getBooleanItem("StringListStat", did.keepLore(), "Any lore starting with '&7' will be", "kept when updating your item.", ChatColor.RED + "May not support every enchant plugin."));
inv.setItem(29, getBooleanItem("Gems", did.keepGems(), "Your item will keep its", "old gems when updated."));
inv.setItem(30, getBooleanItem("Enchants", did.keepEnchants(), "Your item will keep its", "old enchants when updated."));
inv.setItem(38, getBooleanItem("Soulbound", did.keepSoulbound(), "Your item will keep its", "soulbound data when updated."));
inv.setItem(39, getBooleanItem("DefaultDurability", did.keepDurability(), "Your item will keep its", "old durability when updated."));
inv.setItem(32, disable);
int n = 0;
for (KeepOption option : KeepOption.values())
inv.setItem(slots[n++],
getBooleanItem(MMOUtils.caseOnWords(option.name().substring(5).toLowerCase()), did.hasOption(option), option.getLore()));
inv.setItem(32, disable);
inv.setItem(4, getCachedItem());
return inv;
}
private ItemStack getBooleanItem(String name, boolean bool, String... lines) {
private ItemStack getBooleanItem(String name, boolean bool, List<String> list) {
ItemStack stack = (bool ? VersionMaterial.LIME_DYE : VersionMaterial.GRAY_DYE).toItem();
ItemMeta meta = stack.getItemMeta();
meta.setDisplayName(ChatColor.GREEN + "Keep " + name + "?");
List<String> lore = new ArrayList<String>();
for (String line : lines)
lore.add(ChatColor.GRAY + line);
List<String> lore = new ArrayList<>();
list.forEach(line -> lore.add(ChatColor.GRAY + line));
lore.add("");
lore.add(bool ? ChatColor.RED + AltChar.listDash + " Click to toggle off." : ChatColor.GREEN + AltChar.listDash + " Click to toggle on.");
meta.setLore(lore);
@ -83,42 +82,33 @@ public class ItemUpdaterEdition extends EditionInventory {
return;
// safe check
String path = type.getId() + "." + id;
if (!MMOItems.plugin.getUpdater().hasData(path)) {
if (!MMOItems.plugin.getUpdater().hasData(type, id)) {
player.closeInventory();
return;
}
UpdaterData did = MMOItems.plugin.getUpdater().getData(path);
if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "Keep StringListStat?")) {
did.setKeepLore(!did.keepLore());
open();
}
if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "Keep Enchants?")) {
did.setKeepEnchants(!did.keepEnchants());
open();
}
if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "Keep DefaultDurability?")) {
did.setKeepDurability(!did.keepDurability());
open();
}
if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "Keep Name?")) {
did.setKeepName(!did.keepName());
open();
}
if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "Keep Gems?")) {
did.setKeepGems(!did.keepGems());
open();
}
if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "Keep Soulbound?")) {
did.setKeepSoulbound(!did.keepSoulbound());
open();
}
if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "Disable")) {
MMOItems.plugin.getUpdater().disable(path);
MMOItems.plugin.getUpdater().disable(type, id);
player.closeInventory();
player.sendMessage(ChatColor.YELLOW + "Successfully disabled the item updater for " + id + ".");
return;
}
/*
* find clicked option based on item display name. no need to use
* NBTTags
*/
String format = item.getItemMeta().getDisplayName();
if (!format.startsWith(ChatColor.GREEN + "Keep "))
return;
UpdaterData did = MMOItems.plugin.getUpdater().getData(type, id);
KeepOption option = KeepOption.valueOf(format.substring(2, format.length() - 1).replace(" ", "_").toUpperCase());
if (did.hasOption(option))
did.removeOption(option);
else
did.addOption(option);
open();
}
}

View File

@ -71,7 +71,7 @@ public class ItemManager extends BukkitRunnable {
// check cache
if (cache.containsKey(type) && cache.get(type).isCached(id))
return true;
// check type config file
return type.getConfigFile().getConfig().contains(id);
}

View File

@ -18,6 +18,7 @@ import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.PluginUpdate;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.UpdaterData;
public class PluginUpdateManager {
@ -25,7 +26,7 @@ public class PluginUpdateManager {
* the integer as key is used as reference in order to apply an update using
* the update command.
*/
private Map<Integer, PluginUpdate> updates = new HashMap<>();
private final Map<Integer, PluginUpdate> updates = new HashMap<>();
public PluginUpdateManager() {
register(new PluginUpdate(1, new String[] { "Applies a fix for skull textures values in 4.7.1.", "Texture values data storage changed in 4.7.1 due to the UUID change." }, sender -> {
@ -123,13 +124,12 @@ public class PluginUpdateManager {
}
}));
register(new PluginUpdate(2, new String[] { "Enables the item updater for every item.", "&cNot recommended unless you know what you are doing." }, sender -> {
for (Type type : MMOItems.plugin.getTypes().getAll())
for (String id : type.getConfigFile().getConfig().getKeys(false)) {
String itemPath = type.getId() + "." + id;
MMOItems.plugin.getUpdater().enable(MMOItems.plugin.getUpdater().newUpdaterData(itemPath, UUID.randomUUID(), true, true, true, true, true, true));
}
}));
register(new PluginUpdate(2,
new String[] { "Enables the item updater for every item.", "&cNot recommended unless you know what you are doing." }, sender -> {
for (Type type : MMOItems.plugin.getTypes().getAll())
for (String id : type.getConfigFile().getConfig().getKeys(false))
MMOItems.plugin.getUpdater().enable(new UpdaterData(type, id, UUID.randomUUID(), true));
}));
}
public void register(PluginUpdate update) {

View File

@ -1,11 +1,12 @@
package net.Indyuce.mmoitems.manager;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@ -23,6 +24,7 @@ import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.UpdaterData;
import net.Indyuce.mmoitems.api.item.MMOItem;
import net.Indyuce.mmoitems.api.item.VolatileMMOItem;
import net.Indyuce.mmoitems.stat.type.ItemStat;
@ -34,40 +36,84 @@ public class UpdaterManager implements Listener {
private final Map<String, UpdaterData> map = new HashMap<>();
public UpdaterManager() {
FileConfiguration updater = new ConfigFile("/dynamic", "updater").getConfig();
for (String type : updater.getKeys(false))
for (String id : updater.getConfigurationSection(type).getKeys(false)) {
String path = type + "." + id;
enable(new UpdaterData(path, updater));
FileConfiguration config = new ConfigFile("/dynamic", "updater").getConfig();
for (String typeFormat : config.getKeys(false))
try {
MMOItems.plugin.getLogger().log(Level.INFO, "Checking " + typeFormat);
Type type = MMOItems.plugin.getTypes().getOrThrow(typeFormat);
for (String id : config.getConfigurationSection(typeFormat).getKeys(false)) {
MMOItems.plugin.getLogger().log(Level.INFO, "Loading " + id);
enable(new UpdaterData(type, id, config.getConfigurationSection(typeFormat + "." + id)));
}
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING,
"An issue occured while trying to load dynamic updater data: " + exception.getMessage());
}
}
public UpdaterData getData(MMOItem mmoitem) {
return getData(mmoitem.getType(), mmoitem.getId());
}
public UpdaterData getData(Type type, String id) {
return map.get(toPath(type, id));
}
@Deprecated
public UpdaterData getData(String path) {
return map.get(path);
}
public boolean hasData(MMOItem mmoitem) {
return hasData(mmoitem.getType(), mmoitem.getId());
}
public boolean hasData(Type type, String id) {
return map.containsKey(toPath(type, id));
}
@Deprecated
public boolean hasData(String path) {
return map.containsKey(path);
}
public Collection<UpdaterData> getDatas() {
public Collection<UpdaterData> getActive() {
return map.values();
}
public Set<String> getItemPaths() {
return map.keySet();
public void disable(Type type, String id) {
map.remove(toPath(type, id));
}
@Deprecated
public void disable(String path) {
map.remove(path);
}
public void enable(MMOItem mmoitem) {
enable(mmoitem.getType(), mmoitem.getId());
}
public void enable(Type type, String id) {
enable(new UpdaterData(type, id, UUID.randomUUID()));
}
@Deprecated
public void enable(String path) {
enable(new UpdaterData(path, UUID.randomUUID()));
String[] split = path.split("\\.");
Type type = MMOItems.plugin.getTypes().getOrThrow(split[0]);
enable(type, split[1]);
}
public void enable(UpdaterData data) {
map.put(data.path, data);
map.put(data.getPath(), data);
}
/*
* these keys are used to easily save updater data instances
*/
private String toPath(Type type, String id) {
return type.getId() + "." + id;
}
/*
@ -136,10 +182,10 @@ public class UpdaterManager implements Listener {
* calculate every stat data from the older item.
*/
MMOItem itemMMO = new VolatileMMOItem(item);
if (did.keepGems() && itemMMO.hasData(ItemStat.GEM_SOCKETS))
if (did.hasOption(KeepOption.KEEP_GEMS) && itemMMO.hasData(ItemStat.GEM_SOCKETS))
newItemMMO.setData(ItemStat.GEM_SOCKETS, itemMMO.getData(ItemStat.GEM_SOCKETS));
if (did.keepSoulbound() && itemMMO.hasData(ItemStat.SOULBOUND))
if (did.hasOption(KeepOption.KEEP_SOULBOUND) && itemMMO.hasData(ItemStat.SOULBOUND))
newItemMMO.setData(ItemStat.SOULBOUND, itemMMO.getData(ItemStat.SOULBOUND));
// apply amount
@ -154,7 +200,7 @@ public class UpdaterManager implements Listener {
* remember of ANY enchant on the old item, even the enchants that were
* removed!
*/
if (did.keepEnchants()) {
if (did.hasOption(KeepOption.KEEP_ENCHANTS)) {
Map<Enchantment, Integer> enchants = item.getItem().getItemMeta().getEnchants();
for (Enchantment enchant : enchants.keySet())
newItemMeta.addEnchant(enchant, enchants.get(enchant), true);
@ -164,7 +210,7 @@ public class UpdaterManager implements Listener {
* keepLore is used to save enchants from custom enchants plugins that
* only use lore to save enchant data
*/
if (did.keepLore()) {
if (did.hasOption(KeepOption.KEEP_LORE)) {
int n = 0;
for (String s : item.getItem().getItemMeta().getLore()) {
if (!s.startsWith(ChatColor.GRAY + ""))
@ -178,14 +224,14 @@ public class UpdaterManager implements Listener {
* users do not get extra durability when the item is updated
*/
VersionWrapper wrapper = MMOLib.plugin.getVersion().getWrapper();
if (did.keepDurability() && wrapper.isDamageable(item.getItem()) && wrapper.isDamageable(newItem))
if (did.hasOption(KeepOption.KEEP_DURABILITY) && wrapper.isDamageable(item.getItem()) && wrapper.isDamageable(newItem))
wrapper.applyDurability(newItem, newItemMeta, wrapper.getDurability(item.getItem(), item.getItem().getItemMeta()));
/*
* keep name so players who renamed the item in the anvil does not have
* to rename it again
*/
if (did.keepName() && item.getItem().getItemMeta().hasDisplayName())
if (did.hasOption(KeepOption.KEEP_NAME) && item.getItem().getItemMeta().hasDisplayName())
newItemMeta.setDisplayName(item.getItem().getItemMeta().getDisplayName());
newItemMeta.setLore(lore);
@ -193,116 +239,28 @@ public class UpdaterManager implements Listener {
return newItem;
}
public UpdaterData newUpdaterData(String path, FileConfiguration config) {
return new UpdaterData(path, config);
}
public enum KeepOption {
KEEP_LORE("Any lore line starting with '&7' will be", "kept when updating your item.", "", "This option is supposed to keep",
"the item custom enchants.", ChatColor.RED + "May not support every enchant plugin."),
KEEP_ENCHANTS("The item keeps its old enchantments."),
KEEP_DURABILITY("The item keeps its durability.", "Don't use this option if you", "are using texture-by-durability!"),
KEEP_NAME("The item keeps its display name."),
KEEP_GEMS("The item keeps its empty gem", "sockets and applied gems."),
KEEP_SOULBOUND("The item keeps its soulbound data."),
KEEP_SKIN("Keep the item applied skins.");
public UpdaterData newUpdaterData(String path, UUID uuid, boolean keepLore, boolean keepEnchants, boolean keepDurability, boolean keepName,
boolean keepGems, boolean keepSoulbound) {
return new UpdaterData(path, uuid, keepLore, keepEnchants, keepDurability, keepName, keepGems, keepSoulbound);
}
private final List<String> lore;
public class UpdaterData {
// itemType.name() + "." + itemId
private final String path;
/*
* two UUIDs can be found : one on the itemStack in the nbttags, and one
* in the UpdaterData instance. if the two match, the item is up to
* date. if they don't match, the item needs to be updated
*/
private final UUID uuid;
private boolean keepLore, keepDurability, keepEnchants, keepName, keepGems, keepSoulbound;
public UpdaterData(String path, FileConfiguration config) {
this(path, UUID.fromString(config.getString(path + ".uuid")), config.getBoolean(path + ".lore"), config.getBoolean(path + ".enchants"),
config.getBoolean(path + ".durability"), config.getBoolean(path + ".name"), config.getBoolean(path + ".gems"),
config.getBoolean(path + ".soulbound"));
private KeepOption(String... lore) {
this.lore = Arrays.asList(lore);
}
public UpdaterData(String path, UUID uuid) {
this(path, uuid, false, false, false, false, false, false);
public List<String> getLore() {
return lore;
}
public UpdaterData(String path, UUID uuid, boolean keepLore, boolean keepEnchants, boolean keepDurability, boolean keepName, boolean keepGems,
boolean keepSoulbound) {
this.uuid = uuid;
this.path = path;
this.keepLore = keepLore;
this.keepEnchants = keepEnchants;
this.keepDurability = keepDurability;
this.keepName = keepName;
this.keepGems = keepGems;
this.keepSoulbound = keepSoulbound;
}
public void save(FileConfiguration config) {
config.set(path + ".lore", keepLore);
config.set(path + ".enchants", keepEnchants);
config.set(path + ".durability", keepDurability);
config.set(path + ".name", keepName);
config.set(path + ".gems", keepGems);
config.set(path + ".soulbound", keepSoulbound);
config.set(path + ".uuid", uuid.toString());
}
public UUID getUniqueId() {
return uuid;
}
public boolean matches(NBTItem item) {
return uuid.toString().equals(item.getString("MMOITEMS_ITEM_UUID"));
}
public boolean keepLore() {
return keepLore;
}
public boolean keepDurability() {
return keepDurability;
}
public boolean keepEnchants() {
return keepEnchants;
}
public boolean keepName() {
return keepName;
}
public boolean keepGems() {
return keepGems;
}
public boolean keepSoulbound() {
return keepSoulbound;
}
public void setKeepLore(boolean value) {
keepLore = value;
}
public void setKeepDurability(boolean value) {
keepDurability = value;
}
public void setKeepEnchants(boolean value) {
keepEnchants = value;
}
public void setKeepName(boolean value) {
keepName = value;
}
public void setKeepGems(boolean value) {
keepGems = value;
}
public void setKeepSoulbound(boolean value) {
keepSoulbound = value;
public String getPath() {
return name().toLowerCase().replace("_", "-").substring(5);
}
}
}