Violet

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:

PropertyTypeDescription
snnumberUnique serial number (instance ID)
idnumberMob template ID (which monster)
character_idnumberOwner character ID
namestringCustom familiar name
levelnumberCurrent level
max_levelnumberMaximum level cap
expnumberCurrent experience
gradenumberRarity grade (see table below)
grade_expnumberGrade experience
add_padnumberBonus physical attack
add_pddnumberBonus physical defense
option1numberPotential line 1 ID (raw)
option2numberPotential line 2 ID (raw)
potentialstable<string>Array of resolved potential names (e.g. "Boss Damage +30%")
lockedbooleanWhether the familiar is locked
register_datenumberWhen the familiar was obtained, as a Unix timestamp (seconds); 0 if unset

Familiar Grades

GradeName
0Common
1Rare
2Epic
3Unique
4Legendary

Functions

Collection

core.familiar.get_all() -> table<familiar>

Returns an array of all familiars the character owns.

lua
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))
end

core.familiar.get_by_sn(serial_number: number) -> familiar | nil

Returns a specific familiar by its serial number, or nil if not found.

lua
local familiar = core.familiar.get_by_sn(12345)
if familiar then
    core.log("Found: " .. familiar.name)
end

core.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.

lua
local summoned = core.familiar.get_summoned()
if summoned then
    core.log("Summoned: " .. summoned.name .. " (Lv." .. summoned.level .. ")")
end

core.familiar.get_equipped() -> table

Returns a table of up to 3 familiars in the active preset's equipped slots. Empty slots are nil.

lua
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
end

core.familiar.get_badges() -> table

Returns an array of active badge entries (non-empty, non-0xFF slots). Each entry has:

PropertyTypeDescription
slotnumberBadge slot index (0-7)
badge_idnumberBadge group ID
lua
local badges = core.familiar.get_badges()
for _, b in ipairs(badges) do
    core.log("Badge slot " .. b.slot .. ": ID " .. b.badge_id)
end

User Info

core.familiar.get_user_info() -> table | nil

Returns familiar system state for the character, or nil if unavailable.

PropertyTypeDescription
fatiguenumberCurrent fatigue value
inventory_sizenumberFamiliar inventory capacity
summoned_snnumberSerial number of summoned familiar (0 if none)
versionnumberFamiliar system version
active_presetnumberActive familiar preset index (0-4)
active_badge_presetnumberActive badge preset index (0-4)
last_fatigue_timenumberWhen fatigue was last incremented, as a Unix timestamp (seconds); 0 if unset
lua
local info = core.familiar.get_user_info()
if info then
    core.log("Preset: " .. info.active_preset)
    core.log("Fatigue: " .. info.fatigue)
end

core.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.

lua
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.

lua
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
end

core.familiar.get_preset_badges(preset_index: number) -> table

Returns a table of 8 badge IDs for the specified badge preset (0-4).

lua
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
end

Mutations

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.

lua
-- 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.

lua
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.

lua
-- 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")
end

core.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.

lua
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.

lua
core.familiar.set_lock(sn, true) -- protect a keeper

core.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.

lua
core.familiar.decompose({ 111, 222, 333 })

Examples

List all familiars with potentials

lua
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))
end

Show equipped setup

lua
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