Player profile
The query functions read the active player’s profile.
They do not describe an opponent, temporary fight equipment, or an item preview.
Declare profile.read and call after profile loading, such as inside a UI or
combat callback. Calling during mod loading or without an active profile raises
an error.
sf2.profile.set_eclipse_mode
Section titled “sf2.profile.set_eclipse_mode”Request the existing Eclipse switch on the story map, including its native button, map tint, replay battles and tutorial behavior.
Signature: sf2.profile.set_eclipse_mode(enabled)
Returns: A boolean: true if the requested mode is active and the story map
is ready after the request (including when already in that mode). false if the
map is unavailable, blocked, not in story mode, the native switch is absent, or
its quest has not finished. A false result can follow a partial native transition;
retry from a later user action instead of assuming the mode is unchanged.
When: From a user or story callback with an active profile on the ready map.
Do not call from on_close cleanup, which rejects progression changes.
Requires: story.progression. enabled must be a boolean. Missing capability,
wrong argument types or an unavailable host service raise errors.
-- Call from a map notification's button or on_back callback.local function continue_in_normal_mode(view) if sf2.profile.set_eclipse_mode(false) then sf2.ui.close(view) endendThis request follows the native switch policy; it does not unlock Eclipse mode, bypass its tutorial, or write arbitrary profile variables. A refused request can leave your view open until the player can try again.
sf2.profile.level
Section titled “sf2.profile.level”Signature: sf2.profile.level()
Returns: The active player’s level as an integer.
When: After a game profile has loaded.
Requires: profile.read.
-- Inside a UI callback; view contains a text widget named "level".sf2.ui.set_text(view, "level", "Player level: " .. sf2.profile.level())This is a fresh read of profile progression. It neither changes the level nor reports a mode’s generated opponent level.
sf2.profile.fight
Section titled “sf2.profile.fight”Read saved progress for a fight without creating or changing its save record.
Signature: sf2.profile.fight(fight)
Returns: A fresh table with present (boolean), wins (integer) and losses
(integer). A known fight with no saved record returns false, 0, 0.
When: After a profile loads, for example inside a battle_result callback.
The host must have an active content catalog. An unavailable profile or unknown
fight raises an error; it is not reported as an unwon fight.
Requires: profile.read. Pass a qualified fight ID string or a fight handle
created in this script context. Other namespaces, including core, require a
declared dependency. String queries do not require content.register.
local sf2 = require("sf2")-- Also declare story.events and a dependency on core.sf2.story.on("battle_result", function(event) if event.outcome == "win" then local progress = sf2.profile.fight("core:fights/zone_1/tournament/3") if progress.wins >= 1 then sf2.log.info("The first tournament prerequisite is complete.") end endend)These are the native WinCount/LossCount quest counters, stored as
CompletedCount/LossCount. They do not select the separate eclipse counters
based on the current mode, or count rounds. Query a distinct eclipse fight ID
when that content has its own record. Native resets can change these counts;
they are not an immutable lifetime history. Editing the returned table has no
effect on saved progress. A new query reads the currently bound profile.
sf2.profile.has_defeated
Section titled “sf2.profile.has_defeated”Ask whether the player has ever won a fight against an opponent.
Signature: sf2.profile.has_defeated(warrior)
Returns: true if the active profile has a recorded or proven win against
warrior, otherwise false.
When: After a profile loads, for example in a UI, story or battle_result
callback. Without an active profile the call raises an error.
Requires: profile.read. Pass 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".
Other namespaces, including core, require a declared dependency. String
queries do not require content.register.
local sf2 = require("sf2")-- capabilities = ["profile.read"]; also declare the core dependency.-- Call after profile loading, for example from a UI button callback.local function hermit_beaten() return sf2.profile.has_defeated("core:warrior-templates/hermit_swords")endWhat is recorded. Whenever the player wins a fight (story, tournaments, challengers, bosses, Underworld raids run by Eclipse, Sensei or any other fight that ends with the normal result screen), the beaten opponent is remembered permanently:
- its warrior ID, if it is a mod warrior registered with
sf2.warriors.register; - its warrior template ID, if the fight lists it with a template. This covers core
opponents, which have no warrior IDs of their own; story bosses use their
template, for example
core:warrior-templates/lynx_claws,hermit_swords,butcher_backswords,wasp_naginataorshogun_katana. A mod warrior built on a template records both IDs.
A query matches either ID, so asking about a template is true after a win against any opponent built from that template. In a fight with several opponents in a row, only the opponent beaten at the end of the won fight is recorded. Losses, surrenders and draws record nothing. The records are kept in Eclipse’s own save data, which the original game never reads, and survive mod changes.
Earlier wins. Wins from before this record existed are proven by the game’s
own fight records: a fight with at least one saved win counts as a win against
an opponent when every warrior that fight lists has that warrior or template
ID. Fights whose opponents are drawn from a random group, and fights listing
different opponents, prove nothing. Most Underworld raid bosses share the generic
core:warrior-templates/default template, so earlier raid wins do not identify
a particular boss; only new wins against mod raid bosses with their own warrior or
template ID can be told apart.
The answer never goes from true back to false, except when the player
switches to another profile.
sf2.profile.item
Section titled “sf2.profile.item”Signature: sf2.profile.item(item)
Returns: A new snapshot table with these fields:
| Field | Meaning |
|---|---|
type |
Since 0.35: native item type, such as "Weapon" or "Consumable"; nil if the runtime item definition is unavailable. |
subtype |
Since 0.35: native subtype, such as "Nunchaku"; an empty string means unspecified, nil means the runtime definition is unavailable. |
present |
Boolean: an inventory record exists for this item. |
owned |
Boolean: the record exists and its quantity is greater than zero. |
count |
Integer native quantity; zero if no record exists. |
equipped |
Boolean native equipped flag; false if no record exists. |
upgrade |
Integer native upgrade index, or nil if no record exists. The native unspecified sentinel may be -1. |
When: After a game profile has loaded.
Requires: profile.read and either an item handle obtained by this mod context
or a qualified item ID string. Other namespaces, including core,
require a declared dependency. String queries do not require content.register.
local sf2 = require("sf2")-- Obtain handles during mod loading.local nunchaku = sf2.items.get("core:items/weapon/weapon_nunchaku")
-- Call this function from an existing UI or gameplay callback.local function describe_equipment() local item = sf2.profile.item(nunchaku) if item.equipped then return "Nunchaku equipped" elseif item.owned then return "Nunchaku in inventory" end return "Nunchaku not owned"endAn empty inventory record can have present = true and owned = false.
Ownership is current state, not evidence of a past purchase. Type/subtype come from
the runtime item catalog and are available for unowned items too. They preserve
native spelling and case. They describe the resolved item, not a past purchase
event or temporary combat state. Missing catalog metadata does not imply that
an existing inventory record is unowned. Core item handles
resolve to their native names; supported item redirects use the resolved item.
Unknown or forged handles raise an error, rather than appearing to be unowned.
Editing the returned table does not change the inventory. Call again for current values; snapshots are not live handles. These functions expose no inventory, currency or progression mutations, and do not create event subscriptions.
Registration/binding and controlled native roster tests pass. Full-game profile switching and comparisons against the inventory UI remain acceptance checks.
Profile reset temporarily makes queries unavailable. Constructing an internal comparison roster does not change which player these queries read. A newly selected profile becomes readable after native activation and inventory preparation.
sf2.profile.perk
Section titled “sf2.profile.perk”Read whether the active profile has learned a perk and its stored upgrade number. This queries the learned-perk list, not temporary combat effects, equipment enchantments or whether a trigger is currently active.
Signature: sf2.profile.perk(perk)
Returns: A fresh table with learned (boolean) and upgrade (integer when
learned; nil otherwise). Upgrade zero is a valid stored value; it does not mean
unlearned. The number is the native UpgradeLevel, not a count of purchases.
When: After the active game profile has loaded, for example in a UI or story callback. A profile switch or perk upgrade is reflected on the next query.
Requires: profile.read. Pass a perk handle acquired in the same script
context, or a qualified perk ID string. Other namespaces, including
core, require a declared dependency. Strings need no content.register capability.
Malformed IDs, wrong categories, unavailable definitions, forged handles and queries
without an active profile raise errors.
local sf2 = require("sf2")local cobra = sf2.perks.get("core:perks/PERK_COBRA")
-- Call after profile loading, for example from your menu's button callback.local function describe_cobra() local perk = sf2.profile.perk(cobra) if perk.learned then return "Cobra upgrade: " .. perk.upgrade end return "Cobra has not been learned"endChanging this returned table changes no game state. Each call reads the active profile and returns detached values. Disabling/resetting a profile does not leave its old perk list available to subsequent queries.
Automated checks cover native host mapping/lifetime with controlled roster services, Lua capability/handle rejection and snapshot isolation. Full-game learning, upgrading, reset and save/reload acceptance remain pending.
Querying an acquisition event
Section titled “Querying an acquisition event”With story.events, profile.read, and a core dependency, an observer
can inspect core items discovered at runtime without obtaining handles at load time:
local sf2 = require("sf2")sf2.story.on("item_acquired", function(event) -- Other mods need their own declared dependencies before querying their items. if event.item and event.item:match("^core:") then local item = sf2.profile.item(event.item) sf2.log.info(event.item .. " currently owned: " .. item.count) endend)The query reads current inventory; nested grants can make it newer than the event’s
count snapshot. Unknown event items are nil; skip them. A qualified but unavailable
definition raises an error instead of reporting an unowned item.
sf2.profile.equipment
Section titled “sf2.profile.equipment”Inspect the active profile’s equipped records without knowing their item IDs in advance.
Signature: sf2.profile.equipment()
Returns: A fresh contiguous array of snapshot tables. An empty array means no records are marked equipped. Each record contains:
| Field | Meaning |
|---|---|
item |
Qualified definition ID, or nil when the API cannot identify the native item. |
type |
Native item type, or nil when metadata is unavailable. |
subtype |
Native subtype; empty string means unspecified, nil means metadata unavailable. |
count |
Native inventory quantity. |
owned |
Whether quantity is greater than zero. |
upgrade |
Stored upgrade index, possibly the native unspecified sentinel -1; nil if unavailable. |
enchantments |
Array of qualified perk ID strings for the item’s current enchantments, in native order. Always present; empty when the item has none. Perks the API cannot identify are left out. |
When: After an active game profile has loaded, including UI and story callbacks.
Requires: profile.read. No item handles or content.register capability.
An unavailable profile raises an error. Enumeration includes all native equipped
records, including records from other mods. Passing a returned ID to a separate
profile.item query still requires that namespace’s declared dependency.
local sf2 = require("sf2")-- Call from a story condition or UI callback after profile loading.local function wearing_katana() for _, item in ipairs(sf2.profile.equipment()) do if item.owned and item.type == "Weapon" and item.subtype == "Katana" then return true end end return falseendDefinition IDs are normalized to lower case, so compare in lower case, for
example "core:perks/perk_hermitstorm". This example reports whether any worn
item carries a given enchantment, such as a forged ability:
local sf2 = require("sf2")-- Call from a story, UI or combat callback after profile loading.local function wearing_enchantment(perk_id) perk_id = perk_id:lower() for _, item in ipairs(sf2.profile.equipment()) do if item.owned then for _, perk in ipairs(item.enchantments) do if perk == perk_id then return true end end end end return falseend
local storm = wearing_enchantment("core:perks/PERK_HERMITSTORM")This reads profile equipment, not a fight’s temporary rule-imposed loadout or the
opponent. It includes every record marked equipped by the native inventory, rather
than assuming exactly five slots. Do not use array positions as slot identifiers;
inspect type and subtype. Records with zero quantity remain visible with
owned = false. Unknown items retain their available metadata and quantity.
Changing the array or its entries changes no game state; query again after an
equipment change. This function does not equip or grant anything.
Production-query/Lua fixture checks cover identity, missing metadata, empty lists, snapshot isolation and profile unbinding using controlled inventory services. Full-game comparisons against the equipment UI and temporary battle loadouts remain acceptance checks.