diff --git a/README.md b/README.md index d9c0090..7afba16 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ Digigrates can be adjusted by sending messages - they include "on", "off", "togg 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. +### An important note + +Scriptblocks can handle values of various types, such as objects (tables), strings, numbers and booleans. Recent changes mean that scriptblocks no longer convert between them when it is unnecessary. As a result, attempting to compare the equality of, say, a boolean with the string "true" will result in false, because a boolean is not a string. Please keep this in mind as you program with scriptblocks, to avoid messy coding (building? :P) and frustration. + ### 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). @@ -41,6 +45,8 @@ When the Mesecon Detector scriptblock (which is yellow with an exclamation mark 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. +@last usually updates to the previous @info when @info gets changed, however there are exceptions for when the change is small (e.g. SET ATTRIBUTE OF OBJECT and the upcoming NOT) - this change probably broke any scripts larger than a few blocks, but I think it's a positive change in the long run. + ### 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. @@ -49,9 +55,15 @@ Program channels are channels you can set to avoid clashing with other programs 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. +### Booleans + +There are comparison operators which will return true or false depending on whether their condition is true. For example, a "LESS THAN" block with A = 3 and B = 2 will return false, while one with A = 1 instead will return true. Booleans themselves can be manipulated with NOT (turns false into true and vice versa), AND (only true if both operands are true) and OR (only false if both operands are false). + +Note that in the case of AND and OR, the two operands are taken to be @info and @last respectively, since they are expected to be booleans - and being able to input "true" or "false" directly into one doesn't make sense to me (what's the point of "true OR x" or "false AND y"?). + ### 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. +The teal "?" block will lend execution towards the green side if the value reaching it (@info) is non-nil, and non-false. If it /is/ nil or false, execution is lent towards the red side instead. Formerly this took two operands and compared them, but that functionality has been replaced by the comparison operators described above. ### Print diff --git a/scriptblock.lua b/scriptblock.lua index a1e74ee..481d04d 100644 --- a/scriptblock.lua +++ b/scriptblock.lua @@ -29,6 +29,7 @@ local function set_storage(data) end local function stringify(t) + if type(t) ~= "table" then return tostring(t) end return minetest.serialize(t):sub(("return "):len()+1, -1) end @@ -62,7 +63,8 @@ rmod.scriptblock.run = function (pos, sender, info, last, channel) -- 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) + -- The flag tells the code NOT to update @last, even if there is a change in @info. + local new_info, faces, flag = 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} @@ -88,7 +90,13 @@ rmod.scriptblock.run = function (pos, sender, info, last, channel) 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}) + local new_last + if flag or info == new_info then + new_last = last + else + new_last = info + end + table.insert(local_queue, {new_pos, pos, new_info, new_last, channel}) end end end @@ -103,7 +111,7 @@ rmod.scriptblock.escape = function (text, info, last) if type(info) == "table" then info = stringify(info) or "" end if type(last) == "table" then last = stringify(last) or "" end - return text and text:gsub("@info", info or ""):gsub("@last", last or "") or "" + return text and text:gsub("@info", tostring(info) or ""):gsub("@last", tostring(last) or "")-- or "" end local time = 0 @@ -162,6 +170,8 @@ field[value;Value;${value}] if channel == "" or not channel then channel = main_channel end + if not varname then varname = "" end + if not store[channel] then store[channel] = {} end store[channel][varname] = value @@ -205,12 +215,13 @@ field[varname;Varname;${varname}] local store = get_storage() if channel == "" or not channel then channel = main_channel end + if not varname then return end if not store[channel] then store[channel] = {} end debug("GET " .. tostring(channel) .. "." .. tostring(varname) .. ": " .. tostring(store[channel][varname] or "")) - return store[channel][varname] or "" + return store[channel][varname] end }) minetest.register_node("rmod:scriptblock_mesecon", { @@ -222,6 +233,7 @@ minetest.register_node("rmod:scriptblock_mesecon", { local meta = minetest.get_meta(pos) meta:set_string("formspec", [[ field[channel;]] .. program_channel .. [[;${channel}] +field[info;Starting @info;${info}] ]]) end, on_receive_fields = function(pos, formname, fields, sender) @@ -233,6 +245,9 @@ field[channel;]] .. program_channel .. [[;${channel}] if (fields.channel) then minetest.get_meta(pos):set_string("channel", fields.channel) end + if (fields.info) then + minetest.get_meta(pos):set_string("info", fields.info) + end end, scriptblock = function (pos, node, sender, info, last, main_channel) return info @@ -240,10 +255,11 @@ field[channel;]] .. program_channel .. [[;${channel}] mesecons = {effector = { action_on = function (pos, node) local meta = minetest.get_meta(pos) - local channel = meta:get_string("channel") + local channel = meta:get_string("channel") + local info = meta:get_string("info") debug("ACTIVATED") - table.insert(queue, {pos, pos, "", "", channel or ""}) + table.insert(queue, {pos, pos, info or "", "", channel or ""}) end, }} }) @@ -277,6 +293,9 @@ field[message;Message;${message}] local plr = rmod.scriptblock.escape(meta:get_string("player"), info, last) local msg = rmod.scriptblock.escape(meta:get_string("message"), info, last) + if not plr then return info end + if not msg then return info end + if type(msg) == "table" then msg = stringify(msg) end if plr == "" then @@ -289,34 +308,15 @@ field[message;Message;${message}] end }) minetest.register_node("rmod:scriptblock_if", { - description = "Scriptblock: If Equals", + description = "Scriptblock: If", 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) + -- compatibility for back when this was "IF EQUALS" 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) @@ -340,7 +340,11 @@ field[b;B;${b}] b = stringify(b) or b end]] - return unpack(compare(a, b) and {info, truth} or {info, falsth}) + if a == "" and b == "" then + return unpack(info and {info, truth} or {info, falsth}) + else + return unpack(compare(a, b) and {info, truth} or {info, falsth}) + end end }) minetest.register_node("rmod:scriptblock_guide", { @@ -566,6 +570,8 @@ field[value;Value;${value}] local propname = rmod.scriptblock.escape(meta:get_string("propname"), info, last) local value = rmod.scriptblock.escape(meta:get_string("value"), info, last) + if not propname then return end + if type(info) ~= "table" then --[[if type(info) == "string" then local deserialized = minetest.deserialize(info) @@ -585,7 +591,7 @@ field[value;Value;${value}] info[propname] = value - return info + return info, nil, true -- Flag to avoid losing @last. end }) minetest.register_node("rmod:scriptblock_get_attribute", { @@ -613,6 +619,8 @@ field[propname;Attribute Name;${propname}] local meta = minetest.get_meta(pos) local propname = rmod.scriptblock.escape(meta:get_string("propname"), info, last) + if not propname then return end + if type(info) ~= "table" then --[[if type(info) == "string" then local deserialized = minetest.deserialize(info) @@ -680,3 +688,131 @@ field[digichannel;Digiline channel;${digichannel}] end, }} }) + +minetest.register_node("rmod:scriptblock_not", { + description = "Scriptblock: Not Gate", + tiles = {"rmod_scriptblock_not.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + scriptblock = function (pos, node, sender, info, last, main_channel) + return not info + end, +}) +minetest.register_node("rmod:scriptblock_and", { + description = "Scriptblock: And Gate", + tiles = {"rmod_scriptblock_and.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + scriptblock = function (pos, node, sender, info, last, main_channel) + return info and last + end, +}) +minetest.register_node("rmod:scriptblock_or", { + description = "Scriptblock: Or Gate", + tiles = {"rmod_scriptblock_or.png"}, + groups = {oddly_breakable_by_hand = 1}, + use_texture_alpha = true, + scriptblock = function (pos, node, sender, info, last, main_channel) + return info or last + end, +}) + +minetest.register_node("rmod:scriptblock_equals", { + description = "Scriptblock: Equals", + tiles = {"rmod_scriptblock_equals.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) + + return compare(a, b) + end, +}) +minetest.register_node("rmod:scriptblock_lt", { + description = "Scriptblock: Less than", + tiles = {"rmod_scriptblock_lt.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) + + return (tonumber(a) or 0) < (tonumber(b) or 0) + end, +}) +minetest.register_node("rmod:scriptblock_gt", { + description = "Scriptblock: Greater than", + tiles = {"rmod_scriptblock_gt.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) + + return (tonumber(a) or 0) > (tonumber(b) or 0) + end, +}) diff --git a/textures/rmod_scriptblock_and.png b/textures/rmod_scriptblock_and.png new file mode 100644 index 0000000..08b590d Binary files /dev/null and b/textures/rmod_scriptblock_and.png differ diff --git a/textures/rmod_scriptblock_equals.png b/textures/rmod_scriptblock_equals.png new file mode 100644 index 0000000..0d443eb Binary files /dev/null and b/textures/rmod_scriptblock_equals.png differ diff --git a/textures/rmod_scriptblock_gt.png b/textures/rmod_scriptblock_gt.png new file mode 100644 index 0000000..a8dd427 Binary files /dev/null and b/textures/rmod_scriptblock_gt.png differ diff --git a/textures/rmod_scriptblock_if_falsth.png b/textures/rmod_scriptblock_if_falsth.png index 25429ed..09abd67 100644 Binary files a/textures/rmod_scriptblock_if_falsth.png and b/textures/rmod_scriptblock_if_falsth.png differ diff --git a/textures/rmod_scriptblock_if_truth.png b/textures/rmod_scriptblock_if_truth.png index 80f445d..6fe477a 100644 Binary files a/textures/rmod_scriptblock_if_truth.png and b/textures/rmod_scriptblock_if_truth.png differ diff --git a/textures/rmod_scriptblock_lt.png b/textures/rmod_scriptblock_lt.png new file mode 100644 index 0000000..01f5b43 Binary files /dev/null and b/textures/rmod_scriptblock_lt.png differ diff --git a/textures/rmod_scriptblock_not.png b/textures/rmod_scriptblock_not.png new file mode 100644 index 0000000..c2873ba Binary files /dev/null and b/textures/rmod_scriptblock_not.png differ diff --git a/textures/rmod_scriptblock_or.png b/textures/rmod_scriptblock_or.png new file mode 100644 index 0000000..b134683 Binary files /dev/null and b/textures/rmod_scriptblock_or.png differ