Skip to content

Mods and MadCards

m4bwav edited this page Oct 22, 2026 · 3 revisions

A mod is one folder directly inside the game's Mods folder, which on Windows is %USERPROFILE%\AppData\LocalLow\Gibbing Tree\Madness Project Nexus\Mods\. The madnesswright skill writes the folder, runs mpn.py check-mod on it, deploys it, and reads the game's log after you play. The rules below are the game loader's, as read from release build 25615280 on 2026-10-17 (madnesswright RESEARCH.md R-20261017-9), unless a line names the official Modding Overview.

What goes in the folder

  • info.txt or info.json: one JSON object. Name and Description must both be non-empty, or the game skips the mod and Player.log says info.txt contains no data. With both files present the game uses whichever it finds first, so keep one.
  • MadCards: every other .txt and .json file in the folder and its subfolders is read as cards. Keep notes as .md.
  • .mobj bundles from ModTools and .sobj bundles from SceneTools, in any subfolder.
  • Optionally an image named preview, and an assembly named by AssemblyPath (see Assets and Harmony).

The info keys the game reads are Name, Author, Description, Tags, Dependencies, and the five Harmony keys. Keys are case-sensitive. Dependencies holds Steam Workshop item ids, which auto-enable with your mod when the user has them installed; a local mod cannot depend on another local mod. Workshop items take their title and description from the Workshop page but still read info.txt for the assembly keys.

Two card formats

A MadCard is a set of values for one game entity: a character, weapon, armor piece, wave, stage and so on. Every card needs a Filename (the entity's id) and a Type. A Filename the game or another mod already uses overrides that entity; a new one adds an entity. Root names another card of the same Type to inherit every value you do not set.

A .json file is strict JSON, and the game reads it only as a container: an object whose keys are Type names, each holding a list of cards. This one gives Hank green blood:

{ "Character": [ { "Filename": "Hank", "Root": "Hank", "Blood": {"r": 0, "g": 1, "b": 0, "a": 1} } ] }

A bare card in a .json file, or a list name the game does not know (Characters for Character), is ignored without any message. Quotes are not cleaned in .json files.

A .txt file is shorthand: cards one after another at the top level, each carrying its own Type. The game splits the file on balanced braces and straightens curly double quotes first. Here Type is case-sensitive, except Event, Mission, Cutscene and Driver, which match in any case.

The Types the references list (the same list since 0.1.1): Addon, AddonGroup, Ammo, AnimationProfile, Animations, Armor, Campaign, Character, Chatter, Corpus, DoT, Dungeon, Execution, Explosion, Gang, Keycard, Loadout, Melee, Origin, Ranged, Skill, SkillGroup, Skin, SoundPack, Stage_Arena, Stage_Custom, Stage_Dungeon, Stage_Story, Swatch, Thrown, VisualStockpile, VoiceSet, Wardrobe, plus Event, Mission, Cutscene and Driver. The loader and check-mod also accept Stage_CAMPAIGN in a .txt card and Item as a .json list name. Neither is in the official docs, so the references (since 0.1.2) advise the named stage types.

The official docs show only a few field names; the full glossary is the community MadCard Maker app (MCM). The skill never invents one; a name it cannot confirm is marked "confirm in MCM".

Values worth knowing

  • Colours are Unity Color objects with floats from 0 to 1: {"r": 0, "g": 1, "b": 0, "a": 1}. The official doc's ["r":0, ...] is not JSON. Blood on a Character card is one.
  • In lists of cards, objects and enumerators: "*" at an index keeps the Root's element, null, "none" or "" leaves it empty, and ["empty"] empties the whole list.
  • ObjRef names a prefab inside an enabled mod's .mobj by the prefab's name, not the bundle's. "ObjRef": "*M_Pipewrench" borrows a built-in entity's object.
  • Priority: lower converts first, for cards of one Type that depend on each other.
  • Write straight quotes. The official Google Docs examples carry curly ones.

Checking a mod

tests/fixtures/subtle-mod in the repository has two faults that do not crash anything: a misspelt Root in agents.txt and a bare card in hank.json.

python mpn.py check-mod subtle-mod
{
  "mod": "subtle-mod",
  "errors": [
    "hank.json: a bare card in a .json file; the game reads .json only as {\"<Type>\": [cards]} and silently ignores this card. Wrap it, or save it as .txt"
  ],
  "warnings": [
    "agents.txt: Character card Nexus1_Agent has Root \"Nexsu1_Agent\", which looks like a misspelling of Nexus1_Agent; a Root the game cannot find gives no inheritance and no error"
  ],
  "info": {
    "name": "Tough Agents, Green Hank",
    "cards": 1,
    "card_files": 2,
    "card_list": [
      {
        "file": "agents.txt",
        "type": "Character",
        "filename": "Nexus1_Agent",
        "root": "Nexsu1_Agent"
      }
    ],
    "bundles": [],
    "preview": false
  }
}

Exit code 0.

The bare card is an error because the game would drop it. The Root is a warning, because check-mod cannot know every vanilla Filename. It flags a Root only when it is one or two edits from a Filename in the same mod. Names that differ only in their digits, such as Nexus1_Agent and Nexus2_Agent, count as siblings, not typos. info.card_list is what the game will see: here, one card.

The same mod fixed (Root spelt Nexus1_Agent, the Hank card wrapped as shown above):

python mpn.py check-mod green-hank
{
  "mod": "green-hank",
  "errors": [],
  "warnings": [],
  "info": {
    "name": "Tough Agents, Green Hank",
    "cards": 2,
    "card_files": 2,
    "card_list": [
      {
        "file": "agents.txt",
        "type": "Character",
        "filename": "Nexus1_Agent",
        "root": "Nexus1_Agent"
      },
      {
        "file": "hank.json",
        "type": "Character",
        "filename": "Hank",
        "root": "Hank"
      }
    ],
    "bundles": [],
    "preview": false
  }
}

Exit code 0.

tests/fixtures/broken-mod has curly quotes in info.txt and a trailing comma in a .json file:

python mpn.py check-mod broken-mod
{
  "mod": "broken-mod",
  "errors": [
    "info.txt: curly quotes found; the game parses this file as strict JSON, so use straight quotes (examples pasted from the Google Docs carry curly ones)",
    "info.txt does not parse: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
    "agents.json: not valid JSON (Illegal trailing comma before end of object: line 1 column 82 (char 81)). Standard .json must be strict; save shorthand as .txt",
    "the mod has no MadCards, no .mobj/.sobj bundles and no assembly; it would do nothing"
  ],
  "warnings": [],
  "info": {
    "cards": 0,
    "card_files": 1,
    "card_list": [],
    "bundles": [],
    "preview": false
  }
}

Exit code 0.

Wrong Type case, an unknown list name, curly quotes in a .txt card and a notes file, all in one folder (type-mod, built for this page; info.json is a valid info file):

python mpn.py check-mod type-mod
{
  "mod": "type-mod",
  "errors": [
    "hank.txt: Type \"character\" is not a card type the game knows (it logs 'TYPE ERROR! Type Doesn't Exist'); Types are case-sensitive, did you mean Character?"
  ],
  "warnings": [
    "quotes.txt: curly double quotes found; the game straightens them in .txt cards, but other tools will choke, so use straight quotes",
    "chars.json: \"Characters\" is not a card list the game reads; its cards are ignored (did you mean Character?)",
    "notes.txt: no {...} cards found"
  ],
  "info": {
    "name": "Type checks",
    "cards": 3,
    "card_files": 5,
    "card_list": [
      {
        "file": "event.txt",
        "type": "EVENT",
        "filename": "TypeMod_Heal_01",
        "root": null
      },
      {
        "file": "hank.txt",
        "type": "character",
        "filename": "Hank",
        "root": "Hank"
      },
      {
        "file": "quotes.txt",
        "type": "Melee",
        "filename": "M_ModSword",
        "root": "M_SwordLong"
      }
    ],
    "bundles": [],
    "preview": false
  }
}

Exit code 0.

What check-mod cannot see: whether a Filename, Root or field exists in the game, or whether a value makes sense. A Root misspelt with no similar Filename in the mod passes clean:

python mpn.py check-mod root-mod
{
  "mod": "root-mod",
  "errors": [],
  "warnings": [],
  "info": {
    "name": "Tough agent",
    "cards": 1,
    "card_files": 1,
    "card_list": [
      {
        "file": "agent.txt",
        "type": "Character",
        "filename": "Tough_Agent",
        "root": "Nexsu1_Agent"
      }
    ],
    "bundles": [],
    "preview": false
  }
}

Exit code 0.

On Linux and macOS the order of card_list and of the warnings can differ, because it follows the order the folder is listed in (seen in the Linux run of the verification program).

Deploying and enabling

mpn.py deploy <folder> runs the same check, then copies the folder into the game's Mods folder. It refuses when the check has errors or the folder already exists there, unless you pass --force. Examples are on The helper.

Then enable it in the game: main menu, the gear icon at the bottom left, select the mod, tick ENABLED, then Apply. The game saves the list in Mods\enabled.txt as {"Enabled": [folder names]}. Once that file exists, a newly added folder starts disabled until you enable it. Mods are read at launch, so restart the game after changing files. After a session, mpn.py log shows what loaded (see Troubleshooting).

Sharing on the Workshop

There is no uploader inside the game (October 2026). The Modding Resources Master Doc lists two community uploaders: MPN-Workshop-Uploader (now at github.com/placenamehereig/MPN-Workshop-Uploader; its content path must contain a dot, so name the folder like MyMod.content) and Soup-o-Stat's GUI Workshop Uploader. You run the upload with your own Steam account; both go through the Steam client you are logged into. For an upload a script or agent can run, main has examples/WorkshopUpload (arguments, no prompts). Set "Required Items" on the Workshop page for every dependency.

Clone this wiki locally