diff --git a/README.md b/README.md index c0f3951..e5fc7eb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # rmod RMod mod for Minetest with various cool nodes. -Conveyors -- +## Conveyors Conveyors are nodes that can carry entities, such as players and items. There are a few bugs with it, but it works fine for the most part. @@ -16,13 +15,11 @@ Digiconveyors Digiconveyors can not only be turned on and off with digilines, but they can also be reversed (which flips their facing direction). The messages available at the moment are "on", "off", "toggle" (functions as both "on" and "off"), "reverse" (inverts the direction), "left" (turns left 90 degrees) and "right". -Crates -- +## Crates Crates are like chests, but you can pick them up with the items inside. As a result, you can also stack crates indefinitely. -Grates -- +## Grates Grates are nodes which let water flow through, but not players or items. @@ -35,3 +32,42 @@ Digigrates - Digigrates can be adjusted by sending messages - they include "on", "off", "toggle" and "set". That last one, "set", is sent as a table {command = "sent", value = x}, substituting x for the percentage of water you want to pass through (although, tbh, it's more complicated than that). + +## Scriptblocks + +Scriptblocks are blocks that you can use for creating simple programs. They are one of the most complicated parts of this mod, which can be a good thing or a bad thing depending on your viewpoint. + +Basics +- + +When the Mesecon Detector scriptblock (which is yellow with an exclamation mark on it) receives mesecon power, it triggers any scriptblocks adjacent to it. Each scriptblock will then trigger each scriptblock adjacent to itself (excluding the one that triggered it in the first place). + +Variables +- + +You can store data in these scripts with the SET (looks like :=) and GET (looks like []) blocks. Each script can keep track of up to two values during execution (@info and @last), and the GET block will update @last to the previous @info, while updating @info to the value of the chosen variable. All scriptblock inputs may have "@info" or "@last" written inside them, which will be substituted for the corresponding values at runtime. + +Program channels +- + +Program channels are channels you can set to avoid clashing with other programs that may use similar or equal variable names. You can still set the variables of other channels by entering them into SET and GET variable blocks. + +Mathematical operators +- + +The mathematical operators (add, subtract, multiply, divide) work in much the same way as GET - they update @last to the previous @info, and update @info to the result of the calculation. To add two values together, you would do (or rather, build) something along the lines of "GET a; GET b; ADD @last @info; SET c @info;", which will set c to the sum of a and b. + +Conditional +- + +The teal "?" block will lend execution in one direction if the two input values are equal, and the other if they are not equal. + +Print +- + +The purple blocks with a speech bubble on them will print the message specified inside to the chat. + +Guide +- + +The grey blocks which look like three arrows that converge into one are guides - they are used to aid looping by funnelling all execution in one direction - never will a guide execute nearby blocks other than the one it is pointing to. diff --git a/init.lua b/init.lua index 9360726..32f3c45 100644 --- a/init.lua +++ b/init.lua @@ -4,6 +4,7 @@ local modpath = minetest.get_modpath("rmod") dofile(modpath .. "/grate.lua") dofile(modpath .. "/conveyor.lua") dofile(modpath .. "/crate.lua") +dofile(modpath .. "/scriptblock.lua") if minetest.get_modpath("mesecons") then dofile(modpath .. "/meseconveyor.lua") diff --git a/scriptblock.lua b/scriptblock.lua new file mode 100644 index 0000000..cc33ad8 --- /dev/null +++ b/scriptblock.lua @@ -0,0 +1,499 @@ +--[[ + scriptblock = function (pos, node, sender, info, last, main_channel) + -- "pos" and "node" are the position and the node information of the scriptblock being ran. + -- "sender" would be the position of the node responsible for activating it. + -- "info" is any information the previous node has sent to it. + -- "last" is the information that "info" /was/ before it was last changed. + -- "channel" is the channel in which variables are stored. + blah blah blah + return new_info, faces -- Information to pass to the next node(s), and information on which adjacent spaces we should even try to signal to. + end +]] + +local do_debug = false +local function debug(text) + if do_debug then minetest.chat_send_all("[SB] " .. text) end +end + +local storage = minetest.get_mod_storage() + +-- Each section of the RMod gets its own storage, to prevent interference. +-- RMod sections can still affect other sections, though only when intended. +local function get_storage() + return minetest.deserialize(storage:get_string("scriptblock")) or {} +end +local function set_storage(data) + return storage:set_string("scriptblock", minetest.serialize(data)) +end + +-- To avoid lag and stack overflows, we add the data to a queue and then execute it with a globalstep. +local queue = {} + +rmod.scriptblock = {} + +rmod.scriptblock.run = function (pos, sender, info, last, channel) + local local_queue = {} + + -- Get information about this script block we are told to execute. + local node = minetest.get_node(pos) + local name = node.name + local def = minetest.registered_nodes[name] + + -- If the block is a script block... + if def and def.scriptblock then + local new_info, faces = def.scriptblock(pos, node, sender, info, last, channel) + -- if new_info == rmod.scriptblock.stop_signal then return end -- Looks like the block doesn't want to conduct. + if not faces then + faces = {true, true, true, true, true, true} + end + -- Check neighboring nodes; if they also have scriptblock and aren't the sender, execute them. + for i=1,6 do + if faces[i] then + local dir = vector.new(0, 0, 0) + if i == 1 then dir.y = 1 + elseif i == 2 then dir.y = -1 + elseif i == 3 then dir.x = 1 + elseif i == 4 then dir.x = -1 + elseif i == 5 then dir.z = 1 + elseif i == 6 then dir.z = -1 end + + local new_pos = vector.add(pos, dir) + + -- This is required, otherwise you'd have an unintentional feedback loop. + -- Feedback loops can still be created intentionally, though. + if not vector.equals(new_pos, sender) then + local new_node = minetest.get_node(new_pos) + local new_name = new_node.name + local new_def = minetest.registered_nodes[new_name] + + if new_def and new_def.scriptblock then + table.insert(local_queue, {new_pos, pos, new_info, info == new_info and last or info, channel}) + end + end + end + end + end + return local_queue +end +rmod.scriptblock.escape = function (text, info, last) + return text and text:gsub("@info", info):gsub("@last", last) or "" +end + +local time = 0 +minetest.register_globalstep(function (dtime) + time = time + dtime + if time > 0.05 then time = 0 else return end + + local new_queue = {} + for i,data in pairs(queue) do + local new_list = rmod.scriptblock.run(unpack(data)) + if new_list then + for _,new_item in pairs(new_list) do + table.insert(new_queue, new_item) + end + end + end + queue = new_queue +end) + +minetest.register_node("rmod:scriptblock_set", { + description = "Scriptblock: Set", + tiles = {"rmod_scriptblock_set.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", [[ +field[channel;Program channel (optional);${channel}] +field[varname;Varname;${varname}] +field[value;Value;${value}] +]]) + end, + on_receive_fields = function(pos, formname, fields, sender) + local name = sender:get_player_name() + if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then + minetest.record_protection_violation(pos, name) + return + end + if (fields.channel) then + minetest.get_meta(pos):set_string("channel", fields.channel) + end + if (fields.varname) then + minetest.get_meta(pos):set_string("varname", fields.varname) + end + if (fields.value) then + minetest.get_meta(pos):set_string("value", fields.value) + end + end, + scriptblock = function (pos, node, sender, info, last, main_channel) + local meta = minetest.get_meta(pos) + local channel = rmod.scriptblock.escape(meta:get_string("channel"), info, last) + local varname = rmod.scriptblock.escape(meta:get_string("varname"), info, last) + local value = rmod.scriptblock.escape(meta:get_string("value"), info, last) + + local store = get_storage() + + if channel == "" or not channel then channel = main_channel end + + if not store[channel] then store[channel] = {} end + store[channel][varname] = value + + set_storage(store) + + debug("SET " .. channel .. "." .. varname .. ": " .. value) + + return info + end +}) +minetest.register_node("rmod:scriptblock_get", { + description = "Scriptblock: Get", + tiles = {"rmod_scriptblock_get.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", [[ +field[channel;Program channel (optional);${channel}] +field[varname;Varname;${varname}] +]]) + end, + on_receive_fields = function(pos, formname, fields, sender) + local name = sender:get_player_name() + if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then + minetest.record_protection_violation(pos, name) + return + end + if (fields.channel) then + minetest.get_meta(pos):set_string("channel", fields.channel) + end + if (fields.varname) then + minetest.get_meta(pos):set_string("varname", fields.varname) + end + end, + scriptblock = function (pos, node, sender, info, last, main_channel) + local meta = minetest.get_meta(pos) + local channel = rmod.scriptblock.escape(meta:get_string("channel"), info, last) + local varname = rmod.scriptblock.escape(meta:get_string("varname"), info, last) + + local store = get_storage() + + if channel == "" or not channel then channel = main_channel end + + if not store[channel] then store[channel] = {} end + + debug("GET " .. channel .. "." .. varname .. ": " .. (store[channel][varname] or "")) + + return store[channel][varname] or "" + end +}) +minetest.register_node("rmod:scriptblock_mesecon", { + description = "Scriptblock: Mesecon Detector", + tiles = {"rmod_scriptblock_mesecon.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", [[ +field[channel;Program channel;${channel}] +]]) + end, + on_receive_fields = function(pos, formname, fields, sender) + local name = sender:get_player_name() + if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then + minetest.record_protection_violation(pos, name) + return + end + if (fields.channel) then + minetest.get_meta(pos):set_string("channel", fields.channel) + end + end, + scriptblock = function (pos, node, sender, info, last, main_channel) + return info + end, + mesecons = {effector = { + action_on = function (pos, node) + local meta = minetest.get_meta(pos) + local channel = meta:get_string("channel") + + debug("ACTIVATED") + table.insert(queue, {pos, pos, "", "", channel or ""}) + end, + }} +}) +minetest.register_node("rmod:scriptblock_print", { + description = "Scriptblock: Print", + tiles = {"rmod_scriptblock_print.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", [[ +field[player;Player;${player}] +field[message;Message;${message}] +]]) + end, + on_receive_fields = function(pos, formname, fields, sender) + local name = sender:get_player_name() + if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then + minetest.record_protection_violation(pos, name) + return + end + if (fields.player) then + minetest.get_meta(pos):set_string("player", fields.player) + end + if (fields.message) then + minetest.get_meta(pos):set_string("message", fields.message) + end + end, + scriptblock = function (pos, node, sender, info, last, main_channel) + local meta = minetest.get_meta(pos) + local plr = rmod.scriptblock.escape(meta:get_string("player"), info, last) + local msg = rmod.scriptblock.escape(meta:get_string("message"), info, last) + + if plr == "" then + minetest.chat_send_all("Scriptblock -> all: " .. msg) + else + minetest.chat_send_player(plr, "Scriptblock -> you: " .. msg) + end + + return info + end +}) +minetest.register_node("rmod:scriptblock_if", { + description = "Scriptblock: If Equals", + tiles = {"rmod_scriptblock_if_top.png", "rmod_scriptblock_if_bottom.png", + "rmod_scriptblock_if_right.png", "rmod_scriptblock_if_left.png", + "rmod_scriptblock_if_truth.png", "rmod_scriptblock_if_falsth.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + paramtype2 = "facedir", + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", [[ +field[a;A;${a}] +field[b;B;${b}] +]]) + end, + on_receive_fields = function(pos, formname, fields, sender) + local name = sender:get_player_name() + if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then + minetest.record_protection_violation(pos, name) + return + end + if (fields.a) then + minetest.get_meta(pos):set_string("a", fields.a) + end + if (fields.b) then + minetest.get_meta(pos):set_string("b", fields.b) + end + end, + scriptblock = function (pos, node, sender, info, last, main_channel) + local meta = minetest.get_meta(pos) + local a = rmod.scriptblock.escape(meta:get_string("a"), info, last) + local b = rmod.scriptblock.escape(meta:get_string("b"), info, last) + + local facedir = node.param2 + local dir = minetest.facedir_to_dir(facedir) + + -- Y, -Y, X, -X, Z, -Z. + + local truth = {} + local falsth = {} + if dir.x == 1 then truth[3] = true; falsth[4] = true + elseif dir.x == -1 then truth[4] = true; falsth[3] = true + elseif dir.z == 1 then truth[5] = true; falsth[6] = true + elseif dir.z == -1 then truth[6] = true; falsth[5] = true end + + return unpack(a == b and {info, truth} or {info, falsth}) + end +}) +minetest.register_node("rmod:scriptblock_guide", { + description = "Scriptblock: One-Way Guide", + tiles = {"rmod_scriptblock_guide_top.png", "rmod_scriptblock_guide_bottom.png", + "rmod_scriptblock_guide_right.png", "rmod_scriptblock_guide_left.png", + "rmod_scriptblock_guide_front.png", "rmod_scriptblock_guide_back.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + paramtype2 = "facedir", + scriptblock = function (pos, node, sender, info, last, main_channel) + local facedir = node.param2 + local dir = minetest.facedir_to_dir(facedir) + + -- Y, -Y, X, -X, Z, -Z. + + local guide = {} + if dir.x == 1 then guide[3] = true + elseif dir.x == -1 then guide[4] = true + elseif dir.z == 1 then guide[5] = true + elseif dir.z == -1 then guide[6] = true end + + return info, guide + end +}) + +minetest.register_node("rmod:scriptblock_add", { + description = "Scriptblock: Add", + tiles = {"rmod_scriptblock_add.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", [[ +field[a;A;${a}] +field[b;B;${b}] +]]) + end, + on_receive_fields = function(pos, formname, fields, sender) + local name = sender:get_player_name() + if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then + minetest.record_protection_violation(pos, name) + return + end + if (fields.a) then + minetest.get_meta(pos):set_string("a", fields.a) + end + if (fields.b) then + minetest.get_meta(pos):set_string("b", fields.b) + end + end, + scriptblock = function (pos, node, sender, info, last, main_channel) + local meta = minetest.get_meta(pos) + local a = rmod.scriptblock.escape(meta:get_string("a"), info, last) + local b = rmod.scriptblock.escape(meta:get_string("b"), info, last) + + local facedir = node.param2 + local dir = minetest.facedir_to_dir(facedir) + + return tostring((tonumber(a) or 0) + (tonumber(b) or 0)) + end +}) +minetest.register_node("rmod:scriptblock_subtract", { + description = "Scriptblock: Subtract", + tiles = {"rmod_scriptblock_subtract.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", [[ +field[a;A;${a}] +field[b;B;${b}] +]]) + end, + on_receive_fields = function(pos, formname, fields, sender) + local name = sender:get_player_name() + if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then + minetest.record_protection_violation(pos, name) + return + end + if (fields.a) then + minetest.get_meta(pos):set_string("a", fields.a) + end + if (fields.b) then + minetest.get_meta(pos):set_string("b", fields.b) + end + end, + scriptblock = function (pos, node, sender, info, last, main_channel) + local meta = minetest.get_meta(pos) + local a = rmod.scriptblock.escape(meta:get_string("a"), info, last) + local b = rmod.scriptblock.escape(meta:get_string("b"), info, last) + + local facedir = node.param2 + local dir = minetest.facedir_to_dir(facedir) + + return tostring((tonumber(a) or 0) - (tonumber(b) or 0)) + end +}) +minetest.register_node("rmod:scriptblock_multiply", { + description = "Scriptblock: Multiply", + tiles = {"rmod_scriptblock_multiply.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", [[ +field[a;A;${a}] +field[b;B;${b}] +]]) + end, + on_receive_fields = function(pos, formname, fields, sender) + local name = sender:get_player_name() + if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then + minetest.record_protection_violation(pos, name) + return + end + if (fields.a) then + minetest.get_meta(pos):set_string("a", fields.a) + end + if (fields.b) then + minetest.get_meta(pos):set_string("b", fields.b) + end + end, + scriptblock = function (pos, node, sender, info, last, main_channel) + local meta = minetest.get_meta(pos) + local a = rmod.scriptblock.escape(meta:get_string("a"), info, last) + local b = rmod.scriptblock.escape(meta:get_string("b"), info, last) + + local facedir = node.param2 + local dir = minetest.facedir_to_dir(facedir) + + return tostring((tonumber(a) or 0) * (tonumber(b) or 0)) + end +}) +minetest.register_node("rmod:scriptblock_divide", { + description = "Scriptblock: Divide", + tiles = {"rmod_scriptblock_divide.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", [[ +field[a;A;${a}] +field[b;B;${b}] +]]) + end, + on_receive_fields = function(pos, formname, fields, sender) + local name = sender:get_player_name() + if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then + minetest.record_protection_violation(pos, name) + return + end + if (fields.a) then + minetest.get_meta(pos):set_string("a", fields.a) + end + if (fields.b) then + minetest.get_meta(pos):set_string("b", fields.b) + end + end, + scriptblock = function (pos, node, sender, info, last, main_channel) + local meta = minetest.get_meta(pos) + local a = rmod.scriptblock.escape(meta:get_string("a"), info, last) + local b = rmod.scriptblock.escape(meta:get_string("b"), info, last) + + local facedir = node.param2 + local dir = minetest.facedir_to_dir(facedir) + + return tostring((tonumber(a) or 0) / (tonumber(b) or 0)) + end +}) + +minetest.register_node("rmod:scriptblock_nearest", { + description = "Scriptblock: Get Nearest Player", + tiles = {"rmod_scriptblock_nearest.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + scriptblock = function (pos, node, sender, info, last, main_channel) + local players = minetest.get_connected_players() + + local nearest = nil + local min_distance = math.huge + for index, player in pairs(players) do + local distance = vector.distance(pos, player:getpos()) + if distance < min_distance then + min_distance = distance + nearest = player:get_player_name() + end + end + + return nearest or "" + end +}) diff --git a/textures/rmod_scriptblock_add.png b/textures/rmod_scriptblock_add.png new file mode 100644 index 0000000..e72e376 Binary files /dev/null and b/textures/rmod_scriptblock_add.png differ diff --git a/textures/rmod_scriptblock_divide.png b/textures/rmod_scriptblock_divide.png new file mode 100644 index 0000000..b6551d0 Binary files /dev/null and b/textures/rmod_scriptblock_divide.png differ diff --git a/textures/rmod_scriptblock_get.png b/textures/rmod_scriptblock_get.png new file mode 100644 index 0000000..915c4c7 Binary files /dev/null and b/textures/rmod_scriptblock_get.png differ diff --git a/textures/rmod_scriptblock_guide_back.png b/textures/rmod_scriptblock_guide_back.png new file mode 100644 index 0000000..d4d30d4 Binary files /dev/null and b/textures/rmod_scriptblock_guide_back.png differ diff --git a/textures/rmod_scriptblock_guide_bottom.png b/textures/rmod_scriptblock_guide_bottom.png new file mode 100644 index 0000000..7c66116 Binary files /dev/null and b/textures/rmod_scriptblock_guide_bottom.png differ diff --git a/textures/rmod_scriptblock_guide_front.png b/textures/rmod_scriptblock_guide_front.png new file mode 100644 index 0000000..0cb7db0 Binary files /dev/null and b/textures/rmod_scriptblock_guide_front.png differ diff --git a/textures/rmod_scriptblock_guide_left.png b/textures/rmod_scriptblock_guide_left.png new file mode 100644 index 0000000..17d3aec Binary files /dev/null and b/textures/rmod_scriptblock_guide_left.png differ diff --git a/textures/rmod_scriptblock_guide_right.png b/textures/rmod_scriptblock_guide_right.png new file mode 100644 index 0000000..1f6e3f4 Binary files /dev/null and b/textures/rmod_scriptblock_guide_right.png differ diff --git a/textures/rmod_scriptblock_guide_top.png b/textures/rmod_scriptblock_guide_top.png new file mode 100644 index 0000000..f3a35b6 Binary files /dev/null and b/textures/rmod_scriptblock_guide_top.png differ diff --git a/textures/rmod_scriptblock_if.png b/textures/rmod_scriptblock_if.png new file mode 100644 index 0000000..c8ad37d Binary files /dev/null and b/textures/rmod_scriptblock_if.png differ diff --git a/textures/rmod_scriptblock_if_bottom.png b/textures/rmod_scriptblock_if_bottom.png new file mode 100644 index 0000000..75c7883 Binary files /dev/null and b/textures/rmod_scriptblock_if_bottom.png differ diff --git a/textures/rmod_scriptblock_if_falsth.png b/textures/rmod_scriptblock_if_falsth.png new file mode 100644 index 0000000..16b6c92 Binary files /dev/null and b/textures/rmod_scriptblock_if_falsth.png differ diff --git a/textures/rmod_scriptblock_if_left.png b/textures/rmod_scriptblock_if_left.png new file mode 100644 index 0000000..dafab18 Binary files /dev/null and b/textures/rmod_scriptblock_if_left.png differ diff --git a/textures/rmod_scriptblock_if_right.png b/textures/rmod_scriptblock_if_right.png new file mode 100644 index 0000000..481a235 Binary files /dev/null and b/textures/rmod_scriptblock_if_right.png differ diff --git a/textures/rmod_scriptblock_if_top.png b/textures/rmod_scriptblock_if_top.png new file mode 100644 index 0000000..d6577d4 Binary files /dev/null and b/textures/rmod_scriptblock_if_top.png differ diff --git a/textures/rmod_scriptblock_if_truth.png b/textures/rmod_scriptblock_if_truth.png new file mode 100644 index 0000000..bf56ad4 Binary files /dev/null and b/textures/rmod_scriptblock_if_truth.png differ diff --git a/textures/rmod_scriptblock_mesecon.png b/textures/rmod_scriptblock_mesecon.png new file mode 100644 index 0000000..76acad7 Binary files /dev/null and b/textures/rmod_scriptblock_mesecon.png differ diff --git a/textures/rmod_scriptblock_multiply.png b/textures/rmod_scriptblock_multiply.png new file mode 100644 index 0000000..7d91078 Binary files /dev/null and b/textures/rmod_scriptblock_multiply.png differ diff --git a/textures/rmod_scriptblock_nearest.png b/textures/rmod_scriptblock_nearest.png new file mode 100644 index 0000000..c267ca3 Binary files /dev/null and b/textures/rmod_scriptblock_nearest.png differ diff --git a/textures/rmod_scriptblock_print.png b/textures/rmod_scriptblock_print.png new file mode 100644 index 0000000..3340bee Binary files /dev/null and b/textures/rmod_scriptblock_print.png differ diff --git a/textures/rmod_scriptblock_set.png b/textures/rmod_scriptblock_set.png new file mode 100644 index 0000000..ae6206b Binary files /dev/null and b/textures/rmod_scriptblock_set.png differ diff --git a/textures/rmod_scriptblock_subtract.png b/textures/rmod_scriptblock_subtract.png new file mode 100644 index 0000000..4323bf6 Binary files /dev/null and b/textures/rmod_scriptblock_subtract.png differ