================================================================================ VIOLET LUA SDK - COMPLETE API REFERENCE ================================================================================ Ultra-comprehensive plain text reference for LLM consumption. Maximum information density. Token-efficient format. ================================================================================ MANDATORY PLUGIN STRUCTURE ================================================================================ Every script MUST begin with this exact structure: plugin = { name = "Script Name", version = "1.0.0", author = "Author Name", description = "Script description", load = true -- true = auto-load on start, false = manual load } ================================================================================ CORE CALLBACKS ================================================================================ Scripts operate through callback functions. Define what you need: on_tick() - Called every ~25ms during gameplay - Use for: game logic, movement, combat, automation - Runs when player is loaded and game active on_render_visuals() - Called every frame - Use ONLY for: rendering graphics, UI elements, overlays - NEVER put game logic here on_map_load() - Called when loading a new map - Use for: map-specific initialization, resetting state on_login_tick() - Called during login/character selection screen - Use for: pre-game logic, character selection automation on_packet_recv(packet) - Called when receiving network packet from server - Args: packet (CInPacket object) - Use for: packet analysis, custom protocol handling on_packet_send(packet) - Called when sending network packet to server - Args: packet (COutPacket object) - Use for: packet modification, network monitoring ================================================================================ GUARD CLAUSE PATTERN (CRITICAL) ================================================================================ ALWAYS validate objects before use to prevent crashes: function on_tick() local player = core.object_manager.get_local_player() if not player or not player:is_valid() then return end local map = core.object_manager.get_current_map() if not map or not map:is_valid() then return end -- Safe to use player and map here end ================================================================================ CORE MODULE API ================================================================================ core.log(msg: string) - Log informational message to console - Example: core.log("Script started") core.log_error(msg: string) - Log error message to console - Example: core.log_error("Failed to find NPC") core.log_warning(msg: string) - Log warning message to console - Example: core.log_warning("Low HP detected") core.get_world_id() -> number | nil - Returns current world/server ID - nil if not in game core.get_channel_id() -> number | nil - Returns current channel number - nil if not in game core.get_update_time() -> number - Returns time between ticks in milliseconds - Typically ~25ms core.change_channel(channel_id: number) - Change to specified channel - Example: core.change_channel(1) core.is_solving_rune() -> boolean - Returns true if currently solving rune puzzle - Use to pause automation during rune solving core.is_evading() -> boolean - Returns true if evasion system active - Use to pause actions during evasion core.is_in_cutscene() -> boolean | nil - Returns true if in cutscene/dialogue - nil if state unknown ================================================================================ OBJECT MANAGER API ================================================================================ core.object_manager.get_local_player() -> Player | nil - Returns local player object - nil if player not loaded - ALWAYS validate with is_valid() before use core.object_manager.get_current_map() -> Map | nil - Returns current map object - nil if map not loaded - ALWAYS validate with is_valid() before use ================================================================================ PLAYER OBJECT API ================================================================================ player:is_valid() -> boolean - Check if player object is valid - ALWAYS call before using other methods player:get_id() -> number - Returns character ID player:get_name() -> string - Returns character name 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 player:get_level() -> number - Returns character level player:get_exp() -> number - Returns current experience points player:get_meso() -> number - Returns current mesos (currency) player:get_job() -> number - Returns job ID - Example: 100=Warrior, 200=Magician, 300=Bowman, 400=Thief, 500=Pirate player:get_position() -> {x: number, y: number} - Returns current position coordinates - Example: local pos = player:get_position(); print(pos.x, pos.y) player:has_buff(buff_id: number) -> boolean - Check if specific buff is active - Example: if player:has_buff(2001002) then ... end player:get_buff(buff_id: number) -> {id, type, name, time_remaining} | nil - Get detailed buff information - Returns nil if buff not active - time_remaining in milliseconds player:get_buffs() -> table<{id, type, name, time_remaining}> - Returns array of all active buffs - Example: for _, buff in ipairs(player:get_buffs()) do ... end ================================================================================ MAP OBJECT API ================================================================================ map:is_valid() -> boolean - Check if map object is valid - ALWAYS call before using other methods map:get_id() -> number - Returns map ID - Example: 100000000 = Henesys map:is_town() -> boolean - Returns true if map is a town (safe zone) map:get_burning_stage() -> number | nil - Returns burning field stage (0-10) - nil if not a burning field map:get_drops() -> table<{id, position: {x, y}, type}> - Returns array of all drops on map - type: item type identifier - Example: for _, drop in ipairs(map:get_drops()) do ... end map:get_portals() -> table<{position: {x, y}, type, target_map_id}> - Returns array of all portals - Use for navigation and map transitions map:get_mobs() -> table - Returns array of all regular mobs - Excludes bosses map:get_bosses() -> table - Returns array of all boss mobs map:get_mob_count() -> number - Returns total number of mobs (regular + bosses) map:get_npcs() -> table - Returns array of all NPCs on map map:get_footholds() -> table<{x1, y1, x2, y2}> - Returns array of all footholds (ground platforms) - Use for pathfinding and valid position checking map:get_ladders() -> table<{x, y1, y2}> - Returns array of all ladders/ropes - Use for vertical movement pathfinding ================================================================================ MOB OBJECT API ================================================================================ mob:is_valid() -> boolean - Check if mob object is valid - ALWAYS call before using other methods mob:get_id() -> number | nil - Returns mob template ID - nil if not available mob:get_position() -> {x: number, y: number} | nil - Returns mob position coordinates - nil if not available mob:get_name() -> string | nil - Returns mob display name - nil if not available mob:is_elite() -> boolean | nil - Returns true if mob is elite/champion - nil if state unknown ================================================================================ NPC OBJECT API ================================================================================ npc:is_valid() -> boolean - Check if NPC object is valid - ALWAYS call before using other methods npc:get_id() -> number - Returns NPC template ID npc:get_position() -> {x: number, y: number} - Returns NPC position coordinates npc:get_name() -> string - Returns NPC display name ================================================================================ INPUT MODULE API ================================================================================ core.input.use_skill(skill_id: number) - Use skill by ID - Example: core.input.use_skill(2001002) -- Magic Guard core.input.use_item(item_id: number) - Use item by ID - Example: core.input.use_item(2000001) -- Red Potion core.input.talk_to_npc(npc_id: number) - Interact with NPC by object ID (not template ID) - Get object ID from npc:get_id() from map:get_npcs() core.input.enter_portal() - Enter nearest portal - Ensure player is positioned at portal core.input.press_key(key_code: number) - Simulate key press - Use for custom keybinds core.input.teleport(x: number, y: number) - Instant teleport to coordinates - No validation - can teleport to invalid positions core.input.teleport_safe(x: number, y: number, x_offset?: number, y_offset?: number) - Teleport with foothold validation - Optional offsets for fine-tuning position - Automatically finds nearest valid foothold core.input.is_moving() -> boolean - Returns true if character is currently moving core.input.stop_moving() - Stop all movement immediately core.input.move_x(x: number) - Move to X coordinate (maintains Y position) - Character will walk/fly to position core.input.move_y(y: number) - Move to Y coordinate (maintains X position) - Use for ladder/rope climbing ================================================================================ SKILL BOOK API ================================================================================ core.skill_book.get_skill_level(skill_id: number) -> number - Returns skill level (0 if not learned) - Example: local lvl = core.skill_book.get_skill_level(2001002) core.skill_book.get_skill(skill_id: number) -> {id, type, name} | nil - Get skill details - Returns nil if skill not learned core.skill_book.get_skills() -> table<{id, type, name}> - Returns array of all learned skills core.skill_book.is_skill_on_cooldown(skill_id: number) -> boolean - Returns true if skill is on cooldown - Use before attempting to cast ================================================================================ INVENTORY API ================================================================================ TAB IDS: 1 = Equip 2 = Use 3 = Etc 4 = Setup 5 = Cash core.inventory.get_total_slots(tab_id: number) -> number - Returns total inventory slots for tab core.inventory.get_free_slots(tab_id: number) -> number - Returns number of free slots in tab core.inventory.has_item(item_id: number) -> boolean - Check if item exists in any inventory tab - Example: if core.inventory.has_item(2000001) then ... end core.inventory.get_all_items() -> table - Returns array of all items across all tabs core.inventory.get_equip_items() -> table - Returns array of items in Equip tab core.inventory.get_use_items() -> table - Returns array of items in Use tab core.inventory.get_etc_items() -> table - Returns array of items in Etc tab core.inventory.get_setup_items() -> table - Returns array of items in Setup tab core.inventory.get_cash_items() -> table - Returns array of items in Cash tab ITEM OBJECT STRUCTURE: { id = number, -- Item template ID position = number, -- Slot position in inventory count = number, -- Stack count name = string, -- Display name tab_id = number, -- Inventory tab (1-5) potentials = table, -- Array of potential names (strings, up to 7) current_star_count = number, -- Current star force max_star_count = number -- Maximum star force } ================================================================================ QUESTER MODULE API ================================================================================ core.quester.get_quest_state(quest_id: number) -> number - Returns quest state - 0 = Not started, 1 = In progress, 2 = Completed core.quester.start_quest(npc_id: number, quest_id: number) - Start quest from NPC - Use NPC template ID (not object ID) core.quester.complete_quest(npc_id: number, quest_id: number) - Complete quest with NPC - Use NPC template ID (not object ID) core.quester.can_complete_quest(npc_id: number, quest_id: number) -> boolean - Check if quest requirements are met core.quester.queue_npc_selection(selection: string) - Queue dialogue selection - Use for automating NPC conversations core.quester.clear_npc_selection() - Clear queued NPC selections ================================================================================ RUSHER MODULE API ================================================================================ core.rusher.rush(map_id: number) - Auto-navigate to target map ID - Uses portals and pathfinding - Example: core.rusher.rush(100000000) -- Rush to Henesys core.rusher.stop() - Stop current rushing operation core.rusher.is_rushing() -> boolean - Returns true if currently auto-navigating ================================================================================ IN PACKET API (RECEIVING) ================================================================================ Used in on_packet_recv(packet) callback. packet:decode_1() -> number | nil - Decode 1-byte unsigned integer - Returns nil on error packet:decode_2() -> number | nil - Decode 2-byte unsigned integer - Returns nil on error packet:decode_4() -> number | nil - Decode 4-byte unsigned integer - Returns nil on error packet:decode_8() -> number | nil - Decode 8-byte unsigned integer - Returns nil on error packet:decode_string() -> string | nil - Decode MapleStory string (length-prefixed) - Returns nil on error packet:decode_buffer(size: number) -> table | nil - Decode raw bytes into array - Returns nil on error packet:get_opcode() -> number - Get packet opcode (first 2 bytes) packet:get_length() -> number - Get total packet length in bytes packet:get_offset() -> number - Get current read position packet:set_offset(offset: number) - Set read position - Use for non-sequential reading ================================================================================ OUT PACKET API (SENDING) ================================================================================ Constructor: out_packet(opcode: number) -> COutPacket packet:encode_1(value: number) -> self - Encode 1-byte unsigned integer - Returns self for method chaining packet:encode_2(value: number) -> self - Encode 2-byte unsigned integer - Returns self for method chaining packet:encode_4(value: number) -> self - Encode 4-byte unsigned integer - Returns self for method chaining packet:encode_8(value: number) -> self - Encode 8-byte unsigned integer - Returns self for method chaining packet:encode_string(str: string) -> self - Encode MapleStory string (length-prefixed) - Returns self for method chaining packet:encode_buffer(buffer: table) -> self - Encode raw byte array - Returns self for method chaining packet:send() - Send packet to server - Example: packet:encode_1(1):encode_4(100):send() packet:to_string() -> string - Convert packet to hex string for debugging packet:get_opcode() -> number - Get packet opcode packet:get_data() -> string - Get raw packet data as string EXAMPLE: local packet = out_packet(0x0029) packet:encode_1(1):encode_4(map_id):send() ================================================================================ COMMON PATTERNS & EXAMPLES ================================================================================ --- HP/MP MONITORING --- function on_tick() local player = core.object_manager.get_local_player() if not player or not player:is_valid() then return end local hp_percent = (player:get_health() / player:get_max_health()) * 100 if hp_percent < 50 then core.input.use_item(2000001) -- Use Red Potion end end --- MOB HUNTING --- function on_tick() local player = core.object_manager.get_local_player() if not player or not player:is_valid() then return end local map = core.object_manager.get_current_map() if not map or not map:is_valid() then return end local mobs = map:get_mobs() for _, mob in ipairs(mobs) do if mob:is_valid() then local mob_pos = mob:get_position() if mob_pos then core.input.teleport_safe(mob_pos.x, mob_pos.y) core.input.use_skill(2001004) -- Attack skill break end end end end --- ITEM LOOTING --- function on_tick() local player = core.object_manager.get_local_player() if not player or not player:is_valid() then return end local map = core.object_manager.get_current_map() if not map or not map:is_valid() then return end local drops = map:get_drops() for _, drop in ipairs(drops) do local player_pos = player:get_position() local drop_pos = drop.position -- Check if drop is close local dx = math.abs(player_pos.x - drop_pos.x) local dy = math.abs(player_pos.y - drop_pos.y) if dx < 100 and dy < 50 then core.input.teleport_safe(drop_pos.x, drop_pos.y) break end end end --- NPC INTERACTION --- function on_tick() local map = core.object_manager.get_current_map() if not map or not map:is_valid() then return end local npcs = map:get_npcs() for _, npc in ipairs(npcs) do if npc:is_valid() and npc:get_name() == "Gaga" then local npc_pos = npc:get_position() core.input.teleport_safe(npc_pos.x, npc_pos.y) core.input.talk_to_npc(npc:get_id()) break end end end --- QUEST AUTOMATION --- function on_tick() local quest_id = 1000 local npc_id = 1012100 if core.quester.get_quest_state(quest_id) == 0 then -- Quest not started core.quester.start_quest(npc_id, quest_id) elseif core.quester.get_quest_state(quest_id) == 1 then -- Quest in progress if core.quester.can_complete_quest(npc_id, quest_id) then core.quester.complete_quest(npc_id, quest_id) end end end --- BUFF MAINTENANCE --- function on_tick() local player = core.object_manager.get_local_player() if not player or not player:is_valid() then return end local magic_guard_id = 2001002 if not player:has_buff(magic_guard_id) then if not core.skill_book.is_skill_on_cooldown(magic_guard_id) then core.input.use_skill(magic_guard_id) end end end --- MAP RUSHING --- function on_tick() if not core.rusher.is_rushing() then local target_map = 100000000 -- Henesys core.rusher.rush(target_map) end end --- INVENTORY CHECK --- function on_tick() local free_slots = core.inventory.get_free_slots(2) -- Use tab if free_slots < 5 then core.log_warning("Running low on inventory space!") end if core.inventory.has_item(2000001) then -- Has Red Potion end end --- PACKET READING --- function on_packet_recv(packet) local opcode = packet:get_opcode() if opcode == 0x0123 then -- Example opcode local value1 = packet:decode_4() local value2 = packet:decode_2() local str = packet:decode_string() core.log("Received: " .. tostring(value1) .. ", " .. str) end end --- PACKET SENDING --- function send_custom_packet() local packet = out_packet(0x0029) packet:encode_1(1) packet:encode_4(12345) packet:encode_string("Hello") packet:send() end --- STATE MACHINE PATTERN --- local state = "idle" function on_tick() if state == "idle" then -- Do idle logic state = "hunting" elseif state == "hunting" then -- Do hunting logic if some_condition then state = "looting" end elseif state == "looting" then -- Do looting logic state = "idle" end end --- DISTANCE CALCULATION --- function distance(pos1, pos2) local dx = pos1.x - pos2.x local dy = pos1.y - pos2.y return math.sqrt(dx * dx + dy * dy) end function on_tick() local player = core.object_manager.get_local_player() if not player or not player:is_valid() then return end local map = core.object_manager.get_current_map() if not map or not map:is_valid() then return end local player_pos = player:get_position() local closest_mob = nil local closest_dist = math.huge for _, mob in ipairs(map:get_mobs()) do if mob:is_valid() then local mob_pos = mob:get_position() if mob_pos then local dist = distance(player_pos, mob_pos) if dist < closest_dist then closest_dist = dist closest_mob = mob end end end end if closest_mob then local mob_pos = closest_mob:get_position() core.input.teleport_safe(mob_pos.x, mob_pos.y) end end ================================================================================ CRITICAL SAFETY RULES ================================================================================ 1. ALWAYS validate objects with is_valid() before calling methods 2. ALWAYS check for nil returns before using values 3. NEVER assume map or player exists - check every tick 4. Use guard clauses at start of on_tick() to exit early if invalid state 5. Wrap risky operations in pcall() for error handling: local success, result = pcall(function() return risky_operation() end) 6. Avoid infinite loops - always have exit conditions 7. Be mindful of packet manipulation - can cause disconnects/bans 8. Test scripts in safe environments first 9. Use core.log() liberally for debugging 10. Handle nil positions from mobs/npcs gracefully ================================================================================ PERFORMANCE OPTIMIZATION TIPS ================================================================================ 1. Cache frequently accessed objects: local player = core.object_manager.get_local_player() -- Use player multiple times in same tick 2. Limit iterations in loops: for i = 1, math.min(#mobs, 10) do ... end 3. Use early returns to avoid unnecessary computation: if core.is_solving_rune() then return end 4. Avoid calling expensive functions every tick: local tick_count = 0 function on_tick() tick_count = tick_count + 1 if tick_count % 10 == 0 then -- Only run every 10th tick end end 5. Prefer has_buff() over get_buff() when only checking existence 6. Use local variables for frequently accessed values: local pos = player:get_position() local x, y = pos.x, pos.y 7. Break out of loops early when target found: for _, mob in ipairs(mobs) do if condition then break end end 8. Minimize packet parsing - only decode what you need 9. Batch operations when possible instead of one-by-one 10. Profile with core.log() to identify bottlenecks ================================================================================ COMMON JOB IDS ================================================================================ Beginner: 0 Warrior: 100 - Fighter: 110, Hero: 111 - Page: 120, Paladin: 121 - Spearman: 130, Dark Knight: 131 Magician: 200 - Fire/Poison Mage: 210, Arch Mage (F/P): 211 - Ice/Lightning Mage: 220, Arch Mage (I/L): 221 - Cleric: 230, Bishop: 231 Bowman: 300 - Hunter: 310, Bow Master: 311 - Crossbowman: 320, Marksman: 321 Thief: 400 - Assassin: 410, Night Lord: 411 - Bandit: 420, Shadower: 421 Pirate: 500 - Brawler: 510, Buccaneer: 511 - Gunslinger: 520, Corsair: 521 ================================================================================ COMMON MAP IDS ================================================================================ Henesys: 100000000 Ellinia: 101000000 Perion: 102000000 Kerning City: 103000000 Lith Harbor: 104000000 Sleepywood: 105000000 Nautilus: 120000000 Orbis: 200000000 El Nath: 211000000 Ludibrium: 220000000 Leafre: 240000000 Mu Lung: 250000000 Ariant: 260000000 ================================================================================ DEBUGGING TECHNIQUES ================================================================================ 1. Log everything during development: core.log("Player HP: " .. player:get_health()) 2. Use descriptive error messages: core.log_error("Failed to find NPC: " .. npc_name) 3. Print table contents: function print_table(t) for k, v in pairs(t) do core.log(tostring(k) .. " = " .. tostring(v)) end end 4. Track execution flow: core.log("Entering hunting state") 5. Monitor packet opcodes: function on_packet_recv(packet) core.log("Opcode: " .. string.format("0x%04X", packet:get_opcode())) end 6. Use assertions for critical assumptions: assert(player:is_valid(), "Player must be valid") 7. Measure execution time: local start_time = core.get_update_time() -- ... code ... local elapsed = core.get_update_time() - start_time core.log("Execution time: " .. elapsed .. "ms") 8. Test edge cases (empty arrays, nil values, zero HP, etc.) 9. Validate data types before operations: if type(value) == "number" then ... end 10. Use separate logging for different severity levels ================================================================================ ADVANCED PATTERNS ================================================================================ --- COOLDOWN MANAGER --- local cooldowns = {} function can_use(key, cooldown_ms) local now = core.get_update_time() if not cooldowns[key] or now - cooldowns[key] >= cooldown_ms then cooldowns[key] = now return true end return false end function on_tick() if can_use("attack_skill", 1000) then core.input.use_skill(2001004) end end --- POSITION VALIDATION --- function is_valid_position(x, y, map) if not map or not map:is_valid() then return false end local footholds = map:get_footholds() for _, fh in ipairs(footholds) do if x >= fh.x1 and x <= fh.x2 then if math.abs(y - fh.y1) < 50 or math.abs(y - fh.y2) < 50 then return true end end end return false end --- PRIORITY TARGET SELECTION --- function get_priority_target(mobs) local priority_names = {"Boss", "Elite", "Mini Boss"} for _, name in ipairs(priority_names) do for _, mob in ipairs(mobs) do if mob:is_valid() and mob:get_name() == name then return mob end end end return mobs[1] -- Fallback to first mob end --- ROTATION SYSTEM --- local skill_rotation = {2001004, 2001005, 2001003} local rotation_index = 1 function on_tick() local skill = skill_rotation[rotation_index] if not core.skill_book.is_skill_on_cooldown(skill) then core.input.use_skill(skill) rotation_index = (rotation_index % #skill_rotation) + 1 end end --- MAP CHANGE DETECTION --- local last_map_id = nil function on_tick() local map = core.object_manager.get_current_map() if not map or not map:is_valid() then return end local current_map_id = map:get_id() if last_map_id ~= current_map_id then core.log("Changed map from " .. tostring(last_map_id) .. " to " .. current_map_id) last_map_id = current_map_id -- Reset state, initialize new map logic end end ================================================================================ LUA STANDARD LIBRARY NOTES ================================================================================ Violet SDK includes these Lua standard libraries: Math: math.abs, math.sqrt, math.sin, math.cos, math.floor, math.ceil, etc. String: string.format, string.sub, string.len, string.find, string.upper, etc. Table: table.insert, table.remove, table.sort, etc. Pairs/IPairs: for iteration over tables DISABLED LIBRARIES (Not Available): - os library (use core.get_update_time() for timing instead) - io library (file operations not supported) - debug library Use available libraries for data manipulation, calculations, and string operations. ================================================================================ END OF REFERENCE ================================================================================ Last updated: 2025-09-30 This reference covers 100% of discovered Violet Lua SDK APIs. For questions: Refer to Violet community Discord. ================================================================================