Modding Guide

Beta Evolutions 2

ModLoader Beta 1.7.3

A guide for mod developers writing mods for Beta Evolutions 2 - no Bevo 2 source code required.

How to write mods for Beta Evolutions 2 (Bevo 2) as a mod developer - no Bevo 2 source code required, just the shipped client jar.

Bevo 2 ships with a built-in mod loader: ModLoader Beta 1.7.3.
You write a small Java class that extends BaseMod, compile it against the Bevo 2 client jar, package it as a jar, and drop it into a mods folder.
The game discovers and loads it at startup.
Mods use the standard ModLoader API to add blocks, items, entities, recipes, key bindings, and per-tick behavior - and can optionally add their own pages to Bevo 2's in-game settings menu.

Already know ModLoader? The short version

Same BaseMod superclass, same mod_* naming, same classic API (RegisterBlock, AddRecipe, SetInGameHook, RegisterKey, @MLProp).
What's different on Bevo 2:

  • Jar mods, not jar patching. Drop a self-contained .jar into <.minecraft>/evolutions/mods/ (entry class at net/minecraft/src/mod_YourMod.class). You never edit minecraft.jar.
  • Mods are OFF by default. Enable each one in the in-game ModLoader Mods menu or in config/ModLoader.cfg, then restart.
  • New: in-game settings pages. Override getBetaEvolutionsSettings() to add your own BaseBES pages to the Beta Evolutions Settings menu - a step up from @MLProp.
  • New loader API: getDiscoveredModClassNames(), isModEnabled(), setModEnabled(), getModDir().
  • Runs on LWJGL 3 (via KeybindUtils). Prefer a registered KeyBinding + KeyboardEvent over raw org.lwjgl.* calls.
  • Porting a stock mod? One source tree can target both - see Cross-compatibility. Fastest start: the official mod template.

Full details in Differences from stock ModLoader.



Key facts about the loader

Thing Value
Loader ModLoader Beta 1.7.3 (Bevo 2-extended)
Mod class name must start with mod_ (e.g. mod_MyMod)
Mod package net.minecraft.src
Superclass net.minecraft.src.BaseMod
Mods folder <.minecraft>/evolutions/mods/
Enable/disable config <.minecraft>/config/ModLoader.cfg
Per-mod config <.minecraft>/config/<fully.qualified.ClassName>.cfg
Loader log <.minecraft>/ModLoader.txt

<.minecraft> is the game directory ModLoader reports via Minecraft.getMinecraftDir().

Your first mod

Create mod_HelloWorld.java:

package net.minecraft.src;

import net.minecraft.client.Minecraft;

public class mod_HelloWorld extends BaseMod {

    @Override
    public String Version() {
        return "1.0";
    }

    @Override
    public void ModsLoaded() {
        // Called once, after all mods have loaded. Safe place to do setup.
        System.out.println("[mod_HelloWorld] Loaded!");
        // Register for per-tick callbacks (see the lifecycle section):
        ModLoader.SetInGameHook(this, true, false);
    }

    @Override
    public boolean OnTickInGame(Minecraft mc) {
        // Runs every game tick while in a world.
        // Return true to keep receiving ticks; false to unhook.
        return true;
    }
}

Two rules that are easy to miss:

  1. The class must be public, named mod_*, and live in package net.minecraft.src.
  2. You must implement Version() - it's abstract on BaseMod.

Compiling against the client jar

From a terminal, compile your .java file with the Bevo 2 jar on the classpath:

javac -cp app.jar mod_HelloWorld.java

This produces mod_HelloWorld.class.
If your mod has supporting classes, put those in your own package (e.g. com.example.mymod.*) - only the mod_ entry class has to be in net.minecraft.src.
Compile them all together.

Tip: If Bevo 2 targets an older Java bytecode level, add -source/-target (or --release N) so your classes load on the client's JVM.
When in doubt, match the jar.

Then package the result into a jar - this jar is your mod.
Preserve the package folders so the entry class ends up at net/minecraft/src/mod_HelloWorld.class inside the archive:

jar cf mod_HelloWorld.jar net/minecraft/src/mod_HelloWorld.class com/example/mymod/*.class

Every mod ships this way, single-file or not - the loader only reads mods from jars/zips.

Installing & enabling a mod

1. Copy your mod jar into the mods folder:

<.minecraft>/evolutions/mods/mod_HelloWorld.jar

Mods are distributed and loaded as jar mods - a packaged .jar (or .zip).
The loader reads mod_*.class entries stored under net/minecraft/src/ inside the archive.
A bare .class dropped straight into the mods folder is not picked up; it must be inside a jar (see Compiling for how to build one).

2. Enable it. Bevo 2 mods are disabled by default. On first run the loader writes an entry for your mod into config/ModLoader.cfg set to off.
Edit that file and turn it on:

mod_HelloWorld=on

Accepted "enabled" values: on, yes, true.
Accepted "disabled" values: off, no.

3. Launch the game. Watch the console / ModLoader.txt for:

[Mod Loader] Mod Loaded: mod_HelloWorld 1.0

Why isn't my mod loading? The #1 cause is step 2 - a freshly dropped-in mod is off until you flip it on in ModLoader.cfg.
See Troubleshooting.

The BaseMod lifecycle

Override the callbacks you need.
Everything defaults to a no-op, so implement only what you use.

Callback When it fires Notes
String Version() - Required. Return your mod version string.
void ModsLoaded() Once, after all mods load Do setup, register hooks/keys here.
boolean OnTickInGame(Minecraft mc) Every tick in a world Only fires if you called SetInGameHook. Return true to stay hooked.
boolean OnTickInGUI(Minecraft mc, GuiScreen gui) Every tick with a GUI open Only fires if you called SetInGUIHook. Return true to stay hooked.
void KeyboardEvent(KeyBinding key) On a registered key press Register keys with ModLoader.RegisterKey.
void GenerateSurface(World w, Random r, int x, int z) Overworld chunk populate For world-gen mods.
void GenerateNether(World w, Random r, int x, int z) Nether chunk populate
void AddRenderer(Map map) Entity renderer registration Put your Entity → Render mappings in the map.
void RegisterAnimation(Minecraft mc) Texture FX registration Add animated textures.
void RenderInvBlock(RenderBlocks rb, Block b, int meta, int modelID) Custom block inventory render For custom block models.
boolean RenderWorldBlock(...) Custom block world render Return true if you rendered it.
int AddFuel(int itemID) Furnace fuel lookup Return burn time, or 0.
void TakenFromCrafting(EntityPlayer p, ItemStack s) Item crafted
void TakenFromFurnace(EntityPlayer p, ItemStack s) Item smelted
void OnItemPickup(EntityPlayer p, ItemStack s) Item picked up
boolean DispenseEntity(...) Dispenser fires Return true to override behavior.

To actually receive OnTickInGame / OnTickInGUI, opt in from ModsLoaded():

ModLoader.SetInGameHook(this, true, false);   // enable in-world ticks
ModLoader.SetInGUIHook(this, true, false);    // enable in-GUI ticks

The ModLoader API

ModLoader is a static utility class - call it from anywhere in your mod.
The most useful entry points:

Game access

  • ModLoader.getMinecraftInstance() → the live Minecraft instance.
  • ModLoader.getModDir() → the evolutions/mods folder.
  • ModLoader.isModLoaded("net.minecraft.src.mod_Other") → check for another mod.
  • ModLoader.OpenGUI(player, guiScreen) / ModLoader.isGUIOpen(SomeGui.class).

Content registration

  • ModLoader.RegisterBlock(block) / RegisterBlock(block, itemBlockClass)
  • ModLoader.RegisterEntityID(EntityFoo.class, "Foo", ModLoader.getUniqueEntityId())
  • ModLoader.RegisterTileEntity(TileFoo.class, "Foo") (+ optional special renderer)
  • ModLoader.AddSpawn(...) / ModLoader.RemoveSpawn(...)
  • ModLoader.AddRecipe(result, pattern...) / AddShapelessRecipe(...)
  • ModLoader.AddSmelting(inputID, resultStack)
  • ModLoader.AddName(itemOrBlock, "Display Name")
  • ModLoader.AddLocalization("some.key", "Text")

Sprites / IDs

  • ModLoader.getUniqueEntityId()
  • ModLoader.getUniqueBlockModelID(this, renderInInventoryAs3D)
  • ModLoader.addOverride("/gui/items.png", "/mymod/myitem.png") → returns a sprite index

Input

  • ModLoader.RegisterKey(this, keyBinding, allowRepeat) - then handle in KeyboardEvent.

Not every classic ModLoader method is listed here.
If you know a ModLoader Beta 1.7.3 method, it very likely exists - check the jar.
Where Bevo 2 differs from stock ModLoader, this guide calls it out.

Per-mod configuration (@MLProp)

Expose tunables to users with the @MLProp annotation on public static fields.
ModLoader reads/writes them to config/<your.fully.qualified.ClassName>.cfg at load time.

package net.minecraft.src;

public class mod_MyMod extends BaseMod {

    @MLProp(info = "How many widgets to spawn")
    public static int widgetCount = 4;

    @MLProp(min = 0, max = 1, info = "Effect strength 0..1")
    public static double strength = 0.5;

    @Override public String Version() { return "1.0"; }
}

The generated .cfg includes each property's type, default, range, and info string as comments. min/max are enforced for numeric fields.

Full worked example

Official template: retromcorg/Beta-Evolutions-2-Mod-Template is a ready-to-clone starter mod.
It ships mod_BetaEvolutionsExample (a client-side mod that registers a keybind, prints a chat message when a world finishes loading, and shows your XYZ position on keypress), builds with Maven, and is MIT-licensed.
Fork it as the fastest way to start a new mod.

Clone, build, and install:

git clone https://github.com/retromcorg/Beta-Evolutions-2-Mod-Template.git
cd Beta-Evolutions-2-Mod-Template
mvn clean package
# jar is written to target/beta-evolutions-example-mod.jar
# copy it into <.minecraft>/evolutions/mods/ , then enable
# mod_BetaEvolutionsExample via Options -> Beta Evolutions Settings -> ModLoader Mods (and restart)

The rest of this section walks through a self-contained example by hand, so you can see every moving part without a build tool.
It's a small client-side mod: a keybind toggles an effect whose strength is user-configurable via an @MLProp property.

net/minecraft/src/mod_Sparkle.java:

package net.minecraft.src;

import net.minecraft.client.Minecraft;

public class mod_Sparkle extends BaseMod {

    // User-editable via config/net.minecraft.src.mod_Sparkle.cfg
    @MLProp(min = 0, max = 10, info = "Sparkle intensity")
    public static int intensity = 3;

    @MLProp(info = "Enable the effect")
    public static boolean enabled = true;

    private final KeyBinding toggleKey = new KeyBinding("Toggle Sparkle", 33 /* F */);
    private boolean active = false;

    @Override public String Version() { return "1.0"; }

    @Override
    public void ModsLoaded() {
        ModLoader.RegisterKey(this, toggleKey, false);
        ModLoader.SetInGameHook(this, true, false);
    }

    @Override
    public void KeyboardEvent(KeyBinding key) {
        if (key == toggleKey) {
            active = !active;
            Minecraft mc = ModLoader.getMinecraftInstance();
            mc.displayChatMessage("Sparkle: " + (active ? "on" : "off"));
        }
    }

    @Override
    public boolean OnTickInGame(Minecraft mc) {
        if (active && enabled) {
            // ... do your per-tick effect using `intensity` ...
        }
        return true; // stay hooked
    }
}

Build & install:

javac -cp app.jar net/minecraft/src/mod_Sparkle.java
jar cf mod_Sparkle.jar net/minecraft/src/mod_Sparkle.class
# copy mod_Sparkle.jar into <.minecraft>/evolutions/mods/
# then set  mod_Sparkle=on  in <.minecraft>/config/ModLoader.cfg

Differences from stock ModLoader

Bevo 2 ships its own build of ModLoader Beta 1.7.3.
If you already know classic ModLoader from the Beta era, almost everything still applies - the same BaseMod superclass, the same mod_* naming, and the same registration API.
Here is where the Bevo 2 loader differs from the stock loader:

Topic Stock ModLoader Beta 1.7.3 Beta Evolutions 2
How mods are installed Patch .class files into minecraft.jar Drop a jar into evolutions/mods/
Class loading Everything on one classpath Dedicated URLClassLoader per mods folder + classpath scan
Default state Enabled once installed Disabled by default - every mod starts off
Enable/disable Edit ModLoader.cfg by hand Edit ModLoader.cfg or use the in-game ModLoader Mods menu
Config values on / off on/yes/true vs. off/no (both simple + fully-qualified keys)
Settings UI None Mods can add their own pages to the Beta Evolutions Settings menu
Input handling Keyboard.isKeyDown(...) (LWJGL 2) Routed through KeybindUtils (LWJGL 3 compatible)

Everything not in this table behaves like stock ModLoader.
The most important practical differences:

1. Jar mods, not jar patching. Bevo 2 never asks you to touch the client jar.
It scans <.minecraft>/evolutions/mods/ (self-contained jars/zips, loaded via a URLClassLoader) and the client's own classpath.
Inside the jar the entry class must sit at net/minecraft/src/mod_YourMod.class.
A loose .class in the mods folder is not loaded.

2. Mods are OFF by default. The first time the loader sees a mod it writes an off entry to config/ModLoader.cfg and skips loading it.
Turn it on via the in-game menu or the config file, then restart - the mod list is built once at startup.

3. In-game "ModLoader Mods" menu. Under Beta Evolutions Settings, this page lists every detected mod with a toggle, an Open Mods Folder button, and a reminder that changes apply after restarting.
No hand-editing config required.

4. New loader API. Bevo 2 adds static helpers on top of the classic API:

  • ModLoader.getDiscoveredModClassNames() - all mod_* classes found this session (enabled or not).
  • ModLoader.isModEnabled("mod_Foo") - is that mod turned on in config?
  • ModLoader.setModEnabled("mod_Foo", true) - flip a mod on/off and persist it.
  • ModLoader.getModDir() - the evolutions/mods folder as a File.

5. Mods can add their own settings pages. This is the headline new capability and has no stock-ModLoader equivalent.
Bevo 2's BaseMod adds one method beyond the classic lifecycle:

public List<BaseBES> getBetaEvolutionsSettings() {
    return Collections.emptyList();   // default: contribute nothing
}

Return one or more BaseBES pages and Bevo 2 registers them into its own settings menu - headers, descriptions, toggle buttons, keybind pickers, and searchable terms - backed by typed, persisted config values (ConfigValue<T>, ConfigKeybind, …) instead of a raw @MLProp .cfg.
A minimal example:

// net/minecraft/src/mod_MyMod.java
public class mod_MyMod extends BaseMod {

    @Override public String Version() { return "1.0"; }

    @Override
    public List<BaseBES> getBetaEvolutionsSettings() {
        return Collections.singletonList(new BES_MyMod());
    }
}

@MLProp still works exactly as in stock ModLoader; use it for simple tunables and use getBetaEvolutionsSettings() when you want a real in-game page.

Input on LWJGL 3. The Bevo 2 client runs on LWJGL 3, so key detection is routed through KeybindUtils rather than LWJGL 2's Keyboard directly.
Your KeyboardEvent callback and ModLoader.RegisterKey(...) work the same - but a ported mod that polls the keyboard with raw LWJGL 2 calls may not behave identically.
Prefer registering a KeyBinding and handling KeyboardEvent.

Cross-compatibility with stock ModLoader

Can one mod work on both stock ModLoader and Bevo 2?
Yes - at the source/API level.
The Bevo 2 loader keeps the exact same BaseMod superclass and the same classic ModLoader API, so a mod that only touches that common surface is source-compatible with both.
The issue is in binary/runtime compatibility, not the API.

What "just works"™: anything inside the classic ModLoader contract - extends BaseMod + Version() + the standard overrides, content registration (RegisterBlock, AddRecipe, AddSmelting, …), and @MLProp config fields. getBetaEvolutionsSettings() has a default implementation on BaseMod, so a stock mod that never mentions it compiles and loads fine on both.

The three things that break cross-compat:

  1. Direct LWJGL 2 calls. Bevo 2 is LWJGL 3 under a compatibility shim. Whether an old mod's raw org.lwjgl.input.Keyboard / org.lwjgl.opengl.GL11 calls get remapped at runtime is not guaranteed.
    It is safest to not poll LWJGL directly - register a KeyBinding and handle KeyboardEvent, and use the engine helpers instead of hand-rolled GL11.
  2. One compiled jar for both is mapping-dependent. A single .class only loads on both clients if every net.minecraft.src class/method you reference has an identical signature in both jars. The reliable approach is one source tree, two builds - compile the same .java against each client jar.
  3. Packaging and install differ. Stock patches classes into minecraft.jar; Bevo 2 wants a jar in evolutions/mods/. The inside layout (net/minecraft/src/mod_*.class) is the same, so one jar can be installed both ways - but the enable step (off-by-default) is Bevo 2-only.

Recipe for a dual-target mod:

  • Write to the classic ModLoader API + BaseMod overrides only.
  • No direct org.lwjgl.* - go through KeyBinding/KeyboardEvent and engine helpers.
  • Keep Bevo 2-only features optional. Guard references to Bevo 2 classes with reflection so the stock build doesn't hard-reference them:
    try {
        Class.forName("org.retromc.beta_evo_settings.BaseBES");
        // ...only reached on Bevo 2; build your settings page here
    } catch (ClassNotFoundException e) { /* stock: skip */ }
  • Ship two builds from one source - javac against each client jar, matching each one's bytecode target. Distribute a -stock jar and a -be2 jar.

Troubleshooting & gotchas

  • Mod doesn't load at all. Confirm mod_YourMod=on in config/ModLoader.cfg - mods are off by default. Then check ModLoader.txt and the console for a "Failed to load mod" line.
  • ClassNotFoundException / mod ignored. The entry class must be named mod_*, be public, and be in package net.minecraft.src - so inside your jar it sits at net/minecraft/src/mod_YourMod.class. Also make sure you actually shipped a jar: a loose .class file placed directly in evolutions/mods/ is skipped by the loader.
  • AbstractMethodError on load. You forgot to implement Version().
  • Ticks never fire. You didn't call ModLoader.SetInGameHook/SetInGUIHook, or your OnTick* returned false and got unhooked. Return true to keep receiving ticks.
  • Compiled but won't run (UnsupportedClassVersion). Your bytecode target is newer than the client's JVM. Recompile with a matching --release.
  • Version compatibility. A mod is compiled against one Bevo 2 jar. A future Bevo 2 update can rename or change internals; re-test and recompile against each version you support.

Quick reference

Mod class      : public class mod_Name extends BaseMod   (package net.minecraft.src)
Required method: public String Version()
Compile        : javac -cp app.jar mod_Name.java
Package        : jar cf mod_Name.jar net/minecraft/src/mod_Name.class ...   (jar mods only)
Install to     : <.minecraft>/evolutions/mods/mod_Name.jar
Enable in      : <.minecraft>/config/ModLoader.cfg   ->   mod_Name=on
Per-mod cfg    : <.minecraft>/config/<full.ClassName>.cfg   (via @MLProp static fields)
Loader log     : <.minecraft>/ModLoader.txt
Get Minecraft  : ModLoader.getMinecraftInstance()
Per-tick       : ModLoader.SetInGameHook(this, true, false)  +  OnTickInGame(mc)
Keys           : ModLoader.RegisterKey(this, binding, repeat)  +  KeyboardEvent(key)