mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
460 lines
20 KiB
Lua
460 lines
20 KiB
Lua
-- These flags come from the compiled Rust VM, NEVER from a Lua version marker.
|
|
-- An older VM loading this wrapper therefore reports an empty capability set.
|
|
sdk.capabilities = sdk._native_capabilities or {}
|
|
sdk._native_capabilities = nil
|
|
|
|
local submit = sdk._submit
|
|
sdk._submit = nil
|
|
local assets_objects = sdk._assets_objects
|
|
sdk._assets_objects = nil
|
|
local raycast_host = sdk._raycast
|
|
sdk._raycast = nil
|
|
local velocity_at_host = sdk._velocity_at
|
|
sdk._velocity_at = nil
|
|
local effective_inv_mass_host = sdk._effective_inv_mass
|
|
sdk._effective_inv_mass = nil
|
|
local spring_ray_host = sdk._spring_ray
|
|
sdk._spring_ray = nil
|
|
local local_ang_accel_impulse_host = sdk._local_ang_accel_impulse
|
|
sdk._local_ang_accel_impulse = nil
|
|
|
|
function sdk.log(text) submit{kind="log",text=text} end
|
|
|
|
sdk.ui = { version = 1 }
|
|
function sdk.ui.menu(key, options) submit{kind="ui_menu",key=key,options=options} end
|
|
function sdk.ui.remove_menu(key) submit{kind="ui_remove_menu",key=key} end
|
|
function sdk.ui.text(key, text) submit{kind="overlay",key=key,text=text} end
|
|
function sdk.ui.multiplayer_debug(key, text) submit{kind="multiplayer_debug",key=key,text=text} end
|
|
-- Persistent screen-space rectangles and text; update an existing key in place.
|
|
function sdk.ui.canvas(key, options)
|
|
options = options or {}
|
|
-- An empty Lua table is not a JSON array. Omit it to use the native default.
|
|
local copy = {}; for k,v in pairs(options) do copy[k]=v end
|
|
if type(copy.items)=="table" and next(copy.items)==nil then copy.items=nil end
|
|
submit{kind="ui_canvas",key=key,options=copy}
|
|
end
|
|
function sdk.ui.remove(key) submit{kind="ui_remove",key=key} end
|
|
|
|
sdk.physics = {}
|
|
function sdk.physics.spawn(key, body) submit{kind="physics_spawn",key=key,body=body} end
|
|
function sdk.physics.remove(key) submit{kind="physics_remove",key=key} end
|
|
function sdk.physics.force(key, force, point) submit{kind="physics_force",key=key,force=force,point=point} end
|
|
function sdk.physics.impulse(key, impulse, point) submit{kind="physics_impulse",key=key,impulse=impulse,point=point} end
|
|
function sdk.physics.torque(key, torque) submit{kind="physics_torque",key=key,torque=torque} end
|
|
function sdk.physics.torque_impulse(key, torque) submit{kind="physics_torque_impulse",key=key,torque=torque} end
|
|
function sdk.physics.set_linvel(key, linvel) submit{kind="physics_set_linvel",key=key,linvel=linvel} end
|
|
function sdk.physics.set_angvel(key, angvel) submit{kind="physics_set_angvel",key=key,angvel=angvel} end
|
|
function sdk.physics.set_pose(key, position, rotation) submit{kind="physics_set_pose",key=key,position=position,rotation=rotation} end
|
|
function sdk.physics.revolute(key, joint)
|
|
submit{
|
|
kind="physics_revolute",
|
|
key=key,
|
|
body_a=joint.body_a,
|
|
body_b=joint.body_b,
|
|
anchor_a=joint.anchor_a or {0,0,0},
|
|
anchor_b=joint.anchor_b or {0,0,0},
|
|
axis=joint.axis or {0,1,0},
|
|
limits=joint.limits,
|
|
contacts_enabled=joint.contacts_enabled ~= false,
|
|
}
|
|
end
|
|
function sdk.physics.joint_motor(key, motor) submit{kind="physics_joint_motor",key=key,motor=motor} end
|
|
function sdk.physics.prismatic(key, joint)
|
|
submit{
|
|
kind="physics_prismatic",
|
|
key=key,
|
|
body_a=joint.body_a,
|
|
body_b=joint.body_b,
|
|
anchor_a=joint.anchor_a or {0,0,0},
|
|
anchor_b=joint.anchor_b or {0,0,0},
|
|
axis=joint.axis or {0,-1,0},
|
|
limits=joint.limits,
|
|
contacts_enabled=joint.contacts_enabled ~= false,
|
|
}
|
|
end
|
|
function sdk.physics.joint_spring(key, spring)
|
|
submit{
|
|
kind="physics_joint_spring",
|
|
key=key,
|
|
spring={
|
|
target_position=spring.position or spring.target_position or 0,
|
|
stiffness=spring.stiffness,
|
|
damping=spring.damping,
|
|
max_force=spring.max_force,
|
|
},
|
|
}
|
|
end
|
|
function sdk.physics.remove_joint(key) submit{kind="physics_remove_joint",key=key} end
|
|
function sdk.physics.add_collider(key, opts)
|
|
opts = opts or {}
|
|
submit{
|
|
kind="physics_add_collider",
|
|
key=key,
|
|
shape=opts.shape,
|
|
position=opts.position or {0,0,0},
|
|
friction=opts.friction or 0.7,
|
|
}
|
|
end
|
|
-- JSON null reaches Lua as truthy userdata unless the host maps it to nil first.
|
|
local function as_table(v) if type(v) == 'table' then return v end return nil end
|
|
local function as_number(v) if type(v) == 'number' then return v end return nil end
|
|
function sdk.physics.read(key)
|
|
local owned = (as_table(sdk.snapshot.physics) or {}).bodies or {}
|
|
if type(owned) ~= 'table' then return nil end
|
|
return owned[key]
|
|
end
|
|
--- Sync raycast. opts.filter is "all" (default) or "ground".
|
|
function sdk.physics.raycast(origin, direction, opts)
|
|
opts = opts or {}
|
|
return as_table(raycast_host(origin, direction, {
|
|
max_distance=opts.max_distance or 100,
|
|
filter=opts.filter or "all",
|
|
exclude=opts.exclude or {},
|
|
}))
|
|
end
|
|
function sdk.physics.velocity_at(key, point)
|
|
return as_table(velocity_at_host(key, point))
|
|
end
|
|
function sdk.physics.effective_inv_mass(key, point, direction)
|
|
return as_number(effective_inv_mass_host(key, point, direction))
|
|
end
|
|
--- Bullet/Rapier ray spring-damper. Applies impulse immediately; returns load/contact.
|
|
function sdk.physics.spring_ray(key, opts)
|
|
opts = opts or {}
|
|
return as_table(spring_ray_host(key, {
|
|
local_origin = opts.local_origin or {0,0,0},
|
|
local_direction = opts.local_direction or {0,-1,0},
|
|
rest_length = opts.rest_length or 0.25,
|
|
max_travel = opts.max_travel or opts.rest_length or 0.25,
|
|
contact_radius = opts.contact_radius or 0,
|
|
stiffness = opts.stiffness or 30,
|
|
compression = opts.compression or opts.damping or 4,
|
|
relaxation = opts.relaxation or opts.damping or 4,
|
|
max_force = opts.max_force or 6000,
|
|
dt = opts.dt or (1/120),
|
|
}))
|
|
end
|
|
--- World torque impulse for body-local angular acceleration over dt (assists).
|
|
function sdk.physics.local_ang_accel_impulse(key, local_accel, dt)
|
|
return as_table(local_ang_accel_impulse_host(key, local_accel, dt or (1/120)))
|
|
end
|
|
function sdk.physics.contacts()
|
|
return (as_table(sdk.snapshot.physics) or {}).contacts or {}
|
|
end
|
|
function sdk.physics.touching()
|
|
return (as_table(sdk.snapshot.physics) or {}).touching or {}
|
|
end
|
|
|
|
sdk.graphics = { version = 4 }
|
|
-- Root is BODY-LOCAL when bound, WORLD when unbound. Values replace rather
|
|
-- than accumulate. Quaternions use {x,y,z,w}; angles/rates are radians.
|
|
function sdk.graphics.set_transform(key, options)
|
|
submit{kind="graphics_transform",key=key,options=options or {}}
|
|
end
|
|
-- Named nodes are scoped to this mesh instance. Defaults to authored-pose
|
|
-- deltas; relative=false explicitly selects an absolute parent-local pose.
|
|
function sdk.graphics.node_transform(key, node, options)
|
|
submit{kind="graphics_node",key=key,node=node,options=options or {}}
|
|
end
|
|
function sdk.graphics.reset_node(key, node)
|
|
submit{kind="graphics_reset_node",key=key,node=node}
|
|
end
|
|
function sdk.physics.debug_colliders(enabled)
|
|
submit{kind="physics_debug",enabled=enabled == true}
|
|
end
|
|
function sdk.graphics.mesh(key, opts)
|
|
opts = opts or {}
|
|
submit{
|
|
kind="graphics_mesh",
|
|
deform_nodes=(type(opts.deform_nodes)=="table" and next(opts.deform_nodes)) and opts.deform_nodes or nil,
|
|
key=key,
|
|
path=opts.path or "",
|
|
body=opts.body,
|
|
position=opts.position,
|
|
rotation=opts.rotation,
|
|
scale=opts.scale or {1,1,1},
|
|
color=opts.color or {0.85,0.85,0.9},
|
|
opacity=opts.opacity or 1,
|
|
visible=opts.visible ~= false,
|
|
}
|
|
end
|
|
function sdk.graphics.remove(key) submit{kind="graphics_remove",key=key} end
|
|
function sdk.graphics.set_visible(key, visible) submit{kind="graphics_visibility",key=key,visible=visible and true or false} end
|
|
-- Extension 3: procedural mesh buffers and scene lights. No gameplay semantics.
|
|
function sdk.graphics.mesh_buffer(key, opts)
|
|
opts = opts or {}
|
|
submit{kind="graphics_mesh_buffer",key=key,options={
|
|
body=opts.body, position=opts.position, rotation=opts.rotation,
|
|
scale=opts.scale or {1,1,1},
|
|
blend=opts.blend ~= false, unlit=opts.unlit ~= false,
|
|
visible=opts.visible ~= false,
|
|
depth_bias=opts.depth_bias or 0,
|
|
texture=opts.texture, capture=opts.capture,
|
|
tint=opts.tint or {1,1,1},
|
|
}}
|
|
end
|
|
local function mesh_buffer_payload(data)
|
|
data = data or {}
|
|
-- Empty Lua tables are maps, not arrays. Build a clean payload and omit empties.
|
|
local payload = {}
|
|
if data.positions and #data.positions > 0 then
|
|
local positions = {}
|
|
for _, p in ipairs(data.positions) do
|
|
positions[#positions+1] = {p[1], p[2], p[3]}
|
|
end
|
|
payload.positions = positions
|
|
end
|
|
if data.indices and #data.indices > 0 then
|
|
local indices = {}
|
|
for _, idx in ipairs(data.indices) do
|
|
indices[#indices+1] = math.max(0, math.floor(idx))
|
|
end
|
|
payload.indices = indices
|
|
end
|
|
if data.uvs and #data.uvs > 0 then
|
|
local uvs = {}
|
|
if type(data.uvs[1]) == "table" then
|
|
for _, uv in ipairs(data.uvs) do
|
|
uvs[#uvs+1] = uv[1]; uvs[#uvs+1] = uv[2]
|
|
end
|
|
else
|
|
for _, v in ipairs(data.uvs) do uvs[#uvs+1] = v end
|
|
end
|
|
if #uvs > 0 then payload.uvs = uvs end
|
|
end
|
|
if data.colors and #data.colors > 0 then
|
|
local colors = {}
|
|
if type(data.colors[1]) == "table" then
|
|
for _, c in ipairs(data.colors) do
|
|
colors[#colors+1] = c[1]; colors[#colors+1] = c[2]
|
|
colors[#colors+1] = c[3]; colors[#colors+1] = c[4]
|
|
end
|
|
else
|
|
for _, v in ipairs(data.colors) do colors[#colors+1] = v end
|
|
end
|
|
if #colors > 0 then payload.colors = colors end
|
|
end
|
|
if data.normals and #data.normals > 0 then
|
|
local normals = {}
|
|
for _, n in ipairs(data.normals) do
|
|
normals[#normals+1] = {n[1], n[2], n[3]}
|
|
end
|
|
payload.normals = normals
|
|
end
|
|
return payload
|
|
end
|
|
function sdk.graphics.mesh_buffer_write(key, data)
|
|
submit{kind="graphics_mesh_buffer_write",key=key,data=mesh_buffer_payload(data)}
|
|
end
|
|
function sdk.graphics.mesh_buffer_append(key, data)
|
|
submit{kind="graphics_mesh_buffer_append",key=key,data=mesh_buffer_payload(data)}
|
|
end
|
|
function sdk.graphics.light(key, opts)
|
|
opts = opts or {}
|
|
submit{kind="graphics_light",key=key,options={
|
|
kind=opts.kind or "point", body=opts.body, position=opts.position,
|
|
offset=opts.offset or {0,0,0}, direction=opts.direction,
|
|
color=opts.color or {1,1,1},
|
|
intensity=opts.intensity or 1000, range=opts.range or 10,
|
|
inner_angle=opts.inner_angle or 0.4, outer_angle=opts.outer_angle or 0.7,
|
|
}}
|
|
end
|
|
|
|
-- Audio extension 1 (backward-compatible with API 2).
|
|
-- Keys and assets are scoped to the calling mod. WAV: PCM16, mono/stereo.
|
|
-- play replaces the same key; update NEVER restarts playback.
|
|
sdk.audio = { version = 1 }
|
|
function sdk.audio.preload(path) submit{kind="audio_preload",path=path} end
|
|
function sdk.audio.play(key, opts)
|
|
opts = opts or {}
|
|
submit{kind="audio_play",key=key,options={
|
|
path=opts.path, body=opts.body, position=opts.position,
|
|
offset=opts.offset or {0,0,0}, loop=opts.loop == true,
|
|
volume=opts.volume or 1, pitch=opts.pitch or 1,
|
|
spatial=opts.spatial ~= false, spatial_scale=opts.spatial_scale or 0.1,
|
|
paused=opts.paused == true, fade_in=opts.fade_in or 0.01,
|
|
}}
|
|
end
|
|
function sdk.audio.update(key, opts)
|
|
opts = opts or {}
|
|
submit{kind="audio_update",key=key,options={
|
|
volume=opts.volume,pitch=opts.pitch,paused=opts.paused,
|
|
position=opts.position,offset=opts.offset,
|
|
}}
|
|
end
|
|
function sdk.audio.stop(key, fade_out)
|
|
submit{kind="audio_stop",key=key,fade_out=fade_out or 0.03}
|
|
end
|
|
function sdk.audio.stop_all() submit{kind="audio_stop_all"} end
|
|
|
|
sdk.player = {}
|
|
function sdk.player.suspend(suspended) submit{kind="player_suspend",suspended=suspended} end
|
|
function sdk.player.physics() return as_table(sdk.snapshot.player_physics) or {} end
|
|
function sdk.player.contacts() return sdk.player.physics().contacts or {} end
|
|
function sdk.player.joints() return sdk.player.physics().joints or {} end
|
|
function sdk.player.parts() return sdk.player.physics().parts or {} end
|
|
function sdk.player.set_joint(joint, options) submit{kind="player_joint",joint=joint,options=options} end
|
|
function sdk.player.reset_joint(joint) submit{kind="player_reset_joint",joint=joint} end
|
|
function sdk.player.reset_joints() submit{kind="player_reset_joints"} end
|
|
function sdk.player.teleport(options) submit{kind="player_teleport",options=options} end
|
|
function sdk.player.read() return as_table(sdk.snapshot.player) or {} end
|
|
function sdk.player.skaters() return as_table(sdk.snapshot.skaters) or {} end
|
|
function sdk.player.skater(id)
|
|
local all = sdk.player.skaters()
|
|
return all[tostring(id)]
|
|
end
|
|
function sdk.player.attach(body, offset) submit{kind="player_attach",body=body,offset=offset or {0,0,0}} end
|
|
function sdk.player.detach(options) submit{kind="player_detach",options=options or {}} end
|
|
function sdk.player.detach_error() return sdk.snapshot.detach_error end
|
|
function sdk.player.detaching() return sdk.snapshot.detach_pending == true end
|
|
function sdk.player.attached()
|
|
local a = as_table(sdk.snapshot.attach)
|
|
return a and a.body or nil
|
|
end
|
|
|
|
sdk.camera = { version = 2 }
|
|
-- Persistent, render-rate camera. Does not write the body's pose or velocity.
|
|
function sdk.camera.rig(body, options)
|
|
submit{kind="camera_rig",body=body,options=options or {}}
|
|
end
|
|
function sdk.camera.clear() sdk.camera.clear_follow() end
|
|
function sdk.camera.follow(body, offset) submit{kind="camera_follow",body=body,offset=offset or {0,2.5,-6}} end
|
|
function sdk.camera.clear_follow() submit{kind="camera_follow",body=nil,offset={0,2.5,-6}} end
|
|
function sdk.camera.set(position, look_at) submit{kind="camera_set",position=position,look_at=look_at} end
|
|
-- Lock the gameplay camera onto a multiplayer skater. nil restores the native camera.
|
|
function sdk.camera.watch(peer) submit{kind="camera_watch",peer=peer ~= nil and tostring(peer) or nil} end
|
|
function sdk.camera.capture(key, options) submit{kind="camera_capture",key=key,options=options} end
|
|
function sdk.camera.clear_capture(key) submit{kind="camera_clear_capture",key=key} end
|
|
|
|
sdk.session = {}
|
|
function sdk.session.info() return as_table(sdk.snapshot.session) or {} end
|
|
function sdk.session.claim() submit{kind="session_claim"} end
|
|
function sdk.session.transfer(peer) submit{kind="session_transfer",peer=tostring(peer)} end
|
|
function sdk.session.teleport(peer, options) submit{kind="session_teleport",peer=tostring(peer),options=options} end
|
|
|
|
sdk.volumes = {}
|
|
function sdk.volumes.box(key, options) submit{kind="volume_box",key=key,options=options} end
|
|
function sdk.volumes.remove(key) submit{kind="volume_remove",key=key} end
|
|
function sdk.volumes.read(key)
|
|
local owners = as_table(sdk.snapshot.volumes) or {}
|
|
return (owners[sdk.mod_id] or {})[key]
|
|
end
|
|
|
|
sdk.input = {}
|
|
function sdk.input.down(key)
|
|
local keys = sdk.snapshot.keys
|
|
return keys ~= nil and keys[key] == true
|
|
end
|
|
function sdk.input.action(id)
|
|
assert(type(id)=='number' and id%1==0 and id>=64 and id<=81,'action ID must be 64..81')
|
|
local actions = sdk.snapshot.actions
|
|
return actions and actions[id-63] or 0.0
|
|
end
|
|
function sdk.input.pad()
|
|
return as_table(sdk.snapshot.pad) or {buttons=0,triggers={0,0},left={0,0},right={0,0}}
|
|
end
|
|
|
|
sdk.assets = {}
|
|
function sdk.assets.objects(path)
|
|
return assets_objects(path)
|
|
end
|
|
|
|
sdk.net = {}
|
|
function sdk.net.info()
|
|
return as_table(sdk.snapshot.network) or {active=false,local_id="0",is_host=true,host_id="0",players={"0"},states={},status=""}
|
|
end
|
|
function sdk.net.publish(key,value) submit{kind="network_state",key=key,value=value} end
|
|
function sdk.net.read(peer,key)
|
|
local n = as_table(sdk.snapshot.network) or {}
|
|
return ((((n.states or {})[sdk.mod_id] or {})[tostring(peer)]) or {})[key]
|
|
end
|
|
function sdk.net.players()
|
|
local n = sdk.net.info()
|
|
return n.players or {n.local_id or "0"}
|
|
end
|
|
|
|
sdk.time = { elapsed = 0 }
|
|
local timers = {}
|
|
function sdk.time.after(key, seconds, callback)
|
|
assert(type(key)=='string' and #key>0 and #key<=64,'invalid timer key')
|
|
assert(type(seconds)=='number' and seconds>=0 and seconds<=86400,'invalid timer delay')
|
|
assert(type(callback)=='function','timer callback must be a function')
|
|
local count=0; for _ in pairs(timers) do count=count+1 end
|
|
assert(timers[key] or count<64,'64 timers maximum')
|
|
timers[key]={at=sdk.time.elapsed+seconds,callback=callback}
|
|
end
|
|
function sdk.time.cancel(key) timers[key]=nil end
|
|
function sdk._timers_due(dt)
|
|
local at=sdk.time.elapsed+dt
|
|
for _,timer in pairs(timers) do if timer.at<=at then return true end end
|
|
return false
|
|
end
|
|
function sdk._advance(dt)
|
|
sdk.time.elapsed=sdk.time.elapsed+dt
|
|
if next(timers)==nil then return end
|
|
local due={}
|
|
for key,timer in pairs(timers) do if timer.at<=sdk.time.elapsed then due[#due+1]=key end end
|
|
table.sort(due)
|
|
for _,key in ipairs(due) do
|
|
local timer=timers[key]
|
|
if timer and timer.at<=sdk.time.elapsed then timers[key]=nil; timer.callback() end
|
|
end
|
|
end
|
|
|
|
-- Unified low-level surface. All native IDs below are zero-based; Lua lists are not.
|
|
sdk.commands = {}
|
|
local command_serial, command_pending = 0, {}
|
|
function sdk.commands.request(key, command)
|
|
command_serial=command_serial+1;command_pending[key]=command_serial
|
|
submit{kind="request",key=key,token=command_serial,command=command}
|
|
return command_serial
|
|
end
|
|
function sdk.commands.result(key)
|
|
local result=((sdk.snapshot.command_results or {})[sdk.mod_id] or {})[key]
|
|
if result and result.token==command_pending[key] then return result end
|
|
end
|
|
sdk.engine = {version=1}
|
|
function sdk.engine.systems() return (sdk.snapshot.engine or {}).systems or {} end
|
|
function sdk.engine.inspect(key,system) sdk.commands.request(key,{kind="engine_inspect",system=system}) end
|
|
function sdk.engine.read(system)
|
|
local s=sdk.snapshot
|
|
if system=="input" then return {pad=s.pad,actions=s.actions,keys=s.keys} end
|
|
if system=="commands" then return (s.command_results or {})[sdk.mod_id] end
|
|
local key=({player="player",rig="player_physics",bodies="physics",world="map",camera="camera",network="network",scoring="player"})[system]
|
|
if key then return s[key] end
|
|
return (s.engine or {})[system]
|
|
end
|
|
sdk.rig = {}
|
|
function sdk.rig.read(fields)
|
|
if fields and sdk._rig_snapshot then return sdk._rig_snapshot(fields) end
|
|
return sdk.player.physics()
|
|
end
|
|
function sdk.rig.parts() return sdk.rig.read({"parts"}).parts or {} end
|
|
function sdk.rig.joints() return sdk.rig.read({"joints"}).joints or {} end
|
|
function sdk.rig.contacts() return sdk.rig.read({"contacts"}).contacts or {} end
|
|
function sdk.rig.configure_joint(index,options) sdk.player.set_joint(index,options) end
|
|
function sdk.rig.reset_joint(index) sdk.player.reset_joint(index) end
|
|
function sdk.rig.reset() sdk.player.reset_joints() end
|
|
sdk.bodies = {}
|
|
function sdk.bodies.read(ref)
|
|
if ref.kind=="mod" then return sdk.physics.read(ref.key) end
|
|
local rig=sdk.rig.read();local list=ref.kind=="skater" and rig.parts or ref.kind=="board" and rig.board
|
|
for _,b in ipairs(list or {}) do if b.index==ref.index then return b end end
|
|
end
|
|
function sdk.bodies.impulse(ref,value,point)
|
|
if ref.kind=="mod" then sdk.physics.impulse(ref.key,value,point)
|
|
else submit{kind="native_impulse",body={kind=ref.kind,index=ref.index},impulse=value,point=point,angular=false} end
|
|
end
|
|
function sdk.bodies.angular_impulse(ref,value)
|
|
if ref.kind=="mod" then sdk.physics.torque_impulse(ref.key,value)
|
|
else submit{kind="native_impulse",body={kind=ref.kind,index=ref.index},impulse=value,angular=true} end
|
|
end
|
|
function sdk.input.override_action(id,value) submit{kind="input_override",action=id,value=value} end
|
|
|
|
sdk.graphs = {}
|
|
function sdk.graphs.read(graph) return (sdk.engine.read("graphs") or {})[graph] end
|
|
function sdk.graphs.set_enabled(graph,target,index,enabled) submit{kind="graph_gate",graph=graph,target=target,index=index,enabled=enabled} end
|
|
|
|
function sdk.rig.configure_part(index,options) submit{kind="rig_part",index=index,options=options} end
|
|
function sdk.rig.reset_part(index) sdk.rig.configure_part(index,nil) end
|