Skip to content

Instantly share code, notes, and snippets.

@Cadiboo
Last active July 20, 2024 10:43
Show Gist options
  • Save Cadiboo/fbea89dc95ebbdc58d118f5350b7ba93 to your computer and use it in GitHub Desktop.
Save Cadiboo/fbea89dc95ebbdc58d118f5350b7ba93 to your computer and use it in GitHub Desktop.
Conventions (WIP)

What, Why, Consequences of not doing it, How to do it right

Required Java conventions

What: Lowercase package naming.
Why: Some file systems (windows) consider iTeMs and items to be the same, but every other system considers them to be different.
Consequences: Not doing this may never cause any issues (and very likely won't in your dev environment), but on other operating systems with different case-sensitivities from yours this can cause massive problems.
How: Name your packages all in lowercase.

What: Packaging that reflects your web presence.
Why: Avoid name collisions - your web presence is unique so it is commonly used.
Consequences: Errors loading classes with other mods installed, usually happens when 2 mods never changed their packaging from the default in the MDK.
How: If you have an illegal character in your web presence, the convention is to replace it with "_". If you don’t have a website, you can use your version-control repository as your web presence, e.g. com.github.username. If you don’t use version control, start using it! In the meantime you can use the packaging mod.username.modid.

What: Not including spaces in filenames.
Why: Maven and Gradle do not work well with dependencies who’s names contain spaces.
Consequences: People not being able to use your mod as a dependency.
How: Substitute the character "-" for spaces in your file name.

Recommended Java conventions

What: Class naming conventions.
Why: Allow you and others to easily read and understand your code.
Consequences: Hard to read code.
How: Name your classes in TitleCase.

What: Method naming conventions.
Why: Readable code.
Consequences: Hard to read code, other developers vomiting when they read your code.
How: Your method names should be verbs in camelCase.

What: Using @Override.
Why: Using @Override tells the compiler that you intend to override a method. This lets your IDE warn you about bugs before they happen.
Consequences: When you update your mod to a new minecraft version/ update your mappings/ update your Forge version and don’t use @Override, methods will silently stop overriding what they should override due to name changes, and will fail to ever be called, resulting in unexpected behaviour, crashes and bugs.
How: Just annotate methods you intend to override with the @Override annotation.

Required Forge conventions

What: Running Packet callbacks in a thread-safe way.
Why: Almost none of Minecraft’s code is thread safe, when you receive a packet it is handled on a seperate thread (one of the packet handling threads).
Consequences: Race conditions, unreproducible crashes, unexplainable behaviour, any and all issues associated with bad multi-threading. These problems can range from small - like unexplainable behaviours of objects - to catastrophic - corrupted minecraft installs.
How: Simply run your packet callbacks inside a scheduled task on the appropriate thread.

What: Fully-qualifying ResourceLocations in JSON files.
Why: ResourceLocations without a domain default their domain to "minecraft" except in recipes (recipes default to the current active mod’s modid).
Consequences: JSON files (models, blockstates, recipes) containing invalid resource locations, which can lead to multiple errors such as models not loading, recipes not working and textures not loading.
How: Always use fully qualified resource locations in the format "domain:path". If you are not working with a recipe file, it is safe to omit the domain for ResourceLocations with a domain of "minecraft". Recipe JSONs are an exception to this rule, and if you are working with a recipe JSON, you may omit the domain for resource locations pointing to your own files, however all other resource locations (including those with a domain of "minecraft") must be fully qualified! For example, you must qualify types in recipes, like "minecraft:shapeless".

What: Using long, unique and descriptive mod IDs.
Why: Mod IDs cannot be changed once they have been set. So they must be unique.
Consequences: Collisions with mods who use the same Mod ID are fatal. When this occurs one of the mods must undergo a complete rewrite to change its mod ID. This often happens to mods that use the mod ID "tm" for "tutorial mod".
How: Use the name of your mod, replacing spaces with "-". Mod IDs can be up to 64 characters long. Mod IDs under 6 characters long are too short and should be changed before release.

Recommended Forge conventions

What: Class naming conventions.
Why: Allow you and others to easily read and understand your code, and find classes easily.
Consequences: Hard to read code, unable to find classes fast.
How: Name your classes in TitleCase and prefix them by type. For example the class of a tile entity called super chewer would be called TileEntitySuperChewer, an entity called moth would be called EntityMoth, a utility class dealing with maths would be called MathUtil, an event subscriber for client-side only events would be called ClientEventSubscriber.

What: Not using static initialisers.
Why: Using static initialisers does not allow you to control when your objects are created or the order in which your objects are created.
Consequences: Using static initialisers prevents other mods from overriding your objects, prevents forge from being able to dynamically load/unload mods and can cause weird, unreproducible crashes.
How: Use the @ObjectHolder annotation if you need static references to your objects and create & register your objects in the appropriate registry event. (Note: before registry events Modders created and registered their items in preInit. This practice became out of date in 1.7 with the introduction of registry events, and is no longer possible in 1.13).

What: Using registry events.
Why: Using registry events paves the way for Forge to dynamically load and unload mods without restarting minecraft. Registry events are dedicated events with support for mod-sorted execution and overriding objects in the registry and/or querying the registry. Consequences: Using static initialisers can result in weird & unreproducible crashes and creating & registering your objects in preInit is no longer supported.
How: In your event subscriber class subscribe to the appropriate registry event (Registry.Register<typeOfObject>)

What: Using @ObjectHolder.
Why: Using @ObjectHolder allows you to have static field references to your objects, allows other mods to override your objects without problems related to outdated field references. It manages your static references for you, without you having to manually manage and assign all your fields yourself.
Consequences: Not using @ObjectHolder can result in problems with mods overriding your objects, which can lead to undefined behaviour and/or crashes.
How: Put @ObjectHolder at the top of your object holding class (for example ModBlocks or ModItems). Have your modid as the parameter of the annotation. Have your field be public static final and null, and name them the same as the registry name of your object in capitals. After you register your objects (in the appropriate registry event) your fields will be automatically filled with the correct values. For example:

@ObjectHolder(yourModId)
class ModBlocks {
    public static final Block YOUR_BLOCKS_REGISTRY_NAME_IN_CAPITALS = null;
}

What: Not using a "common" proxy.
Why: Proxies exist to seperate side-specific code, if there is code that is common, it should not be in a proxy.
Consequences: Issues with physical sides, hard to read code. A "common" proxy goes against fundamental OOP and logical concepts.
How: Have an interface (usually called IProxy or Proxy) and have your client and sever proxies implement this class. Your interface should be in a common package (usually util) and your sever and client proxies should be in their respective packages.

What: Not using an interface to register models. (Models are registered automatically in 1.13.2, so some of this may not apply)
Why: This interface (commonly called IHasModel) is unnecessary. All items need models and nothing about model registration requires private or protected data.
Consequences: This interface makes you write 4 lines of code in each class and 2+ lines of code in your registry event, when 1 or 2 lines of code could accomplish the exact same thing. It also leads to weird bugs when you forget to make your object implement the interface.
How: Simply register each model in the registry event (1 line of code for each model) or write a loop that does it for you (1 or 2 lines depending on the implementation). For example: Write out registerModel(item, meta, variant) for each item and variant or write a loop like this

for (Item item : allModItemsAndItemBlocks)
    registerModel(item, meta, variant);

A list of all your items can be acquired in many ways, such as looping over registries and checking domain, keeping your own list, looping over registries and using an instanceof check etc. (Models are registered automatically in 1.13.2, so some of this may not apply)

What: Not using an object base class.
Why: Using an object base class (commonly called BlockBase or ItemBase) is unnecessary and is an anti-pattern. There is already a BlockBase class, it’s the minecraft Block class. Making a class just to have its children inherit default implementations of methods goes against the OOP principle of Composition over Inheritance.
Consequences: Using a class like this stops you from extending other classes and because lots of minecraft code uses instanceof checks to specially handle logic, you are likely to encounter weird and hard-to-fix bugs.
How: Instead of putting all your common logic in one class and extending it, extract the logic to utility methods. For example: Instead of calling setRegistryName, setTranslationKey, etc. inside your object base’s constructor, extract it to a helper method and call it on every object when you create it. In this example setup calls the above methods.

registry.register(setup(new CustomItem(), "custom_item"));

What: Prefixing additions with your mod ID.
Why: Avoid naming collisions with vanilla and/or other mods who register objects with the same name.
Consequences: Crashes, undefined behaviour, incompatibility with other mods.
How: Prefix enums, fluids etc. with your mod ID. The easiest way of doing this is to construct a resource location with your mod ID and the name your new object, then call toString to get the name to register it with. For example a new horse armor type called copper should be registered with the name "youModID:copper" and a new fluid called oil should be registered with the name "yourModID:oil".

What: Not using ITileEntityProvider or BlockContainer.
Why: ITileEntityProvider and BlockContainer are legacy vanilla classes.
Consequences: Unexpected behaviour?
How: Override hasTileEntity and createTileEntity in your block class.

  • using @Override
  • capabilities
  • IInventory
  • ITileEntityProvider
  • New armor must extend ItemArmor to have armor points
  • Syncing
  • registry events (resource locations are patched?)
  • all the other shit from the EAQ
  • Never re-release software
  • Don’t make a mod that isn’t/doesn’t try to be compatible with other mods. If you want to make a “standalone” mod, go use MCP, deal with everything the hard way and stay out of anything Forge. Forge is for compatibility

Side-notes: Excuse the copy paste, it’s easier for me to do it, and it also ensures that I don’t forget to include some information When asking for support, be clear. What are you trying to achieve (from an end-user perspective), what have you tried, what is going wrong. Provide your code and logs

http://sol.gfxile.net/dontask.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment