Modding Guide
This is the web copy of the modding guide that ships with the game (_internal/workshop_uploader/README.md in the install folder). The web copy tracks the latest build.
Publishes Terminus mods to Steam Workshop.
Requirements
- Steam client running and signed in.
Usage
- Run
WorkshopUploader.exewith the Steam client running. - Browse... to a mod folder containing
manifest.json. - Choose visibility and click Upload to Workshop.
- On first upload,
workshop_item.jsonis written inside the mod folder. Re-running with the same folder updates that item.
Example mods
examples/survival_pack/ demonstrates the content and language patch types, plus the images patch declaration:
crafting_recipesadding alternate crafting paths for existing items (the patch entry sets"mode": "append"), with alternate components, a tool requirement, and a built-in sound cue (thesoundfield names an existing game sound; audio files can't be bundled)cooking_recipeswith a fire-based recipefoodsfor the cooking resultweaponsfor per-weapon balance overridesstructuresfor placeable item overrides (rain barrel capacity, snare trap catch chance)language_patchesfor English and Koreanimages/for sprites your mod adds or replaces (empty here; add.pngfiles matching the game's sprite paths). Mod images take priority over the game's own: a file at an existing sprite path replaces that base-game sprite (a retexture), and a file at a new path supplies art for mod-added content. When several enabled mods ship the same path, the one later in load order wins, like data patches. The folder is picked up automatically when it exists - the manifestimagesblock is only needed to changebase_path
Each json_patches entry takes a target and a file. Keyed targets like foods and weapons add new keys and merge into matching ones field by field: only the fields you list are overridden and the rest of the entry is kept, so a patch like {"A1": {"fuel_capacity": 120}} on vehicle_stats keeps A1's other stats. Fields whose value is a list are replaced whole, not merged. The recipe lists (crafting_recipes, cooking_recipes) merge by result: an entry whose result matches a built-in recipe overrides only the fields you list and keeps the rest, so you can retune work_amount, swap the tool, or change a recipe's component order without restating the whole recipe. A new result is added at the end and must be a complete recipe. To add a second recipe for an existing result instead of overriding it (an alternate crafting path), set "mode": "append" on that patch entry. The other list targets (vehicles, npc) always append.
examples/italian_pack/ and examples/ukrainian_pack/ add new languages to the language menu.
examples/traits_pack/ adds custom character traits with gameplay effects. See Custom traits.
examples/perks_pack/ adds custom survival perks (a stackable level-up perk and a level-scaling occupation perk). See Custom perks.
examples/occupations_pack/ adds a custom occupation with its own unique perk, and rebalances a base one. See Custom occupations.
examples/config_example/ uses the config target for global balance overrides:
trait_point_override(number): fixes the trait point budget to a set value (a large value for effectively unlimited,0for none). Omit to keep the default level-based budget.max_traits(integer 0-30, default 4): trait slots selectable during character creation. Out-of-range or non-integer values are ignored and the default kept.stat_max(number, default5): the ceiling for each character stat (Strength, Health, Observation, Combat, Agility, Dexterity). Raise it (e.g.8) to let players, companions, and NPCs train stats higher; the character-creation and companion+buttons, the stat-raising thick book, theall_aroundtrait, and thegrant.stattrait hook all respect the new ceiling, and the stat bars keep their width, packing the pips tighter to fit. Must be an integer from 1 to 10; out-of-range values are ignored and the default5is kept. Most stat effects scale linearly with the value, so a high cap makes stats much stronger. Observation is the exception: daytime sight tops out around 6 tiles regardless, while night sight grows by 0.5 for every 5 Observation, so raising the cap mainly extends night vision. Stats trained above a later-lowered cap (for example after disabling the mod) are preserved rather than erased and stay effective; they just can't be raised further until the cap is raised again.melee_condition_loss_min(number, default0.1): floor for per-hit melee condition loss after durability dampening. Lower it (e.g.0.001) so weapons with lowcondition_loss_combatcan wear far more slowly, or0to allow fully disabling combat wear.condition_loss_mult(object, default{"head": 1.5, "body": 1, "legs": 1}): multiplier on a melee weapon's per-hit condition loss, by the body part struck. It applies only to hits against zombies, not human NPCs. Head hits wear weapons 50% faster by default. Set a part to1to remove its extra wear (e.g.{"head": 1}disables the head penalty), or0so hits there cost no condition. Parts you omit keep their default multiplier.
Include only the patch types you need. See each example's manifest.json for the format.
game_version_min (optional) in manifest.json is the lowest game build that will load the mod; older games skip it with a log message. It takes a dotted version number and accepts the full 4-part build (e.g. "1.3.1.33", as printed in the Game build: line of game_log.log on each launch), so a mod can require the specific patch build that introduced the field it uses. Shorter forms like "1.3.1" match any build of that version. Versions compare numerically per segment, not alphabetically.
Disabling or unsubscribing a mod does not break existing saves. A save that contains items or zombies added by a removed mod still loads; those objects are removed from the save on load (game_log.log records one line with the removed count). To keep them, re-enable the mod before loading the save again.
Manifest
manifest.json requires schema_version (the integer 1), id (alphanumerics and underscore, 64 characters or fewer - also the log's name for your mod), and a string name of 128 characters or fewer. author, version, description and game_version_min are optional; description, when present, must be a string of 1000 characters or fewer (the mod manager renders both name and description as-is). A json_patches entry missing target or file (both must be strings), an unknown target, a patch list or entry of the wrong shape (null included), a non-string language / file in language_patches, or an images block that is not an object with a string base_path, rejects the whole mod at discovery, before anything is applied. A base_path pointing outside the mod folder is ignored at load - the images folder is simply not registered.
A patch file that fails to parse (a JSON syntax error, or a file that is not UTF-8 JSON) skips just that patch with a log line while the rest of the mod still applies, the same as a file over the security caps (10 MB, nesting depth 20, strings 10K characters). json_patches, language_patches and font_info files must carry the .json extension; any other extension skips the patch with a log line. Valid JSON with bad values degrades entry by entry as described under Data reference.
Mods are discovered through Steam Workshop subscriptions only - local folders are not scanned. To test a mod before release, upload it with Private visibility and subscribe to it yourself. Mods apply in a deterministic order - the order the mod manager lists them in - but you cannot control where your mod lands relative to another author's, so do not rely on your patch landing before or after another mod's. References the loader resolves only after every mod has loaded are the exception - occupation references to perks, traits and items; cooking recipe results and ingredient names against the foods table; beverage content against liquids; rain collector water_type; vehicle stats keys; zombie drops item names - but class and entry registration itself applies strictly in order, including within one mod: json_patches run top to bottom, so a zombie_stats patch only touches zombies registered when it runs (declare it after your zombies patch).
Data reference
Every data target below has a matching vanilla file in _internal/resources/json/ inside the game's install folder (right-click the game in Steam → Manage → Browse local files; the game ships as a onedir build, so everything but the executable lives under _internal/). Those files are what the game itself loads, so each one is a complete, current example of every field a target accepts - the fastest way to write a patch is to copy an entry out of the vanilla file and change the fields you care about. The loader validates the rules listed below and drops or reverts whatever breaks them, logging the drops (see Debugging your mod for how to read the log and the few silent cases). Fields outside these rules are not checked at load, but the game still reads them later, so give a new entry the same fields as a vanilla entry of the same kind rather than the bare minimum.
How patches merge is described under Example mods: keyed targets merge field by field, the recipe lists merge by result, and vehicles / npc append.
Item targets
One item class per entry, keyed by internal name. Patching an existing name updates that item's fields. A new name creates a new item and must include "parents", a list of base class names - the parents each vanilla file uses are listed below, and the registry also accepts the broader bases Weapon, RangedWeapon, KindleTool, Med, Clothing, BaseWearable and Bedding. A new item's name (the JSON key) follows the occupation rule - alphanumerics and underscore only, 64 characters or fewer - because the name goes straight into the item/<name> image paths; an entry breaking it is skipped. class_id (optional) overrides the generated class name; neither it nor the name generated without it may contain a dot - a dotted class name would make every save fail once such an item exists in the world, so the entry is skipped instead. Keys starting with __ are ignored.
| target | vanilla file (resources/json/) |
covers | parents used by vanilla entries |
|---|---|---|---|
weapons |
weapons.json |
melee weapons, bows, firearms, arrows, gun accessories | MeleeWeapon, BaseKnifeSpear, Archery, Arrow, Firearm, GunAccessory, Suppressor |
tools |
tools.json |
tools, lighters and fire starters, fishing rods, siphon pumps | Tool, MeleeTool, FixElecTool, Needle, BaseLighter, FireStarter, BaseFishingRod, BaseSiphonPump |
medicines |
medicines.json |
medicine | InstantMed, PersistMed, CureMed |
clothing |
clothing.json |
clothes, gloves, bags, bedding | BaseClothing, BaseGloves, BaseBag, BasePillow, BaseBlanket |
other_items |
other_items.json |
materials, valuables, repair kits, dead animals, keepsakes | Other, OtherMelee, RepairItem, BaseRepairKit, DeadAnimal, LostItem, QuestItem |
structures |
structures.json |
placeables: traps, rain collectors, tent, portable generator | AnimalTrap, FishTrap, BaseRainCollector, BaseTent, BasePortableGenerator |
Validated on every item patch (a field that fails is ignored and the existing value kept):
init_condition_range,init_durability_range,condition_loss_combat,durability_range:[min, max]with two numberscondition_loss_craft,charge_use: a number >= 0capacity: a number > 0name: a string of alphanumerics and underscore, 64 characters or fewer (renaming redirects theitem/<name>image lookups too)ailments(medicines): a non-empty list of ailment names; a new entry descending fromCureMedmust include onevalue,weight,rarity,ap_cost,condition,charge,broken,remaining,base_damage,reach,damage,repair_speed_mult,strength,persist: numbers >= 0 when a patch sets them, whether or not the entry touchesfuncs- the spawn and drop rolls comparevalueon every item, the melee value formula multipliesbase_damagebyreach, and the action panel comparesap_costevery framefuncs/action_btns: lists of the game's action names. A name is dropped when the class does not carry what that action's flow reads -turnonneeds the light machinery (is_on/broken/charge/charge_use),break_furniture/forceopen/reinforce/cutchainlinkneedap_cost,smeargutsap_costandremaining,fixcarcondition(its repair popup reads it even without a panel button),kindleaconsumemethod (anyKindleToolline has one),mend/firestart/fixelec/fishtheir base class machinery,cookis_cooking_tool: true. Anaction_btnsname additionally needscondition(the panel button's tooltip reads it), and a new entry'saction_btnsdefaults to itsfuncsthrough that same check - a tool withoutconditionkeeps its right-click action but gets no panel button, like the vanilla lockpick. So listing a real action on an unrelated parent (saytillon a medicine) degrades at load instead of crashing the action panel; a parent that already declares the action passes its instance-held attributes- a new entry with a missing or unknown
parentsis skipped, as is a key that resolves to something that is not a registered item (such asweaponoritem)
weapons fields are described under Tuning weapons and structures fields under Tuning placeable structures. A new item must still define value, weight, rarity and loctypes itself (their presence is not checked, but they are read on every item during world generation and trade) - copy a vanilla entry to see the full set. Every new item also needs two images under the mod's images/ folder: item/<name>.png (inventory) and item/<name>_floor.png (drawn when the item lies on the ground).
Table targets
| target | vanilla file | covers | load-time rules |
|---|---|---|---|
foods |
foods.json |
food used by cooking and spawns | entries must be objects; rarity / weight / decay_rate numbers >= 0; keys that collide with computed properties (satiety, value, ...) are dropped - use the base_ fields the vanilla file uses; weight without rarity is dropped (the food stays out of random spawns) |
beverages |
beverages.json |
drink containers (bottles, cans) | a new entry needs all of content / capacity / weight / material / reusable / rarity; capacity > 0; content must name a liquids entry or the entry is dropped (a vanilla entry broken by a patch reverts instead) |
liquids |
liquids.json |
what beverages contain | entry must be an object with a numeric value; broken vanilla entries revert |
seafood |
seafood.json |
fish and other catches | none (merged field by field) |
cooking_recipes |
cooking_recipes.json |
cooking screen recipes | merges by result, which must be a foods name or clean_water / salt; ingredient groups are lists of food-name alternatives, or one ["water" or "clean_water", amount] group per recipe; capacity an integer >= 1, and above 1 when the recipe takes poured water; clean_water / salt results take only water kinds and salt as ingredients; a recipe left with no valid required ingredients is dropped |
crafting_recipes |
crafting_recipes.json |
crafting screen recipes | merges by result (a string); components is a list of groups, each a list of [name, count] alternatives (a single pair may stand alone); tool a string or null |
recipes_any |
recipes_any.json |
the item groups behind any: ingredient tokens and recipe tools (book, knife, ...) |
none; a list you patch replaces the vanilla list whole |
upgrade |
upgrade.json |
upgrade material costs (generators, antigen extractor, ...) | none; lists replace whole |
vehicles |
vehicles.json |
the car pool locations spawn from | appends; entries need string name / image_name / stats, other keys are stripped; an entry whose stats key is missing or invalid is dropped |
vehicle_stats |
vehicle_stats.json |
stat blocks vehicles point at | fuel_economy / fuel_capacity / capacity finite numbers, length an integer 2-9; broken vanilla stats revert |
boats |
boats.json |
boat stat blocks | none (merged field by field) |
npc |
npc.json |
human NPC appearance and loadout pool | appends; no field validation |
npc_stats |
npc_stats.json |
combat stats per NPC role | none (merged field by field) |
furniture |
furniture.json |
furniture the map generator places | none (merged field by field) |
furniture_stats |
furniture_stats.json |
stat blocks furniture points at (capacity, item spawn rules, dismantle materials) | none (merged field by field) |
objects |
objects.json |
large interactive world objects (control panels, ...) | none (merged field by field) |
Zombie targets
zombies(zombies.json): one zombie per key. A new zombie must define at leastsexand a stringname, and once every mod has loaded it must carry each attribute the game guarantees on vanilla zombies:armor,bottom,difficulty,fire_fx,flags,flags_display_str,flags_display_weak,image,max_action,max_attack,max_hp,min_hp,target_parts,top_image,vision(drops/no_dropdefault to empty and follow thezombie_dropsfield rules). A zombie still missing any of them is unregistered with a log line. The attributes may also come from the same mod'szombie_statspatch, as long as it is declared after thezombiespatch - patches apply in order, andzombie_statsonly touches zombies registered when it runs.zombie_stats(zombie_stats.json): keyed by the zombie'sname, not the class key.max_attack/max_action/vision/difficultyare numbers >= 0.difficultyalso sets the default death drop's value window (difficulty x 2.9up to12 + difficulty x 3, minus the zombie'sno_dropnames): only items lighter than 0.5, non-perishable, non-clothing and carrying a spawnrarityare eligible, and vanilla eligible values top out at 24.7 (potent_vitamins), so oncedifficulty x 2.9passes 24.7 - difficulty above roughly 8.5 - the window holds nothing unless a mod also supplies eligible items. A zombie whose window no loaded item can land in - whether fromdifficulty, fromno_dropcovering every name in the window, or from itemvaluerebalances - never yields the default drop; the loader logs a line after all mods load when no fixed-value item fits the window (items whose value varies at runtime, such as charge-scaled sights or foods, are not counted, so a logged zombie may still drop through one of those).max_hp/min_hp/armorare objects of numbers >= 0 per body part; together with what the class already has they must coverhead/body/legs(a partial object is fine on an existing zombie - it merges part by part).zombie_drops(zombie_drops.json): keyed byname.dropsis a list of{"name": ..., "weight": ...}objects - a weighted pool rolled against the default drop (namea string,weighta finite number >= 0; a list violating this is ignored whole, as is a non-object entry).no_dropis a list of item names excluded from the default random drop; it is compared against runtime item names, and a beverage's runtime name is its content, so to exclude a drink list itsliquidskey, not thebeverageskey. Excluding every name inside the zombie's value window leaves its default drop with nothing to yield (logged - seezombie_stats). A patch that sets only one of the two keys keeps the other as it is. Adropsname the game cannot create on the spot (an unknown name, the fortified-house keepsakes,salt/pepper- the shakers only resolve through the starting-item path) is removed after all mods load, with a log line. The same field rules apply whendrops/no_dropare set through thezombiestarget.
Code-defined targets
traits, perks, occupations and config have no vanilla JSON file - they are defined in code and documented in their own sections: Custom traits, Custom perks, Custom occupations, and the config fields under Example mods. Languages go through language_patches, not json_patches - see Adding a new language.
Debugging your mod
Nearly every entry or field the loader rejects is written with its reason to game_log.log, next to the game's executable in the install folder. The log appends across launches; at each launch a file over 5 MB is emptied before logging starts (it never resets mid-session). Read it bottom-up right after a launch. Each enabled mod leaves a line:
Mod loaded: Miku Expansion (miku_expansion)
Mod load failed: Broken Pack (broken_pack) - <what raised>
Mod load complete: 2/3
and WARNING lines name whatever was dropped or reverted, for example:
WARNING - Mod beverage 'x_cola': new entry missing ['reusable', 'rarity']; dropped (a beverage reads these from the table on every use)
WARNING - Cooking recipe result 'iron_sword' is not a food name; dropped
WARNING - Mod vehicle_stats O15.length: invalid 1, reverted to vanilla 3
A mod can "load" while individual fields or entries were rejected: the in-game mod manager only marks a mod that failed outright, so the log is the only place a partial rejection shows up. When a patch seems to have no effect in game, search the log for the entry name you patched before concluding the field does nothing.
A few rejections leave no log line, so check these by hand when something silently does nothing:
- a
zombie_statsorzombie_dropskey that matches no registered zombie'snameis ignored (a typo there just does nothing) - a
no_dropname that matches no item excludes nothing, silently (unresolvabledropsnames, by contrast, are removed at load with a log line) - an occupation
identify_locsname outside the location types is kept but never matches anything (the loader checks its type only) - an effect
whenkey that its hook does not provide keeps the effect from ever applying, and the only trace is below the log's level - see Conditions
To then test values in a live game, see Debug console.
Adding a new language
To add a language not already in the game, the mod must provide both Data.json and FontInfo.json for that language; otherwise the patch is rejected at load (it would crash on selection).
{
"content": {
"language_patches": [
{
"language": "Italian",
"file": "lang/Italian/Data.json",
"font_info": "lang/Italian/FontInfo.json"
}
]
}
}
FontInfo.json must contain Normal and Bold (Italic is optional):
{
"Normal": "Roboto-Medium.ttf",
"Bold": "Roboto-Bold.ttf",
"Italic": "Roboto-MediumItalic.ttf"
}
Font files are resolved against the directory holding FontInfo.json first, then the game's bundled languages/Fonts/ directory. To reuse a bundled font (Roboto covers most Latin/Cyrillic; NotoSansCJKjp covers CJK), just reference its filename. To ship your own font, drop the .ttf/.otf next to FontInfo.json and reference it by basename. Font filenames must be plain basenames (no path separators), capped at 30 MB.
To extend an existing language (add or override strings), omit font_info and use the existing language name:
{ "language": "Korean", "file": "lang/Korean/Data.json" }
Data.json is one flat object of string values, like the game's own language files (trait_lucky and trait_lucky_desc are two separate keys). A non-string value - for example an object nested under a category key - is dropped at load with a log line naming the dropped keys, and the rest of the file still merges.
Tuning weapons
The weapons target keys any weapon by internal name (e.g., aluminum_bat, metal_knife, machete). The fields below tune melee weapons' spawn condition, durability, and combat wear; ranged weapons such as bows and firearms do not use them. Tools such as fire_axe and crowbar are not weapons, so tune those through the tools target instead.
init_condition_range([min, max], default[40, 100]): spawn-time condition (%) range. Condition is the wear meter that depletes with use and reaches 0 when the weapon breaks.init_durability_range([min, max], default[1, 5]): spawn-time durability tier range. Higher durability increases damage output and reduces condition loss per hit.condition_loss_combat([min, max], default[6, 8]): per-hit condition loss range during combat. The subtracted value is dampened by durability and reduced further by perks (clean attack, rescue expertise).
Example: aluminum bats spawn with at least 80% condition and high durability (4-5), and wear at about half the usual combat rate.
{
"aluminum_bat": {
"init_condition_range": [80, 100],
"init_durability_range": [4, 5],
"condition_loss_combat": [3, 4]
}
}
See examples/survival_pack/data/weapons.json for a working override.
Tuning placeable structures
The structures target keys any placeable (deployed-on-the-ground) item by internal name:
- Animal traps:
makeshift_animal_trap,rat_trap,snare_trap,bird_trap,box_trap,cage_trap - Fish traps:
makeshift_fish_trap,basket_fish_trap,net_fish_trap - Rain collectors:
makeshift_rain_collector,rain_barrel - Others:
tent,portable_generator
Patching an existing name overrides only the fields you list, like weapons. Common fields: weight, fuel (value as campfire fuel), rarity (spawn odds in furniture) and loctypes (location types it can spawn in; omitted or empty allows all), scraps (dismantle yield, e.g. [["stick", 1]]), and base_value (trade value at 100% condition; tent uses a flat value instead). Exception: the portable generator computes weight and value from base_weight / base_value plus carried fuel, so tune those base_ fields there - setting weight or value directly replaces the fuel-aware calculation.
Animal traps
base_chance(number): catch chance per turn in percent, before situational multipliers (time since placed, bait, the trapper trait, nearby fire / traps / humans).target(list): species it can catch, fromrat,squirrel,rabbit,bird. Indoors onlyratcan be caught, outdoors everything butrat; a trap with no valid species for where it stands never triggers.condition_loss(number): condition lost per catch, from 100. A catch that brings it to 0 destroys the trap.can_be_placed(string):"outside"restricts placement to outdoor tiles; omit to allow indoor placement.
Fish traps
base_chance(number): catch chance per turn in percent. Each failed roll banks an eighth of the current chance as a bonus for later turns; the bonus resets on a catch.capacity(number): how many catch stacks it holds; a catch that would exceed it is lost.size(1-3): admits seafood up to this size (each entry inseafood.jsonhas asize).durability(number): dampens per-turn wear by1 - durability / 13. Wear is higher while holding live fish or when placed in the sea. When the trap breaks, itsscrapswash up on the adjacent shore tile.
Rain collectors
capacity(number): stored water cap, in the same amount units beverage capacities use.rain_collect_rate(number): amount gained per turn while it rains or snows on a weather-exposed tile (same units ascapacity).condition_loss(number): condition lost every turn, rain or not, dampened by1 - durability / 13(durability is 0 when player-crafted, up to 5 on world-spawned collectors).init_condition_range([min, max]): spawn-time condition (%) range.water_type(string): what filling a bottle from it yields, defaultdirty_water.
Tent
init_condition_range/init_durability_range([min, max]): spawn-time condition (%) and durability tier ranges.
Portable generator
base_fuel_capacity(number): tank size before upgrades (each capacity upgrade adds 20%).base_fuel_consumption_rate(number): fuel burned per powered hour before upgrades (each upgrade cuts 15%). Upgrade costs live in theupgradetarget under theportable_generatorkey.base_weight/base_value(number): weight and value when empty; carried fuel adds to both.- The patch covers both the inventory item and generators found in the world (placed generators read their base stats from this entry).
Example: the rain barrel holds more and fills faster, and snare traps catch more per condition:
{
"rain_barrel": {
"capacity": 8000,
"rain_collect_rate": 16
},
"snare_trap": {
"base_chance": 7,
"condition_loss": 10
}
}
New entries can also be added with parents (one of AnimalTrap, FishTrap, BaseRainCollector, BaseTent, BasePortableGenerator), the same way new weapons work. A new placeable needs an inventory icon (item/<name>.png) and a ground sprite (item/<name>_floor.png, drawn when it lies on the floor as an item), plus placed-world sprites: animal traps read world/trap/<name>.png; fish traps read three images, world/trap/<name>.png (placement preview), world/trap/<name>_placed.png, and world/trap/<name>_placed_hover.png; rain collectors read world/rain_collector/<name>.png; tents and generators reuse the base placed-world sprites (the two item images are still needed). A missing image logs an error and renders as blank instead of crashing. Ship these under the mod's images/ directory matching those paths.
See examples/survival_pack/data/structures.json for a working override.
Custom traits
The traits target adds new character traits, selectable during character creation. Each JSON key is the trait's internal name:
{
"iron_constitution": {
"cost": 11,
"effects": [
{ "hook": "max_hp", "op": "add", "value": 8 },
{ "hook": "max_ap", "op": "add", "value": 4 }
]
}
}
cost(number): trait point price during character creation.comp_trait(bool, defaulttrue): whether the trait shows on the companion customization screen. Setfalseto hide it there. It does not stop auto-generated NPCs or companions from rolling the trait, so it cannot make one truly player-only.effects(array): what the trait does, one or more entries.- Patching an existing trait name (such as
reading_lover) updates its fields, so you can also rebalance the base game's traits, for example changing theircost.
Every trait needs trait_<name> and trait_<name>_desc strings in a language_patches Data.json, or it shows as an untranslated key in the menu.
Effect format
{ "hook": "damage", "op": "add", "value": 30, "when": { "weapon_type": "melee" } }
hook(string): where the effect applies (see the reference below).op(string): how it applies. Omitting it meansadd, so agate.*hook must spell out"op": "gate"- without it the effect is treated asaddand dropped at load for lacking a numericvalue.add: addvalueinto the total.mul: multiply byvalue(1is no change).gate: switch a behavior on (novalueneeded).grant: give something (recipes, stats, items), at the start of a run or on an event such as an item breaking.set: override a value.value: the magnitude, or a list/object for grants.when(object, optional): conditions that must all hold. See Conditions.
A trait may list several effects, and several traits may target the same hook (they stack).
Hook reference
Stats and survival
| hook | op | effect |
|---|---|---|
max_hp, max_ap |
add | maximum HP / AP, applied at the start of a run |
cond.hp, cond.ap, cond.satiety, cond.hydration, cond.energy, cond.morale |
add | per-turn change to a meter (positive restores, negative drains) |
cond.satiety.drain_mult, cond.energy.drain_mult, cond.morale.drain_mult |
mul | scales the default per-turn drain of a meter (0.5 = drains half as fast) |
cond.sleep.energy_mult |
mul | scales energy recovered while sleeping |
cold_damage_mult |
mul | scales the HP lost per turn while colder than the character's base temperature (0.5 = half). The Park Ranger's Cold Endurance is this hook |
Skills and movement
| hook | op | effect |
|---|---|---|
reading_speed, crafting_speed |
add | speed bonus (0.2 = +20%) |
night_sight |
add | night vision distance |
move_ap |
add | move AP modifier (negative is faster, e.g. -0.1) |
search_extra_items |
add | extra items found when searching |
proficiency.melee, proficiency.ranged |
mul | weapon proficiency multiplier |
carry_weight_offset |
add | weight ignored when carried weight raises move AP (the Construction Worker's Super Strength is this hook). The character panel's weight tooltip shows the total. A negative total is treated as 0 - this hook only ever lightens the load |
Combat
| hook | op | effect |
|---|---|---|
accuracy |
add | hit chance revision in percent. Conditions: weapon_type, attack_part, target_out_of_sight |
damage |
add | damage revision in percent. Conditions: weapon_type, weapon_category, attack_part |
attack.morale |
add | morale change each time the character attacks a zombie or person (once per attack action; negative is a cost, e.g. -1). Breaking doors/windows is exempt. Conditions: weapon_type, attack_part |
drop_chance |
add | added to the zombie drop probability (0-1 scale) before the whole chance is scaled by zombie difficulty - and, for the default random drop, by the matching item category's sandbox setting - so 0.1 is +10 percentage points only at difficulty 0. The final chance is rolled against 0-1, so a total of 1 or more after those multipliers is a guaranteed drop; a sandbox loot setting under 100% can push it back below certainty on low-difficulty zombies (a zombie's custom drops pool skips the sandbox factor) |
butcher.bonus |
add | extra count per butchering result |
dismantle.mult |
mul | scrap yield when dismantling |
cook.ap |
mul | cooking AP cost (0.5 = half) |
counter.chance |
add | chance in percent to counter-attack after dodging a zombie (the Martial Artist's Counter Attack is this hook). Below 100 the roll is halved and then climbs back with each consecutive failed counter, so a low value still lands eventually |
firstaid.heal |
add | HP the firstaid action restores on top of its base 6. The action panel's effect line shows the resulting amount |
firstaid.cooldown |
mul | turns before firstaid can be used again (base 8, rounded) |
item.condition_loss |
mul | condition a melee weapon, tool or melee-capable other_items entry loses per use (after durability dampening and the Clean Attack perk), and a fishing rod's wear per cast. The Firefighter's Rescue Expertise is this hook. It does not reach a bow's or suppressor's per-shot wear, the wear from crafting, cooking or mending with a tool, or clothing. Conditions: item_name, category, usage |
Consumables
| hook | op | effect |
|---|---|---|
food.morale |
mul | morale from food. Conditions: item_name, morale |
beverage.morale |
mul | morale from drinks. Conditions: item_name, item_has_alcohol, morale |
book.morale |
mul | morale from reading a book (bible and novel are the two that give morale). The Believer trait (x2) and the Pastor's Divine Reader (x3) are this hook, both with "item_name": "bible". Conditions: item_name |
book.reread |
add | extra times the same book can be read (the Student's Review is this hook; base is one read, which is also the floor) |
mp3.morale |
mul | morale from music |
cigarette.morale |
mul | morale from smoking |
med.strength |
mul | medicine strength (higher raises headache and faint risk) |
med.effect |
mul | medicine recovery amount |
Actions and production
| hook | op | effect |
|---|---|---|
harvest.mult |
mul | crops a garden yields when harvested (the Farmer's Farm Hands is this hook). A value above 100 is dropped at load, and one harvest yields at most 100 crops however the multipliers stack |
cook.bonus |
mul | a cooking recipe's satiety_bonus / morale_bonus (the Chef's Chef's Dish is this hook) |
cook.duration |
mul | hours a cooked dish's stat bonus lasts (base 6, rounded) |
craft.material_mult |
mul | component counts in every crafting recipe, rounded up (the Engineer's Material Saving is this hook) |
drive.fuel_mult |
mul | fuel a drive consumes (the Driver's Economy Driving is this hook). With any effect present the figure is rounded up to one decimal, and 0 makes driving free |
cleardebris.mult |
mul | debris cleared per AP, by hand or with a shovel |
fixcar.mult |
mul | vehicle damage repaired per AP |
repair.mult |
mul | electronics repaired and clothing mended per AP |
fish.chance |
mul | catch chance per fishing turn |
reinforce.amount |
add | HP added to a door or window when reinforcing (base 20 with a plank, 40 with a metal sheet) |
pray.morale |
add | morale the Pastor's pray action gives each character nearby (base 5) |
preach.duration |
mul | how long the Holy status from preach lasts (base 6 turns) |
kindlespecial.fuel |
add | campfire fuel the Park Ranger's kindlespecial adds (base: one plank's fuel) |
The additive action outputs above (firstaid.heal, reinforce.amount, pray.morale, kindlespecial.fuel) floor their total at 0: a negative value can cancel the base amount but never flips the action into doing the opposite. carry_weight_offset floors the same way, and book.reread floors at the base one read.
On/off behaviors (op is always gate)
| hook | effect |
|---|---|
gate.attack |
cannot attack. The base Pacifist and Frailty traits declare this effect, so a mod that overrides their effects can rebalance them - for example, replace the gate with attack.morale to allow attacks at a morale cost |
gate.alcohol_effect |
immune to drunkenness and fainting from alcohol |
gate.food_disease |
immune to food poisoning |
gate.detectable |
zombies cannot detect you by sound |
gate.sleep_disturb |
noise does not wake you |
gate.sleep_anywhere |
can sleep on any tile |
gate.pickpocket |
can steal during trades |
gate.survive_bite |
a zombie bite applies the zombified status instead of killing on the spot - the status still advances every turn and kills at full progression unless cured |
gate.reveal_contents |
see furniture and corpse contents without searching |
gate.reveal_treasures |
hidden treasures are revealed within sight |
gate.weather_forecast |
the character announces upcoming rain and snow |
gate.firstaid_sprain |
the firstaid action also cures a sprain (the Doctor's First Aider opens this at level 2) |
gate.firstaid_bleeding |
the firstaid action also stops bleeding (First Aider level 3) |
gate.counter_extra |
a successful counter-attack hits a second adjacent zombie (the Martial Artist's Counter Attack level 4) |
Start of run and character creation
| hook | op | effect |
|---|---|---|
grant.learn |
grant | learn recipes. value: a list of recipe names, or "all_crafting" / "all_cooking" |
grant.stat |
grant | raise stats. value: {"dex": 1} (each capped at the stat ceiling stat_max, default 5) or "all" (+1 to every stat) |
grant.unique_perk_lv |
grant | start the occupation's unique perk at this level. value: an integer 1-4 |
companion_count |
add | extra starting companions |
reveal_locations |
add | nearby map locations revealed at the start |
unique_perk_maxlv |
set | maximum unique perk level. value: an integer 1-4 |
levelup_choices |
set | perk choices offered on level up. value: an integer 1-100 |
trust |
mul | multiplier on trust gained from survivors |
on_break_grant |
grant | when a weapon or tool breaks, give items. value: [["scrap_metal", 2]]. Condition: category |
Stat keys for grant.stat, with the in-game name in parentheses: str (Strength), con (Health), obs (Observation), com (Combat), agi (Agility), dex (Dexterity).
Conditions (when)
Every key in a when object must hold for the effect to apply.
| key | values | applies to |
|---|---|---|
time |
"day", "night" |
any hook except the grant.* hooks |
loctype |
a location type, the same names identify_locs takes |
any hook except the grant.* hooks |
inside |
true, false |
any hook except the grant.* hooks |
lv |
2, ">=3" |
any hook except the grant.* hooks |
morale |
">0", "<0", ">=5", "<3" |
food.morale, beverage.morale |
item_name |
a food, drink, book or item name | food.morale, beverage.morale, book.morale, item.condition_loss |
item_has_alcohol |
true, false |
beverage.morale |
weapon_type |
"melee", "ranged" |
accuracy, damage, attack.morale |
weapon_category |
"weapon", "tool", "other" (melee-capable other_items) |
damage |
attack_part |
"head", "body", "legs" |
accuracy, damage, attack.morale |
target_out_of_sight |
true, false |
accuracy |
category |
"weapon", "tool", "other" (melee-capable other_items) |
on_break_grant, item.condition_loss |
usage |
"weapon" (the item was swung at something) or "" |
item.condition_loss |
Any value may also be a list, which matches when the context is one of its entries: "item_name": ["axe", "fire_axe"]. time is the exception: the loader accepts exactly "day" or "night" there, and anything else - a list included - drops that effect with a WARNING line in game_log.log. The check exists because a time value the engine does not know matches neither day nor night, which would leave the effect inert everywhere with nothing in the log to show for it. A list would mean "day or night" anyway, which is what leaving time out already does.
time, loctype, inside and lv work on every hook that reads when at all - that is, everything except the grant.* hooks, which are handed out at the start of a run without consulting conditions - because they do not depend on a call site handing them context. time reads the game clock, so it holds wherever the effect is evaluated. loctype and inside read where the character stands, so a character not yet placed in the world - as during character creation, when the max_hp / max_ap hooks are read - matches neither. lv reads the level of the unique perk carrying the effect - traits and normal perks are always level 1, so "lv" is how a unique perk changes behavior by level rather than just scaling a number (value_table covers scaling). gate effects take when too, which is how the Doctor's First Aider opens its cures at levels 2 and 3.
The remaining conditions only work on the hooks listed for them. Such a key names context that only those hooks supply; on any other hook the check can never pass, so the whole effect never applies anywhere - and the only trace is a debug-level line that does not reach game_log.log (the loader validates when.time only). The morale condition compares the morale the food or drink itself would give, not the character's current morale.
Out-of-range values for unique_perk_maxlv (must be 1-4) and levelup_choices (must be 1-100) are dropped at load time rather than clamped.
Set "is_unique": true and "occu": "<occupation>" (such as chef) to make a trait that characters of that occupation automatically start with, like the base game's occupation-unique traits. A cost you set is still deducted from the character's trait points at creation, so leave cost out (or 0) to make it free like the base game's. A unique trait with no occu falls back to a normal selectable trait.
See examples/traits_pack/ for a working pack.
Custom perks
The perks target adds new survival perks or rebalances existing ones. Perks use the same effect DSL as traits (see Effect format and the Hook reference), plus extra fields for level scaling. Each JSON key is the perk's internal name:
{
"brute_force": {
"max_count": 3,
"bonus": 10,
"effects": [
{ "hook": "damage", "op": "add", "per": "count", "value_per": 10 }
]
},
"close_quarters": {
"is_unique": true,
"occu": "soldier",
"effects": [
{ "hook": "damage", "op": "add", "value_table": { "1": 10, "2": 20, "3": 30, "4": 40 }, "when": { "weapon_type": "melee" } }
]
}
}
There are two kinds of perk:
- Normal perks are offered on level up and can stack up to
max_count(default 1) copies. Setbonus(default 0) to the per-copy number the description shows through{num}(where{num}=bonusx copies, rounded to two decimal places for display). - Unique perks (
"is_unique": truewith an"occu") are free perks a character of that occupation starts with, alongside any base occupation perk. They level up instead of stacking: from 1 to a maximum of 3 by default, or 4 when the character has theelitetrait (or an effect sets theunique_perk_maxlvhook). A unique perk with nooccufalls back to a normal perk.
is_unique, occu, max_count, bonus, and effects are the only recognized perk fields; any other key is dropped at load.
Level scaling
Traits are flat, but perks scale with stack count or level. On an add/mul/set effect:
per("none"default,"count","lv"): selects the scaling index: the number of stacked copies ("count", normal perks) or the perk's level ("lv", unique perks). Onlyvalue_perandvalue_tablescale with it; a flatvalueis a constant base thatpernever multiplies.value_per(number): amount added per unit of the index, on top of the basevalue(final amount =value+value_perx index). For "+10% per stack" use"per": "count", "value_per": 10— putting the 10 invalueinstead gives a flat +10% no matter how many copies stack (the game logs a warning for an inertperat load).value_table(object): maps the unique perk's level directly to a value, e.g.{ "1": 10, "2": 20 }. To index the stack count instead (for a stackable normal perk), add"per": "count"to the same effect; without it a normal perk always reads index 1. The table overridesvalue/value_per, and an index not listed contributes nothing.
A flat value with no scaling also works, exactly like a trait. Exception: the unique_perk_maxlv and levelup_choices hooks accept only a flat value (no per, value_per, or value_table), since their 1-4 / 1-100 ranges are validated on the flat value.
Language and icon
- Normal perks need
perk_<name>andperk_<name>_descstrings. - Unique perks need
perk_<name>andperk_<name>_lv1throughperk_<name>_lv4. The maximum level is 3 by default, but theelitetrait (or aunique_perk_maxlveffect) raises it to 4 — without the_lv4string, elite characters see the raw key in the level-up popup. Avalue_tableshould likewise cover levels up to"4". - Every new perk should ship an icon at
images/perk/<name>.png(the mod'simages/folder is picked up automatically). If it is missing the perk still works, but shows a blank icon in the level-up and character panels (the game logs the missing image and substitutes a 1x1 placeholder rather than crashing). Rebalancing an existing perk needs no new icon.
Patching an existing perk name updates its fields, so you can rebalance base perks, for example adding an effect to skillful_search or changing a max_count.
The base occupation perks
Every base unique perk is declared with the same effects schema your mod uses, so none of them is a black box you can only borrow by name: copy the effect, change the numbers or the when, and you have your own version on a different occupation. The hook each one uses:
| perk | occupation | effect |
|---|---|---|
fighting_instinct |
Soldier | damage +50 per level, "weapon_type": "melee" |
bulls_eye |
Police Officer | accuracy +50 per level, "weapon_type": "ranged" |
rescue_expertise |
Firefighter | item.condition_loss x{1: 0.6, 2: 0.35, 3: 0.15, 4: 0}, "item_name": ["stone_axe", "axe", "fire_axe", "crowbar"] |
farm_hands |
Farmer | harvest.mult x(1 + 0.5 per level) |
cold_endurance |
Park Ranger | cold_damage_mult x{1: 0.5, 2: 0.25, 3: 0.12, 4: 0.05} |
speed_reading |
Student | reading_speed +0.5 per level |
review |
Student | book.reread +1 per level |
super_strength |
Construction Worker | carry_weight_offset +10 per level |
energizer |
Athlete | cond.ap +0.5, and +0.5 per level |
night_watch |
Security Guard | night_sight +1 per level |
chefs_dish |
Chef | cook.bonus x{1: 1.5, 2: 1.75, 3: 2, 4: 2.5}, cook.duration x{3: 2, 4: 3} |
first_aider |
Doctor | gate.firstaid_sprain at "lv": ">=2", gate.firstaid_bleeding at ">=3", firstaid.cooldown x0.5 at ">=4" |
economy_driving |
Driver | drive.fuel_mult x{1: 0.5, 2: 0.3333333333333333, 3: 0.25, 4: 0.2} (fuel divided by 2/3/4/5) |
counter_attack |
Martial Artist | counter.chance +33.4 per level, gate.counter_extra at "lv": ">=4" |
material_saving |
Engineer | craft.material_mult x{1: 0.8, 2: 0.65, 3: 0.5, 4: 0.35} |
The occupation-unique traits work the same way: Pacifist is a gate.attack, and Divine Reader is a book.morale x3 on "item_name": "bible".
So a perk that mirrors the Pastor's church reading for a firefighter in a fire station is the same effect with a different when:
{
"station_reader": {
"is_unique": true,
"occu": "firefighter",
"effects": [
{ "hook": "book.morale", "op": "mul", "value_table": { "1": 2, "2": 3, "3": 4, "4": 5 },
"when": { "loctype": "fire_station", "inside": true } }
]
}
}
What is still fixed in code is the action list: unique_actions only accepts the eight coded buttons (see Custom occupations), and an action's own flow - which tile it targets, what animation it plays, what it costs in AP - is not moddable. What those actions produce mostly is: firstaid reads firstaid.heal / firstaid.cooldown and its two gates, pray reads pray.morale, preach reads preach.duration, kindlespecial reads kindlespecial.fuel, and boost follows from cond.ap since it hands the character next turn's AP regen early. The three with nothing to scale are listen, focusdodge and lift - they reveal, dodge or pick up rather than produce a number. The tool actions read the hooks in Actions and production.
New normal perks also join the level-up pool of existing saves (they are mixed in when the save loads), so a normal-perk mod does not require starting a new game. Unique perks are handed out only when a character is created, so a new unique perk shows up from the next run, not on characters already in a save.
See examples/perks_pack/ for a working pack (add the two images/perk/*.png icons before publishing).
Custom occupations
The occupations target adds an occupation to the character creation screen, or rebalances a base one. Each JSON key is the occupation's internal name:
{
"paramedic": {
"start_stats": { "str": 1, "con": 2, "obs": 2, "agi": 1, "dex": 3, "bonus_points": 3 },
"starting_items": [["bandage", 2], ["weak_medicine", 1]],
"unique_actions": ["firstaid"],
"unique_perks": ["triage"],
"identify_locs": ["pharmacy"]
}
}
start_stats(object): the stats a character of this occupation starts with, plus the points the player distributes freely. Keys arestr(Strength),con(Health),obs(Observation),com(Combat),agi(Agility),dex(Dexterity) andbonus_points; each is an integer from 0 to 10 (bonus_pointsup to 60) and a key you omit is 0. A new occupation must have a validstart_stats- the character creation screen reads it directly, so an occupation without it is not registered (the log says so). On an existing occupation, only the stats you list change and the rest are kept, so{"bonus_points": 13}retunes the Student and nothing else.starting_items(list, optional):[[item name, count], ...]given at the start of a run. Counts are integers from 1 to 100. An item name is anything the game can resolve to an item: a key in thefoodsorbeveragestables,saltorpepper(the shakers), or the internal name of a registered item class - the game's item tables inresources/json(weapons, tools, medicines, clothing, seafood and so on) and classes your own mod adds through an item target such asweaponsorother_items. Names from other data files (furniture, vehicles, NPCs, recipes) are not items. A name the game cannot hand out at the start of a run is dropped at load with a log line, whether it is unknown or an item the game only ever builds in a specific situation:piece_of_map, which is cut from the map that does not exist yet when a run starts, and the fortified house's lost keepsakes, which are built with the resident who owns them. Ordinary loot works, including things found on corpses such asjewelry.unique_actions(list, optional): occupation-only action buttons. Only the base game's occupation actions exist, since each one is a coded action panel button:boost(spend next turn's AP now),listen(hear the whole location),firstaid(heal self or nearby companions),kindlespecial(start a fire without a lighter),lift(lift furniture and crush zombies with it),focusdodge(spend all AP to dodge),pray(restore morale nearby),preach(grant a Holy status effect). Sharing one with a base occupation is fine;firstaidrestores a base 6 HP on its own, and its cures and shorter cooldown come from hooks the Doctor's First Aider perk opens (gate.firstaid_sprain,gate.firstaid_bleeding,firstaid.cooldown), so an occupation that takes the action without that perk - or without a perk of your own declaring those hooks - gets the base heal only. Any other name is dropped at load.unique_perks/unique_traits(list, optional): perks and traits a character of this occupation starts with for free.unique_perksmust name unique perks - a base occupation perk such asfirst_aider, or one your mod declares with"is_unique": trueand an"occu"through theperkstarget. A normal or special perk name is dropped at load: the level system (veteran traits, save files) and theperk_<name>_lv1-_lv3description strings shown in the occupation tooltip and the unlock popup only exist for unique perks.unique_traitsmay be base or mod traits. Unknown names are dropped at load.identify_locs(string or list, optional): location types this occupation knows from the start, like the base Police Officer knowing every police station. Location types are the ones the map generates:church,clothing_store,electronics_store,fire_station,gas_station,grocery_store,gun_shop,hardware_store,library,park,pharmacy,police_station,restaurant, plushouse,fortified_house,marina,military_base,research_center,terminus,checkpoint,danger,helipad,railroad,seasideandtunnel;inside_terminusandplatformare generated only on the Last Escape DLC map. A companion of this occupation reveals them when recruited too. A name outside these types passes loading (only the field's type is checked) and simply never matches, with no log line.
Patching an existing occupation replaces starting_items, unique_actions and identify_locs with what you list. unique_perks and unique_traits are added instead of replaced, because the same lists are also filled by perks and traits that declare an "occu" (see Custom perks) - that way it does not matter which patch the game applies first, and a perk pack and an occupation pack can be shipped together or separately. There is no way to take a unique perk or trait away from an occupation.
A new occupation's name allows only alphanumerics and underscore, up to 64 characters, and cannot be one of the game's achievement ids (a run survived as that occupation would unlock an unrelated Steam achievement). Patches to existing occupation names are not name-checked.
Language and icon
- Every occupation needs
occu_<name>andoccu_<name>_descstrings, plusoccu_loc_info_<name>if it hasidentify_locs(one line describing what it knows, shown in the occupation tooltip). - Ship an icon at
images/newgame/occu_<name>.pngfor the character creation panel (the base icons are 51x51 in a 52x52 cell; the mod'simages/folder is picked up automatically). A missing icon logs an error and renders blank rather than crashing.images/newgame/occu_<name>_gold.pngis the optional variant shown after a player survives a run as that occupation; without it the normal icon stays. images/occu/<name>.pngis a second, smaller icon drawn next to a survivor's name in the Last Escape DLC character list (the base ones are 33x33 and are drawn at half scale, like item images). Ship it too if your mod is meant to be played with the DLC.
How mod occupations behave
- They are selectable from the start: the base occupations unlock three at a time as the player survives runs, but a mod occupation has no such requirement.
- Surviving as one is not counted as a base-game clear, so it does not advance occupation unlocks, does not appear in the unlock popup or the history occupation grid, and grants no achievement. The run's score still counts toward the profile level and the trait points it grants, like any other run.
- The character creation panel holds 15 occupations; beyond that it gains page arrows at the bottom.
- Auto-generated survivors, companions and NPCs can roll a mod occupation, so it also shows up in the world.
- Removing the mod does not break saves: a character keeps the occupation name and the stats it was given. But everything the occupation entry itself defines is gone with the mod, so its strings and icons fall back to placeholders, its unique action button disappears from the action panel, and recruiting a companion of that occupation no longer identifies locations. What the occupation handed out is treated the same as anything else your mod added: perks and traits from your mod are dropped on load (they are resolved by name), and so are items your mod defined through a class target such as
other_items, as with any removed mod's items. Base-game items and perks stay, and so does an item whose name your mod added to thefoodstable, since a food copies its values onto the object as it is created and never reads the table again. Abeveragesentry works the other way around: the object stores only which container it is and reads the table on every use, so bottles whose entry is gone are removed from the save on load along with the rest of the mod's content.
See examples/occupations_pack/ for a working pack (add images/newgame/occu_paramedic.png and images/perk/triage.png before publishing).
Debug console
For testing mods in a live game, launch the game with the --debug option (Steam: game
Properties → Launch Options → add --debug; non-Steam: pass it as a shortcut argument), then
press the backtick key (`) in game to open the debug console. Without the launch option the
console does not exist.
Commands (run help in the console):
debug- enable cheat commands for the current save. Shows a warning and asks you to confirm withy: confirming permanently disables achievements for that save and shows a DEBUG marker on screen. Until then, cheat commands are rejected.spawn <item> [count]- add items to the selected character's inventory (item and zombie ids are the internal names in the game'sresources/json/*.json)zombie [name]- spawn a zombie in the current location (defaultzombie_normal)npc <survivor|bandit|pickpocket|starving_elder>- spawn an NPC in the current locationhp/ap/str/con/obs/com/agi/dex/satiety/hydration/energy/morale/med/alcohol <n>- set a value on the selected characterlevelup- open the perk selection popup for the selected characterafflict <infection|zombified|disease|bleeding|sprain|burn>- apply a status ailment to the selected charactercure [ailment]- remove a status ailment from the selected character (no argument: remove all, including zombification)time <dd:hh:mm>- set the game clock (day 0-999)reveal- reveal and identify every location on the map
Notes:
- Commands target the currently selected character and run only during the player turn.
helpanddebugwork without confirmation; everything else requires thedebugconfirmation once per save.
What gets sent to Steam
The selected folder is uploaded recursively, plus:
- Title from
manifest.name - Description from
manifest.description - Tags from
manifest.typeand eachjson_patches[].target - Visibility chosen via the radio button (Public / Friends Only / Private)
- Preview from
preview.pngorpreview.jpgif present