-
Notifications
You must be signed in to change notification settings - Fork 0
Adding a Feature
The mechanics are small; the surrounding checklist is what makes a change shippable. Read Architecture first if you have not.
One package under src/main/kotlin/net/trilleo/<name>/, with a Feature object at its root:
object MyFeature : Feature {
override val id = "mything"
override val enabled get() = MyConfig.settings.enabled
override fun onInit() {
MyConfig.load()
myKey = KeyMapping("key.hex.mything.do", InputConstants.UNKNOWN.value, Hex.KEY_CATEGORY)
KeyMappingHelper.registerKeyMapping(myKey)
}
override fun onClientTick(client: Minecraft) {
while (myKey.consumeClick()) {
…
}
}
override fun registerCommands(hex: LiteralArgumentBuilder<FabricClientCommandSource>) {
hex.then(Commands.literal("mything").executes { … })
}
override fun settingsCategory(): ConfigCategory = ConfigCategory.build("mything") {
toggle(
"enabled", default = true,
get = { MyConfig.settings.enabled },
set = { MyConfig.settings.enabled = it; MyConfig.save() })
}
}Then one line in Hex.onInitializeClient:
Features.register(MyFeature)Guidelines:
-
Ship keybinds unbound (
InputConstants.UNKNOWN.value) and inHex.KEY_CATEGORY. -
Never register a Fabric callback yourself — override the
Featurehook instead. If the hook you need does not exist, add it toFeatureand wire it once inFeatures. -
onHudRenderis a frame callback, not a tick. Do no work there beyond drawing: format, measure, sort and read config inonClientTickand leave a prepared model behind. -
Nest commands under
/hexa. A bare parent command should print its subcommands rather than guess. -
Opening a screen from a command must be deferred (
client.execute { … }) — a screen opened mid-command is undone when the chat screen that ran it closes.
object MyConfig {
private val config = JsonConfig(
name = "mything",
type = object : TypeToken<MySettings>() {}.type,
default = { MySettings() },
normalizer = { /* repair GSON's reflection gaps: null lists, null enums */ },
)
val handle = ConfigRegistry.register(
ConfigHandle(config, adopt = { settings = it }, current = { settings }),
)
}-
nameis the file base name and the key it is addressed by in an exported profile blob, so it must stay stable across versions. - Pass
global = trueonly if the data describes the installation rather than a loadout (see Architecture). - Keep transient machine state out of
ConfigRegistryentirely.
Every change that touches the shipped mod finishes with all of these, in the same commit or PR:
- Any user-visible string goes through a translation key and
Component.translatable— neverComponent.literal("some English text"). -
Every language file carries exactly the same key set, in the same order. Adding a key to
en_us.jsonmeans adding it tozh_cn.jsontoo, in the same place. A key present in one file and missing from another renders as the raw key id in game — invisible to anyone testing in English. -
%s/%dplaceholders: identical count and order across files. - Run the parity check in Translating before you finish.
Add an entry under ## Unreleased in CHANGELOG.md, in the SkyHanni-style format: a ### category (New Features,
Improvements, Fixes, Technical Details, Removed Features), then a #### Feature Area heading, then + bullets.
Write player-facing entries for gameplay changes; refactors, build and tooling changes go under Technical Details. See Releasing.
Every user-visible feature gets a ## section in docs/FEATURES.md: what it does, how the player enables and
configures it, any limitation worth stating up front. Write for a player — implementation notes belong in the
changelog's Technical Details.
Update this wiki too: a new page, a link in _Sidebar.md, and a row in the Home table.
-
README.mdcarries a one-line bullet per feature — add one, and keep the detail indocs/FEATURES.md. Also update it if the change touches installation, dependencies, MC/Java version, build instructions or licensing. - If the change affects a workflow documented in
docs/(releasing, translations, commit structure), update that doc in the same task.
./gradlew buildand run the client at least once (./gradlew runClient) — a feature that compiles is not a feature that works. Then see
Contributing for the commit convention.
build runs the unit tests under src/test/kotlin. Most of Hex cannot be unit tested — it draws screens and reads a
live client — so that source set is deliberately narrow: it covers the pieces that are pure logic and touch no
Minecraft runtime, and where being wrong loses something the player cannot get back. The notebook's note-file handling
is the current example. If what you are writing fits that description, add tests; if it needs a Minecraft
instance, do not try, and test it by playing.
Getting started
Features
- Config profiles
- Keybind shortcuts
- Control switches
- Attack mode switch
- Freecam
- Hand display
- Per-item swing
- Item customization
- Chroma text
- Reminders
- Regions
- Notebook
- Command suggestions
- Languages
Help
Development