Skip to content

Link API v1

Cheeseworks edited this page May 16, 2026 · 1 revision

Are you a developer looking to fully integrate your mod into Horrible Menu's interface? Well, you found the right place! The following guide teaches you how to register the metadata and handle toggling for your options.

Let's start off by adding this mod as a dependency in your mod.json!

"dependencies": {
    "cubicstudios.horriblemenu": ">=1.0.0"
}

Tip

We recommend shortening include paths in your code by adding the the full path as a private include directory through your project's CMakeLists.txt file. The rest of the documentation will use this practice in its code samples.

target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/geode-deps/cubicstudios.horriblemenu/include)

You can directly access the Horrible Menu mod menu API by including the API.h file in your code. Make sure to include the horrible namespace to directly access all needed classes and methods.

#include <horrible/API.h>

using namespace horrible;

Options

The purpose of this mod is primarily focused on its huge list of togglable troll options. To create the metadata for an option, create a new horrible::Option object pointer.

using namespace horrible;

auto myOption = Option::create("my-option"_spr);

Tip

To avoid conflicts with this or other mods' option IDs, prefix your option IDs with you mod ID by appending _spr at the end of your string.

You should now have a new smart pointer to an option's metadata. Before proceeding, be sure to provide more information about your option with the following setter functions.

  • setName(std::string): Name of the option
  • setDescription(std::string): Description of the option
  • setCategory(std::string): Name of the category this option should be under
  • setSillyTier(SillyTier): How silly the option is
  • setDefaultToggleState(bool): Default toggle state for this option
  • setOnline(bool): If the option requires an active internet connection to work properly
  • setRequiresRestart(bool): If the option requires a game restart to take effect
  • setSupportedPlatforms(std::vector<Platform>): Platforms that the option supports

Each of these setters has its respective getter in case you ever need to read the information you've stored in these objects.

The Option class was designed to allow a readable, syntax-sugary builder pattern to simplify the creation process.

auto myOption = Option::create("my-option"_spr)
    ->setName("My Very Cool Option")
    ->setDescription("A very detailed description about what this option does...")
    ->setCategory("My Stuff!")
    ->setSillyTier(SillyTier::Low);

Registering

There are a few ways to register your option to Horrible Menu's option manager in your code. The example practices below assume you're creating each of your Option objects in their own respective source files.

HORRIBLE_REGISTER_OPTION Macro

This is the safest option, as HORRIBLE_REGISTER_OPTION expands into an $on_mod(Loaded) block which safely calls the register function for you once your mod finishes fully loading. It's recommended you call this right after creating your Option object globally.

static auto myOption = Option::create("my-option"_spr)
    ->setName("My Very Cool Option")
    ->setDescription("A very detailed description about what this option does...")
    ->setCategory("My Stuff!")
    ->setSillyTier(SillyTier::Low);

HORRIBLE_REGISTER_OPTION(myOption);

Option::autoRegister Builder Call

In the same chain where you create and build your option, you can call autoRegister at the very end. This will register your option the moment it's created, during your mod's static initialization.

static auto myOption = Option::create("my-option"_spr)
    ->setName("My Very Cool Option")
    ->setDescription("A very detailed description about what this option does...")
    ->setCategory("My Stuff!")
    ->setSillyTier(SillyTier::Low)
    ->autoRegister();

Note

Metadata such as category names can be cached at option registration for the entire game session to help improve performance. Calling autoRegister on your builder before you actually finish setting up your option's metadata can cause unexpected issues in UI!

Manual OptionManager::registerOption Call

Of course, all of these still call OptionManager::registerOption in the end, if you prefer registering your option during a different loading phase for your mod, feel free to manually call the register function yourself!

static auto myOption = Option::create("my-option"_spr)
    ->setName("My Very Cool Option")
    ->setDescription("A very detailed description about what this option does...")
    ->setCategory("My Stuff!")
    ->setSillyTier(SillyTier::Low);

$on_game(Loaded) {
    OptionManager::get()->registerOption(myOption);
};

Handling

So, your options should now be registered. Great! Now, we can move to actually handling them functionally by listening to when they're being toggled.

Save Data

Before proceeding, it's best to familiarize yourself with the save data object this mod uses to store user data for each option.

struct HorribleOptionSave final {
    bool enabled = false; // If the option is toggled on
    bool pin = false; // If the option is pinned in the menu UI
    bool viewed = false; // If the option has been used or viewed
};

Tip

You'll likely only need to check for enabled, as the other two are reserved for the user interface, already handled by this mod. Here's the OptionManager method to check only for the toggle state.

bool enabled = OptionManager::get()->isEnabled("my-option"_spr);

Hook Delegation

This API has a handy macro that allows you to automatically and safely register your modified classes' hooks to Horrible Menu's options manager, so you aren't the one managing them yourself.

HORRIBLE_DELEGATE_HOOKS(optID)

This macro expands into a static void onModify() function inside your modified class that calls horrible::delegateHooks to register your hooks for automatic toggling.

[!INFO] By including this macro, all hooks in that modified class will be toggled on or off in realtime whenever the option for it is toggled by the player through the UI.

Events

Option toggling can also be listened to via the provided thread-safe, global event class, horrible::OptionEvent - which you can listen for on any thread, with or without a filter.

Filtering

Normally, you'd only want to filter through events that actually have to do with one option in some places. The event allows you to add ID for the option you would like to listen to specifically.

OptionEvent("my-option"_spr);

If you need to listen for global events, which will involve other mods' options, simply provide no ID to make it listen for every single toggle.

OptionEvent();

Listening on a Node

Listening to option events under a node is pretty straightforward.

    this->addEventListener(
        OptionEvent("my-option"_spr),
        [](HorribleOptionSave data) {
            log::info("{} is set to: {}", "my-option"_spr, data.enabled);
        });
    this->addEventListener(
        OptionEvent(),
        [](std::string_view id, HorribleOptionSave data) {
            log::info("{} is set to: {}", id, data.enabled);
        });

Utilities

There are a couple of utility functions you can use to conveniently listen for option toggle events globally.

listenForHorribleOptionChanges

Creates a listener for option toggles for one specific option.

using namespace horrible;

listenForHorribleOptionChanges(
    "my-option"_spr,
    [](HorribleOptionSave data) {
        log::info("{} is set to: {}", "my-option"_spr, data.enabled);
    }
);
listenForAllHorribleOptionChanges

Creates a listener for any option's toggling.

using namespace horrible;

listenForAllHorribleOptionChanges(
    [](std::string_view id, HorribleOptionSave data) {
        log::info("{} is set to: {}", id, data.enabled);
    }
);

Conclusion

That should be all! Hopefully you got the gist of it! If not, feel free to join our Discord server and ask about anything that was potentially missing from here or that you did not fully understand. Happy modding!