Cross-Platform Development
Amber is designed to make cross-platform mod development easier by providing unified APIs that work across Fabric, Forge, and NeoForge. This guide covers best practices and considerations for developing mods that work consistently across all platforms.
Platform Differences
Mod Loading
Each mod loader has different entry points and initialization patterns:
// Fabric entry point
public class MyModFabric implements ModInitializer {
@Override
public void onInitialize() {
MyMod.init();
}
}
// Forge entry point
@Mod(MyMod.MOD_ID)
public class MyModForge {
public MyModForge() {
MyMod.init();
}
}
// NeoForge entry point
@Mod(MyMod.MOD_ID)
public class MyModNeoForge {
public MyModNeoForge() {
MyMod.init();
}
}
// Common initialization
public class MyMod {
public static final String MOD_ID = "mymod";
public static void init() {
// Initialize with Amber
AmberInitializer.initialize(MOD_ID);
// Common initialization code
registerItems();
registerBlocks();
registerEvents();
}
}Event Registration
Events are registered differently on each platform:
// Fabric event registration
public class MyModFabric implements ModInitializer {
@Override
public void onInitialize() {
UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> {
// Event handling
return ActionResult.PASS;
});
}
}
// Forge event registration
@Mod(MyMod.MOD_ID)
public class MyModForge {
public MyModForge() {
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
public void onBlockRightClick(PlayerInteractEvent.RightClickBlock event) {
// Event handling
}
}
// NeoForge event registration (similar to Forge)
@Mod(MyMod.MOD_ID)
public class MyModNeoForge {
public MyModNeoForge() {
NeoForge.EVENT_BUS.register(this);
}
@SubscribeEvent
public void onBlockRightClick(PlayerInteractEvent.RightClickBlock event) {
// Event handling
}
}
// Cross-platform with Amber
public class MyMod {
public static void init() {
// Amber events work on all platforms
BlockEvents.BLOCK_INTERACT.register((player, level, hand, hitResult) -> {
// Event handling
return InteractionResult.PASS;
});
}
}Platform Abstraction Strategies
1. Use Amber APIs First
Always prefer Amber's unified APIs over platform-specific ones:
// Good - Use Amber's platform API
boolean isClient = Platform.isClient();
Path configDir = Platform.getConfigFolder();
boolean isModLoaded = Platform.isModLoaded("fabric-api");
// Avoid - Platform-specific code
// boolean isClient = FabricLoader.getInstance().isDevelopmentEnvironment(); // Fabric only
// Path configDir = FMLPaths.CONFIGDIR.get(); // Forge only2. Conditional Platform Code
When platform-specific code is unavoidable, use platform checks:
public class PlatformSpecificCode {
public static void doPlatformSpecificThing() {
if (Platform.isFabric()) {
// Fabric-specific code
doFabricThing();
} else if (Platform.isForge()) {
// Forge-specific code
doForgeThing();
} else if (Platform.isNeoForge()) {
// NeoForge-specific code
doNeoForgeThing();
}
}
// For client-side only code
public static void doClientThing() {
if (Platform.isClient()) {
// Client-specific initialization
ClientInit.init();
}
}
}3. Service-Based Architecture
Use Amber's service system for platform-specific implementations:
// Define service interface
public interface MyModService {
void doPlatformSpecificThing();
boolean isFeatureSupported();
}
// Platform-specific implementations
public class FabricMyModService implements MyModService {
@Override
public void doPlatformSpecificThing() {
// Fabric implementation
}
@Override
public boolean isFeatureSupported() {
return true;
}
}
public class ForgeMyModService implements MyModService {
@Override
public void doPlatformSpecificThing() {
// Forge implementation
}
@Override
public boolean isFeatureSupported() {
return false;
}
}
// Common usage
public class MyModService {
private static MyModService instance;
public static MyModService get() {
if (instance == null) {
// Initialize platform-specific service
if (Platform.isFabric()) {
instance = new FabricMyModService();
} else if (Platform.isForge()) {
instance = new ForgeMyModService();
} else if (Platform.isNeoForge()) {
instance = new NeoForgeMyModService();
}
}
return instance;
}
}Cross-Platform Best Practices
1. Organize by Feature, Not Platform
Organize your code by feature rather than platform:
Good:
src/main/java/mymod/
├── common/
│ ├── items/
│ ├── blocks/
│ ├── events/
│ └── networking/
├── fabric/
│ └── MyModFabric.java
├── forge/
│ └── MyModForge.java
└── neoforge/
└── MyModNeoForge.java
Bad:
src/main/java/mymod/
├── fabric/
│ ├── items/
│ ├── blocks/
│ └── events/
├── forge/
│ ├── items/
│ ├── blocks/
│ └── events/
└── neoforge/
├── items/
├── blocks/
└── events/2. Minimize Platform-Specific Code
Keep platform-specific code to a minimum:
// Good - Minimal platform code
public class MyMod {
public static void init() {
// Common initialization
registerItems();
registerBlocks();
registerEvents();
// Only necessary platform-specific code
if (Platform.isFabric()) {
registerFabricNetworking();
}
}
}
// Bad - Lots of platform code
public class MyMod {
public static void init() {
if (Platform.isFabric()) {
registerFabricItems();
registerFabricBlocks();
registerFabricEvents();
registerFabricNetworking();
} else if (Platform.isForge()) {
registerForgeItems();
registerForgeBlocks();
registerForgeEvents();
registerForgeNetworking();
}
}
}3. Use Abstract Classes for Platform Differences
When you have small platform differences, use abstract classes:
public abstract class MyAbstractBlock extends Block {
public MyAbstractBlock(Properties properties) {
super(properties);
}
// Common behavior
@Override
public void onPlace(Level level, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack) {
super.onPlace(level, pos, state, placer, stack);
// Common on-place logic
}
// Platform-specific behavior
protected abstract void doPlatformSpecificThing(Level level, BlockPos pos);
}
// Platform-specific implementations
public class MyModBlock extends MyAbstractBlock {
public MyModBlock(Properties properties) {
super(properties);
}
@Override
protected void doPlatformSpecificThing(Level level, BlockPos pos) {
// Common implementation works on all platforms
}
}4. Test on All Platforms
Test your mod on all supported platforms:
public class PlatformTests {
public static void runTests() {
testPlatformDetection();
testEventFiring();
testNetworking();
testRegistration();
String platform = Platform.getPlatformName();
System.out.println("Tests completed on " + platform);
}
private static void testPlatformDetection() {
assert Platform.getPlatformName() != null;
assert Platform.isClient() || Platform.isServer();
}
private static void testEventFiring() {
// Test that events fire correctly
// Implementation depends on your testing framework
}
}Common Cross-Platform Challenges
1. Networking
Networking is one of the most challenging areas for cross-platform development:
// Amber provides unified networking
public class MyNetworking {
public static final NetworkChannel CHANNEL = NetworkChannel.create(
ResourceLocation.fromNamespaceAndPath("mymod", "main")
);
public static void init() {
// Register packets
CHANNEL.register(MyPacket.class, MyPacket::encode, MyPacket::decode, MyPacket::handle);
}
}
// Without Amber, you'd need different implementations for each platform
// Fabric: use PacketByteBufs and ServerPlayNetworking
// Forge: use ChannelBuilder and SimpleChannel (different APIs for different versions)
// NeoForge: similar to Forge but with NeoForge's networking systemAmber's networking implementation handles the complexities of different Forge versions:
- Forge 60.0.0+: Uses the new ChannelBuilder API with VersionTest
- Forge 47.x-59.x: Uses the traditional SimpleChannel API
- Fabric: Uses Fabric's networking API
- NeoForge: Uses NeoForge's payload system
All implementations ensure thread-safe packet handling and proper network transmission.
2. Configuration
Configuration systems vary between platforms:
// Amber provides unified configuration
public class MyModConfig {
private static JsonConfigManager<MyModConfig> manager;
public static void init() {
manager = new JsonConfigManager<>("mymod", new MyModConfig(), null, null);
}
public static MyModConfig get() {
return manager.getConfig();
}
}
// Without Amber, you'd need different implementations
// Fabric: use FabricLoader.getConfigDir() and manual JSON handling
// Forge: use ForgeConfigSpec
// NeoForge: similar to Forge but with NeoForge's configuration system3. Creative Tabs
Creative tab registration differs between platforms:
// Amber provides unified creative tab registration
public static final RegistrySupplier<CreativeModeTab> MY_TAB =
DeferredRegister.create("mymod", Registries.CREATIVE_MODE_TAB)
.register("my_tab", () -> CreativeModeTab.builder()
.icon(() -> new ItemStack(MyItems.MY_ITEM))
.displayItems((params, output) -> {
output.accept(MyItems.MY_ITEM.get());
})
.build());
// Without Amber, you'd need different implementations
// Fabric: use FabricItemGroupBuilder
// Forge: use CreativeModeTab.Builder
// NeoForge: similar to Forge but with NeoForge's creative tab systemMigration Guide
From Single-Platform to Cross-Platform
If you're migrating an existing mod to be cross-platform:
Separate Platform-Specific Code
java// Before public class MyMod implements ModInitializer { @Override public void onInitialize() { // All initialization code } } // After public class MyMod { public static void init() { // Common initialization code } } // Platform-specific entry points public class MyModFabric implements ModInitializer { @Override public void onInitialize() { MyMod.init(); } }Replace Platform APIs with Amber APIs
java// Before if (FabricLoader.getInstance().isModLoaded("fabric-api")) { // Do something } // After if (Platform.isModLoaded("fabric-api")) { // Do something }Use Unified Event System
java// Before UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> { // Event handling }); // After BlockEvents.BLOCK_INTERACT.register((player, level, hand, hitResult) -> { // Event handling });Test on All Platforms
- Test your mod on Fabric, Forge, and NeoForge
- Ensure all features work correctly on each platform
- Handle platform-specific edge cases
Platform-Specific Tips
Fabric Tips
- Fabric has a more modular architecture
- Use Fabric API for additional functionality
- Be aware of Fabric's different entity spawn system
Forge Tips
- Forge has a more monolithic architecture
- Use Forge's capabilities system for custom functionality
- Be aware of Forge's different event firing order
- Networking: Forge 60.0.0+ uses a new ChannelBuilder API that differs significantly from older versions
- Version Compatibility: Amber handles Forge version differences automatically
NeoForge Tips
- NeoForge is similar to Forge but with modernized APIs
- Use NeoForge's new registry system
- Be aware of NeoForge's different networking system
Conclusion
Cross-platform mod development with Amber requires careful planning and organization, but the benefits are significant:
- Wider Audience: Your mod works on more platforms
- Easier Maintenance: Less code to maintain across platforms
- Future-Proof: Your mod is more likely to work with future versions
By following these best practices and using Amber's unified APIs, you can create robust cross-platform mods with minimal platform-specific code.