The possibility to add custom blocks to the craftbukkit server?

Discussion in 'Plugin Development' started by blackwolf12333, Aug 4, 2012.

Thread Status:
Not open for further replies.
  1. Hi guys,

    What i was wondering, is it possible to add custom blocks, without modifying the source of craftbukkit itself? If that would be possible that would be great, but i don't expect it.
    If it is not, how would you have to modify craftbukkit to let that be possible, because i was thinking about adding that possibility to craftbukkit and bukkit.
    For that i would need to understand how blocks work in minecraft, i saw the huge list of public static final Block's but would it not be possible to put that in an ArrayList<Block> ? and in that way be able to just do blockList.add(customblock).

    Thanks in advance, blackwolf12333
     
  2. Offline

    marwzoor

    That is only possible with spout.
    Since the clientside does not download any data from the server that will never work without mojang adding it to their code.
     
  3. I know, but if the clients has the same modification, thus also a client mod...
    I knew it was possible with spout, but that also has it's own client.
     
  4. Offline

    mushroomhostage

    It is possible to add custom blocks, without modifying CraftBukkit itself. Not sure how Spout does it, but the way mods do is by adding a new block to net.minecraft.server.Block. You can do this, but on a CraftBukkit server, you also have to be sure to extend org.bukkit.Material or plugins will crash when interacting with the block.

    Here's an example plugin to add a custom block (named "X255"):
    Code:
    package me.exphc.Squirt;
    
    import org.bukkit.plugin.java.JavaPlugin;
    import org.bukkit.plugin.*;
    import org.bukkit.event.*;
    import org.bukkit.event.block.*;
    import org.bukkit.event.player.*;
    import org.bukkit.event.entity.*;
    import org.bukkit.Material.*;
    import org.bukkit.material.*;
    import org.bukkit.block.*;
    import org.bukkit.entity.*;
    import org.bukkit.command.*;
    import org.bukkit.inventory.*;
    import org.bukkit.configuration.*;
    import org.bukkit.configuration.file.*;
    import org.bukkit.scheduler.*;
    import org.bukkit.enchantments.*;
    import org.bukkit.*;
    
    import java.util.*;
    import java.util.logging.*;
    import java.lang.reflect.*;
    
    class BlockExample extends net.minecraft.server.Block {
        protected BlockExample(int i, int j, net.minecraft.server.Material material) {
            super(i, j, material);
        }
    }
    
    public class Squirt extends JavaPlugin implements Listener {
        Logger log = Logger.getLogger("Minecraft");
    
        public void onEnable() {
            log.info("enabling");
    
            addMaterial("X255", 255);
            try {
                registerBlock(new BlockExample(255, 0, net.minecraft.server.Material.STONE), null);
            } catch (Exception e) {
                // note, re-registers again and conflicts if /reload
                e.printStackTrace();
            }
    
    
            log.info("block = " + net.minecraft.server.Block.byId[255]);
        }
    
        public void onDisable() {
        }
    
        // call ItemBlock constructor
        // see also https://github.com/cpw/FML/blob/master/server/net/minecraft/src/ServerRegistry.java#L44
        public void registerBlock(net.minecraft.server.Block block, Class <? extends net.minecraft.server.ItemBlock > itemclass) {
            if (itemclass == null) {
                itemclass = net.minecraft.server.ItemBlock.class;
            }
    
            try {
                itemclass.getConstructor(int.class).newInstance(block.id - 256);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @SuppressWarnings("unchecked")
        public void addMaterial(String name, int id) {
            // Add to enum itself
            Material material = addEnum(Material.class, name, new Class[] { int.class }, new Object[] { id });
            log.info("added material " + material);
    
            // Add to lookup hash for Material.getMaterial(String)
            try {
                Field field = Material.class.getDeclaredField("BY_NAME");
                field.setAccessible(true);
                Object object = field.get(null);
                Map<String, Material> BY_NAME = (Map<String, Material>)object;
                BY_NAME.put(name, material);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            log.info("getMaterial = " + Material.getMaterial(name));
    
            try {
                Field field = Material.class.getDeclaredField("byId");
                field.setAccessible(true);
                Object object = field.get(null);
                Material[] byId = (Material[])object;
                // Note that byId is fixed at 383 in vanilla Bukkit (MCPC extends to 32000)
                byId[id] = material;
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            log.info("getMaterial = " + Material.getMaterial(id));
        }
    
        /* magic to add new fields to enums - thanks to 
        https://github.com/MinecraftPortCentral/Bukkit/blob/mcportcentral/src/main/java/org/bukkit/Material.java#L516
       	*
    	 * Everything below this is found at the site below, and updated to be able to compile in Eclipse/Java 1.6+
         * Also modified for use in decompiled code.
    	 * Found at: http://niceideas.ch/roller2/badtrash/entry/java_create_enum_instances_dynamically
    	 */
    	private static Object reflectionFactory      = null;
    	private static Method newConstructorAccessor = null;
    	private static Method newInstance            = null;
    	private static Method newFieldAccessor       = null;
    	private static Method fieldAccessorSet       = null;
    	private static boolean isSetup               = false;
    
    	private static void setup()
    	{
    		if (isSetup)
    		{
    			return;
    		}
    		try {
    			Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory");
    			reflectionFactory      = getReflectionFactory.invoke(null);
    			newConstructorAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newConstructorAccessor", Constructor.class);
    			newInstance            = Class.forName("sun.reflect.ConstructorAccessor").getDeclaredMethod("newInstance", Object[].class);
    			newFieldAccessor       = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class);
    			fieldAccessorSet       = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    
    		isSetup = true;
    	}
    
    	private static Object getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes) throws Exception {
    		Class<?>[] parameterTypes = null;
    
    		parameterTypes = new Class[additionalParameterTypes.length + 2];
    		parameterTypes[0] = String.class;
    		parameterTypes[1] = int.class;
    		System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
    
    		return newConstructorAccessor.invoke(reflectionFactory, enumClass.getDeclaredConstructor(parameterTypes));
    	}
    
    	private static <T extends Enum<?>> T makeEnum(Class<T> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues) throws Exception {
    		Object[] parms = null;
    
    		parms = new Object[additionalValues.length + 2];
    		parms[0] = value;
    		parms[1] = Integer.valueOf(ordinal);
    		System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
    
    		return enumClass.cast(newInstance.invoke(getConstructorAccessor(enumClass, additionalTypes), new Object[]{parms}));
    	}
    
    	private static void setFailsafeFieldValue(Field field, Object target, Object value) throws Exception {
    		field.setAccessible(true);
    		Field modifiersField = Field.class.getDeclaredField("modifiers");
    		modifiersField.setAccessible(true);
    		modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    		Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false);
    		fieldAccessorSet.invoke(fieldAccessor, target, value);
    	}
    
    	private static void blankField(Class<?> enumClass, String fieldName) throws Exception {
    		for (Field field : Class.class.getDeclaredFields()) {
    			if (field.getName().contains(fieldName)) {
    				field.setAccessible(true);
    				setFailsafeFieldValue(field, enumClass, null);
    				break;
    			}
    		}
    	}
    
    	private static void cleanEnumCache(Class<?> enumClass) throws Exception {
    		blankField(enumClass, "enumConstantDirectory");
    		blankField(enumClass, "enumConstants");
    	}
    
    	@SuppressWarnings("unchecked")
    	public static <T extends Enum<?>> T addEnum(Class<T> enumType, String enumName, Class<?>[] paramTypes, Object[] paramValues) {
    		if (!isSetup) setup();
    		Field valuesField = null;
    		Field[] fields = enumType.getDeclaredFields();
    		int flags = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL | 0x1000 /*SYNTHETIC*/;
    		String valueType = String.format("[L%s;", enumType.getName()/*.replace('.', '/')*/);
    
    		for (Field field : fields) {
    			if ((field.getModifiers() & flags) == flags &&
    				field.getType().getName().equals(valueType))
    			{
    				valuesField = field;
    				break;
    			}
    		}
    		valuesField.setAccessible(true);
    
    		try {
    			T[] previousValues = (T[])valuesField.get(enumType);
    			List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
    			T newValue = (T)makeEnum(enumType, enumName, values.size(), paramTypes, paramValues);
    			values.add(newValue);
    			setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));
    			cleanEnumCache(enumType);
    
    			return newValue;
    		} catch (Exception e) {
    			e.printStackTrace();
    			throw new RuntimeException(e.getMessage(), e);
    		}
    	}
    }
    
    This works (tested on 1.2.5-R5.0), and the Bukkit plugin successfully adds a new block.

    However, the problem you'll run into if you do this is clients won't know about the new block. If they connect to a server with new blocks, without having the blocks on their client, they will crash immediately. So you need to make sure the client also has the mod installed.
     
    one4me likes this.
  5. Geez really thanks, i'll studie this now:p hope i can use it in some way:D
     
  6. Offline

    plav

    I know this is from quite some time ago...But does this still work? If so, could you possible rework it so that its a block that looks like air but acts like bedrock? I'm trying to make an invisible bridge and the ID36 is causing lag and general glitch. I would do it myself but I'm still pretty new to java.
     
Thread Status:
Not open for further replies.

Share This Page