diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageMagic.java b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageMagic.java index 4931854b9..93bb25f04 100644 --- a/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageMagic.java +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNpcManageMagic.java @@ -7,9 +7,8 @@ import java.util.Vector; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.magic.*; -import noppes.npcs.client.gui.magic.SubGuiMagicCycleEdit; +import noppes.npcs.client.gui.magic.SubGuiMagicCycle; import noppes.npcs.client.gui.magic.SubGuiMagic; -import noppes.npcs.client.gui.magic.SubGuiMagicInteractionsEdit; import noppes.npcs.client.gui.util.*; import noppes.npcs.constants.EnumScrollData; import noppes.npcs.controllers.data.Magic; @@ -390,7 +389,7 @@ public class GuiNpcManageMagic extends GuiNPCInterface2 implements ISubGuiListen break; case 6: // Edit Cycle. if (selectedCycle != null) { - setSubGui(new SubGuiMagicCycleEdit(selectedCycle)); + setSubGui(new SubGuiMagicCycle(this, selectedCycle)); } break; // --- Magic Management Buttons --- @@ -412,11 +411,6 @@ public class GuiNpcManageMagic extends GuiNPCInterface2 implements ISubGuiListen setSubGui(new SubGuiMagic(this, selectedMagic)); } break; - case 3: // Edit Interactions. - if (selectedMagic != null) { - setSubGui(new SubGuiMagicInteractionsEdit(selectedMagic)); - } - break; } updateButtons(); diff --git a/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagic.java b/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagic.java index 34cfc9c06..e3bbdf1ba 100644 --- a/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagic.java +++ b/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagic.java @@ -50,8 +50,8 @@ public class SubGuiMagic extends SubGuiInterface implements ITextfieldListener, addLabel(new GuiNpcLabel(1,"gui.name", guiLeft + 4, y + 5)); addTextField(new GuiNpcTextField(1, this, this.fontRendererObj, guiLeft + 80, y, 200, 20, magic.name)); - addLabel(new GuiNpcLabel(100,"ID", guiLeft + 200 + 105, guiTop + 4)); - addLabel(new GuiNpcLabel(100, magic.id + "", + 200 + 105, guiTop + 14)); + addLabel(new GuiNpcLabel(-10,"ID", guiLeft + 200 + 80 + 5, y + 2)); + addLabel(new GuiNpcLabel(-11, magic.id + "", guiLeft + 200 + 80 + 5, y + 12)); y += 25; diff --git a/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagicCycle.java b/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagicCycle.java new file mode 100644 index 000000000..3a5d93510 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagicCycle.java @@ -0,0 +1,293 @@ +package noppes.npcs.client.gui.magic; + +import kamkeel.npcs.network.PacketClient; +import kamkeel.npcs.network.packets.request.magic.MagicCycleSavePacket; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.global.GuiNpcManageMagic; +import noppes.npcs.client.gui.util.*; +import noppes.npcs.constants.EnumDiagramLayout; +import noppes.npcs.controllers.data.Magic; +import noppes.npcs.controllers.data.MagicAssociation; +import noppes.npcs.controllers.data.MagicCycle; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class SubGuiMagicCycle extends SubGuiInterface implements ITextfieldListener, ISubGuiListener, ICustomScrollListener { + + // Reference to the parent GUI (for accessing magicData) + public GuiNpcManageMagic parent; + // Scroll for available magics (to add as associations) + private GuiCustomScroll allMagic; + // Scroll for magics already associated with the cycle + private GuiCustomScroll associationScroll; + + // The MagicCycle we are editing + private MagicCycle cycle; + + // Search string for available magics + private String search = ""; + + // Text fields for basic cycle data + // ID 1: cycle name, ID 2: cycle display name + // Button ID 13 will cycle through the layouts + // IDs 91 & 92 for editing selected association’s index and priority + private GuiNpcTextField indexField; + private GuiNpcTextField priorityField; + + // The currently selected associated magic (by its name) + private String selectedAssociation; + // A mapping of magic names (from parent.magicData) to their MagicAssociation objects. + private HashMap associationMap = new HashMap<>(); + + public SubGuiMagicCycle(GuiNpcManageMagic parent, MagicCycle cycle) { + this.parent = parent; + this.cycle = cycle; + setBackground("menubg.png"); + xSize = 415; + ySize = 216; + closeOnEsc = true; + processAssociations(); + } + + @Override + public void initGui() { + super.initGui(); + int y = guiTop + 6; + + // --- Basic Cycle Data --- + addLabel(new GuiNpcLabel(1, "gui.name", guiLeft + 4, y + 5)); + addTextField(new GuiNpcTextField(1, this, fontRendererObj, guiLeft + 80, y, 200, 20, cycle.name)); + + addLabel(new GuiNpcLabel(-10,"ID", guiLeft + 200 + 80 + 5, y + 2)); + addLabel(new GuiNpcLabel(-11, cycle.id + "", guiLeft + 200 + 80 + 5, y + 12)); + + // Layout button – cycles through available layouts + addLabel(new GuiNpcLabel(3, "magic.diagram", guiLeft + 307, y + 5)); + + y += 25; + + addButton(new GuiNpcButton(13, guiLeft + 307, y, 100, 20, EnumDiagramLayout.names(), cycle.layout.ordinal())); + getButton(13).setHoverText("diagram.info"); + + addLabel(new GuiNpcLabel(2, "gui.displayName", guiLeft + 4, y + 5)); + addTextField(new GuiNpcTextField(2, this, fontRendererObj, guiLeft + 80, y, 200, 20, cycle.displayName)); + + y += 27; + + // Labels and text fields for editing the selected association's ordering data. + addLabel(new GuiNpcLabel(50, "magic.index", guiLeft + 5, y + 5)); + indexField = new GuiNpcTextField(91, this, fontRendererObj, guiLeft + 100, y, 45, 20, ""); + indexField.setIntegersOnly(); + addTextField(indexField); + + addLabel(new GuiNpcLabel(51, "magic.priority", guiLeft + 150, y + 5)); + priorityField = new GuiNpcTextField(92, this, fontRendererObj, guiLeft + 250, y, 45, 20, ""); + priorityField.setIntegersOnly(); + addTextField(priorityField); + + indexField.enabled = false; + priorityField.enabled = false; + + y += 27; + + // --- Association Management --- + // Scroll for available magics to add + if(allMagic == null){ + allMagic = new GuiCustomScroll(this, 0); + allMagic.setSize(150, 100); + } + allMagic.guiLeft = guiLeft + 5; + allMagic.guiTop = y; + // Set list based on parent's magicData filtered by search + allMagic.setList(getAvailableMagicList()); + this.addScroll(allMagic); + + // Search field for available magics (ID 34) + addTextField(new GuiNpcTextField(34, this, fontRendererObj, guiLeft + 5, y + 105, 150, 20, search)); + + // Scroll for current associations + if(associationScroll == null){ + associationScroll = new GuiCustomScroll(this, 1); + associationScroll.setSize(150, 125); + } + associationScroll.guiLeft = guiLeft + 205; + associationScroll.guiTop = y; + // Populate with keys from associationMap + associationScroll.setList(new ArrayList<>(associationMap.keySet())); + this.addScroll(associationScroll); + + // Buttons to add/remove associations (IDs 60, 61, 62, 63) + addButton(new GuiNpcButton(60, guiLeft + 160, y + 20, 30, 20, ">")); + addButton(new GuiNpcButton(61, guiLeft + 160, y + 45, 30, 20, "<")); + addButton(new GuiNpcButton(62, guiLeft + 160, y + 70, 30, 20, ">>")); + addButton(new GuiNpcButton(63, guiLeft + 160, y + 95, 30, 20, "<<")); + + // Done Button to save changes + addButton(new GuiNpcButton(99, guiLeft + xSize - 50 - 4, guiTop + ySize - 20 - 4, 50, 20, "gui.done")); + } + + @Override + public void actionPerformed(GuiButton button) { + // Layout button: update cycle layout. + if(button.id == 13) { + cycle.layout = EnumDiagramLayout.values()[((GuiNpcButton)button).getValue()]; + } + // Add single association + if(button.id == 60 && allMagic.hasSelected() && !associationMap.containsKey(allMagic.getSelected()) + && parent.magicData.containsKey(allMagic.getSelected())) { + MagicAssociation assoc = new MagicAssociation(); + assoc.magicId = parent.magicData.get(allMagic.getSelected()); + assoc.index = 0; + assoc.priority = 0; + associationMap.put(allMagic.getSelected(), assoc); + associationScroll.list.add(allMagic.getSelected()); + } + // Remove single association + if(button.id == 61 && associationScroll.hasSelected()) { + associationMap.remove(associationScroll.getSelected()); + associationScroll.list.remove(associationScroll.selected); + associationScroll.selected = -1; + indexField.enabled = false; + indexField.setText(""); + priorityField.enabled = false; + priorityField.setText(""); + } + // Add All available associations + if(button.id == 62) { + for(String name : parent.magicData.keySet()){ + if(!associationMap.containsKey(name)){ + MagicAssociation assoc = new MagicAssociation(); + assoc.magicId = parent.magicData.get(name); + assoc.index = 0; + assoc.priority = 0; + associationMap.put(name, assoc); + } + } + associationScroll.setList(new ArrayList<>(associationMap.keySet())); + } + // Remove All associations + if(button.id == 63) { + associationMap.clear(); + associationScroll.setList(new ArrayList<>()); + associationScroll.selected = -1; + indexField.enabled = false; + indexField.setText(""); + priorityField.enabled = false; + priorityField.setText(""); + } + // Done – save changes and close + if(button.id == 99) { + close(); + } + } + + @Override + public void unFocused(GuiNpcTextField textField) { + // Update cycle basic data. + if(textField.id == 1) { + cycle.name = textField.getText(); + } + if(textField.id == 2) { + cycle.displayName = textField.getText(); + } + // Update association ordering values when fields lose focus. + if(textField.id == 91 && selectedAssociation != null) { + try { + int idx = Integer.parseInt(textField.getText()); + associationMap.get(selectedAssociation).index = idx; + } catch(NumberFormatException e) { } + } + if(textField.id == 92 && selectedAssociation != null) { + try { + int prio = Integer.parseInt(textField.getText()); + associationMap.get(selectedAssociation).priority = prio; + } catch(NumberFormatException e) { } + } + } + + @Override + public void keyTyped(char c, int i) { + super.keyTyped(c, i); + // Available magic search (text field ID 34) + if(getTextField(34) != null && getTextField(34).isFocused()){ + if(search.equals(getTextField(34).getText())) + return; + search = getTextField(34).getText().toLowerCase(); + allMagic.setList(getAvailableMagicList()); + allMagic.resetScroll(); + } + } + + /** + * Returns a filtered list of available magic names based on parent's magicData and the search string. + * Only those not already in the associationMap are shown. + */ + private List getAvailableMagicList(){ + List original = new ArrayList<>(parent.magicData.keySet()); + List filtered = new ArrayList<>(); + for(String name : original){ + if(!associationMap.containsKey(name) && name.toLowerCase().contains(search)){ + filtered.add(name); + } + } + return filtered; + } + + /** + * Populates associationMap from cycle.associations using parent's magicData. + */ + public void processAssociations(){ + associationMap.clear(); + // For each association stored in the cycle, find the corresponding magic name. + for(MagicAssociation assoc : cycle.associations.values()){ + for(String name : parent.magicData.keySet()){ + if(parent.magicData.get(name) == assoc.magicId){ + associationMap.put(name, assoc); + break; + } + } + } + } + + @Override + public void customScrollClicked(int id, int index, int clickType, GuiCustomScroll scroll) { + // When clicking the association scroll, update the ordering text fields. + if(scroll.id == 1){ + selectedAssociation = scroll.getSelected(); + if(selectedAssociation != null && associationMap.containsKey(selectedAssociation)){ + indexField.enabled = true; + priorityField.enabled = true; + indexField.setText(associationMap.get(selectedAssociation).index + ""); + priorityField.setText(associationMap.get(selectedAssociation).priority + ""); + } + } + } + + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) { } + + @Override + public void subGuiClosed(SubGuiInterface subgui) { + // No subgui handling in this example. + } + + /** + * Called when the user clicks "Done". Updates the cycle's associations from associationMap, + * writes the NBT data, and sends a MagicCycleSavePacket. + */ + public void close(){ + // Clear the cycle's associations and update them from our associationMap. + cycle.associations.clear(); + for(String name : associationMap.keySet()){ + cycle.associations.put(associationMap.get(name).magicId, associationMap.get(name)); + } + NBTTagCompound compound = new NBTTagCompound(); + cycle.writeNBT(compound); + PacketClient.sendClient(new MagicCycleSavePacket(compound)); + super.close(); + } +} diff --git a/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagicCycleEdit.java b/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagicCycleEdit.java deleted file mode 100644 index a5caaeaae..000000000 --- a/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagicCycleEdit.java +++ /dev/null @@ -1,108 +0,0 @@ -package noppes.npcs.client.gui.magic; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import kamkeel.npcs.network.PacketClient; -import kamkeel.npcs.network.packets.request.magic.MagicCycleSavePacket; -import net.minecraft.client.gui.GuiButton; -import noppes.npcs.client.gui.util.*; -import noppes.npcs.controllers.data.MagicAssociation; -import noppes.npcs.controllers.data.MagicCycle; -import net.minecraft.nbt.NBTTagCompound; - -public class SubGuiMagicCycleEdit extends SubGuiInterface implements ITextfieldListener { - - private MagicCycle cycle; - private GuiCustomScroll assocScroll; - private List assocList = new ArrayList<>(); - - public SubGuiMagicCycleEdit(MagicCycle cycle) { - this.cycle = cycle; - setBackground("menubg.png"); - xSize = 360; - ySize = 240; - } - - @Override - public void initGui() { - super.initGui(); - addLabel(new GuiNpcLabel(0, "Cycle: " + cycle.name, guiLeft + 4, guiTop + 4)); - - // Build list of associations in the cycle (format: "MagicID: X | Index: Y | Priority: Z") - assocList.clear(); - for (Map.Entry entry : cycle.associations.entrySet()) { - MagicAssociation assoc = entry.getValue(); - assocList.add("MagicID: " + entry.getKey() + " | Index: " + assoc.index + " | Priority: " + assoc.priority); - } - if (assocScroll == null) { - assocScroll = new GuiCustomScroll(this, 0, 0); - } - assocScroll.guiLeft = guiLeft + 4; - assocScroll.guiTop = guiTop + 20; - assocScroll.setSize(200, 100); - assocScroll.setList(assocList); - this.addScroll(assocScroll); - - // Text fields for editing the selected association’s index and priority - addLabel(new GuiNpcLabel(1, "Index:", guiLeft + 210, guiTop + 30)); - addTextField(new GuiNpcTextField(1, this, fontRendererObj, guiLeft + 250, guiTop + 26, 50, 20, "")); - addLabel(new GuiNpcLabel(2, "Priority:", guiLeft + 210, guiTop + 60)); - addTextField(new GuiNpcTextField(2, this, fontRendererObj, guiLeft + 250, guiTop + 56, 50, 20, "")); - - // Buttons to update or remove the selected association - addButton(new GuiNpcButton(10, guiLeft + 210, guiTop + 90, 70, 20, "Update")); - addButton(new GuiNpcButton(11, guiLeft + 290, guiTop + 90, 70, 20, "Remove")); - - // Done button to save cycle changes via packet - addButton(new GuiNpcButton(99, guiLeft + 4, guiTop + 130, 60, 20, "Done")); - } - - @Override - public void actionPerformed(GuiButton button) { - if (button.id == 10) { // Update association - String selected = assocScroll.getSelected(); - if (selected != null && !selected.isEmpty()) { - try { - String[] parts = selected.split(" "); - int magicId = Integer.parseInt(parts[1]); - MagicAssociation assoc = cycle.associations.get(magicId); - if (assoc != null) { - int newIndex = Integer.parseInt(getTextField(1).getText()); - int newPriority = Integer.parseInt(getTextField(2).getText()); - assoc.index = newIndex; - assoc.priority = newPriority; - } - } catch (NumberFormatException e) { - // Handle parse error if needed. - } - initGui(); - } - } - if (button.id == 11) { // Remove association - String selected = assocScroll.getSelected(); - if (selected != null && !selected.isEmpty()) { - try { - String[] parts = selected.split(" "); - int magicId = Integer.parseInt(parts[1]); - cycle.associations.remove(magicId); - } catch (NumberFormatException e) { - // Handle error if needed. - } - initGui(); - } - } - if (button.id == 99) { // Done: send save packet - NBTTagCompound compound = new NBTTagCompound(); - cycle.writeNBT(compound); - PacketClient.sendClient(new MagicCycleSavePacket(compound)); - close(); - } - } - - @Override - public void unFocused(GuiNpcTextField textfield) { - - } -} diff --git a/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagicInteractionsEdit.java b/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagicInteractionsEdit.java deleted file mode 100644 index 0419cf3a8..000000000 --- a/src/main/java/noppes/npcs/client/gui/magic/SubGuiMagicInteractionsEdit.java +++ /dev/null @@ -1,100 +0,0 @@ -package noppes.npcs.client.gui.magic; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import kamkeel.npcs.network.PacketClient; -import net.minecraft.client.gui.GuiButton; -import noppes.npcs.client.gui.util.*; -import noppes.npcs.controllers.data.Magic; -import net.minecraft.nbt.NBTTagCompound; - -public class SubGuiMagicInteractionsEdit extends SubGuiInterface implements ITextfieldListener { - - private Magic magic; - private GuiCustomScroll interactionsScroll; - private List interactionsList = new ArrayList<>(); - - public SubGuiMagicInteractionsEdit(Magic magic) { - this.magic = magic; - setBackground("menubg.png"); - xSize = 360; - ySize = 280; - } - - @Override - public void initGui() { - super.initGui(); - addLabel(new GuiNpcLabel(0, "Edit Interactions: " + magic.name, guiLeft + 4, guiTop + 4)); - - // Build interactions list from magic.interactions map. - interactionsList.clear(); - for (Map.Entry entry : magic.interactions.entrySet()) { - interactionsList.add("MagicID: " + entry.getKey() + " | Percent: " + entry.getValue()); - } - if (interactionsScroll == null) { - interactionsScroll = new GuiCustomScroll(this, 0, 0); - } - interactionsScroll.guiLeft = guiLeft + 4; - interactionsScroll.guiTop = guiTop + 20; - interactionsScroll.setSize(200, 100); - interactionsScroll.setList(interactionsList); - this.addScroll(interactionsScroll); - - // Text fields for adding/updating an interaction - addLabel(new GuiNpcLabel(1, "Target Magic ID:", guiLeft + 210, guiTop + 30)); - addTextField(new GuiNpcTextField(1, this, fontRendererObj, guiLeft + 310, guiTop + 26, 40, 20, "")); - addLabel(new GuiNpcLabel(2, "Percent:", guiLeft + 210, guiTop + 60)); - addTextField(new GuiNpcTextField(2, this, fontRendererObj, guiLeft + 310, guiTop + 56, 40, 20, "")); - - // Buttons to add, update, or remove an interaction - addButton(new GuiNpcButton(10, guiLeft + 210, guiTop + 90, 70, 20, "Add")); - addButton(new GuiNpcButton(11, guiLeft + 290, guiTop + 90, 70, 20, "Update")); - addButton(new GuiNpcButton(12, guiLeft + 210, guiTop + 115, 70, 20, "Remove")); - - // Done button to send changes - addButton(new GuiNpcButton(99, guiLeft + 4, guiTop + 240, 60, 20, "Done")); - } - - @Override - public void actionPerformed(GuiButton button) { - if (button.id == 10) { // Add interaction - try { - int targetId = Integer.parseInt(getTextField(1).getText()); - float percent = Float.parseFloat(getTextField(2).getText()); - magic.interactions.put(targetId, percent); - } catch (NumberFormatException e) { } - initGui(); - } - if (button.id == 11) { // Update interaction - String selected = interactionsScroll.getSelected(); - if (selected != null && !selected.isEmpty()) { - try { - int targetId = Integer.parseInt(selected.split(" ")[1]); - float percent = Float.parseFloat(getTextField(2).getText()); - magic.interactions.put(targetId, percent); - } catch (NumberFormatException e) { } - initGui(); - } - } - if (button.id == 12) { // Remove interaction - String selected = interactionsScroll.getSelected(); - if (selected != null && !selected.isEmpty()) { - try { - int targetId = Integer.parseInt(selected.split(" ")[1]); - magic.interactions.remove(targetId); - } catch (NumberFormatException e) { } - initGui(); - } - } - - if (button.id == 99) { - close(); - } - } - - @Override - public void unFocused(noppes.npcs.client.gui.util.GuiNpcTextField textField) { - } -} diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNpcButton.java b/src/main/java/noppes/npcs/client/gui/util/GuiNpcButton.java index 820ad12ca..1052b9c21 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNpcButton.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNpcButton.java @@ -157,10 +157,10 @@ public class GuiNpcButton extends GuiButton { oldHover = hoverState; hoverCount = 0; } else { - if (hoverCount < 110) + if (hoverCount < 65) hoverCount++; } - if (hoverState == 2 && hoverCount > 100) { + if (hoverState == 2 && hoverCount > 60) { GL11.glPushMatrix(); Minecraft mc = Minecraft.getMinecraft(); String displayString = StatCollector.translateToLocal(hoverableText); diff --git a/src/main/java/noppes/npcs/constants/EnumDiagramLayout.java b/src/main/java/noppes/npcs/constants/EnumDiagramLayout.java index 2919c49e4..c27422317 100644 --- a/src/main/java/noppes/npcs/constants/EnumDiagramLayout.java +++ b/src/main/java/noppes/npcs/constants/EnumDiagramLayout.java @@ -1,5 +1,7 @@ package noppes.npcs.constants; +import java.util.ArrayList; + public enum EnumDiagramLayout { CIRCULAR("diagram.circular"), SQUARE("diagram.square"), @@ -18,4 +20,12 @@ public enum EnumDiagramLayout { public String getLang(){ return lang; } + + public static String[] names(){ + ArrayList list = new ArrayList(); + for(EnumDiagramLayout e : values()) + list.add(e.lang); + + return list.toArray(new String[list.size()]); + } } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 7b26fe40c..131ff872a 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1339,6 +1339,19 @@ profile.cooldown=Cooldown profile.individual=Individual magic.interaction=Interaction +magic.diagram=Diagram Type +magic.index=Index +magic.priority=Priority + +diagram.circular=Circular +diagram.square=Square +diagram.tree=Tree +diagram.generated=Generated +diagram.circular_manual=Circular [Manual] +diagram.square_manual=Square [Manual] +diagram.tree_manual=Tree [Manual] +diagram.chart=Chart +diagram.info=Manual: Index is the level, Priority is the order. rpgcore:tooltip=Hold %key% to view attributes rpgcore:attribute.damage=Damage