Item sets, perk choices, and forging
These functions run in your entry script. Declare a dependency on core when using its items, perks, or forge profiles. A handle is the Lua value returned by a lookup or registration function; pass that value directly instead of its name.
sf2.itemsets.register
Section titled “sf2.itemsets.register”Signature: sf2.itemsets.register(definition)
Returns: An item-set handle.
When: During mod loading.
Requires: content.register.
Groups items into a named set. It does not automatically add a set bonus or grant the items to the player.
| Field | Meaning |
|---|---|
id |
Required local identifier, such as training_set. |
title, text, brief |
Required localization handles from sf2.localization.key. |
members |
Required, nonempty array of member tables. |
Each member requires an item handle in item. Optional presentation fields are scale (default 1), rotate, x, y, and icons_y (all default 0). Values must be finite numbers. Each item can appear only once in this set.
local katana = sf2.items.get("core:items/weapon/weapon_katana")local set = sf2.itemsets.register { id = "training_set", title = sf2.localization.key("set.title"), text = sf2.localization.key("set.description"), brief = sf2.localization.key("set.brief"), members = { { item = katana, scale = 1 } },}Add the three localization keys to your mod’s language file. Registering a set does not create a shop listing; use the shop API for items you want players to buy.
sf2.progression.replace_perk_branch
Section titled “sf2.progression.replace_perk_branch”Signature: sf2.progression.replace_perk_branch(definition)
Returns: Nothing (nil).
When: During mod loading, before the perk tree is used.
Requires: content.patch; looking up or registering perks also requires content.register.
Replaces the available choices at one perk-tree level. It does not immediately teach a perk, add experience, or change upgrade prices. Two mods replacing the same level conflict.
| Field | Meaning |
|---|---|
level |
Required integer from 1 to 52. |
entries |
Required, nonempty array of perk choices. |
entries[].perk |
Required perk handle. |
entries[].action |
"unlock" (default) or "upgrade". |
-- opening_focus is a perk handle registered earlier in this script.sf2.progression.replace_perk_branch { level = 2, entries = { { perk = opening_focus, action = "unlock" } },}An upgrade entry must refer to a perk that supports upgrades. Keep alternative choices appropriate for players who have already progressed through the tree.
sf2.items.set_presentation
Section titled “sf2.items.set_presentation”Change an existing equipment item’s shop icon, fighter model, or both while keeping its identity and purchase state. Use typed asset handles so the asset host checks their kind and ownership before registration.
Signature: sf2.items.set_presentation { item, icon?, model? }
Returns: Nothing (nil).
When: During mod loading, before shop and fighter copies are built. Apply & Restart to load or remove the patch.
Requires: content.patch; item lookup needs content.register. Declare a
dependency on the item’s owner and on any asset owner.
| Field | Type | Meaning |
|---|---|---|
item |
Equipment handle | Required weapon, armor, helm, ranged or magic item. |
icon |
Sprite handle | Optional icon from sf2.assets.sprite. |
model |
Model handle | Optional model from sf2.assets.model. |
Provide at least one of icon or model. An omitted field keeps its existing
value. Another presentation patch for the same item conflicts. Removing the mod
restores the previous icon and model for future copies; copies already built
keep their snapshot. Existing saved item identity stays the same.
local sf2 = require("sf2")local weapon = sf2.items.get("core:items/weapon/WEAPON_KUNAI")sf2.items.set_presentation { item = weapon, icon = sf2.assets.sprite("core:ui/items/weapon_kunai"),}The core dependency is required in mod.toml for this example. You can also
use a model owned by your mod. The content fingerprint includes both asset IDs.
Native checks cover registration, copying and rollback; verify the actual shop
thumbnail and fighter appearance in the game. DE128’s isolated Unity check
also selects one changed icon in the live shop and equips a changed-model armor.
sf2.items.set_initial_profile
Section titled “sf2.items.set_initial_profile”Replace an existing equipment definition’s starting level, upgrade level, initial stat snapshot and optionally its upgrade template or legacy paid marker. Use this when the item already exists and a mod changes its progression without replacing its identity or shared purchase price.
Signature: sf2.items.set_initial_profile { item, level, upgrade_level, initial_stats, upgrade_template?, legacy_paid_item?, clear_local_upgrades? }
Returns: Nothing (nil).
When: During mod loading, after obtaining the item handle and before shop, inventory and fighter copies are built. Apply & Restart to load or remove it.
Requires: content.patch; sf2.items.get also needs content.register.
Declare a dependency on the item’s owner.
| Field | Type | Meaning |
|---|---|---|
item |
Equipment handle | Required. Weapon, armor, helm, ranged or magic. |
level |
Integer 1–52 | Required starting item level. |
upgrade_level |
Integer 0–5200 | Required starting upgrade level. This is the item’s progression marker, not a price. |
initial_stats |
Table | Required complete initial stat snapshot. Values are integers 0–1,000,000. Allowed keys match the category tables in explicit initial stats. Omitted keys remain absent. |
upgrade_template |
String | Optional. Leave out to retain the item’s current template. To replace it, use Weapon_Bonus or Paid_Weapon_Bonus for a weapon, or the matching Armor, Helm, Ranged, or Magic pair. The selected template must exist in the native item catalog. |
legacy_paid_item |
"none", "paid" or "super_paid" |
Optional. Leave out to retain the item’s current legacy PaidItem marker. "none" clears it; the other values set the corresponding native marker. This is metadata used by legacy conditions and statistics, not the shop price or currency. |
clear_local_upgrades |
Boolean | Optional, default false. Set true to remove the item’s own XML-authored upgrade rows from future catalog copies. The selected shared upgrade_template still supplies its rows. |
local blade = sf2.items.get("core:items/weapon/WEAPON_BP_S1_GUARDIAN")sf2.items.set_initial_profile { item = blade, level = 15, upgrade_level = 1500, initial_stats = { weapon_damage = 342 }, upgrade_template = "Weapon_Bonus", legacy_paid_item = "none",}The patched catalog item supplies new purchase and fighter copies. Its source XML,
item ID, coin/gem price and shared upgrade table definitions stay unchanged.
Changing upgrade_template selects a different existing table, which can change
future upgrade prices and power. Changing legacy_paid_item does not change shop
visibility, the acquisition route, or the price; use the shop availability and
listing APIs for those behaviors.
Use clear_local_upgrades = true when replacing an item whose source definition
has one-off upgrade rows that should no longer supplement its selected shared
template. Removing the patch restores those rows; already saved inventory is not
rewritten.
Removing the mod restores the original catalog profile. Already saved inventory
is not migrated or re-leveled by this declaration. The host rejects wrong-category
stat keys or templates, invalid values, unavailable templates, unknown items and
competing patches to the same item.
The native copy and rollback paths have detached tests. DE128’s isolated Unity
check also buys, upgrades, equips and reloads representative patched items.
Validate combat balance for each content pack in the game.
sf2.items.set_subtype
Section titled “sf2.items.set_subtype”Change the combat family of an existing weapon, ranged item or magic definition. Native animation conditions and projectile selection use this subtype.
Signature: sf2.items.set_subtype { item, subtype }
Returns: Nothing (nil).
When: During mod loading, before profile equipment and fighters are built.
Requires: content.patch; obtaining an item handle also requires
content.register. Declare a dependency on the item’s owner.
| Field | Meaning |
|---|---|
item |
Required weapon, ranged or magic handle. Armor, helms and consumables are rejected. |
subtype |
Required case-sensitive string of 1–128 ASCII letters, digits or underscores. |
local weapon = sf2.items.get("core:items/weapon/WEAPON_CHNY22_SPEAR")sf2.items.set_subtype { item = weapon, subtype = "Naginata" }Choose a family with compatible native moves, rig edges and projectile behavior,
or register that behavior yourself. A syntactically valid family does not create
missing animations. For example, changing to ChineseSwords in the current base
also needs its stance and attack graph ported; supplying an animation file alone
is insufficient.
Item IDs, models, prices, power, enchantments and saved ownership are unchanged.
The subtype feeds AI grouping only when the item has no explicit tactic group;
use set_tactic_subtype separately to change an explicit group. Competing patches
to the same item’s subtype fail atomically, while an AI-group or availability
patch is a separate field and can coexist.
The native item must exist when content is applied; an owned item normally needs a supported listing or another supported registration path that creates it. Duplicate legacy names are resolved against their original definition nodes. Missing native targets roll back earlier applied subtype patches. Apply & Restart without the mod restores the original subtype. Existing fighter copies retain their snapshot until rebuilt; this is not a mid-fight transformation operation. Subtype patches participate in the content compatibility fingerprint.
sf2.items.set_tactic_subtype
Section titled “sf2.items.set_tactic_subtype”Replace the AI table group of a core or owned weapon without changing its animation subtype.
Signature: sf2.items.set_tactic_subtype { item = ItemHandle, group = string }
Returns: Nothing (nil).
When: During entrypoint registration. The override applies when content is loaded, before subsequent fights are constructed.
Requires: content.patch; item lookup also requires content.register. Declare dependencies for referenced namespaces.
Both fields are required. item must resolve to a weapon. group is a native AI table group containing at most 128 ASCII letters, digits or underscores. An empty string explicitly selects the weapon’s actual subtype instead of an existing separate AI group. It does not create AI tables or animation moves; supply a group supported by your tactics.
local sf2 = require("sf2")sf2.items.set_tactic_subtype { item = sf2.items.get("core:items/weapon/WEAPON_TWO_HANDED_MACE"), group = "TwoHanded",}Only one override may target a weapon; duplicate registrations or another mod’s override fail instead of silently winning by load order. This can compose with default enchantments and innate perks on the same item. An owned weapon must have a supported shop listing so its native item exists when applying content. Prefer the tactic_subtype registration field for a new weapon unless a separate override is needed.
The group participates in the content compatibility fingerprint. Disabling the mod and applying/restarting restores the original native group, and partial application failures restore earlier targets. Existing fight copies retain their snapshot; this operation does not refresh a running fighter or edit inventory saves. Native projection, rollback and Lua validation have automated coverage; physical combat acceptance remains a separate playtest.
sf2.items.set_innate_perks
Section titled “sf2.items.set_innate_perks”Signature: sf2.items.set_innate_perks(definition)
Returns: Nothing (nil).
When: During mod loading, before fighters are built.
Requires: content.patch; item/perk lookup or registration also requires content.register. Declare dependencies for referenced core or other-mod content.
Replaces the permanent perks attached to an equipment definition. The normal combat equipment collector includes these perks whenever that equipment is used. This applies to already owned equipment in subsequently built fighters too. It does not write an enchantment to inventory or change acquisition defaults.
| Field | Meaning |
|---|---|
item |
Required weapon, armor, helm, ranged or magic handle. |
entries |
Required dense array of zero to 64 perk entries. Empty removes the item’s innate perks. |
entries[].perk |
Required existing perk handle, once per loadout. Register owned perks first. |
entries[].parameters |
Optional table of up to 64 named finite numbers. Defaults to empty, preserving the perk’s authored defaults. |
Parameter names must begin with an ASCII letter or underscore, contain only ASCII letters, digits or underscores, and be at most 128 characters. Values are stored as single-precision numbers; values that overflow that format are rejected. Strings, expressions, booleans, NaN and infinity are not accepted. Use names understood by the chosen perk: this API does not invent behavior for unknown parameters.
local sf2 = require("sf2")-- capabilities = ["content.register", "content.patch"]-- Also declare the core dependency in mod.toml.sf2.items.set_innate_perks { item = sf2.items.get("core:items/weapon/WEAPON_KNIVES"), entries = { { perk = sf2.perks.get("core:perks/PERK_ITEM_SPECIAL_PRECISION_WEAPON"), parameters = { Aspect = 100 } }, },}This attaches the native precision perk to Knives as an innate equipment effect. It is separate from the forge enchantment saved on a particular inventory item. For custom procedures, register a Lua-backed perk using the perk API and attach its handle with no loadout parameters. Lua-backed perks take their initial parameters at perk registration; nonempty loadout parameters for them are rejected. Keep conditional behavior in their callbacks rather than encoding expressions in this table.
The target must be present in the applied native item catalog. Owned items need their supported shop registration so they are materialized. Perk instances are cloned per item, preserving the registry and other equipment from the combat collector’s mutable weapon/non-weapon marker.
Two innate loadouts targeting the same item conflict. An innate loadout and a default-enchantment loadout can coexist on that item. Their entry order and parameter values participate in the compatibility fingerprint. Failed application restores earlier changes from that adapter; unloading restores the original innate list. Use Apply & Restart when changing active mods. Already constructed fighters are not refreshed by this operation.
Player-side Lua callbacks for active innate perks receive fighter.source = "innate", item_id, item_type and perk_id. They do not require a saved inventory enchantment, including when a rule supplies the equipment. Native perk filtering still controls eligibility. One perk ID dispatches once per event across equipment: learned/profile perks with an available saved instance take precedence, then innate perks, then saved enchantments. A missing learned save node does not suppress an otherwise active innate perk. An innate instance retains state between events within its fight but has no persistent profile backing; use mod-owned saved state for progress that must survive fights. Opponent callbacks retain their existing "warrior" provenance.
This does not expose automatic activated abilities, arbitrary item metadata or permanent profile mutation. Native collection, ownership, rollback, player dispatch and Lua validation are tested; live combat behavior still needs acceptance testing with the chosen perk.
sf2.items.set_default_enchantments
Section titled “sf2.items.set_default_enchantments”Signature: sf2.items.set_default_enchantments(definition)
Returns: Nothing (nil).
When: During mod loading.
Requires: content.patch; item/perk lookup or registration also requires content.register. Declare dependencies for referenced content from core or other mods.
Replaces an equipment item’s default enchantment loadout. Shop previews and the native acquisition path use the replacement. This does not grant the item, re-enchant existing inventory, or attach permanent innate effects. Previously saved enchantments retain their values.
| Field | Meaning |
|---|---|
item |
Required equipment handle, from registration or sf2.items.get. Weapon, armor, helm, ranged and magic items are supported. |
entries |
Required dense array of zero to 64 entries. An empty array removes the acquisition defaults. |
entries[].perk |
Required existing perk handle. Each perk may appear only once. Register owned perks before this call. |
entries[].aspect |
Optional signed 32-bit integer, copied as the enchantment’s explicit aspect. Omit it to preserve the perk’s normal default. This is a value, not a formula or expression. |
local sf2 = require("sf2")-- capabilities = ["content.register", "content.patch"]-- Also declare the core dependency in mod.toml.sf2.items.set_default_enchantments { item = sf2.items.get("core:items/weapon/WEAPON_KNIVES"), entries = { { perk = sf2.perks.get("core:perks/PERK_ITEM_SPECIAL_PRECISION_WEAPON"), aspect = 100 }, },}Choose an aspect appropriate for your content; accepting an integer does not mean every value produces useful gameplay. Prices, equipment levels and shared stat scaling remain unchanged. The native acquisition path still determines when default enchantments are applied.
The target must exist in the applied native item catalog. For an owned item, use its supported shop registration so the adapter materializes it before applying the loadout. This call does not create a missing native item or substitute an acquisition route.
Two mods targeting the same item conflict, even if their loadouts match. Different items can coexist. Unknown fields, duplicate perks, sparse arrays and unresolved dependencies fail loading. A later native apply failure restores earlier loadouts from that adapter. Unloading restores the original preview and acquisition lists; use Apply & Restart after enabling or disabling the mod.
Loadout order, perk IDs and optional aspects participate in the content compatibility fingerprint. Changing defaults does not migrate existing equipment. Automated checks cover Lua registration, native projection and rollback; full-game acquisition, saves and preview rendering still need acceptance testing.
sf2.forge.profile
Section titled “sf2.forge.profile”Signature: sf2.forge.profile(reference)
Returns: A forge economy-profile handle.
When: During mod loading.
Requires: content.register, and a declared dependency when reading another mod’s content.
Looks up an existing profile that supplies the forge’s costs and timing. A short name such as "Simple" is resolved as "core:forge-profiles/Simple". "Complex" is another existing core profile. Definition IDs normalize casing; an unknown profile fails loading.
local economy = sf2.forge.profile("Simple")The handle is an input for a recipe. It is not a table of editable prices. To give
your own recipes their own prices, register a profile with
sf2.forge.register_profile.
sf2.forge.register_profile
Section titled “sf2.forge.register_profile”Signature: sf2.forge.register_profile(definition)
Returns: A forge economy-profile handle, usable as a recipe’s economic_profile.
When: During mod loading, before the recipes that use it.
Requires: content.register.
Declares the forge-material prices for your own recipe families. A forge price
profile says how many of each forge material (ForgeMaterial1, 2 and 3) one
enchantment costs, depending on the player’s level at the moment of
enchanting (as in the original game; the level of the item does not matter). Core
profiles such as Simple and Complex keep their prices: a profile you register
is used only by recipes from your mod, never by core recipes or another mod’s.
| Field | Type | Meaning |
|---|---|---|
id |
String | Required local identifier, such as abilities. Becomes <your mod>:forge-profiles/<id>. |
prices |
Array | Required. One price table per equipment category, each category at most once. |
Each prices entry has:
| Field | Type | Meaning |
|---|---|---|
equipment |
Equipment constant | Required: sf2.forge.WEAPON, ARMOR, HELM, RANGED or MAGIC. |
rows |
Array | Required, 1–64 rows in ascending, non-overlapping level order. |
Each row covers a range of player levels:
| Field | Type | Default | Meaning |
|---|---|---|---|
level |
Integer | — | One exact player level. Use this or min_level/max_level, not both. |
min_level |
Integer | 1 |
First player level the row applies to, 1–1000. |
max_level |
Integer | No upper limit | Last player level, up to 1000. Omit it on the final row to cover every higher level. |
materials |
Integer array | Required | 1–3 counts for ForgeMaterial1, 2 and 3, each 0–10,000,000. At least one must be above zero. |
A recipe item can only use a category the profile prices, so give every
equipment category in your recipe’s items a table here. The first row whose
range contains the player’s current level is the price. Cover every player
level: when no row matches, the recipe has no price, and the forge then treats
it like a free recipe (the material check passes and nothing is spent), exactly
as the original game does. Rows have no delivery timer: the enchantment completes
immediately.
local abilities_price = sf2.forge.register_profile { id = "abilities", prices = { { equipment = sf2.forge.WEAPON, rows = { { max_level = 2, materials = { 38, 10, 5 } }, -- levels 1-2 { level = 3, materials = { 46, 13, 6 } }, { min_level = 4, materials = { 56, 17, 8 } }, -- level 4 and above } }, },}
sf2.forge.register_recipe { id = "abilities", economic_profile = abilities_price, items = { { equipment = sf2.forge.WEAPON } }, candidates = { { perk = my_ability_perk, equipment = sf2.forge.WEAPON } },}sf2.forge.exclude_candidate and sf2.forge.override_deviation still target core
profiles only. Profile prices are part of the content compatibility fingerprint,
so changing them is a content change. Disabling the mod removes the profile
together with its recipes.
sf2.forge.register_recipe
Section titled “sf2.forge.register_recipe”Signature: sf2.forge.register_recipe(definition)
Returns: A forge-recipe handle.
When: During mod loading.
Requires: content.register.
Creates a recipe using an existing economy profile and a pool of eligible enchantments.
| Field | Meaning |
|---|---|
id |
Required local identifier. |
alias |
Optional compatibility alias; defaults to an empty string. |
economic_profile |
Required handle from sf2.forge.profile, or from sf2.forge.register_profile in this mod. |
items |
Required array of equipment-category settings. |
candidates |
Required array of eligible perks/enchantments. |
Equipment categories are sf2.forge.WEAPON, ARMOR, HELM, RANGED, and MAGIC (string values "weapon", "armor", "helm", "ranged", "magic").
Each items entry requires equipment. Optional fields are enchantments (integer, default 1), bar_scale (string, default empty), min_deviation and max_deviation (integers, default 0), and random_aspect (boolean, default false). These describe the recipe’s enchantment presentation and roll behavior; they are not currency prices.
Each candidates entry requires perk (a perk handle from sf2.perks.register) and equipment. Optional integer min_level and max_level restrict eligibility; omitting them leaves the candidate unrestricted by level. Use explicit sensible bounds for your content.
-- training_perk is a handle from sf2.perks.register.local recipe = sf2.forge.register_recipe { id = "training_recipe", economic_profile = sf2.forge.profile("Simple"), items = { { equipment = sf2.forge.WEAPON, enchantments = 1 } }, candidates = { { perk = training_perk, equipment = sf2.forge.WEAPON, min_level = 1, max_level = 52 }, },}A registered enchantment must still be made obtainable through a recipe or another supported equipment/perk route before its combat behavior can run.
Registering a new family does not change the original family’s candidate pool or costs.
How the forge works
Section titled “How the forge works”These rules come from the original game (version 2.46.0) and apply to core recipes and to recipes your mod registers. They explain what players will see when they use your recipes.
Which recipes are offered. A recipe is listed for an item when the recipe has
an items entry for the item’s equipment category and at least one of its
candidate groups accepts the item. A mod candidate counts as such a group when its
min_level/max_level range contains the item’s level. Prices, materials and
whether a candidate is ready do not hide a recipe; a recipe with no ready
candidate is shown as unavailable instead.
What a roll picks. When the player enchants, the forge takes the recipe’s
whole candidate pool for the item, shuffles it with the game’s shared random
number generator, and then walks the shuffled list. It keeps each candidate that
is ready and not already on the item until it has the recipe item’s
enchantments count. If fewer candidates qualify, the item gets fewer
enchantments; if none qualify, nothing happens and nothing is spent. Every
candidate in the pool has the same chance; listing a perk twice gives it two
chances.
Ready candidates.
- A set (combo) perk that names an item set is ready only when the player owns every item of that set. This applies to core set perks in your own recipe families too.
- A combo perk registered with
sf2.perks.registerhas no item set and is always ready. - A perk whose alias is
ENCHANTMENT_MAGIC_RECHARGEis not ready while the player’s current zone isZONE_2. - Any other perk is ready.
The forge preview lists every candidate the item does not already have and dims the ones that are not ready.
Costs. The price is the profile row for the player’s current level (see
sf2.forge.register_profile). Materials are spent
only when an enchantment is actually applied, or when a timed order starts.
Timed orders. When a forge timer policy gives an order a duration, starting it spends the materials and stores the order on the item if the recipe can enchant the item. The roll happens when the order completes. The order is closed even if, by then, no candidate qualifies; the materials are not returned.
Choose recipes. Everything above describes the original random forge, which
stays the default. A mod can switch a recipe to choose selection with
sf2.forge.configure_recipe. The candidate pool,
the readiness rules and the price row stay the same, but there is no roll:
- The player sees every candidate of the pool once (in data order), except
candidates already on the item. Candidates that are not ready are shown locked;
sf2.forge.configure_candidatecan also lock a candidate until the player has beaten a listed opponent. - The player picks as many candidates as the recipe item’s
enchantmentscount (or every available one, when fewer are available) and one strength tier. - The tier fixes the enchantment power. A tier’s
strengthis a fraction of the recipe item’s deviation range:0givesmin_deviation,1givesmax_deviation,0.5the midpoint, rounded to the nearest integer. The power is the level’s base aspect plus that deviation, the same formula as a roll without the randomness. If the recipe item declares no deviation range, a candidate’s ownRandomAspectrange is used. Candidates with a fixed power, such as set enchantments and boss abilities, keep it: the forge hides the tier choice when none of the picks has a power range. - The price is the price row times the picked candidate’s
pricemultiplier times the tier’spricemultiplier. With several picks the largest candidate multiplier is used. The tier multiplier applies only when at least one pick has a power range, so a fixed-power pick never costs more for a higher tier. Each material count is rounded up to a whole number. - A timed order stores the picks and the tier and applies exactly them when it completes. Orders saved without picks (older saves, random recipes) complete with the random roll. If none of the stored picks exists any more (for example, the mod that added them was removed), the order completes with the random roll instead of being lost.
Loading saved enchantments. When a profile loads, saved core enchantments pass the original game’s checks:
- Only the saved
Aspect(power) is kept; other saved parameters fall back to the perk’s own values. - An
Aspectthat is at least 105 above the largest value a forge roll can give at the player’s level (the level’s base aspect plus 30) is reset to that largest value. - An item keeps one combo enchantment (the first) and normally one single enchantment. Extra saved enchantments, and a single enchantment that neither is one of the item’s default enchantments nor can be rolled by one of its recipes, are removed from the save.
Enchantments placed by mods (mod perks, scripted enchantments and candidates added
by sf2.forge.register_recipe) are loaded unchanged and skip these checks.
sf2.forge.exclude_candidate
Section titled “sf2.forge.exclude_candidate”Signature: sf2.forge.exclude_candidate(definition)
Returns: Nothing (nil).
When: During mod loading.
Requires: content.patch, content.register for handle lookups, and a declared core dependency.
Removes a native enchantment from one core recipe’s eligible pool for one equipment category while the mod configuration is applied. Both candidate previews and subsequent rolls use this pool. It does not remove enchantments already on equipment, delete the perk, or change prices, timers or power deviations.
| Field | Meaning |
|---|---|
profile |
Required core profile handle from sf2.forge.profile. Selects the native recipe with that profile’s name. |
perk |
Required existing core perk handle from sf2.perks.get. |
equipment |
Required category: sf2.forge.WEAPON, ARMOR, HELM, RANGED, or MAGIC. |
local sf2 = require("sf2")-- capabilities = ["content.register", "content.patch"]-- Also declare the core dependency in mod.toml.sf2.forge.exclude_candidate { profile = sf2.forge.profile("Complex"), perk = sf2.perks.get("core:perks/PERK_MONK_SET_WHIRL"), equipment = sf2.forge.WEAPON,}This example excludes the Monk set enchantment from Complex weapon rolls. Other equipment categories remain unchanged. All native occurrences of that candidate are filtered; independently added mod candidates are unaffected.
Duplicate exclusions for the same recipe, equipment and perk conflict, including duplicates from different mods. Different targets can coexist. Unknown fields, invalid categories and non-core targets are rejected. Registration validates the handles; the native adapter additionally checks that the recipe supports the equipment and contains the candidate when applying content. Failure aborts application and restores exclusions already applied by that adapter.
Unloading the applied content restores the original native candidate pool. Enable or disable the mod through Apply & Restart to change the active configuration. This function does not yet edit native candidate level conditions, replace entire families or modify native recipe item settings.
sf2.forge.override_deviation
Section titled “sf2.forge.override_deviation”Signature: sf2.forge.override_deviation(definition)
Returns: Nothing (nil).
When: During mod loading.
Requires: content.patch, content.register for the profile lookup, and a declared core dependency.
Changes the random enchantment-power delta for one equipment category in an existing core recipe. The game still calculates the base aspect from its normal level curve, then adds a random integer from minimum through maximum, inclusive. Prices, timers and the base curve remain unchanged. Already enchanted equipment keeps its existing power.
| Field | Meaning |
|---|---|
profile |
Required core recipe profile handle from sf2.forge.profile. |
equipment |
Required sf2.forge.WEAPON, ARMOR, HELM, RANGED, or MAGIC. |
minimum |
Required integer, at least -10000 and no greater than maximum. |
maximum |
Required integer, at most 10000 and no less than minimum. |
The limits are validation bounds, not recommended balance values. Start close to the original recipe’s range and test the resulting power at several equipment levels. The base aspect is read at the player’s level. Keep maximum at 134 or less: when a profile loads, a saved core enchantment whose power is 135 or more above its level’s base aspect is reset to base + 30 (see How the forge works).
local sf2 = require("sf2")-- capabilities = ["content.register", "content.patch"]-- Also declare the core dependency in mod.toml.sf2.forge.override_deviation { profile = sf2.forge.profile("Simple"), equipment = sf2.forge.WEAPON, minimum = 15, maximum = 75,}This replaces the Simple weapon category’s native -30..30 range with 15..75. Both the effective recipe item settings and copied roll candidates use the override. Other equipment categories retain their original settings. The source recipe and perk definitions remain intact.
Only categories already using random aspect are supported. Simple and Medium have such categories; Complex’s fixed-aspect categories are rejected when content is applied. A candidate’s complete native RandomAspect expression is updated; fixed values, missing aspect values and compound expressions are preserved. Independently added candidates with a complete random-aspect expression use the same category override.
Two overrides targeting the same recipe/category conflict, even with equal bounds. Different categories compose, and candidate exclusions can coexist with a deviation override. Applying an invalid native target rolls back earlier forge changes from that adapter. Unloading restores the original settings; use Apply & Restart when changing the active mods.
The bounds participate in the content compatibility fingerprint. Changing them is a content change even if the mod’s version is unchanged. This function does not replace entire recipe families, change fixed-aspect recipes into random ones, or edit arbitrary candidate conditions.
sf2.forge.configure_recipe
Section titled “sf2.forge.configure_recipe”Signature: sf2.forge.configure_recipe(definition)
Returns: Nothing (nil).
When: During mod loading.
Requires: content.patch for a core recipe or another mod’s recipe, plus a
declared dependency on that namespace (core for core recipes).
content.register when the recipe is one your mod registered with
sf2.forge.register_recipe. Tier names use
sf2.localization.key handles.
Switches one recipe from the original random roll to choose selection: the player picks the exact enchantments and a strength tier, and the price follows the picks. See How the forge works for the full rules.
| Field | Meaning |
|---|---|
recipe |
Required. A core recipe name such as "Simple", "Medium" or "Complex"; a qualified ID such as "core:forge-profiles/Simple" or "my_mod:forge-recipes/storm"; a handle from sf2.forge.profile; or a handle from sf2.forge.register_recipe. |
selection |
Optional. "random" (default, the original forge) or "choose". "random" changes nothing and cannot undo another mod’s configuration. |
tiers |
Required for "choose", not allowed for "random". An array of 1 to 5 tiers, in the order the forge shows them. |
Each tier:
| Field | Meaning |
|---|---|
id |
Required. 1 to 32 characters of a-z, 0-9, _ or -; unique within the recipe. Saved with timed orders. |
name |
Optional localization handle shown to the player. Default: the id. |
strength |
Required number from 0 to 1: the fraction of the recipe item’s deviation range (0 = minimum, 1 = maximum). |
price |
Optional price multiplier from 0.01 to 100. Default 1. |
local sf2 = require("sf2")-- capabilities = ["content.register", "content.patch"]-- Also declare the core dependency in mod.toml. The forge.tier.* keys come-- from your mod's localization files.sf2.forge.configure_recipe { recipe = "Simple", selection = "choose", tiers = { { id = "low", name = sf2.localization.key("forge.tier.low"), strength = 0.0, price = 0.5 }, { id = "mid", name = sf2.localization.key("forge.tier.mid"), strength = 0.5, price = 1.0 }, { id = "high", name = sf2.localization.key("forge.tier.high"), strength = 1.0, price = 2.0 }, },}With Simple’s weapon range of -30..30, Low gives the base aspect -30, Mid the
base aspect and High +30, for half, the normal and double the normal price.
Only one mod can configure a recipe; a second configuration conflicts even if it is identical. The tiers participate in the content compatibility fingerprint. Unloading the mod (Apply & Restart) restores the random roll. Enchantments already made keep their power, and a timed order that is already running still applies its stored picks.
sf2.forge.configure_candidate
Section titled “sf2.forge.configure_candidate”Signature: sf2.forge.configure_candidate(definition)
Returns: Nothing (nil).
When: During mod loading, for a recipe that this mod or a mod loaded before
it switched to "choose" with
sf2.forge.configure_recipe. The order of the two
calls inside your script does not matter; the check runs when your content is
committed.
Requires: The same capability as configure_recipe for that recipe
(content.patch for core or another mod’s recipe, content.register for your
own), and declared dependencies for every namespace you reference.
Sets the price multiplier and an optional unlock requirement for one candidate of a choose recipe. Candidates without a configuration cost the normal price and are available whenever they are ready.
| Field | Meaning |
|---|---|
recipe |
Required. Same forms as in configure_recipe. |
perk |
Required. A perk handle (sf2.perks.get/sf2.perks.register), a scripted enchantment handle (sf2.enchantments.register), or a qualified perks/... or enchantments/... ID such as "core:perks/PERK_HERMITSTORM". |
equipment |
Optional sf2.forge.WEAPON, ARMOR, HELM, RANGED or MAGIC. Omitted: the setting applies to every equipment category of the recipe. A category-specific setting wins over one without equipment for the same perk. |
price |
Optional multiplier from 0.01 to 100. Default 1. |
unlock |
Optional table { defeated = { ... } }. The candidate stays locked until the player has won a fight against any one of the listed opponents (see below). 1 to 32 entries. |
locked_text |
Optional localization handle shown while the candidate is locked, for either reason (incomplete set or missing win). Default: a short English message. |
Each unlock.defeated entry is one of:
- a warrior handle from
sf2.warriors.register, or a qualified ID such as"my_mod:warriors/storm_master"; - a warrior template handle from
sf2.warriors.get_templateorsf2.warriors.register_template, or a qualified ID such as"core:warrior-templates/hermit_swords".
Core story opponents, including the bosses, have no warrior IDs of their own:
use their template. The template ID is the native template name in lower case,
for example core:warrior-templates/lynx_claws, hermit_swords,
butcher_backswords, wasp_naginata, shogun_katana. What counts as a win is
described in sf2.profile.has_defeated.
local sf2 = require("sf2")-- capabilities = ["content.register", "content.patch"]-- Also declare the core dependency in mod.toml.local storm = sf2.perks.get("core:perks/PERK_HERMITSTORM")
sf2.forge.configure_candidate { recipe = "Complex", perk = storm, price = 1.5, unlock = { defeated = { "core:warrior-templates/hermit_swords" } }, locked_text = sf2.localization.key("forge.locked.hermit"),}
-- A mythical set enchantment: available once the whole set is owned (the-- original rule), at double price.sf2.forge.configure_candidate { recipe = "Complex", perk = sf2.perks.get("core:perks/PERK_MONK_SET_WHIRL"), equipment = sf2.forge.WEAPON, price = 2,}A set (combo) enchantment remains locked until the player owns every item of its
set, whatever unlock says; unlock adds a requirement, it never removes one.
Two settings for the same recipe, perk and equipment value conflict, including
across mods. A perk that is not in the recipe’s pool is accepted but has no
effect. Settings participate in the content compatibility fingerprint and are
removed when the mod unloads.