Modding Guide
Code Tycoon loads mods from a local folder and from subscribed Steam Workshop items at startup. A mod can change or add game content (programming languages, contract templates, frameworks), override translations, and replace entire UI panels — all data-driven through JSON files, no code required.
Quick start
- Open the mods folder (created automatically on first launch):
~/.code-tycoon/mods/(Windows:C:\Users\<name>\.code-tycoon\mods\) — in general, themods/folder next to the game'ssaves/directory - Create a subfolder, e.g.
mods/my-first-mod/ - Add the manifest
mod.json(see below) - Put content next to it and start the game — the boot screen shows
LOAD MODS ... 1
my-first-mod/
mod.json required: manifest
data/ optional: balancing content (merged key-wise)
languages.json
project_templates.json
frameworks.json
locales/ optional: translations (merged key-wise)
en.json
de.json
ui/ optional: panel layouts (replace the original wholesale)
desktop.ui.json
mod.json
{
"id": "my-first-mod",
"name": "My First Mod",
"version": "1.0",
"loadOrder": 0
}
| Field | Required | Meaning |
|---|---|---|
| id | yes | Unique identifier, only a-z A-Z 0-9 _ -. Stamped into savegames — never change it after release! |
| name | no | Display name (default: id) |
| version | no | Free-text version, stamped into savegames |
| loadOrder | no | Load order, lower loads earlier (default: 0) |
Rules: load order, conflicts, errors
- Order: local mods load first (sorted by
loadOrder, then folder name), Workshop mods after them. Later overrides earlier. - Duplicate ids: the first loaded mod wins — local mods beat same-id Workshop mods.
- Broken mods never kill the game start. Invalid files are skipped, logged (
game.login the game directory) and reported in-game as a warning. - Savegames record the active mod list. Loading a save without those mods shows a warning.
- Steam: while mods are active, Steam achievements and leaderboard uploads are disabled. In-game achievements keep working.
Modding content: data/
The three files in data/ are merged key-wise over the base
data (the originals live in resources/data/ inside the game
installation — use them as a reference for all default values):
- Patch a known id: specify only the fields you change; the rest stays.
- Add a new id: the entry must be complete (all fields).
- Deleting is not possible — entries can only be rebalanced (e.g. an absurd
unlockCost).
languages.json — programming languages
{
"assembler": { "unlockCost": 250 },
"zig": {
"name": "Zig", "symbol": "Zg", "level": 0, "xp": 0,
"unlocked": false, "unlockCost": 9000,
"releaseYear": 2016, "devSpeed": 1.0, "bugProb": 0.1,
"learnDifficulty": 7, "maxComplexity": 90, "priceMult": 1.9
}
}
| Field | Meaning |
|---|---|
| name | Display name (a proper name, not a locale key) |
| symbol | Short tag on the language card (2–3 ASCII characters) |
| level, xp | Starting values for a new game (usually 0/0; starter languages 1/0) |
| unlocked | Unlocked from the start? |
| unlockCost | Unlock cost |
| releaseYear | Available from this year; also counts toward era goals |
| devSpeed | Development speed multiplier (base data: 0.3–1.4) |
| bugProb | Bug probability 0..1 (lower is better) |
| learnDifficulty | Learning effort 1–10 (higher = slower XP progress) |
| maxComplexity | Highest project complexity the language can handle |
| priceMult | Payment multiplier for contracts in this language |
New languages appear in the Languages panel automatically. For contracts
to use them, at least one template must list them in
compatibleLanguages.
project_templates.json — contract templates
A JSON list, matched by id. Field names must match exactly —
a row with an unknown field name or missing required fields is skipped and
reported in-game as a warning.
[
{ "id": "tpl_website", "basePayment": 2000 },
{ "id": "tpl_gamedev_studio",
"name": "tpl.gamedev_studio.name", "description": "tpl.gamedev_studio.desc",
"projectType": "game",
"minFeatures": 4, "maxFeatures": 8,
"baseDeadlineDays": 40, "deadlinePerFeature": 4,
"basePayment": 8000, "paymentPerFeature": 900,
"earlyCompletionBonus": 500, "lateCompletionPenalty": 400,
"minComplexity": 5, "maxComplexity": 9,
"possibleFeatures": [],
"compatibleLanguages": ["cpp", "csharp", "rust"],
"availableFromYear": 2000,
"minOfficeRank": 2, "maxOfficeRank": 4 }
]
| Field | Meaning |
|---|---|
| name, description | Locale keys (tpl.<x>.name / .desc) — the texts themselves go into locales/ |
| projectType | Domain (web, game, ai, mobile, cloud, security, …) — drives specialization bonuses |
| minFeatures / maxFeatures | Feature range per contract |
| baseDeadlineDays + deadlinePerFeature | Deadline = base + features × perFeature (game days) |
| basePayment + paymentPerFeature | Payment = base + features × perFeature + complexity × 100 |
| earlyCompletionBonus / lateCompletionPenalty | Bonus/penalty for early/late delivery |
| minComplexity / maxComplexity | Complexity range 1–10 |
| compatibleLanguages | Language ids the contract can be built with |
| availableFromYear | Appears on the market from this year |
| minOfficeRank / maxOfficeRank | Office tiers 0–4 (garage → headquarters) |
frameworks.json — frameworks
[
{ "id": "react", "speedBonus": 0.2 },
{ "id": "phpolygon", "languageId": "php", "name": "PHPolygon",
"year": 2025, "speedBonus": 0.2, "unlockCost": 5000,
"popularityPeak": 2030, "popularityDecline": 10 }
]
| Field | Meaning |
|---|---|
| languageId | Which language the framework belongs to |
| year | Available from |
| speedBonus | Development bonus (base data: 0.05–0.20), scales with experience and popularity |
| unlockCost | Unlock cost |
| popularityPeak | Year of peak popularity (full bonus) |
| popularityDecline | Years after the peak until the bonus starts decaying ~5%/year (floor: 30%) |
Modding translations: locales/
locales/<language>.json merges key-wise over the game's
translation — same structure as resources/locales/*.json in
the game. Use it to add texts for your own content (a new template needs
its tpl.* keys) or to override any existing game text.
{
"tpl": {
"gamedev_studio": {
"name": "Game Studio Contract",
"desc": "A AAA studio needs reinforcements."
}
}
}
- Placeholders in the
:name/:countformat must be preserved. - Untranslated keys fall back to English — for a small mod a single
en.jsonis enough. - Supported are the 31 game locales (
en,de,fr, …,zh-CN,zh-TW). - ASCII-friendly symbols only in UI texts — the engine does not render symbol glyphs (★ ✓ ⚠ become empty boxes).
Modding UI: ui/
ui/<panel>.ui.json replaces the same-named layout from
the game's resources/ui/ wholesale (no merge; with several
mods, the last one wins). Best practice: copy the original file and modify
it. Layouts are declarative widget trees with
{"$bind": "..."} data bindings and
{"$on": {...}} events — bindings and event names must
be preserved; layout, sizes, colors and static texts are yours to change.
The design space is 1280×720. Good for rearranging/reskinning existing
panels; new panels with new logic are not possible.
Steam Workshop
- Subscribing (players): subscribe to an item in the Workshop → Steam downloads it to
steamapps/workshop/content/4347780/<publishedfileid>/→ on the next game start it loads like a local mod. Unsubscribing removes it again. - Publishing (modders): a Workshop item is exactly the mod folder (with
mod.jsonat the top level), uploaded viasteamcmdusing a VDF build config:
// workshop_item.vdf
"workshopitem"
{
"appid" "4347780"
"publishedfileid" "0" // 0 = new item, otherwise the assigned id to update
"contentfolder" "C:\\path\\to\\my-first-mod"
"previewfile" "C:\\path\\to\\preview.png" // JPG/PNG/GIF, max 1 MB
"visibility" "0" // 0 public, 1 friends only, 2 private, 3 unlisted
"title" "My First Mod"
"description" "Adds Zig."
"changenote" "Initial version"
}
steamcmd +login <account> <password> +workshop_build_item C:\path\to\workshop_item.vdf +quit
Field names and order follow Steam's build config; the VDF path in the call
must be absolute. On the first upload Steam prints a
publishedfileid — put it into the VDF to update the same item
later (rather than re-uploading a new one).
What can't be modded (yet)
| Area | Why |
|---|---|
| Deleting entries | The merge can only patch/add; workaround: make it unreachably expensive or availableFromYear: 9999 |
| Skills, eras, events, achievements, offices | Their effects live in game code, not data |
| New panels / new game logic | No scripting/hook API |
| Graphics, sounds, fonts | No resource overlay wired up for these |
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| LOAD MODS ... NONE | Folder in the wrong place (must be mods/<mod>/mod.json) or invalid manifest |
| "Mod could not be loaded" | mod.json missing / not valid JSON / id invalid or duplicate — details in game.log |
| "Invalid data file ignored" | JSON error (commas, quotes — JSON has no comments) or a typo in a field name |
Template shows tpl.x.name | Locale keys missing — add them to locales/en.json |
| Language without contracts | No template lists the language id in compatibleLanguages |
| Boxes instead of special characters | Symbol glyphs are not rendered — use ASCII |
| Steam achievements don't pop | Intentional while mods are active |
Note: list fields (e.g. compatibleLanguages) are replaced
wholesale when patching — include the existing values.