Local Player
On this page
The Player object represents the local player character. Provides access to stats, buffs, position, and other player data.
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.
local player = core.object_manager.get_local_player()
if not player or not player:is_valid() then
return -- Exit early
endBasic 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 (0–100), 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.
| Field | Description |
|---|---|
str | Total STR |
dex | Total DEX |
int | Total INT |
luk | Total LUK |
ap | Unspent 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 (0–100). It decays over time and the pet despawns at 0, so a long-running script should watch it and feed.
for i, pet in ipairs(player:get_pets()) do
if pet.repleteness < 30 then
core.log("pet " .. i .. " is hungry")
end
endplayer:get_job() -> number
Returns the job ID.
Common Job IDs
| Job ID | Class |
|---|---|
0 | Beginner |
100 | Warrior |
200 | Magician |
300 | Bowman |
400 | Thief |
500 | Pirate |
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.
local hp_percent = (player:get_health() / player:get_max_health()) * 100
if hp_percent < 50 then
core.input.use_item(2000001) -- Use HP potion
endCurrency & 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.
| Field | Description |
|---|---|
runtime_sec | Seconds since tracking started |
meso_gained | Gross meso earned this session |
meso_per_hour | Meso per hour |
exp_gained | Gross EXP earned this session |
exp_per_hour | EXP per hour |
exp_percent_per_hour | Level progress per hour, in percent |
Rates are per hour, matching the web dashboard. For a per-minute figure, divide by 60:
local stats = player:get_session_stats()
if stats then
core.log(string.format("%.2fm meso/min", stats.meso_per_hour / 60 / 1e6))
endThe 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.
local pos = player:get_position()
-- pos.x, pos.yplayer: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.
local sp = player:get_server_position()
-- sp.x, sp.yplayer: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.
if player:is_left() then
core.log("Facing left")
else
core.log("Facing right")
endHyperstats
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.
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.
tier0= beginner skills (separate novice-SP pool)tier1+ = 1st / 2nd / 3rd / 4th / 5th job advancements
local sp = player:get_skill_sp(4) -- 4th job SP availableplayer: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.
-- 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)
endPresets
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 (0–2). Returns true, 0 when sent, otherwise false and the client's reason code (15 = already active, 12 = dead, 1 = blocked).
-- 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
endSymbols
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
| Property | Type | Description |
|---|---|---|
index | number | Region slot 0-5 (region order) |
position | number | Equip position |
level | number | Current symbol level |
exp | number | Growth EXP toward the next level |
max_level | number | Level cap (arcane 20, sacred 11) |
exp_to_next | number | EXP required to reach the next level |
can_level_up | boolean | true when exp >= exp_to_next and below the cap |
is_max | boolean | true 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.
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
endBuff Management
Buff Object Structure
| Property | Type | Description |
|---|---|---|
id | number | Unique buff identifier |
type | number | Buff type identifier |
name | string | Readable buff name |
time_remaining | number | Milliseconds 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
-- 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