Minecraft Modding: Java Tips

Learning Java


It is very difficult to mod without being fairly fluent in Java. It is actually harder to modify other people's code than to write your own because you will have to "reverse engineer" the code in order to understand it enough to begin to attempt to modify. But to reverse engineer you need to understand Java well.

While some youngsters seem to like learning with videos, I strongly suggest that programming should be learned from a book. This is for a couple reasons:
  1. You'll want to be coding at the same time you're learning, so flipping back and forth from a video to your development environment will be distracting. Much easier to have a book open beside you.
  2. People who watch videos tend to skip to the interesting parts. However, the most important parts of a programming language might not seem too interesting to a student. A book presents all the information in a very specific order that is important, and I find the tendency to skip is less. For example a topic like inheritance or scope is extremely important, but I doubt someone would generally watch a video on such a topic.
The problem with programming books is that often are a bit scary (very large) and boring (lots of text). However, I found a book that is very nice: Java In Easy Steps by Mike McGrath. It is not a large book but it covers all the topics you need in order to mod with Java, and it is colorful and simply written.

Tip: Videos are not the best way to learn programming. Use an actual book to learn programming.

After you've gone through a book like Java In Easy Steps, then you might want to get a more comprehensive programming reference book.

Tip: Jakob Jenkov provides great tutorials on Java as well.

Java Reflection And Access Modification


Most programming languages have very strict control of "scope", which means limiting the access to classes, methods and fields. In addition to security, this can also help with keeping your code bug-free because you can "encapsulate" your code such that interactions happen through well-defined interfaces.

Minecraft modding requires you to have access Minecraft code that may not provide the scope to give you access. Forge helps because it uses access transformations to provide "hooks" that allow public access to commonly modded things. For example, Forge events can let you intercept rendering, or mob drops, etc. However, sometimes you'll want to modify something that doesn't have a Minecraft public method and also doesn't have Forge hooks -- in that case you'll need to use some sort of access modification method.

The main access modification method you should consider is called Java "reflection". Reflection effectively changes the scope of fields and methods such that they accessible when they normally wouldn't be.

See Jakob Jenkov's Java Reflection Tutorial to get an understanding of how to use reflection.

Reflection sometimes is criticized for poor performance, but really that is only a consideration in very intense performance situations -- simply accessing a field or two with reflection every tick shouldn't cause any noticeable performance issue. However, if you're really concerned about the performance, it is possible to minimize the impact with a trick explained by diesieben07:


private static final MethodHandle fieldGet;

static {
    Field field = SomeClass.class.getDeclaredField("field");
    field.setAccessible(true);
    fieldGet = MethodHandles.publicLookup().unreflectGetter(field);
}

then to call it:


int value = (int) fieldGet.invokeExact((SomeClass) someClassInstance);

Important hints:

  • It is crucial that the field storing the MethodHandle is static and final. Otherwise the JVM will at the moment not optimize this as well as it could. If you follow this point the Java will optimize very nicely and there is no (yes, no, not almost no) noticeable difference in performance when compared to a direct field access.

  • The casts when calling invokeExact are necessary. The "exact" part means that you if you pass e.g. a subclass of SomeClass into it you still have to explicitly specify the cast to SomeClass, otherwise it will not work.
  • For setting repeat the process and replace unreflectGetter() with unreflectSetter().
  • For invoking methods just use unreflect on a Method instance.
  • If you follow the first point this will optimize very nicely and there is no (yes, no, not almost no) noticeable difference in performance when compared to a direct field access.

No comments:

Post a Comment