================================================================================ VIOLET LUA SDK - COMPLETE API REFERENCE FOR AI/LLM ================================================================================ Ultra-comprehensive plain text reference optimized for AI consumption. Maximum information density. Token-efficient format. Last Updated: 2026-03-16 (recently updated — includes HTTP module, on_evasion callback, core.menu, JSON table support, and complete settings reference) ================================================================================ MANDATORY PLUGIN STRUCTURE ================================================================================ Every script MUST begin with this structure: plugin = { name = "Script Name", version = "1.0.0", author = "Author Name", description = "Brief description", load = true -- Auto-load on injection } ================================================================================ CORE CALLBACKS ================================================================================ Scripts execute through callbacks. Define only what you need: on_tick() - Called every ~30ms during gameplay - Primary location for game logic, movement, combat - Runs when player is loaded and game active on_map_load() - Called when loading a new map - Use for map-specific initialization - Called BEFORE on_tick for the new map on_login_tick() - Called during login/character selection screen - Use for pre-game automation on_packet_recv(packet: in_packet) -> boolean - Called when receiving packet from server - Return true to allow, false to block - Requires plugin metadata structure on_packet_send(packet: in_packet) -> boolean - Called when sending packet to server - Packet is provided as in_packet for read-only decoding (same type as on_packet_recv) - Return true to allow, false to block - Requires plugin metadata structure on_evasion(player_names: table) - Called once when evasion first triggers - player_names: array of strings (detected non-whitelisted player names) - Fires BEFORE the evasion action begins (CC, logout, etc.) - Use for logging, notifications, or custom evasion logic ================================================================================ GUARD CLAUSE PATTERN (CRITICAL) ================================================================================ ALWAYS validate objects before use: 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 - LOGGING ================================================================================ core.log(message: string) - Log white message with [-] prefix core.log_error(message: string) - Log red message with [!] prefix core.log_warning(message: string) - Log yellow message with [?] prefix NOTE: Use tostring() to convert numbers/booleans for logging ================================================================================ CORE MODULE - GAME INFORMATION ================================================================================ 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.change_channel(channel_id: number) - Switch to specified channel - Must be out of combat core.is_evading() -> boolean - Returns true if currently evading core.is_solving_rune() -> boolean - Returns true if solving rune puzzle - Use to pause automation during runes core.is_in_cutscene() -> boolean | nil - Returns true if in cutscene/dialogue - nil if state unknown core.get_update_time() -> number - Returns current game update time in milliseconds - Use for timing operations and delays core.is_rushing() -> boolean - Returns true if currently rushing to a map core.use_hyper_rock(map_id: number) -> boolean - Teleport to map using Hyper Teleport Rock - Returns true on success, false if unavailable ================================================================================ CORE MODULE - SETTINGS API ================================================================================ Dynamic runtime access to game settings. Changes sync to web UI automatically. FINDING SETTING PATHS: Developer Mode (Recommended): 1. Open Settings in web menu 2. Press Ctrl+Shift+D for Developer Mode 3. Click any setting badge to copy its path Path Format: "category.subcategory.property" Examples: "autos.auto_pot.hp.enabled", "hacks.godmode", "combat.kami.enabled" core.get_setting(path: string) -> value | nil - Retrieves setting value by path - Returns boolean, number, string, or table - Array/object settings (e.g. skill injection lists) returned as Lua tables - Returns nil if setting not found Example: local auto_hp = core.get_setting("autos.auto_pot.hp.enabled") local threshold = core.get_setting("autos.auto_pot.hp.value") local skills = core.get_setting("combat.skill_injection.skills") -- skills is a table: {{id=123, delay=200, hits=1, type=0}, ...} core.set_setting(path: string, value: boolean|number|string|table) -> boolean - Updates setting value - Value type MUST match setting's expected type - Lua tables are converted to JSON arrays/objects for array settings - Returns true on success, false on failure - Changes sync automatically to web UI Example: core.set_setting("autos.auto_pot.hp.enabled", true) core.set_setting("autos.auto_pot.hp.value", 75) core.set_setting("hacks.godmode", false) core.set_setting("combat.skill_injection.skills", { { id = 2321006, delay = 200, hits = 1, type = 0 }, { id = 2321007, delay = 150, hits = 2, type = 1 } }) TYPE MISMATCH ERROR: -- WRONG: Will throw Lua error core.set_setting("autos.auto_pot.hp.enabled", 123) -- CORRECT: core.set_setting("autos.auto_pot.hp.enabled", true) NOTE: Dropdown settings store a 0-based INDEX, not the display string. COMMON SETTING PATHS (75 total — see Settings Reference page for complete list): Autos: "autos.auto_pot.hp.enabled" -- boolean "autos.auto_pot.hp.value" -- number (0-100) "autos.auto_pot.hp.keybind" -- string "autos.auto_pot.mp.enabled" -- boolean "autos.auto_pot.mp.value" -- number (0-100) "autos.auto_pot.mp.keybind" -- string "autos.auto_login.enabled" -- boolean "autos.auto_login.world_id" -- number (dropdown index, see world table) "autos.auto_login.channel" -- number (dropdown: 0=random, 1-40=specific) "autos.auto_login.char_index" -- number "autos.evasion.type" -- number (dropdown: 0=Next Map CC, 1=Disable, 2=Logout, 3=Terminate) "autos.evasion.whitelisted_igns" -- table (string array) Hacks: "hacks.godmode" -- boolean "hacks.bossing_godmode" -- boolean "hacks.pet_loot" -- boolean "hacks.speedy_fma" -- boolean Combat: "combat.kami.enabled" -- boolean "combat.kami.type" -- number (dropdown: 0=Closest, 1=Random, 2=Random Speedy) "combat.kami.kami_exp" -- boolean "combat.kami.kami_loot" -- boolean "combat.kami.x_offset" -- number "combat.kami.y_offset" -- number "combat.skill_injection.enabled" -- boolean "combat.skill_injection.safe_mode" -- boolean "combat.skill_injection.skills" -- table (structured array, see below) Items: "items.filter_enabled" -- boolean "items.filter_mode" -- number (dropdown: 0=blacklist, 1=whitelist) "items.filtered_items" -- table (int array) "items.meso_filter_enabled" -- boolean "items.min_meso_amount" -- number Map: "map.rush_by_level" -- table (structured array, see below) "map.spawn_points" -- table (structured array, see below) Packets: "packets.streaming_enabled" -- boolean "packets.blocked_incoming_opcodes" -- table (int array) "packets.blocked_outgoing_opcodes" -- table (int array) "packets.ignored_incoming_opcodes" -- table (int array) "packets.ignored_outgoing_opcodes" -- table (int array) Macros: "macros.list" -- table (structured array, see below) WORLD INDEX TABLE (for autos.auto_login.world_id dropdown): 0 - Scania (NA, default) 1 - Bera (NA) 2 - Kronos (NA, Reboot, 40 channels) 3 - Hyperion (NA) 4 - NA CW Heroic (10 channels) 5 - NA CW Interactive (10 channels) 6 - Luna (EU) 7 - Solis (EU) 8 - EU CW Heroic (10 channels) 9 - EU CW Interactive (10 channels) Example - Auto login to Kronos, Channel 5, Character 2: core.set_setting("autos.auto_login.world_id", 2) -- Kronos (index 2) core.set_setting("autos.auto_login.channel", 5) -- Channel 5 core.set_setting("autos.auto_login.char_index", 2) -- 3rd character ARRAY SETTING STRUCTURES: skill_injection.skills: { id = int, delay = int, hits = int, type = int } type: 0=Generic, 1=Melee, 2=Magic, 3=Shoot, 4=Use Skill, 5=Safe Mode map.rush_by_level: { min_level = int, max_level = int, map_id = int } map.spawn_points: { map_id = int, x = int, y = int, label = string } macros.list: { name = string, key = string, delay = int, enabled = bool } Simple arrays (int): items.filtered_items, packets.blocked_*_opcodes, packets.ignored_*_opcodes Simple arrays (string): autos.evasion.whitelisted_igns ================================================================================ MENU MODULE (core.menu) - CUSTOM SETTINGS CREATION ================================================================================ Scripts can create settings that appear in the web UI under the "Lua" tab. All functions are idempotent (re-calling with existing ID returns true). Use core.get_setting/core.set_setting to read/write values by ID. SANDBOXING: - IDs are auto-prefixed with "lua." (e.g. "myScript.enabled" -> "lua.myScript.enabled") - All elements forced to "Lua" tab — the tab parameter is accepted but ignored - Use the panel parameter to organize sections within the Lua tab core.menu.create_checkbox(id, label, tab, panel, default_value [, tooltip]) -> boolean - Create a checkbox (boolean) setting core.menu.create_slider_int(id, label, tab, panel, default, min, max [, step [, tooltip]]) -> boolean - Create an integer slider setting (step defaults to 1) core.menu.create_slider_float(id, label, tab, panel, default, min, max [, step [, tooltip [, rounding]]]) -> boolean - Create a float slider setting (step defaults to 0.1, rounding defaults to 2) core.menu.create_dropdown(id, label, tab, panel, options_table, default_index [, tooltip]) -> boolean - Create a dropdown setting - options_table: Lua array of strings {"Option A", "Option B"} - default_index: 0-based index core.menu.create_keybind(id, label, tab, panel [, default_key [, tooltip]]) -> boolean - Create a keybind setting core.menu.create_input_text(id, label, tab, panel [, default_value [, tooltip]]) -> boolean - Create a text input setting Example: core.menu.create_checkbox("myScript.enabled", "Enable", "Lua", "My Script", false) core.menu.create_slider_int("myScript.delay", "Delay", "Lua", "My Script", 100, 0, 5000, 50) -- IDs become "lua.myScript.enabled" and "lua.myScript.delay" local enabled = core.get_setting("lua.myScript.enabled") ================================================================================ OBJECT MANAGER ================================================================================ 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 ================================================================================ player:is_valid() -> boolean - ALWAYS call before using other methods player:get_id() -> number player:get_name() -> string player:get_health() -> number player:get_max_health() -> number player:get_mana() -> number player:get_max_mana() -> number player:get_level() -> number player:get_exp() -> number player:get_meso() -> number player:get_job() -> number player:get_position() -> {x: number, y: number} - Returns position table - Example: local pos = player:get_position(); print(pos.x, pos.y) player:has_buff(buff_id: number) -> boolean - Returns true if buff active - More efficient than get_buff() for checking existence player:get_buff(buff_id: number) -> buff | nil - Returns buff object or nil - Buff: {id, type, name, time_remaining} - time_remaining in milliseconds player:get_buffs() -> table - Returns array of all active buffs - Buff: {id, type, name, time_remaining} ================================================================================ MAP OBJECT ================================================================================ map:is_valid() -> boolean - ALWAYS call before using other methods map:get_id() -> number - Returns map ID (e.g., 100000000 = Henesys) map:is_town() -> boolean - Returns true if map is a town map:get_burning_stage() -> number | nil - Returns burning XP stage (0-10) - nil if not burning field map:get_drops() -> table - Returns all drops on map - Drop: {id, position: {x, y}, type} map:get_portals() -> table - Returns all portals - Portal: {position: {x, y}, type, target_map_id} map:get_mobs() -> table - Returns all mobs on map map:get_bosses() -> table - Returns all boss mobs map:get_mob_count() -> number - Returns total mob count - More efficient than iterating get_mobs() map:get_npcs() -> table - Returns all NPCs on map map:get_footholds() -> table - Returns all platforms - Foothold: {x1, y1, x2, y2} map:get_ladders() -> table - Returns all ladders/ropes - Ladder: {x, y1, y2} ================================================================================ MOB OBJECT ================================================================================ mob:is_valid() -> boolean - ALWAYS call before using other methods mob:get_id() -> number | nil - Returns mob template ID mob:get_position() -> {x: number, y: number} | nil - Returns position or nil mob:get_name() -> string | nil - Returns mob name or nil mob:is_elite() -> boolean | nil - Returns true if elite/champion - nil if unknown ================================================================================ NPC OBJECT ================================================================================ npc:is_valid() -> boolean - ALWAYS call before using other methods npc:get_id() -> number - Returns NPC template ID npc:get_position() -> {x: number, y: number} - Returns NPC position npc:get_name() -> string - Returns NPC name ================================================================================ INPUT MODULE - GAME INTERACTION ================================================================================ core.input.use_skill(skill_id: number) - Cast skill by ID - Check cooldown first with skill_book.is_skill_on_cooldown() core.input.use_item(item_id: number) - Use item from inventory - Verify existence first with inventory.has_item() core.input.talk_to_npc(npc_id: number) - Initiate NPC dialogue - Ensure NPC is on current map core.input.enter_portal() - Enter portal at player position - Ensure player is on portal first core.input.press_key(key_code: number) - Simulate key press - Requires valid local player - See: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes ================================================================================ INPUT MODULE - MOVEMENT ================================================================================ core.input.teleport(x: number, y: number) - Instant teleport to coordinates - WARNING: Spamming triggers anti-cheat - Use only for one-time teleports to portals/NPCs core.input.teleport_safe(x: number, y: number, x_offset?: number, y_offset?: number) - Safe teleport with foothold validation - Moves toward position over time (like Kami) - Optional x_offset and y_offset parameters core.input.is_moving() -> boolean - Returns true if player moving core.input.stop_moving() - Stop all movement immediately core.input.move_x(x_coord: number) - Move to X coordinate (maintains Y) core.input.move_y(y_coord: number) - Move to Y coordinate (maintains X) NOTE: Movement functions don't account for manual player input ================================================================================ HTTP MODULE (core.http) - ASYNC HTTP REQUESTS ================================================================================ All functions are async. Callback fires on the next tick after completion. Last argument is always the callback function. core.http.get(url, [headers,] callback) - GET request - headers: optional table of string key-value pairs core.http.post(url, body, [content_type, [headers,]] callback) - POST request - content_type defaults to "application/json" core.http.put(url, body, [content_type, [headers,]] callback) - PUT request - content_type defaults to "application/json" core.http.delete(url, [headers,] callback) - DELETE request core.http.request(method, url, [body, [headers,]] callback) - Any HTTP method RESPONSE OBJECT (passed to callback): { status = number, -- HTTP status code (200, 404, etc.) or 0 if failed body = string, -- Response body headers = table -- Response headers as key-value pairs } EXAMPLES: -- Simple GET core.http.get("https://httpbin.org/get", function(response) core.log("Status: " .. response.status) core.log("Body: " .. response.body) end) -- POST with headers core.http.post("https://api.example.com/webhook", '{"event":"test"}', "application/json", { ["Authorization"] = "Bearer token" }, function(response) core.log("Status: " .. response.status) end ) -- Discord webhook from on_evasion function on_evasion(player_names) local names = table.concat(player_names, ", ") core.http.post("https://discord.com/api/webhooks/YOUR_URL", '{"content":"Evasion: ' .. names .. '"}', "application/json", function(response) end ) end ================================================================================ SKILL BOOK ================================================================================ core.skill_book.get_skill_level(skill_id: number) -> number - Returns skill level (0 if not learned) core.skill_book.get_skill(skill_id: number) -> skill | nil - Returns skill object or nil - Skill: {id, type, name} core.skill_book.get_skills() -> table - Returns all learned skills - Skill: {id, type, name} core.skill_book.is_skill_on_cooldown(skill_id: number) -> boolean - Returns true if on cooldown ================================================================================ INVENTORY ================================================================================ TAB IDs: 1 = Equip 2 = Use 3 = Etc 4 = Setup 5 = Cash core.inventory.get_total_slots(tab_id: number) -> number - Returns total slots in tab core.inventory.get_free_slots(tab_id: number) -> number - Returns free slots in tab core.inventory.has_item(item_id: number) -> boolean - Returns true if item exists in any tab core.inventory.get_all_items() -> table - Returns all items across all tabs core.inventory.get_equip_items() -> table - Returns unequipped equip items in inventory (tab_id=1) - These are equips in your bag, NOT currently worn core.inventory.get_equipped_items() -> table - Returns items currently worn by the character - Position values are NEGATIVE (equipment slot IDs) - Includes potentials and star counts - Example: for _, item in ipairs(core.inventory.get_equipped_items()) do ... end core.inventory.get_use_items() -> table - Returns Use tab items (tab_id=2) core.inventory.get_etc_items() -> table - Returns Etc tab items (tab_id=3) core.inventory.get_setup_items() -> table - Returns Setup tab items (tab_id=4) core.inventory.get_cash_items() -> table - Returns Cash tab items (tab_id=5) ITEM STRUCTURE: { id = number, -- Item template ID position = number, -- Slot index count = number, -- Quantity name = string, -- Display name tab_id = number, -- Tab (1-5) potentials = table, -- Array of strings (Equip only, up to 7) current_star_count = number, -- Star force (Equip only) max_star_count = number -- Max star force (Equip only) } ================================================================================ QUESTER ================================================================================ core.quester.get_quest_state(quest_id: number) -> number - Returns quest state: -1 = Not available 0 = Available 1 = In progress 2 = Completed core.quester.start_quest(npc_id: number, quest_id: number) - Start quest with NPC - Check state first with get_quest_state() core.quester.complete_quest(npc_id: number, quest_id: number) - Complete quest with NPC - Check if completable first with can_complete_quest() core.quester.can_complete_quest(npc_id: number, quest_id: number) -> boolean - Returns true if quest requirements met core.quester.queue_npc_selection(selection: string) - Queue dialogue option substring - When NPC dialogue appears, option containing substring is selected - For text input, queued string is used as answer core.quester.clear_npc_selection() - Clear entire NPC selection queue ================================================================================ RUSHER ================================================================================ core.rusher.rush(map_id: number) - Auto-navigate to target map - Handles pathfinding and portals automatically core.rusher.stop() - Stop current rushing immediately core.rusher.is_rushing() -> boolean - Returns true if currently rushing ================================================================================ IN PACKET (RECEIVING) ================================================================================ Used in on_packet_recv(packet) callback. packet:get_opcode() -> number - Returns packet opcode packet:decode_1() -> number - Read 1 byte (0-255) packet:decode_2() -> number - Read 2 bytes (0-65535) packet:decode_4() -> number - Read 4 bytes (0-4294967295) packet:decode_8() -> number - Read 8 bytes packet:decode_string() -> string - Read length-prefixed string packet:decode_buffer(size: number) -> table | nil - Read N bytes as a Lua table of byte values (1-indexed) - Returns nil if not enough data remains packet:get_length() -> number - Returns total packet length in bytes packet:get_offset() -> number - Returns current read offset position packet:set_offset(offset: number) - Set read offset to specific position - Allows rewinding to re-read data NOTE: Decode methods read sequentially. Each call advances read position. Use set_offset() to rewind if you need to re-read data. ================================================================================ OUT PACKET (SENDING) ================================================================================ Constructor: out_packet(opcode: number) -> OutPacket packet:encode_1(value: number) -> self - Encode 1 byte (0-255) - Returns self for chaining packet:encode_2(value: number) -> self - Encode 2 bytes (0-65535) - Returns self for chaining packet:encode_4(value: number) -> self - Encode 4 bytes (0-4294967295) - Returns self for chaining packet:encode_8(value: number) -> self - Encode 8 bytes - Returns self for chaining packet:encode_string(text: string) -> self - Encode length-prefixed string - Returns self for chaining packet:encode_buffer(data: string) -> self - Encode raw binary data - Returns self for chaining packet:to_string() -> string - Returns hex string for debugging packet:get_opcode() -> number - Returns the packet opcode packet:get_data() -> string - Returns hex string (alias of to_string) packet:send() - Send packet to server EXAMPLE: out_packet(0x0029) :encode_1(1) :encode_4(map_id) :encode_string("test") :send() ================================================================================ COMMON PATTERNS ================================================================================ --- 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) -- 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 pos = mob:get_position() if pos then core.input.teleport_safe(pos.x, pos.y) core.input.use_skill(2001004) break end 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 buff_id = 2001002 if not player:has_buff(buff_id) then if not core.skill_book.is_skill_on_cooldown(buff_id) then core.input.use_skill(buff_id) end 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 --- 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 --- STATE MACHINE --- local state = "idle" function on_tick() if state == "idle" then state = "hunting" elseif state == "hunting" then -- hunting logic if condition then state = "looting" end elseif state == "looting" then -- looting logic state = "idle" 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("Map changed to: " .. current_map_id) last_map_id = current_map_id -- Reset state for new map 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 - validate every tick 4. Use guard clauses at start of on_tick() to exit early if invalid 5. Wrap risky operations in pcall(): local success, result = pcall(function() return operation() end) 6. Avoid infinite loops - always have exit conditions 7. Be mindful of packet manipulation - can cause disconnects 8. Test scripts in safe environments first 9. Use core.log() liberally for debugging 10. Handle nil positions from mobs/npcs gracefully ================================================================================ PERFORMANCE OPTIMIZATION ================================================================================ 1. Cache frequently accessed objects within tick 2. Limit loop iterations: for i = 1, math.min(#mobs, 10) do 3. Use early returns: if core.is_solving_rune() then return end 4. Throttle expensive operations: tick_count = tick_count + 1 if tick_count % 10 == 0 then ... end 5. Prefer has_buff() over get_buff() for existence checks 6. Use local variables for frequently accessed values 7. Break out of loops early when target found 8. Minimize packet parsing - decode only what you need 9. Batch operations when possible ================================================================================ 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 ================================================================================ COMMON JOB IDS ================================================================================ Beginner: 0 Warrior: 100 Fighter: 110, Hero: 111 Page: 120, Paladin: 121 Spearman: 130, Dark Knight: 131 Magician: 200 Fire/Poison: 210, Arch Mage (F/P): 211 Ice/Lightning: 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 ================================================================================ MODULES (require) ================================================================================ Shared utility scripts live in scripts/libs/ and are imported with require(). require("module_name") -- loads scripts/libs/module_name.lua require("combat.targeting") -- loads scripts/libs/combat/targeting.lua Module names: alphanumeric, underscores, dots only (no paths like ../). Results are CACHED — each module executes once, subsequent calls return cached value. Cache clears when any script is toggled (enabled/disabled), so edits to libs are picked up. Modules return a table of functions/values. No plugin table needed. WRITING A MODULE: -- scripts/libs/utils.lua local M = {} function M.distance(pos1, pos2) local dx = pos1.x - pos2.x local dy = pos1.y - pos2.y return math.sqrt(dx * dx + dy * dy) end function M.find_closest_mob(player_pos, map) local closest, closest_dist = nil, math.huge for _, mob in ipairs(map:get_mobs()) do if mob and mob:is_valid() then local mpos = mob:get_position() if mpos then local dist = M.distance(player_pos, mpos) if dist < closest_dist then closest_dist = dist closest = mob end end end end return closest, closest_dist end return M USING A MODULE IN A SCRIPT: local utils = require("utils") plugin = {name="Hunter", version="1.0.0", author="You", description="...", load=true} 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 mob = utils.find_closest_mob(player:get_position(), map) if mob then core.input.teleport_safe(mob:get_position().x, mob:get_position().y) end end COOLDOWN MANAGER MODULE: -- scripts/libs/cooldowns.lua local M = {} local timers = {} function M.ready(key, cooldown_ms) local now = core.get_update_time() if not timers[key] or now - timers[key] >= cooldown_ms then timers[key] = now return true end return false end return M MODULE RULES: - Same sandbox: no io, os, dofile, loadfile - Place require() at script top level (outside callbacks) - Modules run in global environment (shared across all scripts) - File layout: scripts/libs/*.lua for modules, scripts/*.lua for plugins ================================================================================ LUA STANDARD LIBRARY ================================================================================ AVAILABLE: 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 Table: table.insert, table.remove, table.sort Pairs/IPairs: for iteration require(name): import modules from scripts/libs/ (see MODULES section) NOT AVAILABLE: os library (use core.get_update_time() for timing) io library (file operations not supported) debug library loadfile / dofile (use require() for shared code instead) ================================================================================ DEBUGGING TECHNIQUES ================================================================================ 1. Log everything during development 2. Use descriptive error messages 3. Print table contents with pairs/ipairs 4. Track execution flow with log statements 5. Monitor packet opcodes in packet handlers 6. Measure execution time with get_update_time() 7. Test edge cases (empty arrays, nil values, zero HP) 8. Validate data types before operations 9. Use pcall for error handling ================================================================================ END OF REFERENCE ================================================================================ This reference covers 100% of documented Violet Lua SDK APIs. Optimized for AI/LLM consumption with maximum information density. For support: Violet community Discord ================================================================================