Scrap Mechanic

Scrap Mechanic

71 ratings
Scriptable Computer: What is it and How to use it
By TheFattestCat and 1 collaborators
This is a kind of guide for the Scrap Mechanic mod "ScriptableComputer". It helps you to understand basic mod API, features of using, etc. I don't know if it helps you to learn Lua...
2
2
   
Award
Favorite
Favorited
Unfavorite
Introduction
I wrote this guide to help people get comfortable with ScriptableComputer API. Lua knowledge will be useful, because it replicates the Scrap Mechanic Lua API[scrapmechanic.com]
At the moment this mod is still in development, it has some bugs and issues, but they are not critical.
Basics
Press [E] to open block interface. There will be 2 textboxes: script editor and error details. Also, there are 2 buttons: DONE - save script, CLOSE - close without saving. GUI is not synchronized in real time, so don't press DONE, when your friend is editing the script, because your friend will kill you.

Your first program
print("Hello world!")
Connect ScriptableComputer to the trigger and activate it to start up the computer.

Your second program
Place CompositeWriter and CompositeReader, connect Writer to Computer and Computer to Reader. You have to specify the register names in Writer and Reader, Reader will be read from a specific register and Writer - write value into.
For example, you may specify K in reader and K1 in writer.
local input = getreg("K") if input == 0 then setreg("K1", true) else setreg("K1", false) end
You have created the logical inverter!

If you activate the reader and remove it, you can see that the writer's signal doesn't change. It happens because register values are saved between ticks. If you restart the computer, you also can see that the writer is off. If you print the variable input, it will be nil (nil == 0 will return false), this means the computer will set K1 to false.

Your third program
Let's write the tick generator. Connect computer to the logic gate.
TIMER = TIMER or 0 if TIMER < 10 then TIMER = TIMER + 1 out(0) else out(1) TIMER = 0 end
If you turn on the computer, logic gate with be activated one time in 10 ticks.
What is TIMER = TIMER or 0? This defines a variable.
When TIMER is nil,
It will look like this:
    First tick - TIMER == nil: nil or 0 == 0
    Next tick - TIMER == 1: 1 or 0 == 1

Using sm and self example
local jZ = self.shape:getZAxis() local lX = self.shape:getRight() local lY = self.shape:getAt() local lZ = self.shape:getUp() local angle = 0 if sm.vec3.new(0, 0, 1):cross(lZ):length() > 0.001 then local fakeX = sm.vec3.new(0, 0, 1):cross(lZ):normalize() local fakeY = lZ:cross(fakeX) angle = math.atan2(fakeX:dot(lY), fakeY:dot(lY)) else local rot = sm.vec3.getRotation(lZ, jZ) lY = rot * lY angle = math.atan2(-lY.x, lY.y) end out(angle)
This program measure the angle as xo-meter from modpack. ScriptableComputer mod is compatible with modpack and you can connect the computer to number blocks to output value in radians, or convert it into degrees. You can use this mod to improve speed of calculations or to make cheat programs. It's up to you ;)
Multi-Computer System & Other Basics
Script environment has more interesting custom functions:
getParentComputers()
getChildComputers()
getDisplays()
getMotors()
getRadars()

Multi-Computer System
You can use registers to connect computers together, but this method has a problem: big delays. You can connect computers to each other and use API to get globals from env directly.
Parent computer:
SHARED_SCORE = SHARED_SCORE or 0 SHARED_SCORE = SHARED_SCORE + 1
Child-connected computer:
local parent = getParentComputers()[1] if parent == nil then return end local shared_score = parent.env.SHARED_SCORE print(shared_score)
Now if you activate both computers, you will see numbers in chat. First printed value may be nil, but may be 1, because of the SM block updating order. After loading from lift, this order can be changed, so you need to write optional if to check is shared value not nil.

Computer Data Structure
data = {
env, -- environment of computer
registers -- registers of computer
}

Display Basics
Displays are very laggy. You should know that.
Connect the computer to some display.
Code:
local display = getDisplays()[1] if display == nil then return end display.clear("222222ff") display.drawText(4, 5, "Hello world!", "eeee22") display.flush() -- send data to render
If you connect quite big display, you will see some yellow text.
Function clear() is time-consuming for big displays. If possible, use fillRect() to clear color in a special region.

Display Data Structure
data = {
getWidth(),
getHeight(),
clear(color),
drawPixel(x, y, color),
drawRect(x, y, w, h, color),
fillRect(x, y, w, h, color),
drawCircle(x, y, r, color),
fillCircle(x, y, r, color),
drawLine(x, y, x2, y2, color),
drawText(x, y, text, color),
optimize(), -- optimize image if you don't want to use clear method for a long time
flush(),

setClicksAllowed(bool) -- activate | deactivate sensor screen,
getClicksAllowed(),
getClick() -> { 1: x, 2: y, 3: "pressed"|"drag"|"released", 4: 1|2 }, -- last number is 1 when "E" button and 2 when "U"
setMaxClicks(n: int [1; 16]), -- sets the buffer size
getMaxClicks(),
clearClicks(), -- clears the click buffer

setRenderAtDistance(bool), -- calculate rendering of pixels not rendering, can break well-optimized rendering
getRenderAtDistance()
}

Motor Basics
local motor = getMotors()[1] if motor == nil then return end motor.setVelocity(10) motor.setStrength(10) motor.setActive(true)

StepperMotor Data Structure
data = {
getVelocity(),
setVelocity(num),
getStrength(),
setStrength(num),
getAngle(),
setAngle(num | nil),
isActive(),
setActive(num | bool)
}
Radar Basics
Radar can't be used to detect static objects (because of API issues). Method getTargets() returns targets array. Every target contains 5 values: body id, horizontal angle, vertical angle, distance and signal force (depends on mass and distance). Angles are in [-pi, pi) and depend on radar shape local coordinates system.
Radar Data Structure
data = {
getTargets(), -- returns array of { body id, hangle, vangle, distance, force }
setAngle(num),
getAngle(),
setHFov(num), -- (0; pi]
getHFov(),
setVFov(num), -- (0; pi]
getVFov()
}

Networking Basics
Q: How to use antenna?
A: Connect antenna to the network port.

Q: How to send data?
A: Convert data to string, if its table, use sm.json.writeJsonString & sm.json.parseJsonString to receive.

Q: How to receive data?
A:
local port = getPorts()[1] while port.getPacketsCount() > 0 do local data = port.nextPacket() end


NetworkPort Data Structure
data = {
getMaxPacketsCount(),
getPacketsCount(),
nextPacket(), -- give next packet to process
send(str),
clear() -- clear buffered packets}

Disk Data Structure
data = {
createFile(path),
readFile(path),
writeFile(path),
deleteFile(path),
hasFile(path),
getFileSize(path),

createFolder(path),
deleteFolder(path),
hasFolder(path),

getUsedSize(),
getFileList(path),
getFolderList(path),
openFolder(path), -- change default tracing path (e.g. cd)
getCurrentPath()}
Raycasting Camera Basics
Lets write simple code
local display = getDisplays()[1] local camera = getCameras()[1] camera.drawColorWithDepth(display) display.optimize() display.flush()
Calling display.optimize() is so important in such case, because camera render image pixel by pixel and you need to optimize screen quadtree for better performance. Also you can draw something above camera picture. You may write 'display.drawText(0, 0, "hello", "eeeeee")' after calling some camera draw function. Calling display clear function in that sutiation is wrong, because you will get colorful stripes (only if the render step isnt big enough).

Raycasting Camera Data Structure
data = {
drawColor(display),
drawDepth(display, baseColor),
drawColorWithDepth(display),

setStep(int), -- sets the rendering step (how much pixel will be rendered at draw call)
getStep(),
setDistance(num),
getDistance(num),
setFov(num), -- when low it has more details, when high it has more coverange,
getFov(),
getNextPixel(), -- get pixel camera starts render from
resetCounter(), -- reset that pixel to 0}
Script Template
This template can make coding easier. It provides 4 general methods/callbacks (onStart, onTick, onStop, onError).

if STARTED ~= nil then if input() then local ran, err = pcall(onTick) if not ran then onError() error(err) end else onStop() end else -- it is beautyful place where you can write your code -- there you can declare global variables/functions function onStart() -- it invokes when computer turns on -- there you can declare global variables too -- you shouldn't declare functions there (but you can) -- or invoke some methods end function onTick() -- it invokes every tick computer is active end function onStop() -- it invokes when computer turns off -- there you can deactivate motors end function onError() -- this method invokes when computer gets error in onTick method -- you can invoke onStop method onStop() end onStart() STARTED = true end
Script Environment
  • print(str) - print in chat
  • debug(str) - print in console
  • tostring(v) - see[www.gammon.com.au]
  • tonumber(str) - see[www.gammon.com.au]
  • getreg(name: str) -> [number | bool] - get value from register
  • setreg(name: str, value: [number | bool]) - set value to register
  • clearregs() - clear all registers
  • type(val) - see[www.gammon.com.au]
  • assert(cond: bool, msg: [str | nil]) - see[www.gammon.com.au]
  • error(msg, level: [num | nil]) - see[www.gammon.com.au]
  • pairs(table) -> (function, value, nil) - see[www.gammon.com.au]
  • ipairs(table) -> (function, value, 0) - see[www.gammon.com.au]
  • next(table, index) -> (index, value) - see[www.gammon.com.au]
  • pcall(f, ...) -> (num, ...) - see[www.gammon.com.au]
  • xpcall(f, err) -> (num, result) - see[www.gammon.com.au]
  • self - link to ScriptableComputer script
  • select(index, ...) -> ... - see[www.gammon.com.au]
  • global - link to global namespace
  • getParentComputersData() - deprecated, use getParentComputers
  • getParentComputers() -> table - returns table of parent computers datas
  • getChildComputersData() - deprecated, use getChildComputers
  • getChildComputers() -> table - returns table of child computers datas
  • getConnectedDisplaysData() - deprecated, use getDisplays
  • getDisplays() -> table - returns table of child displays datas
  • getConnectedMotorsData() - deprecated, use getMotors
  • getMotors() -> table - returns table of child motors datas
  • getConnectedRadarsData() - deprecated, use getRadars
  • getRadars() -> table - returns table of child radars datas
  • getPorts() -> table - returns table of child network ports datas
  • getDisks() -> table - returns table of child discks datas
  • clientInvoke(code: str) - performs invokation some code on clients
  • input(color: [str | nil]) -> bool - input selector (if color specified); returns true if one on selected inputs is true else returns false
  • ninput(color: [str | nil]) -> table[num] - if color specified returns table of parent interactable powers; if no color - returns full table of powers
  • out(value: [num | bool]) - set power and active state of self.interactable
  • loadstring(code: str, env: table|nil) -> function - make function from code
  • execute(code: str, env: table|nil) - execute string code
Script Environment Continuation
  • string.byte(str, index=nil) -> number - see[lua-users.org]
  • string.char(num) -> str - see[lua-users.org]
  • string.find(str, what: str) -> (start: num, end: num) - see[lua-users.org]
  • string.format(format, ...) -> str - see[lua-users.org]
  • string.gmatch(str, pattern: regex) -> function - see[lua-users.org]
  • string.gsub(str, pattern: regex, replace, n: [number | nil]) -> function - see[lua-users.org]
  • string.len(str) -> number - see[lua-users.org]
  • string.lower(str) -> str - see[lua-users.org]
  • string.match(str, pattern: regex, index: [number | nil]) -> str - see[lua-users.org]
  • string.rep(str, n: number) -> str - see[lua-users.org]
  • string.sub(str, start: number, end: [number | nil]) -> str - see[lua-users.org]
  • string.upper(str) -> str - see[lua-users.org]
  • table.insert(table, value) & table.insert(table, pos: num, value) - see[lua-users.org]
  • table.maxn(table) -> [num | nil] - see[www.gammon.com.au]
  • table.remove(table, pos: [num | nil]) -> value - see[lua-users.org]
  • table.sort(table, comp: [function | nil]) -> table - see[lua-users.org]
  • table.concat(table, sep: [str | nil], start: [num | nil], end: [num | nil]) -> str - see[lua-users.org]
  • math.abs(num) -> num - see[lua-users.org]
  • math.acos(num) -> num - see[lua-users.org]
  • math.asin(num) -> num - see[lua-users.org]
  • math.atan(num) -> num - see[lua-users.org]
  • math.atan2(y: num, x: num) -> num - see
  • math.ceil(num) -> num - see[lua-users.org]
  • math.cos(num) -> num - see[lua-users.org]
  • math.cosh(num) -> num - see[www.gammon.com.au]
  • math.deg(num) -> num - see[lua-users.org]
  • math.exp(num) -> num - see[lua-users.org]
  • math.floor(num) -> num - see[lua-users.org]
  • math.fmod(num, num) -> num - see[www.gammon.com.au]
  • math.frexp(num, num) -> num - see[www.gammon.com.au]
  • math.huge - see[lua-users.org]
  • math.ldexp(num, num) -> num - see[www.gammon.com.au]
  • math.log(num) -> num - see[lua-users.org]
  • math.log10(num) -> num - see
  • math.max(num, ...) -> num - see[lua-users.org]
  • math.min(num, ...) -> num - see[lua-users.org]
  • math.modf(num) -> (num, num) - see[lua-users.org]
  • math.pi - see[lua-users.org]
  • math.pow(num, num) -> num - see[www.gammon.com.au]
  • math.rad(num) -> num - see[lua-users.org]
  • math.random(m: [num | nil], n: [num | nil]) -> num - see[lua-users.org]
  • math.randomseed(num) - see[lua-users.org]
  • math.sin(num) -> num - see[lua-users.org]
  • math.sinh(num) -> num - see[www.gammon.com.au]
  • math.sqrt(num) -> num - see[lua-users.org]
  • math.tan(num) -> num - see[lua-users.org]
  • math.tanh(num) -> num - see[www.gammon.com.au]
  • sm [full] - see[scrapmechanic.com]
  • bit [full] - see[lua-users.org]
  • os.clock() -> num - see[lua-users.org]
  • os.difftime(t2: num, t1: num) -> num - see[lua-users.org]
  • os.time(t: [nil | table]) -> num - see[lua-users.org]
ScriptableComputer Mod Features
  • This mod is compatible with Modpack
  • This mod uses Lua interpreter (not native, because of SM Lua api, thanks axolot) from this github[github.com], thanks to the author; also I tried LBI (with better performance as I tested) from ScrapVM mod, but it has problems with environment
  • Because of interpreter, performance is worse than native in about 4-6 times
  • Script performs every game tick (in fixed update)
  • Script performs only at server, if you need to perform something on client, use clientInvoke(str)
  • Registers and global variables save between ticks, but reset after computer turning off
  • Script has iteration limit and you can't freeze the game
  • Exceptions have a really bad tracing (but in the future it must be fixed)
  • Displays are so laggy (in general 64x64 and upper, but last time so laggy is only render), if you place lot of high-resolution displays, pixels can start disappearing (because of SM effect limitations)
  • Displays have 15 meter render distance
  • Radars can't detect bodies, connected to map directionally
Conclusion
Please, report all bugs and issues in the comment section.
90 Comments
stalkerassassim 26 Jan @ 3:29am 
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2979217633 - это что бы передавать изображение с камеры на дисплей на расстоянии
Cactussss 11 Jan @ 8:52am 
привет,напиши пожалуйста код к луа компьютеру чтобы передавать изображение с камеры на дисплей на расстоянии, я и антенны эти ковырял, и порты, и переводил в json, ничего не получалось
TheFattestCat  [author] 19 Dec, 2024 @ 4:39am 
do you allow using this namespaces with permission block?
Harbor811 18 Dec, 2024 @ 5:58am 
trying to copy the example code, local jZ = self.shape:getZAxis() is not working. Is there some extra setup I need or what's going on
stalkerassassim 8 Nov, 2024 @ 2:36am 
can you send an example of how the HDD works?
TheFattestCat  [author] 16 Aug, 2024 @ 6:32am 
you can only determine mass of object using `force` return parameter (linear dependency)
турбо картель 15 Aug, 2024 @ 11:42am 
Is it possible to determine the height of an object using radar?
TheFattestCat  [author] 14 Aug, 2024 @ 6:13am 
it could be flush buffer overflow
TheFattestCat  [author] 14 Aug, 2024 @ 6:13am 
try display numbers after calling `flush` and then call `flush` again
Arsen4ik 14 Aug, 2024 @ 12:07am 
Why i cant display numbers like Raycast camera distance in real time? Display freeze and its fixing only if display be replased