Custom Enchantments. You say what?!

Discussion in 'Resources' started by CaptainBern, Jul 17, 2013.

Thread Status:
Not open for further replies.
  1. Hello all, CaptainBern here.
    Today I will show you how to create your own custom enchantments because I couldn't find any tutorials on how to do it and because the ones I found weren't really "custom", if you know what I'm talking about.

    So let's start, oh yeah, this is my first tutorial so don't be mad at me if it isn't clear enough for you :p

    To create custom enchantments you will first have to create a class that extends Enchantment.
    Like this:

    Code:java
    1.  
    2. package your.custom.enchantment.plugin;
    3.  
    4. import org.bukkit.enchantments.Enchantment;
    5. import org.bukkit.enchantments.EnchantmentTarget;
    6. import org.bukkit.inventory.ItemStack;
    7.  
    8. public class CustomEnchantment extends Enchantment {
    9.  
    10. public CustomEnchantment(int id) {
    11. super(id);
    12. }
    13.  
    14. @Override
    15. public boolean canEnchantItem(ItemStack item) {
    16. return true; //can it enchant items?
    17. }
    18.  
    19. @Override
    20. public boolean conflictsWith(Enchantment other) {
    21. return false; //will it conflict? In this case, nope
    22. }
    23.  
    24. @Override
    25. public EnchantmentTarget getItemTarget() {
    26. return null; //you can define a target here, I just setted it to null...
    27. }
    28.  
    29. @Override
    30. public int getMaxLevel() {
    31. return 2; //the maxmimum level.
    32. }
    33.  
    34. @Override
    35. public String getName() {
    36. return "CoolFlyEnchantThingy"; //the name
    37. }
    38.  
    39. @Override
    40. public int getId(){
    41. return 69;//the id, in this case I choose 69 because no serious person would ever choose that number, lucky me! Oh yeah, the max id = 256
    42. }
    43.  
    44. @Override
    45. public int getStartLevel() {
    46. return 1; //the start level of your enchantment
    47. }
    48.  
    49. }
    50.  

    The enchantment will do nothing on it's own. So first we go to our main class, and add this:

    Code:java
    1.  
    2. public static CustomEnchantment ench = new CustomEnchantment(69);
    3.  

    Now you would think you can simply do:

    Code:java
    1.  
    2. item.addUnsafeEnchantment(urMainClass.ench, 1);
    3.  

    But nope, this will work but when other plugins try to get the enchantment it will throw an npe. (Or when some minecraft/bukkit thingy tries to get it)
    So, yes we will need reflection to register the enchantment.

    Like this:

    Code:java
    1.  
    2. try{
    3. try {
    4. Field f = Enchantment.class.getDeclaredField("acceptingNew");
    5. f.setAccessible(true);
    6. f.set(null, true);
    7. } catch (Exception e) {
    8. e.printStackTrace();
    9. }
    10. try {
    11. Enchantment.registerEnchantment(ench);
    12. //if this is thrown it means the id is already taken.
    13. }
    14. }catch(Exception e){
    15. e.printStackTrace();
    16. }
    17.  

    (I would place it in my main class under the onEnable() method)
    Now you successfully created a custom enchantment.

    Because the enchantmentsystem doesn't like reload, ELCHILEN0 made us a nice tool that resets the byId and byName hashmap (the stuff that causes the id error)

    Here it is:

    Code:java
    1.  
    2. try {
    3. Field byIdField = Enchantment.class.getDeclaredField("byId");
    4. Field byNameField = Enchantment.class.getDeclaredField("byName");
    5.  
    6. byIdField.setAccessible(true);
    7. byNameField.setAccessible(true);
    8.  
    9. @SuppressWarnings("unchecked")
    10. HashMap<Integer, Enchantment> byId = (HashMap<Integer, Enchantment>) byIdField.get(null);
    11. @SuppressWarnings("unchecked")
    12. HashMap<String, Enchantment> byName = (HashMap<String, Enchantment>) byNameField.get(null);
    13.  
    14. if(byId.containsKey(id))
    15. byId.remove(id);
    16.  
    17. if(byName.containsKey(getName()))
    18. byName.remove(getName());
    19. } catch (Exception ignored) { }
    20.  


    Leave him a like!

    Hope you learned something today!

    (Oh yeah, btw this enchantment will not show up in the enchantment table and it will crash the client when a player puts it in an anvil, but with a simpel listener you can avoid this)
     
  2. Offline

    DarkBladee12

    CaptainBern Does the enchantment show up when you hover over an item which has it?
     
  3. Offline

    Omerrg

    Nicely done, I made a simmiliar thing, But yours seems better.
     
    CaptainBern likes this.
  4. Like on the lore? It never did for me; But you can set it yourself or maybe it will if you define a target item...

    Edit: I took al look at the minecraft source, and no, the level will not show up, if you want this then you will have to build against CraftBukkit and use the enchantment class of net.minecraft.server, this includes a method (a) that will display the name; here is it's code:

    public String a() {
    return "enchantment." + this.name;
    }

    And then there is the method c that shows up the level;

    public String c(int j){
    Strings=LocaleI18n.get(this.a());
    return s + " " + LocaleI18n.get("enchantment.level." + i);
    }

    ^copy pasted from: https://github.com/Bukkit/mc-dev/blob/master/net/minecraft/server/Enchantment.java#L84 Maybe later I will make a tutorial on how to make a custom enchantments using craftbukkit.
     
  5. Offline

    kreashenz

    This helped me in my thread! Thanks again! +1 (like)
     
    CaptainBern likes this.
  6. Thanks!
     
  7. Offline

    Ultimate_n00b

    I think if you add a ItemStack of defined items, you can enchant it in a table.
    But then again, just me guessing.
     
  8. Hmmm interesting, but I don't think it will work because it seems like Bukkit and CraftBukkit enchantments are stored/created seperatly, like when you change a bukkit enchantment, it will not automagically change the craftbukkit enchantment, don't get me wrong but in the bukkit enchantment class, the registerEnchantment() method to be precise, in there they never makes use of the vanilla enchantment class where the enchantments for the table get registered. The custom enchantment will get added to the enchantcommand if you use the Enchantment.stopAcceptingRegistrations() after you initilialized your enchantment.(or EnchantmentCommand.buildEnchantments();)
     
  9. Offline

    jb_aero

    I've always wondered, does this corrupt worlds if you add a plugin with custom enchants and then remove that plugin while there are items with a custom enchant in the world?
     
  10. jb_aero Nope, but there is a chance the enchantments get's removed / or another plugin will crash because it can't find the enchantment stated in the ench tag.
     
  11. Offline

    foodyling

    CaptainBern Now put your custom enchanted item in an anvil.
     
    CaptainBern likes this.
  12. Oops I knew I forgot something. Thanks for the reminder ;d
     
  13. Offline

    ELCHILEN0

    I have been using this in a few of my plugins before and have found that when the server is reloaded the enchantment can no longer be typecasted to a CustomEnchantment. After doing some digging I have come to the conclusion that while the enchantments may persist in between reloads they are serialized and unserialized back into the default Enchantment wrapper. This in turn means that what was previously an instance of CustomEnchantment now no longer is an instance of that class. However, you can no longer re-register the enchantment because the id and name are already stored. So to fix issues these issues I added some additional reflection that unregisters the enchantment if it is already registered (See below).
    Code:java
    1. try {
    2. Field byIdField = Enchantment.class.getDeclaredField("byId");
    3. Field byNameField = Enchantment.class.getDeclaredField("byName");
    4.  
    5. byIdField.setAccessible(true);
    6. byNameField.setAccessible(true);
    7.  
    8. @SuppressWarnings("unchecked")
    9. HashMap<Integer, Enchantment> byId = (HashMap<Integer, Enchantment>) byIdField.get(null);
    10. @SuppressWarnings("unchecked")
    11. HashMap<String, Enchantment> byName = (HashMap<String, Enchantment>) byNameField.get(null);
    12.  
    13. if(byId.containsKey(id))
    14. byId.remove(id);
    15.  
    16. if(byName.containsKey(getName()))
    17. byName.remove(getName());
    18. } catch (Exception ignored) { }

    Hope this helps somebody! :)
     
    filbert66, Paxination and CaptainBern like this.
  14. ELCHILEN0 I added it to the post, thanks for making this!
     
  15. Offline

    Garris0n

    Anybody know a work-around for the anvil problem? I was thinking just making my own anvil system, but out of curiosity, is there a way to make them work?
     
  16. Nope.
     
  17. Offline

    Cirno

    Don't know when it happened, but probably around 1.7
    Custom enchantments now cause the client to be kicked.
     
  18. Offline

    SoThatsIt

    CaptainBern the max id for enchantments is actually 255 ;)

    i use custom enchantments in my plugins in 1.7 and it works fine :p mine does nothing though except add a glow to the item :p
     
    DrJava likes this.
  19. Offline

    Paxination

    So how do you make the enchantments do things? So far right now I am using it as a form of anti conterfeit process with my in game coupons and for the glow effect for my custom arrows. But if I could get them to do something.....
     
  20. Offline

    Garris0n

    You write the code to do...things...like if you wanted the enchantment to poison people, you'd listen to the EntityDamageByEntityEvent and poison the entity if the attacker had a poison enchant on their item.
     
  21. Offline

    Paxination

    yeah i figured that would be it.

    So, as far as i can tell, arrow entities cant have enchantments on them.

    Is this correct?
     
  22. Paxination I have no idea if this would or wouldn't work. As some people reported this to be broken now, I wouldn't rely too much on it. If you're using the enchantment to 'identify' the arrow, than I would rather take a look at the Metadata API.
     
  23. Offline

    Paxination

    Its working for me. It was an idea to identify the arrow, but i figured you cant put an enchantment on an entity.

    Also, i was using metadata, but felt like it was more code than necessary just to tag something.
     
  24. Paxination Keep in mind the metadata api is one of the safest ways to do what you were trying to do. And eventually, with the metadata api you have about 5 - 6 lines. By using a custom enchantment you have to create a whole new class (about 50 lines). Anyways, have a nice day!
     
  25. Offline

    Paxination

    @CaptainBernWell I needed the enchantment glow effect anyways. This other method I fount did work. Something about adding an enchantment and then removing it?

    You too!
     
  26. Paxination :) Thanks. I don't know about the other method you're talking about so wouldn't know if it is better then this :/
     
  27. Offline

    CookCraftHD

    DarkBladee12 I think its possible to show the name under the item by edditing the lore.
    what you basicly do when you add the enchantment is:
    - get a list of the enchants equiped (by doing this, you include the other enchants like protection.)
    - translate those to strings, and translate the arabic numbers to roman numbers (see the bottom)
    - add them as lore to the itemstack

    the only problem I have is it simpely won't show up.
    debug does show it up normaly
    Maybe becuse the "normal" enchant lore override it?
    does anyone know how I can fix this?

    This is what I have so far:
    Code:java
    1. public static ItemStack addSunEnchant(ItemStack target, int level) {
    2. target.addUnsafeEnchantment(ench, level);
    3. //first, add the enchant (ench) to the itemstack, this is created with the code of this page.
    4. ItemMeta targetmeta = (ItemMeta) target.getItemMeta();
    5. //get the metadata
    6. Map<Enchantment,Integer> enchantments = target.getEnchantments();
    7. //real, not good formated enchants
    8. Iterator<Entry<Enchantment,Integer>> iter = enchantments.entrySet().iterator();
    9. ArrayList<String> enc = new ArrayList<String>();
    10. //array with the correct enchants
    11. while(iter.hasNext()){
    12. Entry<Enchantment,Integer> entry = iter.next();
    13. String lvl = RomanNumbers.run(entry.getValue());
    14. //translate the number, see the bottom for the code
    15. enc.add(entry.getKey().getName().toString() + " " + lvl);
    16. //add the enchant name to the array
    17. }
    18. targetmeta.setLore(enc);
    19. //add the lore
    20. for(String encname : enc) {
    21. log.info(encname);
    22. //debug stuff
    23. }
    24. return target;
    25. //return the itemstack
    26. }


    I swear its something stupits, that is always with me xD

    Roman numbers:
    Code:java
    1. package cookcraft;
    2.  
    3. import java.util.LinkedHashMap;
    4. import java.util.Map.Entry;
    5.  
    6. public class RomanNumbers {
    7. public static String run(int Int) {
    8. LinkedHashMap<String, Integer> roman_numerals = new LinkedHashMap<String, Integer>();
    9. roman_numerals.put("M", 1000);
    10. roman_numerals.put("CM", 900);
    11. roman_numerals.put("D", 500);
    12. roman_numerals.put("CD", 400);
    13. roman_numerals.put("C", 100);
    14. roman_numerals.put("XC", 90);
    15. roman_numerals.put("L", 50);
    16. roman_numerals.put("XL", 40);
    17. roman_numerals.put("X", 10);
    18. roman_numerals.put("IX", 9);
    19. roman_numerals.put("V", 5);
    20. roman_numerals.put("IV", 4);
    21. roman_numerals.put("I", 1);
    22. String res = "";
    23. for(Entry<String, Integer> entry : roman_numerals.entrySet()){
    24. int matches = Int/entry.getValue();
    25. res += repeat(entry.getKey(), matches);
    26. Int = Int % entry.getValue();
    27. }
    28. return res;
    29. }
    30. public static String repeat(String s, int n) {
    31. if(s == null) {
    32. return null;
    33. }
    34. final StringBuilder sb = new StringBuilder();
    35. for(int i = 0; i < n; i++) {
    36. sb.append(s);
    37. }
    38. return sb.toString();
    39. }
    40.  
    41. }
    42.  
     
  28. Offline

    CookCraftHD

  29. Offline

    raGan.

    CookCraftHD
    Code:
    target.setItemMeta(targetmeta)
    Also, CONVENTIONS. That "int Int" scared me a bit.
     
  30. Offline

    CookCraftHD

    I said it, it was stupid....
    That second thing was a method I copied from stack overflow :p

    I will test it in a hour, and if it works post my final code.

    Thanks, it works!
    the only thing is its purple and Italic?
    Will have a look with ChatColor

    Code:java
    1. public static ItemStack addSunEnchant(ItemStack target, int level) {
    2. target.addUnsafeEnchantment(ench, level);
    3. //first, add the enchant (ench) to the itemstack, this is created with the code of this page.
    4. ItemMeta targetmeta = (ItemMeta) target.getItemMeta();
    5. //get the metadata
    6. Map<Enchantment,Integer> enchantments = target.getEnchantments();
    7. //real, not good formated enchants
    8. Iterator<Entry<Enchantment,Integer>> iter = enchantments.entrySet().iterator();
    9. ArrayList<String> enc = new ArrayList<String>();
    10. //array with the correct enchants
    11. while(iter.hasNext()){
    12. Entry<Enchantment,Integer> entry = iter.next();
    13. String lvl = RomanNumbers.run(entry.getValue());
    14. //translate the number, see the bottom for the code
    15. enc.add(ChatColor.RESET + "" + ChatColor.GRAY + entry.getKey().getName().toString() + " " + lvl);
    16. //add the enchant name to the array
    17. }
    18. targetmeta.setLore(enc);
    19. //add the lore
    20. target.setItemMeta(targetmeta);
    21. return target;
    22. //return the itemstack
    23. }


    Edit: Works now, code updated

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 3, 2016
Thread Status:
Not open for further replies.

Share This Page