Skip to content

Porting a Forge or NeoForge mod by hand

Status: Implemented as a mapping to the API that exists; rows marked planned name CLight features that aren’t in the game yet.

Celestial does not run, convert or translate Forge or NeoForge mods, and there is no conversion tool. This guide maps Forge and NeoForge concepts to CLight so you can rewrite your own mod. Read Porting existing mods to CLight: overview first.

Forge and NeoForge mods are often large, event-driven and content-heavy. CLight’s structure will feel familiar: a registration phase, event subscriptions with priorities, configs, and client and server code apart. Many content systems of big Forge mods (block entities, entities, capabilities, custom renderers) are still planned in CLight, so check the tables before you start.

Project and metadata

Forge / NeoForge CLight
mods.toml / neoforge.mods.toml clight.mod.json (Writing the manifest)
modId, version id, version (SemVer)
[[dependencies]] with type, versionRange, ordering depends / optional / breaks with SemVer ranges, load.after / load.before
displayTest side and sync of your own mod
The @Mod class The entry point: CLIGHT_MOD_ENTRY(fn) ©, CLIGHT_MOD(Class) (C++), the entry script (Lua)
Access transformers, coremods, mixins Events, behaviours, CLight mixins and accessors on named game targets
Gradle (ForgeGradle / ModDevGradle) clight build (C/C++), nothing (Lua)
Data generators Write the JSON by hand or with your own tools; CLight loads the result

The two event buses

In CLight the mod bus and the game bus are one event system; loading events are the lifecycle.* family.

Forge / NeoForge CLight
Mod constructor The entry point
Register events / deferred registers lifecycle.register
Common, client, dedicated server setup lifecycle.common_setup, client_setup, server_setup
Load complete lifecycle.registries_frozen
Server starting / started / stopping / stopped lifecycle.server_starting / server_started / server_stopping / server_stopped
Level load / unload / save lifecycle.level_load / level_unload / level_save
Reload listeners lifecycle.data_reloaded, lifecycle.resources_reloaded (listeners with their own stage planned)
@SubscribeEvent, addListener cl_event_subscribe ©, clight::on<...> (C++), cl.on (Lua)
Priority HIGHEST … LOWEST CL_PRIORITY_FIRST, EARLY, NORMAL, LATE, LAST (lower runs first); observe-only MONITOR
receiveCanceled CL_SUB_RECEIVE_CANCELLED

Registration

Forge / NeoForge CLight
DeferredRegister<Block>, <Item> cl_block_register / cl_item_register in lifecycle.register (vanilla-backed)
BlockBehaviour.Properties cl_block_desc (destroy_time, friction, speed_factor, jump_factor, light_emission, sound_type, copy_properties_from)
Block / Item subclasses Behaviour tables (use_without_item, attack, random_tick; use)
BlockEntityType, EntityType, MenuType, ParticleType, MobEffect, data components planned
SoundEvent sounds.json in assets/ (code registration planned)
Creative tabs creative_tab on the item (vanilla tabs). Custom tabs planned

Capabilities and data attachments

Forge / NeoForge CLight
Capabilities, data attachments, saved data typed attachments with defaults and copy_on_death like NeoForge’s AttachmentType (cl_attachment_register), entity / chunk data and saved data (Persistence and data attachments)
Item handler / energy capabilities planned (containers, services)

Gameplay events

Forge / NeoForge event CLight
Player logged in / out, respawn player.join, player.leave, player.respawn
Block break / place block.break, block.place
Right-click block / item player.interact_block, player.interact_item
Attack entity player.attack_entity
Living damage / death entity.damage (amount mutable), entity.death
Server chat, command player.chat, player.command
Register commands server.command_register
Server / level / client tick tick.server_*, tick.level_*, tick.client_*
Loot table load, entity join level, tooltip, render events planned

Networking

Forge / NeoForge CLight
Payload registrar / SimpleChannel cl_payload_register (byte buffers)
PacketDistributor to a player / the server cl_net_send_to_player, cl_net_send_to_server
Other distributors (tracking, chunk, all), typed codecs, configuration tasks planned

Configuration

Forge / NeoForge CLight
ModConfigSpec client / common / server cl_config_open(CL_CONFIG_CLIENT / COMMON / SERVER) with define_bool / _i32 / _f64 / _str and comments
Config screen The Mods screen’s generated Config page
Server config synced to clients planned

Client side

Forge / NeoForge CLight
Key mappings cl_keybind_register
GUI layers / overlays cl_hud_layer_register
Screens cl_screen_open with vanilla widgets, RmlUi documents
Entity / block entity renderers, model loaders, colour handlers, particle providers planned

Threading notes for Forge authors

Forge mods assume one logical server thread and use enqueueWork to get onto it. In CLight, server callbacks run on the tick, and one mod’s callbacks never run on two threads at once, so you need no locks. To run something later on the tick, use cl_sched_run_server (C++ clight::after). Don’t keep long work in one callback: it has a time limit (Threading model).

Big mods: split them

Large Forge mods often bundle a library, content, worldgen and client features. On CLight, consider a content mod (C++ or Lua) plus a separate client_only mod for client features, linked with depends. Smaller mods with clear side and sync are easier to keep compatible.