Template Extension
Collapse repetitive content into reusable skeletons with a shared extends / params / overrides DSL
Why templates
When a pack ships many near-identical entries - the same milestone reward on every skill, a family of near-identical daily quests - a template captures the shared shape once and each concrete entry fills in only what differs. The Mastery Pack's mastery-point milestone rewards collapsed from 2,386 lines to 6 (measured from its shipped MasteryPointMilestones template).
This DSL covers the older raw-payload types only
"Parent": "<id>" inheritance instead, plus a generator file where a whole family is written from one table. Mastery tracks work the same way: an Abstract base named as Parent, and a whole per-skill family from one MasteryGenerators file. See Reuse on the shared types.The DSL
Every resolver runs the same pipeline:
- Deep-clone the template payload (the cached template is never mutated).
- {{paramName}} substitution - every string value is walked and
{{key}}tokens are replaced with the matchingparamsvalue. An empty param drops the holding key entirely, so an entry can opt out of an optional template field by passing"". - Field overlay - every top-level field on the concrete entry except the reserved keys (
extends,params, the overrides/extras keys) wins over the template. - Overrides - for each id in the overrides map, find the matching entry in the primary array (by post-substitution
id) and deep-merge: object keys merge recursively; primitives and arrays replace wholesale. - Extras - append wholly new entries. A new id must NOT collide with a template id (use overrides to modify existing entries).
{{key}} in the resolved JSON (never silently dropped), so content typos are visible during validation. Unknown template ids log a warning and the entry is dropped. Template id lookup is case-insensitive.Per-type field names
| Type | Template store | Overrides | Extras | Array |
|---|---|---|---|---|
| CommandReward | CommandRewardTemplates/ | levelOverrides | extraLevels | level map |
| Quest (older shape only) | QuestTemplates/ | objectiveOverrides | extraObjectives | objectives |
| Achievement (older shape only) | AchievementTemplates/ | criterionOverrides | extraCriteria | criteria |
The Quest and Achievement rows serve the older Server/MMOSkillTree/Quests and .../Achievements shape, which the mod still loads (a quest converted into the shared shape at every start, an achievement through its compat reader). A shared-schema quest or achievement reuses through native Parent plus a generator file, not this DSL.
ShopTemplates, MasteryTemplates and ClassTemplates are retired
Parent and a whole family of offers is written from one Server/ZiggfreedCommon/ShopEntryGenerators/ file; the old Server/MMOSkillTree/ShopTemplates/ store, its extends/params resolver and the forEachSkill fan-out flag are gone - see Offer generators. Mastery tracks (and the dormant Class System) left the DSL the same way: a track is a structured file whose shared skeleton is an Abstract base named as Parent, and a whole per-skill family is one MasteryGenerators file. A pack still shipping MasteryTemplates/ or ClassTemplates/ files gets a start-up warning naming each one, and nothing is folded; the Migration Converter and /mmomigrate both point at the replacement.CommandReward templates & {{ALL_SKILLS}}
A CommandReward template is a level → rewards map (the shape a single skill block carries). A CommandRewards payload then references it per skill - or uses the {{ALL_SKILLS}} sentinel as a top-level key to fan the template out to every known skill that isn't otherwise explicitly listed:
{
"Name": "MasteryPointMilestones",
"Payload": {
"15": [ { "command": "/mmocurrency give --player={player} --currency=mastery_point --amount=1" } ],
"30": [ { "command": "/mmocurrency give --player={player} --currency=mastery_point --amount=1" } ]
}
}
// CommandRewards/MyPack.json - fan to every skill in six lines
{
"Name": "MyPack",
"Payload": { "{{ALL_SKILLS}}": { "extends": "mastery_point_milestones" } }
}The sentinel only fans to skill keys - the special TOTAL and GLOBAL_SKILL keys are never touched, and an explicit per-skill entry always wins over the fan-out. Per-skill blocks can also use levelOverrides (replace a level's rewards) and extraLevels (add new levels).
Quest & achievement templates (older shape)
{
"Name": "Pack_Daily_Kill_Goblin_T1",
"Payload": {
"extends": "daily_kill_template",
"params": { "id": "pack_daily_kill_goblin_t1", "displayName": "Goblin Hunter I",
"mobId": "Mob_Goblin_T1", "count": "10", "reward": "5" },
"objectiveOverrides": { "kill_target": { "displayText": "Slay 10 Goblins" } }
}
}The easiest way to replace a template's rewards is to supply a fresh rewards: [ … ] at the top level (it overlays the whole array).
Class System - unreleased
Server/MMOSkillTree/Classes/ file reusing through native Parent, the same paradigm as mastery tracks; the old ClassTemplates/ store is retired alongside MasteryTemplates/.Conversations don't use this DSL either
"Parent": "<id>" inheritance (a per-screen keyed merge), not extends/params. A reusable base is an ordinary conversation file marked "Abstract": true. See Dialogue authoring."<Type>Templates": "add" key (e.g. "QuestTemplates": "add") so the templates load before the content that extends them. Then zip the pack folder with the manifest at the root and drop the .zip in your mods directory - see Building the zip.