V Matrix
Provides access to the V Matrix system — read your owned and equipped V Cores, and send craft / equip / remove / enhance requests to the server.
Action semantics — read before scripting any send_* function
1. The return value is dispatch-only. send_* returns true once the packet is on the wire — not when the server applies it. The server can silently reject (wrong state, equipped core, rate limit, lock check, etc.). Do not treat true as success.
2. inventory_index is positional and is NOT stable across server mutations. It's the index into the owned-cores array. When the server accepts a remove / disassemble / craft, the array reshuffles and every higher inventory_index decrements by one. A snapshot from a previous tick will then point at a different core, or off the end. Re-read get_slots() after every accepted action.
3. Verify by observed state change, not by the return value. Watch a server-published signal:
send_disassemble_single_request/send_craft_request→get_shard_count()changesend_equip_request/send_remove_request→get_equipped()change for that board slotsend_enhance_slot_request→get_matrix_point()change and the target board slot'senhancechange
4. Use item_sn as the stable identity. If you're tracking a specific core across ticks (per-core cooldowns, "already tried this one" sets), key on item_sn (int64 serial). The SN survives every inventory mutation; the inventory_index does not.
5. One mutation per tick. The real client UI gates these actions behind confirmation dialogs, so the server does not expect bursts. Send one request, wait until you observe the state change (or time out), then send the next.
Canonical send-and-verify loop
For anything that mutates the inventory in bulk (mass-disassemble, mass-craft), drive it from on_tick one request at a time and gate the next send on the observed effect:
local pending_sn = nil -- item_sn we last asked to disassemble
local pending_shards = 0 -- get_shard_count() at time of request
local sent_at_ms = 0
local attempted = {} -- item_sn -> tick we last tried (skip on retry)
local TIMEOUT_MS = 3000
function on_tick()
local shards_now = core.vmatrix.get_shard_count()
if pending_sn then
if shards_now > pending_shards then
-- Server accepted: shards went up, slot list has reshuffled.
pending_sn = nil
elseif core.get_update_time() - sent_at_ms > TIMEOUT_MS then
-- 3s with no shard change — give up on this one and move on.
pending_sn = nil
else
return -- still waiting for confirmation
end
end
for _, s in ipairs(core.vmatrix.get_slots()) do
if not s.protect_lock and not attempted[s.item_sn] and is_junk(s) then
attempted[s.item_sn] = core.get_update_time()
pending_sn = s.item_sn
pending_shards = shards_now
sent_at_ms = core.get_update_time()
core.vmatrix.send_disassemble_single_request(s.inventory_index)
return
end
end
endSlot Object Structure
get_slots() and get_slot_by_index() return objects with these fields. A slot is one owned V Core (in your inventory of cores, equipped or not).
| Property | Type | Description |
|---|---|---|
inventory_index | number | Position in the owned-cores array — this is the data_index value passed to send_*_request. Not stable across server mutations: when a remove / disassemble / craft is accepted, every higher inventory_index decrements by one. Re-read get_slots() after each accepted action; for cross-tick bookkeeping, key on item_sn instead |
item_sn | number | Unique int64 serial number of the core. Stable across all inventory mutations — use this when you need to identify the same core on a later tick |
id | number | V Core template ID |
grade | number | Core grade |
exp | number | Current experience |
slot | number | Inventory slot |
skill1 | number | Skill ID embedded in slot 1 |
skill2 | number | Skill ID embedded in slot 2 |
skill3 | number | Skill ID embedded in slot 3 |
slot_index | number | Board position when equipped, or -1 if the core is not on the V Matrix board. Not the right key for send_*_request — use inventory_index |
expire_date | number | FILETIME (100ns ticks since 1601-01-01). A value pointing at 2079-01-01 is the "never expires" sentinel |
protect_lock | boolean | Whether the core is protect-locked from disassembly |
Equipped Slot Object Structure
get_equipped() returns objects representing equipped V Matrix board slots. Each entry references a slot from get_slots() via data_index.
| Property | Type | Description |
|---|---|---|
data_index | number | inventory_index of the equipped core (-1 if empty) |
slot_index | number | Position on the V Matrix board |
enhance | number | Slot enhancement level |
extension | boolean | Whether the board slot has been extended |
Read Functions
core.vmatrix.get_slots() -> table<slot>
Returns an array of all owned V Core slots.
local slots = core.vmatrix.get_slots()
for _, s in ipairs(slots) do
core.log(string.format("Core %d (slot_index=%d, grade=%d)", s.id, s.slot_index, s.grade))
endcore.vmatrix.get_equipped() -> table<equip_slot>
Returns an array of equipped V Matrix board slots.
local equipped = core.vmatrix.get_equipped()
for _, e in ipairs(equipped) do
core.log(string.format("Board slot %d: data_index=%d enhance=%d",
e.slot_index, e.data_index, e.enhance))
endcore.vmatrix.get_slot_count() -> number
Returns the number of owned V Core slots.
core.vmatrix.get_equip_count() -> number
Returns the number of equipped V Matrix board slots.
core.vmatrix.get_shard_count() -> number
Returns the player's current V Core Shard count — the currency consumed by V Core crafting / enhancement. Reads the count field of the player's V Core Shard quest record. Returns 0 if the character isn't loaded.
local shards = core.vmatrix.get_shard_count()
core.log("V Core Shards: " .. shards)core.vmatrix.get_matrix_point() -> number
Returns the player's current Matrix Point balance — the currency consumed by slot enhancement / extension. Returns 0 if the character isn't loaded.
local mp = core.vmatrix.get_matrix_point()
core.log("Matrix Points: " .. mp)core.vmatrix.get_slot_by_index(slot_index: number) -> slot | nil
Looks up an owned slot by its board slot_index (i.e. its position on the V Matrix board). Returns nil if no equipped slot has that board position. To look up a core by its inventory index, just index get_slots() directly: get_slots()[inventory_index + 1].
Action Functions
core.vmatrix.send_craft_request(v_core_id: number, craft_count: number) -> boolean
Crafts craft_count V Cores of template v_core_id. Returns true if the packet was sent.
core.vmatrix.send_craft_request(100001, 1)core.vmatrix.send_equip_request(data_index1: number, data_index2: number, equip_index1: number, equip_index2: number, drag: boolean) -> boolean
Equips, swaps, or moves cores between board slots. The four indices match the LONG nDataIndex1, LONG nDataIndex2, LONG nEquipIndex1, LONG nEquipIndex2 packet shape; drag indicates the action originated from a drag-and-drop UI.
| Parameter | Description |
|---|---|
data_index1 | Source core's inventory_index (the one being placed) |
data_index2 | inventory_index of the core currently at the destination, or -1 if the slot is empty |
equip_index1 | Source board position, or -1 if the source is unequipped |
equip_index2 | Destination board position |
drag | true if performed by drag-drop, false for click-equip |
core.vmatrix.send_remove_request(data_index: number) -> boolean
Unequips the core with the given inventory_index from its board slot.
for _, e in ipairs(core.vmatrix.get_equipped()) do
if e.data_index ~= -1 then
core.vmatrix.send_remove_request(e.data_index)
end
endcore.vmatrix.send_enhance_request(data_index: number, src_data_index: number) -> boolean
Enhances the core at data_index (an inventory_index) by consuming the core at src_data_index as fuel.
core.vmatrix.send_enhance_request(target.inventory_index, fuel.inventory_index)core.vmatrix.send_disassemble_single_request(data_index: number) -> boolean
Disassembles a single unequipped core back into V Core Shards. The server rejects this against currently-equipped cores — call send_remove_request first if the core is on a board slot.
Do not call this in a tight loop
Each successful disassemble shifts every higher inventory_index down by one, so a burst of requests built from one get_slots() snapshot will target the wrong cores after the first one is accepted. Send one request per tick and gate the next on get_shard_count() going up — see the canonical send-and-verify loop above.
-- Disassemble every unequipped, non-locked core into V Core Shards.
-- One request per tick; advances only after the previous one is confirmed by
-- the shard count incrementing.
local pending_sn, pending_shards, sent_at = nil, 0, 0
function on_tick()
local shards_now = core.vmatrix.get_shard_count()
if pending_sn then
if shards_now > pending_shards then
pending_sn = nil
elseif core.get_update_time() - sent_at > 3000 then
pending_sn = nil -- timeout — server probably rejected
else
return
end
end
local equipped = {}
for _, e in ipairs(core.vmatrix.get_equipped()) do
if e.data_index ~= -1 then equipped[e.data_index] = true end
end
for _, s in ipairs(core.vmatrix.get_slots()) do
if not s.protect_lock and not equipped[s.inventory_index] then
pending_sn, pending_shards = s.item_sn, shards_now
sent_at = core.get_update_time()
core.vmatrix.send_disassemble_single_request(s.inventory_index)
return
end
end
endcore.vmatrix.send_disassemble_multiple_request(data_indices: table) -> boolean
Disassembles several unequipped cores in one packet. Takes an array of inventory_index values. Returns false if the table is empty.
Unlike send_disassemble_single_request, this is the bulk-disassemble path used by the in-game "select multiple → disassemble" UI — the whole batch is processed server-side as one transaction, so you don't need the one-per-tick get_shard_count() gating loop for it.
Snapshot once, send once
Build the indices from a single get_slots() snapshot. Don't call this in a loop — the server will reshuffle every inventory_index once the batch lands, and any subsequent batch built from the same snapshot will target the wrong cores. Re-read get_slots() after the shard count goes up if you need to send another batch.
-- Mass-disassemble every unequipped, non-locked core in one go
local equipped = {}
for _, e in ipairs(core.vmatrix.get_equipped()) do
if e.data_index ~= -1 then equipped[e.data_index] = true end
end
local indices = {}
for _, s in ipairs(core.vmatrix.get_slots()) do
if not s.protect_lock and not equipped[s.inventory_index] then
table.insert(indices, s.inventory_index)
end
end
if #indices > 0 then
core.vmatrix.send_disassemble_multiple_request(indices)
endcore.vmatrix.send_enhance_slot_request(slot_index: number) -> boolean
Raises the enhance level of the board slot at slot_index, spending Matrix Points. Returns false without doing anything if the player has no Matrix Points, so you don't have to check first.
for _, e in ipairs(core.vmatrix.get_equipped()) do
if e.enhance < 25 and core.vmatrix.get_matrix_point() > 0 then
core.vmatrix.send_enhance_slot_request(e.slot_index)
end
endcore.vmatrix.use_nodestone(amount: number) -> boolean
Uses amount Nodestones from the player's inventory.
core.vmatrix.use_nodestone(1)Examples
List every owned V Core with its skills
for _, s in ipairs(core.vmatrix.get_slots()) do
core.log(string.format("[inv=%d] id=%d grade=%d skills=%d/%d/%d %s",
s.inventory_index, s.id, s.grade, s.skill1, s.skill2, s.skill3,
s.protect_lock and "(LOCKED)" or ""))
endMap equipped board slots to core IDs
local slots = core.vmatrix.get_slots()
for _, e in ipairs(core.vmatrix.get_equipped()) do
if e.data_index == -1 then
core.log(string.format("Board %d: empty", e.slot_index))
else
local s = slots[e.data_index + 1] -- inventory_index is 0-based
core.log(string.format("Board %d: core %d (enh=%d)",
e.slot_index, s and s.id or -1, e.enhance))
end
end