Mod manager for the game CrossCode!
Open the menu from the options menu and look for the Mods button.
Read the in-game manual in the help menu for usage instructions.
You can attach this image at the top of your mod's README.md:
Markdown code:
[](https://github.com/CCDirectLink/CCModManager)You can define custom options pages for your own mod.
See full example below.
{
    type: 'CHECKBOX',
    init: true,
    changeEvent() { /* optional */
        // code
    },
    name: 'My checkbox',
    description: 'My least favorite button.',
}{
    type: 'BUTTON_GROUP',
    init: 0 /* option1 */,
    enum: { option1: 0, option2: 1 },
    buttonNames: ['Option 1', 'Option 2'],
    name: 'My buttons',
    description: 'My (not really) favorite button group.',
}// Or with an enum variable
const myenum = {
    option1: 0,
    option2: 1,
}
/// ...
{
    type: 'BUTTON_GROUP',
    init: myenum.option1 /* option1 */,
    enum: myenum,
    buttonNames: ['Option 1', 'Option 2'],
    name: 'My buttons',
    description: 'My beloved button group.',
}{
    // Creates a slider that displays values from 1 to 8
    type: 'OBJECT_SLIDER',
    init: 1,
    min: 0.8,
    max: 1.5,
    step: 0.1,
    fill: true,
    name: 'Slider',
    description: 'My somewhat favorite slider.',
}{
    // Creates a slider that displays values from 0% to 100%
    type: 'OBJECT_SLIDER',
    init: 0.5,
    min: 0,
    max: 1,
    step: 0.1,
    fill: true,
    showPercentage: true,
    thumbWidth: 50, // Force the thumb width to 50px
    name: 'Percentage slider',
    description: 'My (maybe) somewhat favorite slider.',
}{
    // Creates a slider that displays values from 4 to 13 
    type: 'OBJECT_SLIDER',
    init: 7,
    min: 4,
    max: 13,
    step: 1,
    fill: true,
    // note: when using typescript, you need to specify
    // the return type of this function manually
    customNumberDisplay(index) {
        return this.min + index
    },
    name: 'Number slider',
    description: 'My (definitely) somewhat favorite slider.',
}{
    type: 'BUTTON',
    name: 'Button',
    description: 'My favorite button.',
    onPress(button) {
        // code
        // you can update the button text as you please
        button.setText("hi!!")
    },
}{
    type: 'INFO',
    name: 'Hello to all!\n<-- New line.',
    description: '',
}{
    type: 'CONTROLS',
    init: { key1: ig.KEY.I },
    // If false, the keybinding only works in-game
    // If true, the keybinding works everywhere
    global: false, /* optional, false by default */
    pressEvent() { /* optional */
        // keybinding pressed! I will trigger only once
        // code
    },
    holdEvent() { /* optional */
        // keybinding is pressed now! I will trigger every frame the key is pressed
        // code
    },
    name: 'My keybinding',
    description: 'Does something I guess.',
}// Without name
{
    type: 'INPUT_FIELD',
    name: '',
    description: 'boring text, carry on',
    init: 'initial text',
    changeEvent() { /* optional */
        // code
    }
},// With name
{
    type: 'INPUT_FIELD',
    name: 'favorite food',
    description: 'whats your favorite food??',
    init: 'bricks',
    changeEvent() { /* optional */
        // code
    }
},// Without name
{
    type: 'INPUT_FIELD',
    name: '',
    init: 'crossthecodes 123',
    description: 'how to cross the codes',
    changeEvent() { /* optional */
        // code
    },
    isValid(text) {
        return text.includes('crossthecodes')
    }
}
},// With name
{
    type: 'INPUT_FIELD',
    name: 'favorite plushie',
    init: 'lea',
    changeEvent() { /* optional */
        // code
    },
    isValid(text) {
        return text == 'lea'
    }
}
},{
    type: 'JSON_DATA',
    init: 123,
    changeEvent() { /* optional */
        // code
    }
}Each visible option has the optional functions onInit and onDeinit.
For example:
{
    type: 'BUTTON',
    name: 'Button',
    description: 'My favorite button.',
    onPress() {
        console.log('Button presssed!')
    },
    onInit(gui) {
        gui.button.setText("(evil) Button")
    },
    onDeinit(gui) {
        console.log("the evil button will return someday!")
    }
},On any option with a name visible, you can set the noNamePadding field to true to disable the padding.
For example:
{
    type: 'CHECKBOX',
    init: true,
    name: 'My checkbox',
    noNamePadding: true,
    description: "It's initialized as true by default.",
}Here's the whole page with noNamePadding set to true:
// for typescript:
// import type {} from 'ccmodmanager/types/plugin'
const Opts = modmanager.registerAndGetModOptions(
    {
        modId: 'my-mod', // the same as the `id` field in `ccmod.json`
        title: 'My mod', // the same as the `title` field in `ccmod.json`
    },
    {
        general: {
            settings: {
                title: 'General', // tab title
                tabIcon: 'general', // icon id
            },
            headers: {
                'My header title': {
                    myCheckbox: {
                        type: 'CHECKBOX',
                        init: true,
                        name: 'My checkbox',
                        description: "It's initialized as true by default.",
                    },
                    myEnum: {
                        type: 'BUTTON_GROUP',
                        init: 0 /* option1 */,
                        enum: { option1: 0, option2: 1 },
                        buttonNames: ['Option 1', 'Option 2'],
                        name: 'My buttons',
                        description: 'Hello.',
                    },
                    mySlider: {
                        // Creates a slider with the following text:
                        // 1   2   3   4   5   6   7   8
                        // that resolve to the following values:
                        // 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5
                        type: 'OBJECT_SLIDER',
                        init: 1,
                        min: 0.8,
                        max: 1.5,
                        step: 0.1,
                        fill: true,
                        showPercentage: false /* Show the values as % (example: 0.9 => 90%) */,
                        name: 'Slider',
                        description: 'My somewhat favorite slider.',
                    },
                    myButton: {
                        type: 'BUTTON',
                        name: 'Button',
                        description: 'My favorite button.',
                        onPress() {
                            console.log('Button presssed!')
                        },
                    },
                    myInfo: {
                        type: 'INFO',
                        name: 'Hello!',
                        description: '',
                    },
                    myKeybinding: {
                        type: 'CONTROLS',
                        init: { key1: ig.KEY.I },
                        // If false, the keybinding only works in-game
                        // If true, the keybinding works everywhere
                        global: false,
                        pressEvent() {
                            console.log('keybinding pressed!')
                        },
                        name: 'Keybinding',
                        description: 'Does something I guess.',
                    },
                    // JSON_DATA is not visible in the menu
                    myNumberStorage: {
                        type: 'JSON_DATA',
                        init: 123,
                    },
                    myJsonStorage: {
                        type: 'JSON_DATA',
                        init: { a: 1, b: 2, c: 3 },
                    },
                    myInputField: {
                        type: 'INPUT_FIELD',
                        name: '',
                        description: 'boring text, carry on',
                        init: 'initial text',
                        changeEvent() {
                            /* optional */
                            // code
                        },
                    },
                    myValidatedInputField: {
                        type: 'INPUT_FIELD',
                        name: '',
                        description: 'how to cross the codes',
                        init: 'crossthecodes 123',
                        changeEvent() {
                            /* optional */
                            // code
                        },
                        isValid(text) {
                            return text.includes('crossthecodes')
                        },
                    },
                },
            },
        },
    }
)
// Usage
Opts.myCheckbox // boolean
Opts.myEnum // 0 | 1
Opts.mySlider // 0.8 | 0.9 | 1.0 | 1.1 | 1.2 | 1.3 | 1.4 | 1.5
// Opts.myInfo is not accessible, since it does not store data
// Opts.myKeybinding is not accessible, since it does not store data
Opts.myInputField // string
Opts.myValidatedInputField // string
Opts.myNumberStorage // number
Opts.myJsonStorage.a // number
Opts.myJsonStorage.b // number
Opts.myJsonStorage.c // number
/* Invalid code: */
// Opts.myJsonStorage.a = 2 // The stored data is read-only, you need to override the whole object
/* This is how you do it: */
Opts.myJsonStorage = { ...Opts.myJsonStorage, a: 2 }- (javascript) cc-staircase-effect-fix as an example of INFO,CHECKBOXandOBJECT_SLIDER
- (typescript) cc-fancy-crash as an example of BUTTON_GROUPandCHECKBOX
- (typescript) cc-record as an example of OBJECT_SLIDERandCONTROLS
- (typescript) CCModManager as an example of JSON_DATA,BUTTONandINPUT_FIELD
- (typescript) CrossedEyes as an example of a big multi-tab menu with a custom language getter
git clone https://github.com/CCDirectLink/CCModManager
cd CCModManager
pnpm install
pnpm run start
# this should return no errors (hopefully)
npx tsc

















