BetaEvolutions/Modding Guide
This guide covers writing mods for Beta Evolutions 2 using its built-in ModLoader Beta 1.7.3 - no Beta Evolutions 2 source code required, just the shipped client jar. The fastest way to start is the official mod template on GitHub.
Beta Evolutions 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 Beta Evolutions 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 Beta Evolutions 2's in-game settings menu.
Quick start
| # | Step | What to do |
|---|---|---|
| 1 | Write | A mod_* class extending BaseMod
|
| 2 | Compile | javac against the client jar
|
| 3 | Package | Jar it and drop it in evolutions/mods/
|
| 4 | Enable | Mods are off by default - turn it on and restart |
Already know ModLoader? The short version
Same BaseMod superclass, same mod_* naming, same classic API (RegisterBlock, AddRecipe, SetInGameHook, RegisterKey, @MLProp). What's different on Beta Evolutions 2:
- Jar mods, not jar patching. Drop a self-contained
.jarinto<.minecraft>/evolutions/mods/(entry class atnet/minecraft/src/mod_YourMod.class). You never editminecraft.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 ownBaseBESpages 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 registeredKeyBinding+KeyboardEventover raworg.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 (extended for Beta Evolutions 2) |
| 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:
- The class must be
public, namedmod_*, and live in packagenet.minecraft.src. - You must implement
Version()- it's abstract onBaseMod.
Compiling against the client jar
From a terminal, compile your .java file with the Beta Evolutions 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 Beta Evolutions 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
Beta Evolutions 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 liveMinecraftinstance.ModLoader.getModDir()→ theevolutions/modsfolder.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 inKeyboardEvent.
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 Beta Evolutions 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
Beta Evolutions 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 Beta Evolutions 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
Beta Evolutions 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
Beta Evolutions 2 adds static helpers on top of the classic API:
ModLoader.getDiscoveredModClassNames()- allmod_*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()- theevolutions/modsfolder as aFile.
5. Mods can add their own settings pages
This is the headline new capability and has no stock-ModLoader equivalent. Beta Evolutions 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 Beta Evolutions 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 Beta Evolutions 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 Beta Evolutions 2? Yes - at the source/API level. The Beta Evolutions 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
- Direct LWJGL 2 calls. Beta Evolutions 2 is LWJGL 3 under a compatibility shim. Whether an old mod's raw
org.lwjgl.input.Keyboard/org.lwjgl.opengl.GL11calls get remapped at runtime is not guaranteed. It is safest to not poll LWJGL directly - register aKeyBindingand handleKeyboardEvent, and use the engine helpers instead of hand-rolledGL11. - One compiled jar for both is mapping-dependent. A single
.classonly loads on both clients if everynet.minecraft.srcclass/method you reference has an identical signature in both jars. The reliable approach is one source tree, two builds - compile the same.javaagainst each client jar. - Packaging and install differ. Stock patches classes into
minecraft.jar; Beta Evolutions 2 wants a jar inevolutions/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 Beta Evolutions 2-only.
Recipe for a dual-target mod
- Write to the classic ModLoader API +
BaseModoverrides only. - No direct
org.lwjgl.*- go throughKeyBinding/KeyboardEventand engine helpers. - Keep Beta Evolutions 2-only features optional. Guard references to Beta Evolutions 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 Beta Evolutions 2; build your settings page here
} catch (ClassNotFoundException e) { /* stock: skip */ }
- Ship two builds from one source -
javacagainst each client jar, matching each one's bytecode target. Distribute a-stockjar and a-be2jar.
Troubleshooting & gotchas
- Mod doesn't load at all
- Confirm
mod_YourMod=oninconfig/ModLoader.cfg- mods are off by default. Then checkModLoader.txtand the console for a "Failed to load mod" line. ClassNotFoundException/ mod ignored- The entry class must be named
mod_*, bepublic, and be in packagenet.minecraft.src- so inside your jar it sits atnet/minecraft/src/mod_YourMod.class. Also make sure you actually shipped a jar: a loose.classfile placed directly inevolutions/mods/is skipped by the loader. AbstractMethodErroron load- You forgot to implement
Version(). - Ticks never fire
- You didn't call
ModLoader.SetInGameHook/SetInGUIHook, or yourOnTick*returnedfalseand got unhooked. Returntrueto 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 Beta Evolutions 2 jar. A future Beta Evolutions 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)