Familiar
On this page
Provides access to the familiar system including owned familiars, equipped presets, badges, and user state.
Familiar Object Structure
All familiar-returning functions provide objects with the following properties:
| Property | Type | Description |
|---|---|---|
sn | number | Unique serial number (instance ID) |
id | number | Mob template ID (which monster) |
character_id | number | Owner character ID |
name | string | Custom familiar name |
level | number | Current level |
max_level | number | Maximum level cap |
exp | number | Current experience |
grade | number | Rarity grade (see table below) |
grade_exp | number | Grade experience |
add_pad | number | Bonus physical attack |
add_pdd | number | Bonus physical defense |
option1 | number | Potential line 1 ID (raw) |
option2 | number | Potential line 2 ID (raw) |
potentials | table<string> | Array of resolved potential names (e.g. "Boss Damage +30%") |
locked | boolean | Whether the familiar is locked |
register_date | number | When the familiar was obtained, as a Unix timestamp (seconds); 0 if unset |
Familiar Grades
| Grade | Name |
|---|---|
0 | Common |
1 | Rare |
2 | Epic |
3 | Unique |
4 | Legendary |
Functions
Collection
core.familiar.get_all() -> table<familiar>
Returns an array of all familiars the character owns.
local familiars = core.familiar.get_all()
for _, f in ipairs(familiars) do
core.log(string.format("%s (Lv.%d, Grade %d)", f.name, f.level, f.grade))
endcore.familiar.get_by_sn(serial_number: number) -> familiar | nil
Returns a specific familiar by its serial number, or nil if not found.
local familiar = core.familiar.get_by_sn(12345)
if familiar then
core.log("Found: " .. familiar.name)
endcore.familiar.get_count() -> number
Returns the total number of familiars owned.
Active State
core.familiar.get_summoned() -> familiar | nil
Returns the currently summoned familiar's full data, or nil if none is summoned.
local summoned = core.familiar.get_summoned()
if summoned then
core.log("Summoned: " .. summoned.name .. " (Lv." .. summoned.level .. ")")
endcore.familiar.get_equipped() -> table
Returns a table of up to 3 familiars in the active preset's equipped slots. Empty slots are nil.
local equipped = core.familiar.get_equipped()
for i = 1, 3 do
if equipped[i] then
core.log("Slot " .. i .. ": " .. equipped[i].name)
else
core.log("Slot " .. i .. ": empty")
end
endcore.familiar.get_badges() -> table
Returns an array of active badge entries (non-empty, non-0xFF slots). Each entry has:
| Property | Type | Description |
|---|---|---|
slot | number | Badge slot index (0-7) |
badge_id | number | Badge group ID |
local badges = core.familiar.get_badges()
for _, b in ipairs(badges) do
core.log("Badge slot " .. b.slot .. ": ID " .. b.badge_id)
endUser Info
core.familiar.get_user_info() -> table | nil
Returns familiar system state for the character, or nil if unavailable.
| Property | Type | Description |
|---|---|---|
fatigue | number | Current fatigue value |
inventory_size | number | Familiar inventory capacity |
summoned_sn | number | Serial number of summoned familiar (0 if none) |
version | number | Familiar system version |
active_preset | number | Active familiar preset index (0-4) |
active_badge_preset | number | Active badge preset index (0-4) |
last_fatigue_time | number | When fatigue was last incremented, as a Unix timestamp (seconds); 0 if unset |
local info = core.familiar.get_user_info()
if info then
core.log("Preset: " .. info.active_preset)
core.log("Fatigue: " .. info.fatigue)
endcore.familiar.get_active_familiar_max_count() -> number
Returns the maximum number of familiars that can be active simultaneously. This is a dynamic cap — the base value is 1, and familiar slot-expansion quests raise it to 2 or 3. Returns 0 if character data is unavailable.
local max = core.familiar.get_active_familiar_max_count()
local equipped = core.familiar.get_equipped()
local active = 0
for i = 1, 3 do
if equipped[i] then active = active + 1 end
end
core.log(string.format("Active familiars: %d / %d", active, max))Presets
core.familiar.get_preset_familiars(preset_index: number) -> table
Returns a table of 3 familiar serial numbers for the specified preset (0-4). SNs of 0 indicate empty slots.
local sns = core.familiar.get_preset_familiars(0)
for i, sn in ipairs(sns) do
if sn ~= 0 then
local f = core.familiar.get_by_sn(sn)
if f then core.log("Preset slot " .. i .. ": " .. f.name) end
end
endcore.familiar.get_preset_badges(preset_index: number) -> table
Returns a table of 8 badge IDs for the specified badge preset (0-4).
local badges = core.familiar.get_preset_badges(0)
for i, id in ipairs(badges) do
if id ~= 0 and id ~= 255 then
core.log("Badge " .. i .. ": " .. id)
end
endMutations
core.familiar.apply_preset(preset_index: number, sn1: number, sn2: number, sn3: number) -> boolean
Writes the three familiar SNs into preset_index (0-4). Pass 0 for empty slots. Each non-zero SN is validated against the owned-familiar map before the packet is sent — stale SNs cause the server to disconnect, so this returns false and skips the send instead.
Does not change which preset is active — combine with set_active_preset if you want to switch.
-- Save familiars 1234, 5678 into preset 0 with the third slot empty
core.familiar.apply_preset(0, 1234, 5678, 0)core.familiar.set_active_preset(preset_index: number) -> boolean
Switches which familiar preset is currently active (0-4). Does not modify the preset's familiar layout — only changes which preset's familiars are active.
core.familiar.set_active_preset(2)core.familiar.pull(inv_pos: number, card_item_id: number, count?: number) -> boolean
Opens familiar cards (item id card_item_id) to add familiars to the familiar inventory.
inv_pos is the card's USE-tab slot, but it is only a hint: like the in-game window, the card is looked up by id and whichever USE slot actually holds it is used — so a stale or wrong inv_pos still works. count is how many cards to open and defaults to the whole stack; it is clamped to the number you own.
Returns false without sending when the card isn't in your USE tab. It is also an exclusive request — the client only allows one outstanding at a time and throttles them ~500 ms apart — so it likewise returns false (sending nothing) when a request is already pending; retry on a later tick. A previous call that is still throttled is the usual reason a follow-up call returns false, so pass the full count in a single call rather than looping.
-- Open the entire familiar-card stack sitting in USE slot 1
-- (use the real card item id, e.g. from core.inventory.get_use_items()).
local card_id = 2855000
if not core.familiar.pull(1, card_id) then
core.log("card not found or a pull is still pending — retrying next tick")
endcore.familiar.fuse(target_sn: number, fodder: number | table<number>) -> boolean
Feeds one or more fodder familiars into target_sn, raising its fusion gauge (grade experience). fodder may be a single SN or an array of SNs — at most 255 per call, since the wire count is a single byte. target_sn and every fodder SN are validated against the owned-familiar map first; returns false if any is unknown.
core.familiar.fuse(target_sn, { 111, 222, 333 })core.familiar.rank_up(sn: number) -> boolean
Ranks the familiar up a grade (e.g. Epic → Unique). In-game this requires the familiar to be at max level with a full fusion gauge. Returns false if sn is not owned.
Ranking up rerolls the grade's potential lines, so reveal and lock any keepers first.
core.familiar.reveal_potential(sn: number) -> boolean
Reveals the hidden potential lines on an Epic-or-higher familiar. Returns false if sn is not owned. Once the request round-trips, the lines appear in the familiar's potentials field.
core.familiar.set_lock(sn: number, locked: boolean) -> boolean
Locks or unlocks a familiar. locked defaults to true when omitted. The underlying action is a toggle, so this reads the current lock state and only sends when a change is needed — calling it with the state it already has is a no-op that returns true. Returns false if sn is not owned.
core.familiar.set_lock(sn, true) -- protect a keepercore.familiar.decompose(familiars: number | table<number>) -> boolean
Decomposes (extracts) familiars for materials. familiars may be a single SN or an array of SNs; all are validated against the owned-familiar map first. Returns false if any is unknown.
core.familiar.decompose({ 111, 222, 333 })Examples
List all familiars with potentials
local familiars = core.familiar.get_all()
for _, f in ipairs(familiars) do
local pot = ""
if f.option1 ~= 0 then pot = pot .. " opt1=" .. f.option1 end
if f.option2 ~= 0 then pot = pot .. " opt2=" .. f.option2 end
core.log(string.format("[%s] %s Lv.%d Grade:%d%s",
f.locked and "LOCK" or " ",
f.name, f.level, f.grade, pot))
endShow equipped setup
local info = core.familiar.get_user_info()
if not info then return end
core.log("=== Familiar Preset " .. info.active_preset .. " ===")
local equipped = core.familiar.get_equipped()
for i = 1, 3 do
if equipped[i] then
core.log(string.format(" Card %d: %s (Lv.%d)", i, equipped[i].name, equipped[i].level))
end
end
core.log("=== Badges (Preset " .. info.active_badge_preset .. ") ===")
local badges = core.familiar.get_badges()
for _, b in ipairs(badges) do
core.log(string.format(" Slot %d: Badge %d", b.slot, b.badge_id))
end