Violet

Local Player

On this page

The Player object represents the local player character. Provides access to stats, buffs, position, and other player data.

Always Validate

Call player:is_valid() before using any other methods. Player objects can become invalid at any time.

Functions

Validation

player:is_valid() -> boolean

Validates the object exists in the game world. Always call this first.

lua
local player = core.object_manager.get_local_player()
if not player or not player:is_valid() then
    return  -- Exit early
end

Basic Information

player:get_id() -> number

Returns the character ID.

player:get_account_id() -> number

Returns the account ID (the same for every character on the account).

player:get_name() -> string

Returns the character name.

player:get_level() -> number

Returns the character level.

player:get_exp() -> number

Returns the experience points earned within the current level.

player:get_exp_percent() -> number

Returns progress through the current level as a percentage (0100), derived from get_exp() and the current level. Returns 0 at max level.

player:get_stats() -> table | nil

Returns a snapshot of the primary stats, or nil if the character data is not loaded yet.

FieldDescription
strTotal STR
dexTotal DEX
intTotal INT
lukTotal LUK
apUnspent ability points

Totals are what the stat window shows — base plus gear and buffs.

player:is_alive() -> boolean | nil

Returns false while dead (HP 0, tombstone up), or nil if the character context is unavailable.

player:get_pets() -> table<pet>

Returns the summoned pets. Each entry has repleteness and is_active.

repleteness is the fullness meter (0100). It decays over time and the pet despawns at 0, so a long-running script should watch it and feed.

lua
for i, pet in ipairs(player:get_pets()) do
    if pet.repleteness < 30 then
        core.log("pet " .. i .. " is hungry")
    end
end

player:get_job() -> number

Returns the job ID.

Common Job IDs
Job IDClass
0Beginner
100Warrior
200Magician
300Bowman
400Thief
500Pirate

Health & Mana

player:get_health() -> number

Returns current HP.

player:get_max_health() -> number

Returns maximum HP.

player:get_mana() -> number

Returns current MP.

player:get_max_mana() -> number

Returns maximum MP.

HP/MP Percentage
lua
local hp_percent = (player:get_health() / player:get_max_health()) * 100
if hp_percent < 50 then
    core.input.use_item(2000001)  -- Use HP potion
end

Currency & Position

player:get_meso() -> number

Returns the amount of meso (currency).

player:get_sol_erda() -> number

Returns the amount of Sol Erda (the HEXA Matrix currency). Returns 0 if unavailable.

player:get_session_stats() -> table | nil

Returns what this play session has earned so far, and the rate it is earning at. Returns nil until the first sample lands.

FieldDescription
runtime_secSeconds since tracking started
meso_gainedGross meso earned this session
meso_per_hourMeso per hour
exp_gainedGross EXP earned this session
exp_per_hourEXP per hour
exp_percent_per_hourLevel progress per hour, in percent

Rates are per hour, matching the web dashboard. For a per-minute figure, divide by 60:

lua
local stats = player:get_session_stats()
if stats then
    core.log(string.format("%.2fm meso/min", stats.meso_per_hour / 60 / 1e6))
end
A rate of `0` means 'no rate available'

The totals are live from the first sample, but the rates are read off a rolling five-minute window and stay 0 until that window spans at least a minute. They also drop back to 0 whenever sampling stops — see below.

So a script acting on the rate should treat 0 as "not known yet", never as "earning nothing".

Gross totals, and what a park does

"Gross" means only positive changes count. Spending meso does not reduce meso_gained, so the figure stays a record of what was farmed rather than what is left over. Levelling up does not reset exp_gained either.

Samples are only taken while in a field. Parking in the cash shop or auction house freezes the totals rather than resetting them — they resume where they left off.

The rates behave differently: once the newest sample is more than a minute old they go to 0, rather than continuing to quote a figure from before the break. After returning to a field they need about a minute of fresh samples before reading non-zero again.

player:get_position() -> table

Returns position as a table with x and y fields.

lua
local pos = player:get_position()
-- pos.x, pos.y

player:get_server_position() -> table

Returns the position the server believes the character is at, as a table with x and y. Useful when client-side movement and the server's view can drift apart (e.g. during teleport/rush automation) — checks against this position survive a desync where get_position() would lie.

lua
local sp = player:get_server_position()
-- sp.x, sp.y

player:get_move_action() -> number

Returns the raw move action value for the character.

player:is_left() -> boolean

Returns true when the character is currently facing left.

lua
if player:is_left() then
    core.log("Facing left")
else
    core.log("Facing right")
end

Hyperstats

player:get_hyperstat_sp() -> number | nil

Returns the hyperstat skill points available on the currently active preset. Returns nil if no character data is available.

lua
local sp = player:get_hyperstat_sp()

Skill Points

player:get_skill_sp(tier: number) -> number

Returns the skill points available to spend on the given job-advancement tier (the skill window's tab). Most modern jobs use per-tier Extended SP — each job advancement has its own pool — so a single SP value isn't enough; pass the tier you want.

  • tier 0 = beginner skills (separate novice-SP pool)
  • tier 1+ = 1st / 2nd / 3rd / 4th / 5th job advancements
lua
local sp = player:get_skill_sp(4)  -- 4th job SP available

player:level_up_skill(skill_id: number, count: number)

Sends a skill-up request, spending SP to raise skill_id by count levels. Server-validated (same path as the in-game + button), so invalid requests are simply rejected.

lua
-- Auto-distribute a tier's available SP into one skill
local sp = player:get_skill_sp(4)
if sp > 0 then
    player:level_up_skill(target_skill_id, sp)
end

Presets

The Character Preset window (PRESET1–5) bundles one preset per category: equip, hyper_stat, ability, union, link_skill, shortcut, familiar. All preset indices are 0-based.

player:get_character_presets() -> table<character_preset> | nil

Returns the 5 Character Preset bundles. Each is { index, name, <category> = preset }; a category the bundle leaves unchanged has no field. nil if character data is unavailable.

player:get_active_presets() -> table | nil

Returns the preset currently active in each category, keyed the same way (equip, hyper_stat, …).

player:apply_character_preset(index: number) -> boolean

Applies Character Preset bundle index — the same request the swap prompt's Confirm sends, without the prompt. Only categories that would actually change are sent. Returns false when nothing would change, the character is dead, or an exclusive request is still pending. There is no result packet; poll get_active_presets() to see it land.

player:change_equip_preset(index: number) -> boolean, number

Switches the equipment window's preset tab (02). Returns true, 0 when sent, otherwise false and the client's reason code (15 = already active, 12 = dead, 1 = blocked).

lua
-- Swap to the second bundle and wait for the equip preset to flip
local bundle = player:get_character_presets()[2]
if bundle and bundle.equip and player:apply_character_preset(bundle.index) then
    swapping_to = bundle.equip
end

function on_tick()
    if swapping_to and player:get_active_presets().equip == swapping_to then
        swapping_to = nil
        core.log("preset swap landed")
    end
end

Symbols

Arcane and Sacred symbols are individual equipped items, each with its own level and growth EXP. These return one entry per equipped symbol — unequipped region slots are skipped.

Symbol Object Structure

PropertyTypeDescription
indexnumberRegion slot 0-5 (region order)
positionnumberEquip position
levelnumberCurrent symbol level
expnumberGrowth EXP toward the next level
max_levelnumberLevel cap (arcane 20, sacred 11)
exp_to_nextnumberEXP required to reach the next level
can_level_upbooleantrue when exp >= exp_to_next and below the cap
is_maxbooleantrue when at the level cap

player:get_arcane_symbols() -> table<symbol>

Returns an array of the player's equipped Arcane symbols, in Arcane River region order.

player:get_sacred_symbols() -> table<symbol>

Returns an array of the player's equipped Sacred (Authentic) symbols, in Grandis region order.

lua
for _, sym in ipairs(player:get_arcane_symbols()) do
    if sym.can_level_up then
        core.log(("arcane #%d ready: lvl %d (%d/%d)"):format(
            sym.index, sym.level, sym.exp, sym.exp_to_next))
    end
end

Buff Management

Buff Object Structure

PropertyTypeDescription
idnumberUnique buff identifier
typenumberBuff type identifier
namestringReadable buff name
time_remainingnumberMilliseconds remaining

player:has_buff(buff_id: number) -> boolean

Returns true if the buff is currently active. More efficient than iterating get_buffs().

player:get_buff(buff_id: number) -> buff | nil

Returns a specific buff object if active, or nil if not found.

player:get_buffs() -> table<buff>

Returns all active buffs as an array.

Example

lua
-- Maintain buff and monitor HP
local MAGIC_GUARD = 2001002

function on_tick(stage)
    if stage ~= core.stage.FIELD then return end

    local player = core.object_manager.get_local_player()
    if not player or not player:is_valid() then return end

    -- HP monitoring
    local hp_percent = (player:get_health() / player:get_max_health()) * 100
    if hp_percent < 30 then
        core.input.use_item(2000001)  -- Red Potion
    end

    -- Buff maintenance
    if not player:has_buff(MAGIC_GUARD) then
        if not core.skill_book.is_skill_on_cooldown(MAGIC_GUARD) then
            core.input.use_skill(MAGIC_GUARD)
        end
    end
end