From 121082859fd8d68f66bd5723c9c23fe8b1e3b0d6 Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Sun, 11 Sep 2022 08:50:26 -0400 Subject: [PATCH 01/10] Handle invalid param2 in presets.lua (#623) --- mesecons/presets.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mesecons/presets.lua b/mesecons/presets.lua index e10dd36..bf16d99 100644 --- a/mesecons/presets.lua +++ b/mesecons/presets.lua @@ -64,6 +64,8 @@ local rules_buttonlike = { } local function rules_from_dir(ruleset, dir) + if not dir then return {} end + if dir.x == 1 then return ruleset.xp end if dir.y == 1 then return ruleset.yp end if dir.z == 1 then return ruleset.zp end From bd07fb0c79fe332d900fdd089014c20cb21fa0d8 Mon Sep 17 00:00:00 2001 From: Niklp <89982526+Niklp09@users.noreply.github.com> Date: Fri, 7 Oct 2022 23:28:30 +0200 Subject: [PATCH 02/10] Fix typo in hydroturbine recipe (#624) --- mesecons_hydroturbine/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesecons_hydroturbine/init.lua b/mesecons_hydroturbine/init.lua index 1993d87..a993085 100644 --- a/mesecons_hydroturbine/init.lua +++ b/mesecons_hydroturbine/init.lua @@ -97,7 +97,7 @@ nodenames = {"mesecons_hydroturbine:hydro_turbine_on"}, minetest.register_craft({ output = "mesecons_hydroturbine:hydro_turbine_off 2", recipe = { - {"","grup:stick", ""}, + {"","group:stick", ""}, {"group:stick", "mesecons_gamecompat:steel_ingot", "group:stick"}, {"","group:stick", ""}, } From a780298cfcb8fd81bce903551f190456409cf9de Mon Sep 17 00:00:00 2001 From: fluxionary <25628292+fluxionary@users.noreply.github.com> Date: Thu, 1 Dec 2022 09:26:47 -0800 Subject: [PATCH 03/10] Add recipe to straighten wire (#629) Add recipe to straighten corner insulated wires --- mesecons_extrawires/corner.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mesecons_extrawires/corner.lua b/mesecons_extrawires/corner.lua index 45ed6f0..65f17e9 100644 --- a/mesecons_extrawires/corner.lua +++ b/mesecons_extrawires/corner.lua @@ -67,3 +67,9 @@ minetest.register_craft({ {"", "mesecons_insulated:insulated_off"}, } }) + +minetest.register_craft({ + output = "mesecons_insulated:insulated_off", + type = "shapeless", + recipe = {"mesecons_extrawires:corner_off"} +}) From 2ede29df9c34ee85cde894b2aada979ff38453ca Mon Sep 17 00:00:00 2001 From: jolesh Date: Thu, 1 Dec 2022 18:33:19 +0100 Subject: [PATCH 04/10] Add Esperanto translation (#625) --- mesecons/locale/mesecons.eo.tr | 4 +++ mesecons/locale/template.txt | 4 +++ mesecons/oldwires.lua | 4 ++- mesecons_blinkyplant/init.lua | 4 ++- .../locale/mesecons_blinkyplant.eo.tr | 4 +++ mesecons_blinkyplant/locale/template.txt | 4 +++ mesecons_button/init.lua | 5 ++-- mesecons_button/locale/mesecons_button.eo.tr | 4 +++ mesecons_button/locale/template.txt | 4 +++ mesecons_commandblock/init.lua | 10 ++++--- .../locale/mesecons_commandblock.eo.tr | 7 +++++ mesecons_commandblock/locale/template.txt | 7 +++++ mesecons_delayer/init.lua | 6 +++-- .../locale/mesecons_delayer.eo.tr | 5 ++++ mesecons_delayer/locale/template.txt | 5 ++++ mesecons_detector/init.lua | 6 +++-- .../locale/mesecons_detector.eo.tr | 5 ++++ mesecons_detector/locale/template.txt | 5 ++++ mesecons_extrawires/corner.lua | 4 ++- mesecons_extrawires/crossover.lua | 10 ++++--- mesecons_extrawires/doublecorner.lua | 4 ++- .../locale/mesecons_extrawires.eo.tr | 20 ++++++++++++++ mesecons_extrawires/locale/template.txt | 20 ++++++++++++++ mesecons_extrawires/mesewire.lua | 4 ++- mesecons_extrawires/tjunction.lua | 4 ++- mesecons_extrawires/vertical.lua | 8 +++--- mesecons_fpga/init.lua | 4 ++- mesecons_fpga/locale/mesecons_fpga.eo.tr | 7 +++++ mesecons_fpga/locale/template.txt | 7 +++++ mesecons_fpga/tool.lua | 4 ++- mesecons_hydroturbine/init.lua | 7 ++--- .../locale/mesecons_hydroturbine.eo.tr | 4 +++ mesecons_hydroturbine/locale/template.txt | 4 +++ mesecons_insulated/init.lua | 6 +++-- .../locale/mesecons_insulated.eo.tr | 4 +++ mesecons_insulated/locale/template.txt | 4 +++ mesecons_lamp/init.lua | 4 ++- mesecons_lamp/locale/mesecons_lamp.eo.tr | 4 +++ mesecons_lamp/locale/template.txt | 4 +++ mesecons_lightstone/init.lua | 26 ++++++++++--------- .../locale/mesecons_lightstone.eo.tr | 15 +++++++++++ mesecons_lightstone/locale/template.txt | 15 +++++++++++ mesecons_luacontroller/init.lua | 4 ++- .../locale/mesecons_luacontroller.eo.tr | 4 +++ mesecons_luacontroller/locale/template.txt | 4 +++ mesecons_materials/init.lua | 8 +++--- .../locale/mesecons_materials.eo.tr | 6 +++++ mesecons_materials/locale/template.txt | 6 +++++ mesecons_microcontroller/init.lua | 4 ++- .../locale/mesecons_microcontroller.eo.tr | 4 +++ mesecons_microcontroller/locale/template.txt | 4 +++ mesecons_movestones/init.lua | 10 ++++--- .../locale/mesecons_movestones.eo.tr | 7 +++++ mesecons_movestones/locale/template.txt | 7 +++++ mesecons_noteblock/init.lua | 4 ++- .../locale/mesecons_noteblock.eo.tr | 4 +++ mesecons_noteblock/locale/template.txt | 4 +++ mesecons_pistons/init.lua | 14 +++++----- .../locale/mesecons_pistons.eo.tr | 9 +++++++ mesecons_pistons/locale/template.txt | 9 +++++++ mesecons_powerplant/init.lua | 4 ++- .../locale/mesecons_powerplant.eo.tr | 4 +++ mesecons_powerplant/locale/template.txt | 4 +++ mesecons_pressureplates/init.lua | 6 +++-- .../locale/mesecons_pressureplates.eo.tr | 5 ++++ mesecons_pressureplates/locale/template.txt | 5 ++++ mesecons_random/init.lua | 6 +++-- mesecons_random/locale/mesecons_random.eo.tr | 5 ++++ mesecons_random/locale/template.txt | 5 ++++ mesecons_solarpanel/init.lua | 4 ++- .../locale/mesecons_solarpanel.eo.tr | 4 +++ mesecons_solarpanel/locale/template.txt | 4 +++ mesecons_stickyblocks/init.lua | 4 ++- .../locale/mesecons_stickyblocks.eo.tr | 4 +++ mesecons_stickyblocks/locale/template.txt | 4 +++ mesecons_switch/init.lua | 4 ++- mesecons_switch/locale/mesecons_switch.eo.tr | 4 +++ mesecons_switch/locale/template.txt | 4 +++ mesecons_torch/init.lua | 4 ++- mesecons_torch/locale/mesecons_torch.eo.tr | 4 +++ mesecons_torch/locale/template.txt | 4 +++ mesecons_walllever/init.lua | 4 ++- .../locale/mesecons_walllever.eo.tr | 4 +++ mesecons_walllever/locale/template.txt | 4 +++ mesecons_wires/init.lua | 4 ++- mesecons_wires/locale/mesecons_wires.eo.tr | 4 +++ mesecons_wires/locale/template.txt | 4 +++ 87 files changed, 444 insertions(+), 70 deletions(-) create mode 100644 mesecons/locale/mesecons.eo.tr create mode 100644 mesecons/locale/template.txt create mode 100644 mesecons_blinkyplant/locale/mesecons_blinkyplant.eo.tr create mode 100644 mesecons_blinkyplant/locale/template.txt create mode 100644 mesecons_button/locale/mesecons_button.eo.tr create mode 100644 mesecons_button/locale/template.txt create mode 100644 mesecons_commandblock/locale/mesecons_commandblock.eo.tr create mode 100644 mesecons_commandblock/locale/template.txt create mode 100644 mesecons_delayer/locale/mesecons_delayer.eo.tr create mode 100644 mesecons_delayer/locale/template.txt create mode 100644 mesecons_detector/locale/mesecons_detector.eo.tr create mode 100644 mesecons_detector/locale/template.txt create mode 100644 mesecons_extrawires/locale/mesecons_extrawires.eo.tr create mode 100644 mesecons_extrawires/locale/template.txt create mode 100644 mesecons_fpga/locale/mesecons_fpga.eo.tr create mode 100644 mesecons_fpga/locale/template.txt create mode 100644 mesecons_hydroturbine/locale/mesecons_hydroturbine.eo.tr create mode 100644 mesecons_hydroturbine/locale/template.txt create mode 100644 mesecons_insulated/locale/mesecons_insulated.eo.tr create mode 100644 mesecons_insulated/locale/template.txt create mode 100644 mesecons_lamp/locale/mesecons_lamp.eo.tr create mode 100644 mesecons_lamp/locale/template.txt create mode 100644 mesecons_lightstone/locale/mesecons_lightstone.eo.tr create mode 100644 mesecons_lightstone/locale/template.txt create mode 100644 mesecons_luacontroller/locale/mesecons_luacontroller.eo.tr create mode 100644 mesecons_luacontroller/locale/template.txt create mode 100644 mesecons_materials/locale/mesecons_materials.eo.tr create mode 100644 mesecons_materials/locale/template.txt create mode 100644 mesecons_microcontroller/locale/mesecons_microcontroller.eo.tr create mode 100644 mesecons_microcontroller/locale/template.txt create mode 100644 mesecons_movestones/locale/mesecons_movestones.eo.tr create mode 100644 mesecons_movestones/locale/template.txt create mode 100644 mesecons_noteblock/locale/mesecons_noteblock.eo.tr create mode 100644 mesecons_noteblock/locale/template.txt create mode 100644 mesecons_pistons/locale/mesecons_pistons.eo.tr create mode 100644 mesecons_pistons/locale/template.txt create mode 100644 mesecons_powerplant/locale/mesecons_powerplant.eo.tr create mode 100644 mesecons_powerplant/locale/template.txt create mode 100644 mesecons_pressureplates/locale/mesecons_pressureplates.eo.tr create mode 100644 mesecons_pressureplates/locale/template.txt create mode 100644 mesecons_random/locale/mesecons_random.eo.tr create mode 100644 mesecons_random/locale/template.txt create mode 100644 mesecons_solarpanel/locale/mesecons_solarpanel.eo.tr create mode 100644 mesecons_solarpanel/locale/template.txt create mode 100644 mesecons_stickyblocks/locale/mesecons_stickyblocks.eo.tr create mode 100644 mesecons_stickyblocks/locale/template.txt create mode 100644 mesecons_switch/locale/mesecons_switch.eo.tr create mode 100644 mesecons_switch/locale/template.txt create mode 100644 mesecons_torch/locale/mesecons_torch.eo.tr create mode 100644 mesecons_torch/locale/template.txt create mode 100644 mesecons_walllever/locale/mesecons_walllever.eo.tr create mode 100644 mesecons_walllever/locale/template.txt create mode 100644 mesecons_wires/locale/mesecons_wires.eo.tr create mode 100644 mesecons_wires/locale/template.txt diff --git a/mesecons/locale/mesecons.eo.tr b/mesecons/locale/mesecons.eo.tr new file mode 100644 index 0000000..76961f0 --- /dev/null +++ b/mesecons/locale/mesecons.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons + +### oldwires.lua ### +Mesecons=Mesekonduktilo diff --git a/mesecons/locale/template.txt b/mesecons/locale/template.txt new file mode 100644 index 0000000..ca5f5d1 --- /dev/null +++ b/mesecons/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons + +### oldwires.lua ### +Mesecons= diff --git a/mesecons/oldwires.lua b/mesecons/oldwires.lua index 28dd4ec..dfbe067 100644 --- a/mesecons/oldwires.lua +++ b/mesecons/oldwires.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + minetest.register_node("mesecons:mesecon_off", { drawtype = "raillike", tiles = {"jeija_mesecon_off.png", "jeija_mesecon_curved_off.png", "jeija_mesecon_t_junction_off.png", "jeija_mesecon_crossing_off.png"}, @@ -11,7 +13,7 @@ minetest.register_node("mesecons:mesecon_off", { fixed = {-0.5, -0.5, -0.5, 0.5, -0.45, 0.5}, }, groups = {dig_immediate=3, mesecon=1, mesecon_conductor_craftable=1}, - description="Mesecons", + description= S("Mesecons"), mesecons = {conductor={ state = mesecon.state.off, onstate = "mesecons:mesecon_on" diff --git a/mesecons_blinkyplant/init.lua b/mesecons_blinkyplant/init.lua index 28cb054..44dd3ff 100644 --- a/mesecons_blinkyplant/init.lua +++ b/mesecons_blinkyplant/init.lua @@ -1,5 +1,7 @@ -- The BLINKY_PLANT +local S = minetest.get_translator(minetest.get_current_modname()) + local toggle_timer = function (pos) local timer = minetest.get_node_timer(pos) if timer:is_started() then @@ -20,7 +22,7 @@ local on_timer = function (pos) end mesecon.register_node("mesecons_blinkyplant:blinky_plant", { - description="Blinky Plant", + description= S("Blinky Plant"), drawtype = "plantlike", inventory_image = "jeija_blinky_plant_off.png", paramtype = "light", diff --git a/mesecons_blinkyplant/locale/mesecons_blinkyplant.eo.tr b/mesecons_blinkyplant/locale/mesecons_blinkyplant.eo.tr new file mode 100644 index 0000000..db628ba --- /dev/null +++ b/mesecons_blinkyplant/locale/mesecons_blinkyplant.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_blinkyplant + +### init.lua ### +Blinky Plant=Palpebruma Planto diff --git a/mesecons_blinkyplant/locale/template.txt b/mesecons_blinkyplant/locale/template.txt new file mode 100644 index 0000000..e2f5e44 --- /dev/null +++ b/mesecons_blinkyplant/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_blinkyplant + +### init.lua ### +Blinky Plant= diff --git a/mesecons_button/init.lua b/mesecons_button/init.lua index 7c70923..878a6e1 100644 --- a/mesecons_button/init.lua +++ b/mesecons_button/init.lua @@ -1,6 +1,7 @@ -- WALL BUTTON -- A button that when pressed emits power for 1 second -- and then turns off again +local S = minetest.get_translator(minetest.get_current_modname()) mesecon.button_turnoff = function (pos) local node = minetest.get_node(pos) @@ -45,7 +46,7 @@ minetest.register_node("mesecons_button:button_off", { } }, groups = {dig_immediate=2, mesecon_needs_receiver = 1}, - description = "Button", + description = S("Button"), on_rightclick = function (pos, node) minetest.swap_node(pos, {name = "mesecons_button:button_on", param2=node.param2}) mesecon.receptor_on(pos, mesecon.rules.buttonlike_get(node)) @@ -92,7 +93,7 @@ minetest.register_node("mesecons_button:button_on", { }, groups = {dig_immediate=2, not_in_creative_inventory=1, mesecon_needs_receiver = 1}, drop = 'mesecons_button:button_off', - description = "Button", + description = S("Button"), sounds = mesecon.node_sound.stone, mesecons = {receptor = { state = mesecon.state.on, diff --git a/mesecons_button/locale/mesecons_button.eo.tr b/mesecons_button/locale/mesecons_button.eo.tr new file mode 100644 index 0000000..463f65b --- /dev/null +++ b/mesecons_button/locale/mesecons_button.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_button + +### init.lua ### +Button=Butono diff --git a/mesecons_button/locale/template.txt b/mesecons_button/locale/template.txt new file mode 100644 index 0000000..2a4d5b9 --- /dev/null +++ b/mesecons_button/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_button + +### init.lua ### +Button= diff --git a/mesecons_commandblock/init.lua b/mesecons_commandblock/init.lua index c96cfe8..63fa702 100644 --- a/mesecons_commandblock/init.lua +++ b/mesecons_commandblock/init.lua @@ -1,6 +1,8 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + minetest.register_chatcommand("say", { params = "", - description = "Say as the server", + description = S("Say as the server"), privs = {server=true}, func = function(name, param) minetest.chat_send_all(name .. ": " .. param) @@ -9,7 +11,7 @@ minetest.register_chatcommand("say", { minetest.register_chatcommand("tell", { params = " ", - description = "Say to privately", + description = S("Say to privately"), privs = {shout=true}, func = function(name, param) local found, _, target, message = param:find("^([^%s]+)%s+(.*)$") @@ -26,7 +28,7 @@ minetest.register_chatcommand("tell", { minetest.register_chatcommand("hp", { params = " ", - description = "Set health of to hitpoints", + description = S("Set health of to hitpoints"), privs = {ban=true}, func = function(name, param) local found, _, target, value = param:find("^([^%s]+)%s+(%d+)$") @@ -180,7 +182,7 @@ local function can_dig(pos, player) end minetest.register_node("mesecons_commandblock:commandblock_off", { - description = "Command Block", + description = S("Command Block"), tiles = {"jeija_commandblock_off.png"}, inventory_image = minetest.inventorycube("jeija_commandblock_off.png"), is_ground_content = false, diff --git a/mesecons_commandblock/locale/mesecons_commandblock.eo.tr b/mesecons_commandblock/locale/mesecons_commandblock.eo.tr new file mode 100644 index 0000000..3a9f077 --- /dev/null +++ b/mesecons_commandblock/locale/mesecons_commandblock.eo.tr @@ -0,0 +1,7 @@ +# textdomain: mesecons_commandblock + +### init.lua ### +Say as the server=Diru kiel la servilo +Say to privately=Diru al private +Set health of to hitpoints=Agordu sanon de al +Command Block=Komando-Bloko diff --git a/mesecons_commandblock/locale/template.txt b/mesecons_commandblock/locale/template.txt new file mode 100644 index 0000000..1fdd37c --- /dev/null +++ b/mesecons_commandblock/locale/template.txt @@ -0,0 +1,7 @@ +# textdomain: mesecons_commandblock + +### init.lua ### +Say as the server= +Say to privately= +Set health of to hitpoints= +Command Block= diff --git a/mesecons_delayer/init.lua b/mesecons_delayer/init.lua index bd2eca8..2c4b0f4 100644 --- a/mesecons_delayer/init.lua +++ b/mesecons_delayer/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + -- Function that get the input/output rules of the delayer local delayer_get_output_rules = mesecon.horiz_rules_getter({{x = 1, y = 0, z = 0}}) @@ -69,7 +71,7 @@ if i > 1 then end local off_state = { - description = "Delayer", + description = S("Delayer"), tiles = { "mesecons_delayer_off_"..tostring(i)..".png", "mesecons_delayer_bottom.png", @@ -112,7 +114,7 @@ minetest.register_node("mesecons_delayer:delayer_off_"..tostring(i), off_state) -- Activated delayer definition defaults local on_state = { - description = "You hacker you", + description = S("You hacker you"), tiles = { "mesecons_delayer_on_"..tostring(i)..".png", "mesecons_delayer_bottom.png", diff --git a/mesecons_delayer/locale/mesecons_delayer.eo.tr b/mesecons_delayer/locale/mesecons_delayer.eo.tr new file mode 100644 index 0000000..f6d4350 --- /dev/null +++ b/mesecons_delayer/locale/mesecons_delayer.eo.tr @@ -0,0 +1,5 @@ +# textdomain: mesecons_delayer + +### init.lua ### +Delayer=Prokrasto +You hacker you=Vi hakisto diff --git a/mesecons_delayer/locale/template.txt b/mesecons_delayer/locale/template.txt new file mode 100644 index 0000000..e5e06f2 --- /dev/null +++ b/mesecons_delayer/locale/template.txt @@ -0,0 +1,5 @@ +# textdomain: mesecons_delayer + +### init.lua ### +Delayer= +You hacker you= diff --git a/mesecons_detector/init.lua b/mesecons_detector/init.lua index d460936..d5e3c4f 100644 --- a/mesecons_detector/init.lua +++ b/mesecons_detector/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local side_texture = mesecon.texture.steel_block or "mesecons_detector_side.png" local GET_COMMAND = "GET" @@ -71,7 +73,7 @@ minetest.register_node("mesecons_detector:object_detector_off", { is_ground_content = false, walkable = true, groups = {cracky=3}, - description="Player Detector", + description= S("Player Detector"), mesecons = {receptor = { state = mesecon.state.off, rules = mesecon.rules.pplate @@ -247,7 +249,7 @@ minetest.register_node("mesecons_detector:node_detector_off", { is_ground_content = false, walkable = true, groups = {cracky=3}, - description="Node Detector", + description = S("Node Detector"), mesecons = {receptor = { state = mesecon.state.off }}, diff --git a/mesecons_detector/locale/mesecons_detector.eo.tr b/mesecons_detector/locale/mesecons_detector.eo.tr new file mode 100644 index 0000000..0db21e2 --- /dev/null +++ b/mesecons_detector/locale/mesecons_detector.eo.tr @@ -0,0 +1,5 @@ +# textdomain: mesecons_detector + +### init.lua ### +Player Detector=Ludanta Detektilo +Node Detector=Noda Detektilo diff --git a/mesecons_detector/locale/template.txt b/mesecons_detector/locale/template.txt new file mode 100644 index 0000000..1a8a6a7 --- /dev/null +++ b/mesecons_detector/locale/template.txt @@ -0,0 +1,5 @@ +# textdomain: mesecons_detector + +### init.lua ### +Player Detector= +Node Detector= diff --git a/mesecons_extrawires/corner.lua b/mesecons_extrawires/corner.lua index 65f17e9..6d85b6b 100644 --- a/mesecons_extrawires/corner.lua +++ b/mesecons_extrawires/corner.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local corner_selectionbox = { type = "fixed", fixed = { -16/32, -16/32, -16/32, 5/32, -12/32, 5/32 }, @@ -36,7 +38,7 @@ minetest.register_node("mesecons_extrawires:corner_on", { minetest.register_node("mesecons_extrawires:corner_off", { drawtype = "mesh", - description = "Insulated Mesecon Corner", + description = S("Insulated Mesecon Corner"), mesh = "mesecons_extrawires_corner.obj", tiles = { { name = "jeija_insulated_wire_sides_off.png", backface_culling = true }, diff --git a/mesecons_extrawires/crossover.lua b/mesecons_extrawires/crossover.lua index bf7f0db..1001467 100644 --- a/mesecons_extrawires/crossover.lua +++ b/mesecons_extrawires/crossover.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local crossover_rules = { {--first wire {x=-1,y=0,z=0}, @@ -17,7 +19,7 @@ local crossover_states = { } minetest.register_node("mesecons_extrawires:crossover_off", { - description = "Insulated Mesecon Crossover", + description = S("Insulated Mesecon Crossover"), drawtype = "mesh", mesh = "mesecons_extrawires_crossover.b3d", tiles = { @@ -43,7 +45,7 @@ minetest.register_node("mesecons_extrawires:crossover_off", { }) minetest.register_node("mesecons_extrawires:crossover_01", { - description = "You hacker you!", + description = S("You hacker you!"), drop = "mesecons_extrawires:crossover_off", drawtype = "mesh", mesh = "mesecons_extrawires_crossover.b3d", @@ -70,7 +72,7 @@ minetest.register_node("mesecons_extrawires:crossover_01", { }) minetest.register_node("mesecons_extrawires:crossover_10", { - description = "You hacker you!", + description = S("You hacker you!"), drop = "mesecons_extrawires:crossover_off", drawtype = "mesh", mesh = "mesecons_extrawires_crossover.b3d", @@ -97,7 +99,7 @@ minetest.register_node("mesecons_extrawires:crossover_10", { }) minetest.register_node("mesecons_extrawires:crossover_on", { - description = "You hacker you!", + description = S("You hacker you!"), drop = "mesecons_extrawires:crossover_off", drawtype = "mesh", mesh = "mesecons_extrawires_crossover.b3d", diff --git a/mesecons_extrawires/doublecorner.lua b/mesecons_extrawires/doublecorner.lua index 4f8e839..36059e1 100644 --- a/mesecons_extrawires/doublecorner.lua +++ b/mesecons_extrawires/doublecorner.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local doublecorner_selectionbox = { type = "fixed", fixed = { -8/16, -8/16, -8/16, 8/16, -6/16, 8/16 }, @@ -31,7 +33,7 @@ for k, state in ipairs(doublecorner_states) do minetest.register_node(state, { drawtype = "mesh", mesh = "mesecons_extrawires_doublecorner.obj", - description = "Insulated Mesecon Double Corner", + description = S("Insulated Mesecon Double Corner"), tiles = { { name = "jeija_insulated_wire_sides_" .. w1 .. ".png", backface_culling = true }, { name = "jeija_insulated_wire_ends_" .. w1 .. ".png", backface_culling = true }, diff --git a/mesecons_extrawires/locale/mesecons_extrawires.eo.tr b/mesecons_extrawires/locale/mesecons_extrawires.eo.tr new file mode 100644 index 0000000..e8d8415 --- /dev/null +++ b/mesecons_extrawires/locale/mesecons_extrawires.eo.tr @@ -0,0 +1,20 @@ +# textdomain: mesecons_extrawires + +### corner.lua ### +Insulated Mesecon Corner=Izolita Mesekonduktila Angulo + +### crossover.lua ### +Insulated Mesecon Crossover=Izolita Mesekonduktila Interkruciĝo +You hacker you!=Vi hakisto + +### doublecorner.lua ### +Insulated Mesecon Double Corner=Izolita Mesekonduktila Duobla Angulo + +### mesewire.lua ### +Mese Wire=Mesea Drato + +### tjunction.lua ### +Insulated Mesecon T-junction=Izolita Mesekonduktila T-Kruciĝo + +### vertical.lua ### +Vertical Mesecon=Vertikala Mesekonduktilo diff --git a/mesecons_extrawires/locale/template.txt b/mesecons_extrawires/locale/template.txt new file mode 100644 index 0000000..44d3abb --- /dev/null +++ b/mesecons_extrawires/locale/template.txt @@ -0,0 +1,20 @@ +# textdomain: mesecons_extrawires + +### corner.lua ### +Insulated Mesecon Corner= + +### crossover.lua ### +Insulated Mesecon Crossover= +You hacker you!= + +### doublecorner.lua ### +Insulated Mesecon Double Corner= + +### mesewire.lua ### +Mese Wire= + +### tjunction.lua ### +Insulated Mesecon T-junction= + +### vertical.lua ### +Vertical Mesecon= diff --git a/mesecons_extrawires/mesewire.lua b/mesecons_extrawires/mesewire.lua index f74fdcb..cdb87a8 100644 --- a/mesecons_extrawires/mesewire.lua +++ b/mesecons_extrawires/mesewire.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local mese_nodename = minetest.registered_aliases["mesecons_gamecompat:mese"] if mese_nodename then -- Convert placeholders. @@ -6,7 +8,7 @@ else -- Register placeholder. mese_nodename = "mesecons_extrawires:mese" minetest.register_node("mesecons_extrawires:mese", { - description = "Mese Wire", + description = S("Mese Wire"), tiles = {"mesecons_wire_off.png"}, paramtype = "light", light_source = 3, diff --git a/mesecons_extrawires/tjunction.lua b/mesecons_extrawires/tjunction.lua index a69b14a..d6186f2 100644 --- a/mesecons_extrawires/tjunction.lua +++ b/mesecons_extrawires/tjunction.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local tjunction_nodebox = { type = "fixed", -- ±0.001 is to prevent z-fighting @@ -48,7 +50,7 @@ minetest.register_node("mesecons_extrawires:tjunction_on", { minetest.register_node("mesecons_extrawires:tjunction_off", { drawtype = "nodebox", - description = "Insulated Mesecon T-junction", + description = S("Insulated Mesecon T-junction"), tiles = { "jeija_insulated_wire_tjunction_tb_off.png", "jeija_insulated_wire_tjunction_tb_off.png^[transformR180", diff --git a/mesecons_extrawires/vertical.lua b/mesecons_extrawires/vertical.lua index 72084fc..9e132ac 100644 --- a/mesecons_extrawires/vertical.lua +++ b/mesecons_extrawires/vertical.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local vertical_box = { type = "fixed", fixed = {-1/16, -8/16, -1/16, 1/16, 8/16, 1/16} @@ -77,7 +79,7 @@ end -- Vertical wire mesecon.register_node("mesecons_extrawires:vertical", { - description = "Vertical Mesecon", + description = S("Vertical Mesecon"), drawtype = "nodebox", walkable = false, paramtype = "light", @@ -110,7 +112,7 @@ mesecon.register_node("mesecons_extrawires:vertical", { -- Vertical wire top mesecon.register_node("mesecons_extrawires:vertical_top", { - description = "Vertical mesecon", + description = S("Vertical Mesecon"), drawtype = "nodebox", walkable = false, paramtype = "light", @@ -142,7 +144,7 @@ mesecon.register_node("mesecons_extrawires:vertical_top", { -- Vertical wire bottom mesecon.register_node("mesecons_extrawires:vertical_bottom", { - description = "Vertical mesecon", + description = S("Vertical Mesecon"), drawtype = "nodebox", walkable = false, paramtype = "light", diff --git a/mesecons_fpga/init.lua b/mesecons_fpga/init.lua index 1898177..fe86ad4 100644 --- a/mesecons_fpga/init.lua +++ b/mesecons_fpga/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local plg = {} plg.rules = {} -- per-player formspec positions @@ -59,7 +61,7 @@ plg.register_nodes = function(template) end plg.register_nodes({ - description = "FPGA", + description = S("FPGA"), drawtype = "nodebox", tiles = { "", -- replaced later diff --git a/mesecons_fpga/locale/mesecons_fpga.eo.tr b/mesecons_fpga/locale/mesecons_fpga.eo.tr new file mode 100644 index 0000000..5ad42b1 --- /dev/null +++ b/mesecons_fpga/locale/mesecons_fpga.eo.tr @@ -0,0 +1,7 @@ +# textdomain: mesecons_fpga + +### init.lua ### +FPGA=FPGA + +### tool.lua ### +FPGA Programmer=FPGA Programilo diff --git a/mesecons_fpga/locale/template.txt b/mesecons_fpga/locale/template.txt new file mode 100644 index 0000000..5764150 --- /dev/null +++ b/mesecons_fpga/locale/template.txt @@ -0,0 +1,7 @@ +# textdomain: mesecons_fpga + +### init.lua ### +FPGA= + +### tool.lua ### +FPGA Programmer= diff --git a/mesecons_fpga/tool.lua b/mesecons_fpga/tool.lua index 73d6c0f..fa708b3 100644 --- a/mesecons_fpga/tool.lua +++ b/mesecons_fpga/tool.lua @@ -1,8 +1,10 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + return function(plg) minetest.register_tool("mesecons_fpga:programmer", { - description = "FPGA Programmer", + description = S("FPGA Programmer"), inventory_image = "jeija_fpga_programmer.png", stack_max = 1, on_place = function(itemstack, placer, pointed_thing) diff --git a/mesecons_hydroturbine/init.lua b/mesecons_hydroturbine/init.lua index a993085..6d0038c 100644 --- a/mesecons_hydroturbine/init.lua +++ b/mesecons_hydroturbine/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + -- HYDRO_TURBINE -- Water turbine: -- Active if flowing >water< above it @@ -17,7 +19,7 @@ minetest.register_node("mesecons_hydroturbine:hydro_turbine_off", { is_ground_content = false, wield_scale = {x=0.75, y=0.75, z=0.75}, groups = {dig_immediate=2}, - description="Water Turbine", + description = S("Water Turbine"), paramtype = "light", selection_box = { type = "fixed", @@ -47,7 +49,7 @@ minetest.register_node("mesecons_hydroturbine:hydro_turbine_on", { inventory_image = "jeija_hydro_turbine_inv.png", drop = "mesecons_hydroturbine:hydro_turbine_off 1", groups = {dig_immediate=2,not_in_creative_inventory=1}, - description="Water Turbine", + description = S("Water Turbine"), paramtype = "light", selection_box = { type = "fixed", @@ -102,4 +104,3 @@ minetest.register_craft({ {"","group:stick", ""}, } }) - diff --git a/mesecons_hydroturbine/locale/mesecons_hydroturbine.eo.tr b/mesecons_hydroturbine/locale/mesecons_hydroturbine.eo.tr new file mode 100644 index 0000000..7ca3172 --- /dev/null +++ b/mesecons_hydroturbine/locale/mesecons_hydroturbine.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_hydroturbine + +### init.lua ### +Water Turbine=Akva Turbino diff --git a/mesecons_hydroturbine/locale/template.txt b/mesecons_hydroturbine/locale/template.txt new file mode 100644 index 0000000..ebf8921 --- /dev/null +++ b/mesecons_hydroturbine/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_hydroturbine + +### init.lua ### +Water Turbine= diff --git a/mesecons_insulated/init.lua b/mesecons_insulated/init.lua index f98565c..a805230 100644 --- a/mesecons_insulated/init.lua +++ b/mesecons_insulated/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local insulated_wire_get_rules = mesecon.horiz_rules_getter({ {x = 1, y = 0, z = 0}, {x = -1, y = 0, z = 0}, @@ -5,7 +7,7 @@ local insulated_wire_get_rules = mesecon.horiz_rules_getter({ minetest.register_node("mesecons_insulated:insulated_on", { drawtype = "nodebox", - description = "Straight Insulated Mesecon", + description = S("Straight Insulated Mesecon"), tiles = { "jeija_insulated_wire_sides_on.png", "jeija_insulated_wire_sides_on.png", @@ -42,7 +44,7 @@ minetest.register_node("mesecons_insulated:insulated_on", { minetest.register_node("mesecons_insulated:insulated_off", { drawtype = "nodebox", - description = "Straight Insulated Mesecon", + description = S("Straight Insulated Mesecon"), tiles = { "jeija_insulated_wire_sides_off.png", "jeija_insulated_wire_sides_off.png", diff --git a/mesecons_insulated/locale/mesecons_insulated.eo.tr b/mesecons_insulated/locale/mesecons_insulated.eo.tr new file mode 100644 index 0000000..878908b --- /dev/null +++ b/mesecons_insulated/locale/mesecons_insulated.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_insulated + +### init.lua ### +Straight Insulated Mesecon=Rekta Izolita Mesekonduktilo diff --git a/mesecons_insulated/locale/template.txt b/mesecons_insulated/locale/template.txt new file mode 100644 index 0000000..8fbde4f --- /dev/null +++ b/mesecons_insulated/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_insulated + +### init.lua ### +Straight Insulated Mesecon= diff --git a/mesecons_lamp/init.lua b/mesecons_lamp/init.lua index aff1f26..ff183fd 100644 --- a/mesecons_lamp/init.lua +++ b/mesecons_lamp/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + -- MESELAMPS -- A lamp is "is an electrical device used to create artificial light" (wikipedia) -- guess what? @@ -50,7 +52,7 @@ minetest.register_node("mesecons_lamp:lamp_off", { node_box = mesecon_lamp_box, selection_box = mesecon_lamp_box, groups = {dig_immediate=3, mesecon_receptor_off = 1, mesecon_effector_off = 1}, - description = "Mesecon Lamp", + description = S("Mesecon Lamp"), sounds = mesecon.node_sound.glass, mesecons = {effector = { action_on = function (pos, node) diff --git a/mesecons_lamp/locale/mesecons_lamp.eo.tr b/mesecons_lamp/locale/mesecons_lamp.eo.tr new file mode 100644 index 0000000..3434e65 --- /dev/null +++ b/mesecons_lamp/locale/mesecons_lamp.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_lamp + +### init.lua ### +Mesecon Lamp=Mesekonduktila Lampo diff --git a/mesecons_lamp/locale/template.txt b/mesecons_lamp/locale/template.txt new file mode 100644 index 0000000..208d3c2 --- /dev/null +++ b/mesecons_lamp/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_lamp + +### init.lua ### +Mesecon Lamp= diff --git a/mesecons_lightstone/init.lua b/mesecons_lightstone/init.lua index f2ea147..d601f07 100644 --- a/mesecons_lightstone/init.lua +++ b/mesecons_lightstone/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local lightstone_rules = { {x=0, y=0, z=-1}, {x=1, y=0, z=0}, @@ -59,15 +61,15 @@ function mesecon.lightstone_add(name, base_item, texture_off, texture_on, desc) end -mesecon.lightstone_add("red", "mesecons_gamecompat:dye_red", "jeija_lightstone_red_off.png", "jeija_lightstone_red_on.png", "Red Lightstone") -mesecon.lightstone_add("green", "mesecons_gamecompat:dye_green", "jeija_lightstone_green_off.png", "jeija_lightstone_green_on.png", "Green Lightstone") -mesecon.lightstone_add("blue", "mesecons_gamecompat:dye_blue", "jeija_lightstone_blue_off.png", "jeija_lightstone_blue_on.png", "Blue Lightstone") -mesecon.lightstone_add("gray", "mesecons_gamecompat:dye_grey", "jeija_lightstone_gray_off.png", "jeija_lightstone_gray_on.png", "Grey Lightstone") -mesecon.lightstone_add("darkgray", "mesecons_gamecompat:dye_dark_grey", "jeija_lightstone_darkgray_off.png", "jeija_lightstone_darkgray_on.png", "Dark Grey Lightstone") -mesecon.lightstone_add("yellow", "mesecons_gamecompat:dye_yellow", "jeija_lightstone_yellow_off.png", "jeija_lightstone_yellow_on.png", "Yellow Lightstone") -mesecon.lightstone_add("orange", "mesecons_gamecompat:dye_orange", "jeija_lightstone_orange_off.png", "jeija_lightstone_orange_on.png", "Orange Lightstone") -mesecon.lightstone_add("white", "mesecons_gamecompat:dye_white", "jeija_lightstone_white_off.png", "jeija_lightstone_white_on.png", "White Lightstone") -mesecon.lightstone_add("pink", "mesecons_gamecompat:dye_pink", "jeija_lightstone_pink_off.png", "jeija_lightstone_pink_on.png", "Pink Lightstone") -mesecon.lightstone_add("magenta", "mesecons_gamecompat:dye_magenta", "jeija_lightstone_magenta_off.png", "jeija_lightstone_magenta_on.png", "Magenta Lightstone") -mesecon.lightstone_add("cyan", "mesecons_gamecompat:dye_cyan", "jeija_lightstone_cyan_off.png", "jeija_lightstone_cyan_on.png", "Cyan Lightstone") -mesecon.lightstone_add("violet", "mesecons_gamecompat:dye_violet", "jeija_lightstone_violet_off.png", "jeija_lightstone_violet_on.png", "Violet Lightstone") +mesecon.lightstone_add("red", "mesecons_gamecompat:dye_red", "jeija_lightstone_red_off.png", "jeija_lightstone_red_on.png", S("Red Lightstone")) +mesecon.lightstone_add("green", "mesecons_gamecompat:dye_green", "jeija_lightstone_green_off.png", "jeija_lightstone_green_on.png", S("Green Lightstone")) +mesecon.lightstone_add("blue", "mesecons_gamecompat:dye_blue", "jeija_lightstone_blue_off.png", "jeija_lightstone_blue_on.png", S("Blue Lightstone")) +mesecon.lightstone_add("gray", "mesecons_gamecompat:dye_grey", "jeija_lightstone_gray_off.png", "jeija_lightstone_gray_on.png", S("Grey Lightstone")) +mesecon.lightstone_add("darkgray", "mesecons_gamecompat:dye_dark_grey", "jeija_lightstone_darkgray_off.png", "jeija_lightstone_darkgray_on.png", S("Dark Grey Lightstone")) +mesecon.lightstone_add("yellow", "mesecons_gamecompat:dye_yellow", "jeija_lightstone_yellow_off.png", "jeija_lightstone_yellow_on.png", S("Yellow Lightstone")) +mesecon.lightstone_add("orange", "mesecons_gamecompat:dye_orange", "jeija_lightstone_orange_off.png", "jeija_lightstone_orange_on.png", S("Orange Lightstone")) +mesecon.lightstone_add("white", "mesecons_gamecompat:dye_white", "jeija_lightstone_white_off.png", "jeija_lightstone_white_on.png", S("White Lightstone")) +mesecon.lightstone_add("pink", "mesecons_gamecompat:dye_pink", "jeija_lightstone_pink_off.png", "jeija_lightstone_pink_on.png", S("Pink Lightstone")) +mesecon.lightstone_add("magenta", "mesecons_gamecompat:dye_magenta", "jeija_lightstone_magenta_off.png", "jeija_lightstone_magenta_on.png", S("Magenta Lightstone")) +mesecon.lightstone_add("cyan", "mesecons_gamecompat:dye_cyan", "jeija_lightstone_cyan_off.png", "jeija_lightstone_cyan_on.png", S("Cyan Lightstone")) +mesecon.lightstone_add("violet", "mesecons_gamecompat:dye_violet", "jeija_lightstone_violet_off.png", "jeija_lightstone_violet_on.png", S("Violet Lightstone")) diff --git a/mesecons_lightstone/locale/mesecons_lightstone.eo.tr b/mesecons_lightstone/locale/mesecons_lightstone.eo.tr new file mode 100644 index 0000000..7dab604 --- /dev/null +++ b/mesecons_lightstone/locale/mesecons_lightstone.eo.tr @@ -0,0 +1,15 @@ +# textdomain: mesecons_lightstone + +### init.lua ### +Red Lightstone=Ruĝa Lumŝtono +Green Lightstone=Verda Lumŝtono +Blue Lightstone=Blua Lumŝtono +Grey Lightstone=Griza Lumŝtono +Dark Grey Lightstone=Malhela Griza Lumŝtono +Yellow Lightstone=Flava Lumŝtono +Orange Lightstone=Oranĝa Lumŝtono +White Lightstone=Blanka Lumŝtono +Pink Lightstone=Rozkolora Lumŝtono +Magenta Lightstone=Magenta Lumŝtono +Cyan Lightstone=Cejana Lumŝtono +Violet Lightstone=Viola Lumŝtono diff --git a/mesecons_lightstone/locale/template.txt b/mesecons_lightstone/locale/template.txt new file mode 100644 index 0000000..884c66f --- /dev/null +++ b/mesecons_lightstone/locale/template.txt @@ -0,0 +1,15 @@ +# textdomain: mesecons_lightstone + +### init.lua ### +Red Lightstone= +Green Lightstone= +Blue Lightstone= +Grey Lightstone= +Dark Grey Lightstone= +Yellow Lightstone= +Orange Lightstone= +White Lightstone= +Pink Lightstone= +Magenta Lightstone= +Cyan Lightstone= +Violet Lightstone= diff --git a/mesecons_luacontroller/init.lua b/mesecons_luacontroller/init.lua index 50a6747..2eba817 100644 --- a/mesecons_luacontroller/init.lua +++ b/mesecons_luacontroller/init.lua @@ -28,6 +28,8 @@ -- (see where local env is defined) -- Something nice to play is is appending minetest.env to it. +local S = minetest.get_translator(minetest.get_current_modname()) + local BASENAME = "mesecons_luacontroller:luacontroller" local rules = { @@ -851,7 +853,7 @@ for d = 0, 1 do } minetest.register_node(node_name, { - description = "Luacontroller", + description = S("Luacontroller"), drawtype = "nodebox", tiles = { top, diff --git a/mesecons_luacontroller/locale/mesecons_luacontroller.eo.tr b/mesecons_luacontroller/locale/mesecons_luacontroller.eo.tr new file mode 100644 index 0000000..f477ebd --- /dev/null +++ b/mesecons_luacontroller/locale/mesecons_luacontroller.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_luacontroller + +### init.lua ### +Luacontroller=Luaregilo diff --git a/mesecons_luacontroller/locale/template.txt b/mesecons_luacontroller/locale/template.txt new file mode 100644 index 0000000..ece4275 --- /dev/null +++ b/mesecons_luacontroller/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_luacontroller + +### init.lua ### +Luacontroller= diff --git a/mesecons_materials/init.lua b/mesecons_materials/init.lua index c3ed73b..ae95eb5 100644 --- a/mesecons_materials/init.lua +++ b/mesecons_materials/init.lua @@ -1,14 +1,16 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + -- Glue and fiber minetest.register_craftitem("mesecons_materials:glue", { image = "mesecons_glue.png", on_place_on_ground = minetest.craftitem_place_item, - description="Glue", + description = S("Glue"), }) minetest.register_craftitem("mesecons_materials:fiber", { image = "mesecons_fiber.png", on_place_on_ground = minetest.craftitem_place_item, - description="Fiber", + description = S("Fiber"), }) minetest.register_craft({ @@ -29,7 +31,7 @@ minetest.register_craft({ minetest.register_craftitem("mesecons_materials:silicon", { image = "mesecons_silicon.png", on_place_on_ground = minetest.craftitem_place_item, - description="Silicon", + description = S("Silicon"), }) minetest.register_craft({ diff --git a/mesecons_materials/locale/mesecons_materials.eo.tr b/mesecons_materials/locale/mesecons_materials.eo.tr new file mode 100644 index 0000000..501c386 --- /dev/null +++ b/mesecons_materials/locale/mesecons_materials.eo.tr @@ -0,0 +1,6 @@ +# textdomain: mesecons_materials + +### init.lua ### +Glue=Gluo +Fiber=Fibro +Silicon=Silicio diff --git a/mesecons_materials/locale/template.txt b/mesecons_materials/locale/template.txt new file mode 100644 index 0000000..4c1eb74 --- /dev/null +++ b/mesecons_materials/locale/template.txt @@ -0,0 +1,6 @@ +# textdomain: mesecons_materials + +### init.lua ### +Glue= +Fiber= +Silicon= diff --git a/mesecons_microcontroller/init.lua b/mesecons_microcontroller/init.lua index 05384da..d28df2b 100644 --- a/mesecons_microcontroller/init.lua +++ b/mesecons_microcontroller/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local EEPROM_SIZE = 255 local microc_rules = {} @@ -57,7 +59,7 @@ if nodename ~= "mesecons_microcontroller:microcontroller0000" then end minetest.register_node(nodename, { - description = "Microcontroller", + description = S("Microcontroller"), drawtype = "nodebox", tiles = { top, diff --git a/mesecons_microcontroller/locale/mesecons_microcontroller.eo.tr b/mesecons_microcontroller/locale/mesecons_microcontroller.eo.tr new file mode 100644 index 0000000..39c2c90 --- /dev/null +++ b/mesecons_microcontroller/locale/mesecons_microcontroller.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_microcontroller + +### init.lua ### +Microcontroller=Mikroregilo diff --git a/mesecons_microcontroller/locale/template.txt b/mesecons_microcontroller/locale/template.txt new file mode 100644 index 0000000..f4274a0 --- /dev/null +++ b/mesecons_microcontroller/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_microcontroller + +### init.lua ### +Microcontroller= diff --git a/mesecons_movestones/init.lua b/mesecons_movestones/init.lua index e50103c..715e75c 100644 --- a/mesecons_movestones/init.lua +++ b/mesecons_movestones/init.lua @@ -8,6 +8,8 @@ -- Pushes all block in front of it -- Pull all blocks in its back +local S = minetest.get_translator(minetest.get_current_modname()) + -- settings: local timer_interval = 1 / mesecon.setting("movestone_speed", 3) local max_push = mesecon.setting("movestone_max_push", 50) @@ -128,7 +130,7 @@ mesecon.register_movestone("mesecons_movestones:movestone", { "jeija_movestone_arrows.png", }, groups = {cracky = 3}, - description = "Movestone", + description = S("Movestone"), sounds = mesecon.node_sound.stone }, false, false) @@ -142,7 +144,7 @@ mesecon.register_movestone("mesecons_movestones:sticky_movestone", { "jeija_sticky_movestone.png", }, groups = {cracky = 3}, - description = "Sticky Movestone", + description = S("Sticky Movestone"), sounds = mesecon.node_sound.stone, }, true, false) @@ -156,7 +158,7 @@ mesecon.register_movestone("mesecons_movestones:movestone_vertical", { "jeija_movestone_arrows.png^[transformR90", }, groups = {cracky = 3}, - description = "Vertical Movestone", + description = S("Vertical Movestone"), sounds = mesecon.node_sound.stone }, false, true) @@ -170,7 +172,7 @@ mesecon.register_movestone("mesecons_movestones:sticky_movestone_vertical", { "jeija_movestone_arrows.png^[transformR90", }, groups = {cracky = 3}, - description = "Vertical Sticky Movestone", + description = S("Vertical Sticky Movestone"), sounds = mesecon.node_sound.stone, }, true, true) diff --git a/mesecons_movestones/locale/mesecons_movestones.eo.tr b/mesecons_movestones/locale/mesecons_movestones.eo.tr new file mode 100644 index 0000000..b4f8d37 --- /dev/null +++ b/mesecons_movestones/locale/mesecons_movestones.eo.tr @@ -0,0 +1,7 @@ +# textdomain: mesecons_movestones + +### init.lua ### +Movestone=Movŝtono +Sticky Movestone=Glueca Movŝtono +Vertical Movestone=Vertikala Movŝtono +Vertical Sticky Movestone=Vertikala Glueca Movŝtono diff --git a/mesecons_movestones/locale/template.txt b/mesecons_movestones/locale/template.txt new file mode 100644 index 0000000..a35e6bc --- /dev/null +++ b/mesecons_movestones/locale/template.txt @@ -0,0 +1,7 @@ +# textdomain: mesecons_movestones + +### init.lua ### +Movestone= +Sticky Movestone= +Vertical Movestone= +Vertical Sticky Movestone= diff --git a/mesecons_noteblock/init.lua b/mesecons_noteblock/init.lua index f0e7044..4080478 100644 --- a/mesecons_noteblock/init.lua +++ b/mesecons_noteblock/init.lua @@ -1,5 +1,7 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + minetest.register_node("mesecons_noteblock:noteblock", { - description = "Noteblock", + description = S("Noteblock"), tiles = {"mesecons_noteblock.png"}, is_ground_content = false, groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2}, diff --git a/mesecons_noteblock/locale/mesecons_noteblock.eo.tr b/mesecons_noteblock/locale/mesecons_noteblock.eo.tr new file mode 100644 index 0000000..bea3735 --- /dev/null +++ b/mesecons_noteblock/locale/mesecons_noteblock.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_noteblock + +### init.lua ### +Noteblock=Sonbloko diff --git a/mesecons_noteblock/locale/template.txt b/mesecons_noteblock/locale/template.txt new file mode 100644 index 0000000..bcd3754 --- /dev/null +++ b/mesecons_noteblock/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_noteblock + +### init.lua ### +Noteblock= diff --git a/mesecons_pistons/init.lua b/mesecons_pistons/init.lua index bc940d0..3d75b35 100644 --- a/mesecons_pistons/init.lua +++ b/mesecons_pistons/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local specs = { normal = { offname = "mesecons_pistons:piston_normal_off", @@ -265,7 +267,7 @@ local piston_on_box = { -- Normal (non-sticky) Pistons: -- offstate minetest.register_node("mesecons_pistons:piston_normal_off", { - description = "Piston", + description = S("Piston"), tiles = { "mesecons_piston_top.png", "mesecons_piston_bottom.png", @@ -290,7 +292,7 @@ minetest.register_node("mesecons_pistons:piston_normal_off", { -- onstate minetest.register_node("mesecons_pistons:piston_normal_on", { - description = "Activated Piston Base", + description = S("Activated Piston Base"), drawtype = "nodebox", tiles = { "mesecons_piston_top.png", @@ -319,7 +321,7 @@ minetest.register_node("mesecons_pistons:piston_normal_on", { -- pusher minetest.register_node("mesecons_pistons:piston_pusher_normal", { - description = "Piston Pusher", + description = S("Piston Pusher"), drawtype = "nodebox", tiles = { "mesecons_piston_pusher_top.png", @@ -344,7 +346,7 @@ minetest.register_node("mesecons_pistons:piston_pusher_normal", { -- Sticky ones -- offstate minetest.register_node("mesecons_pistons:piston_sticky_off", { - description = "Sticky Piston", + description = S("Sticky Piston"), tiles = { "mesecons_piston_top.png", "mesecons_piston_bottom.png", @@ -369,7 +371,7 @@ minetest.register_node("mesecons_pistons:piston_sticky_off", { -- onstate minetest.register_node("mesecons_pistons:piston_sticky_on", { - description = "Activated Sticky Piston Base", + description = S("Activated Sticky Piston Base"), drawtype = "nodebox", tiles = { "mesecons_piston_top.png", @@ -398,7 +400,7 @@ minetest.register_node("mesecons_pistons:piston_sticky_on", { -- pusher minetest.register_node("mesecons_pistons:piston_pusher_sticky", { - description = "Sticky Piston Pusher", + description = S("Sticky Piston Pusher"), drawtype = "nodebox", tiles = { "mesecons_piston_pusher_top.png", diff --git a/mesecons_pistons/locale/mesecons_pistons.eo.tr b/mesecons_pistons/locale/mesecons_pistons.eo.tr new file mode 100644 index 0000000..0eba1cc --- /dev/null +++ b/mesecons_pistons/locale/mesecons_pistons.eo.tr @@ -0,0 +1,9 @@ +# textdomain: mesecons_pistons + +### init.lua ### +Piston=Piŝto +Activated Piston Base=Aktivigita Piŝta Bazo +Piston Pusher=Piŝta Pushero +Sticky Piston=Glueca Piŝto +Activated Sticky Piston Base=Aktivigita Glueca Piŝta Bazo +Sticky Piston Pusher=Glueca Piŝta Puŝilo diff --git a/mesecons_pistons/locale/template.txt b/mesecons_pistons/locale/template.txt new file mode 100644 index 0000000..05bed8a --- /dev/null +++ b/mesecons_pistons/locale/template.txt @@ -0,0 +1,9 @@ +# textdomain: mesecons_pistons + +### init.lua ### +Piston= +Activated Piston Base= +Piston Pusher= +Sticky Piston= +Activated Sticky Piston Base= +Sticky Piston Pusher= diff --git a/mesecons_powerplant/init.lua b/mesecons_powerplant/init.lua index 90724bd..28f21b5 100644 --- a/mesecons_powerplant/init.lua +++ b/mesecons_powerplant/init.lua @@ -1,6 +1,8 @@ -- The POWER_PLANT -- Just emits power. always. +local S = minetest.get_translator(minetest.get_current_modname()) + minetest.register_node("mesecons_powerplant:power_plant", { drawtype = "plantlike", visual_scale = 1, @@ -11,7 +13,7 @@ minetest.register_node("mesecons_powerplant:power_plant", { walkable = false, groups = {dig_immediate=3, mesecon = 2}, light_source = minetest.LIGHT_MAX-9, - description="Power Plant", + description=S("Power Plant"), selection_box = { type = "fixed", fixed = {-0.3, -0.5, -0.3, 0.3, -0.5+0.7, 0.3}, diff --git a/mesecons_powerplant/locale/mesecons_powerplant.eo.tr b/mesecons_powerplant/locale/mesecons_powerplant.eo.tr new file mode 100644 index 0000000..8e539c2 --- /dev/null +++ b/mesecons_powerplant/locale/mesecons_powerplant.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_powerplant + +### init.lua ### +Power Plant=Elektra Planto diff --git a/mesecons_powerplant/locale/template.txt b/mesecons_powerplant/locale/template.txt new file mode 100644 index 0000000..71eceaf --- /dev/null +++ b/mesecons_powerplant/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_powerplant + +### init.lua ### +Power Plant= diff --git a/mesecons_pressureplates/init.lua b/mesecons_pressureplates/init.lua index 737d7cd..5c7346a 100644 --- a/mesecons_pressureplates/init.lua +++ b/mesecons_pressureplates/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + local pp_box_off = { type = "fixed", fixed = { -7/16, -8/16, -7/16, 7/16, -7/16, 7/16 }, @@ -87,7 +89,7 @@ end mesecon.register_pressure_plate( "mesecons_pressureplates:pressure_plate_wood", - "Wooden Pressure Plate", + S("Wooden Pressure Plate"), {"jeija_pressure_plate_wood_off.png","jeija_pressure_plate_wood_off.png","jeija_pressure_plate_wood_off_edges.png"}, {"jeija_pressure_plate_wood_on.png","jeija_pressure_plate_wood_on.png","jeija_pressure_plate_wood_on_edges.png"}, "jeija_pressure_plate_wood_wield.png", @@ -98,7 +100,7 @@ mesecon.register_pressure_plate( mesecon.register_pressure_plate( "mesecons_pressureplates:pressure_plate_stone", - "Stone Pressure Plate", + S("Stone Pressure Plate"), {"jeija_pressure_plate_stone_off.png","jeija_pressure_plate_stone_off.png","jeija_pressure_plate_stone_off_edges.png"}, {"jeija_pressure_plate_stone_on.png","jeija_pressure_plate_stone_on.png","jeija_pressure_plate_stone_on_edges.png"}, "jeija_pressure_plate_stone_wield.png", diff --git a/mesecons_pressureplates/locale/mesecons_pressureplates.eo.tr b/mesecons_pressureplates/locale/mesecons_pressureplates.eo.tr new file mode 100644 index 0000000..8af30a0 --- /dev/null +++ b/mesecons_pressureplates/locale/mesecons_pressureplates.eo.tr @@ -0,0 +1,5 @@ +# textdomain: mesecons_pressureplates + +### init.lua ### +Wooden Pressure Plate=Ligna Prema Plato +Stone Pressure Plate=Ŝtona Prema Plato diff --git a/mesecons_pressureplates/locale/template.txt b/mesecons_pressureplates/locale/template.txt new file mode 100644 index 0000000..2241344 --- /dev/null +++ b/mesecons_pressureplates/locale/template.txt @@ -0,0 +1,5 @@ +# textdomain: mesecons_pressureplates + +### init.lua ### +Wooden Pressure Plate= +Stone Pressure Plate= diff --git a/mesecons_random/init.lua b/mesecons_random/init.lua index cc57a46..5342fb3 100644 --- a/mesecons_random/init.lua +++ b/mesecons_random/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + -- REMOVESTONE minetest.register_node("mesecons_random:removestone", { @@ -5,7 +7,7 @@ minetest.register_node("mesecons_random:removestone", { is_ground_content = false, inventory_image = minetest.inventorycube("jeija_removestone_inv.png"), groups = {cracky=3}, - description="Removestone", + description = S("Removestone"), sounds = mesecon.node_sound.stone, mesecons = {effector = { action_on = function (pos, node) @@ -29,7 +31,7 @@ minetest.register_craft({ -- GHOSTSTONE minetest.register_node("mesecons_random:ghoststone", { - description="Ghoststone", + description = S("Ghoststone"), tiles = {"jeija_ghoststone.png"}, is_ground_content = false, inventory_image = minetest.inventorycube("jeija_ghoststone_inv.png"), diff --git a/mesecons_random/locale/mesecons_random.eo.tr b/mesecons_random/locale/mesecons_random.eo.tr new file mode 100644 index 0000000..ceca540 --- /dev/null +++ b/mesecons_random/locale/mesecons_random.eo.tr @@ -0,0 +1,5 @@ +# textdomain: mesecons_random + +### init.lua ### +Removestone=Forigŝtono +Ghoststone=Fantomŝtono diff --git a/mesecons_random/locale/template.txt b/mesecons_random/locale/template.txt new file mode 100644 index 0000000..55a69e8 --- /dev/null +++ b/mesecons_random/locale/template.txt @@ -0,0 +1,5 @@ +# textdomain: mesecons_random + +### init.lua ### +Removestone= +Ghoststone= diff --git a/mesecons_solarpanel/init.lua b/mesecons_solarpanel/init.lua index 84bbfd3..228f7a2 100644 --- a/mesecons_solarpanel/init.lua +++ b/mesecons_solarpanel/init.lua @@ -1,6 +1,8 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + -- Solar Panel mesecon.register_node("mesecons_solarpanel:solar_panel", { - description = "Solar Panel", + description = S("Solar Panel"), drawtype = "nodebox", tiles = {"mesecons_solarpanel.png"}, inventory_image = "mesecons_solarpanel.png", diff --git a/mesecons_solarpanel/locale/mesecons_solarpanel.eo.tr b/mesecons_solarpanel/locale/mesecons_solarpanel.eo.tr new file mode 100644 index 0000000..cdbf000 --- /dev/null +++ b/mesecons_solarpanel/locale/mesecons_solarpanel.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_solarpanel + +### init.lua ### +Solar Panel=Suna Panelo diff --git a/mesecons_solarpanel/locale/template.txt b/mesecons_solarpanel/locale/template.txt new file mode 100644 index 0000000..bb8464a --- /dev/null +++ b/mesecons_solarpanel/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_solarpanel + +### init.lua ### +Solar Panel= diff --git a/mesecons_stickyblocks/init.lua b/mesecons_stickyblocks/init.lua index 622455a..cf8695f 100644 --- a/mesecons_stickyblocks/init.lua +++ b/mesecons_stickyblocks/init.lua @@ -1,10 +1,12 @@ -- Sticky blocks can be used together with pistons or movestones to push / pull -- structures that are "glued" together using sticky blocks +local S = minetest.get_translator(minetest.get_current_modname()) + -- All sides sticky block minetest.register_node("mesecons_stickyblocks:sticky_block_all", { -- TODO: Rename to “All-Faces Sticky Block” when other sticky blocks become available - description = "Sticky Block", + description = S("Sticky Block"), tiles = {"mesecons_stickyblocks_sticky.png"}, is_ground_content = false, groups = {choppy=3, oddly_breakable_by_hand=2}, diff --git a/mesecons_stickyblocks/locale/mesecons_stickyblocks.eo.tr b/mesecons_stickyblocks/locale/mesecons_stickyblocks.eo.tr new file mode 100644 index 0000000..8ac7f94 --- /dev/null +++ b/mesecons_stickyblocks/locale/mesecons_stickyblocks.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_stickyblocks + +### init.lua ### +Sticky Block=Glueca Bloko diff --git a/mesecons_stickyblocks/locale/template.txt b/mesecons_stickyblocks/locale/template.txt new file mode 100644 index 0000000..b96a697 --- /dev/null +++ b/mesecons_stickyblocks/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_stickyblocks + +### init.lua ### +Sticky Block= diff --git a/mesecons_switch/init.lua b/mesecons_switch/init.lua index 4d650b1..a8115de 100644 --- a/mesecons_switch/init.lua +++ b/mesecons_switch/init.lua @@ -1,8 +1,10 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + -- mesecons_switch mesecon.register_node("mesecons_switch:mesecon_switch", { paramtype2="facedir", - description="Switch", + description=S("Switch"), is_ground_content = false, sounds = mesecon.node_sound.stone, on_rightclick = function (pos, node) diff --git a/mesecons_switch/locale/mesecons_switch.eo.tr b/mesecons_switch/locale/mesecons_switch.eo.tr new file mode 100644 index 0000000..abd93f0 --- /dev/null +++ b/mesecons_switch/locale/mesecons_switch.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_switch + +### init.lua ### +Switch=Ŝaltilo diff --git a/mesecons_switch/locale/template.txt b/mesecons_switch/locale/template.txt new file mode 100644 index 0000000..c493778 --- /dev/null +++ b/mesecons_switch/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_switch + +### init.lua ### +Switch= diff --git a/mesecons_torch/init.lua b/mesecons_torch/init.lua index 34f3c4d..ab2df98 100644 --- a/mesecons_torch/init.lua +++ b/mesecons_torch/init.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + --MESECON TORCHES local rotate_torch_rules = function (rules, param2) @@ -85,7 +87,7 @@ minetest.register_node("mesecons_torch:mesecon_torch_on", { selection_box = torch_selectionbox, groups = {dig_immediate=3}, light_source = minetest.LIGHT_MAX-5, - description="Mesecon Torch", + description = S("Mesecon Torch"), sounds = mesecon.node_sound.default, mesecons = {receptor = { state = mesecon.state.on, diff --git a/mesecons_torch/locale/mesecons_torch.eo.tr b/mesecons_torch/locale/mesecons_torch.eo.tr new file mode 100644 index 0000000..393dc4d --- /dev/null +++ b/mesecons_torch/locale/mesecons_torch.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_torch + +### init.lua ### +Mesecon Torch=Mesekonduktila Torĉo diff --git a/mesecons_torch/locale/template.txt b/mesecons_torch/locale/template.txt new file mode 100644 index 0000000..981e850 --- /dev/null +++ b/mesecons_torch/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_torch + +### init.lua ### +Mesecon Torch= diff --git a/mesecons_walllever/init.lua b/mesecons_walllever/init.lua index 670dab5..651cd8d 100644 --- a/mesecons_walllever/init.lua +++ b/mesecons_walllever/init.lua @@ -1,8 +1,10 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + -- WALL LEVER -- Basically a switch that can be attached to a wall -- Powers the block 2 nodes behind (using a receiver) mesecon.register_node("mesecons_walllever:wall_lever", { - description="Lever", + description = S("Lever"), drawtype = "mesh", inventory_image = "jeija_wall_lever_inv.png", wield_image = "jeija_wall_lever_inv.png", diff --git a/mesecons_walllever/locale/mesecons_walllever.eo.tr b/mesecons_walllever/locale/mesecons_walllever.eo.tr new file mode 100644 index 0000000..73d0a9a --- /dev/null +++ b/mesecons_walllever/locale/mesecons_walllever.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_walllever + +### init.lua ### +Lever=Levilo diff --git a/mesecons_walllever/locale/template.txt b/mesecons_walllever/locale/template.txt new file mode 100644 index 0000000..505a962 --- /dev/null +++ b/mesecons_walllever/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_walllever + +### init.lua ### +Lever= diff --git a/mesecons_wires/init.lua b/mesecons_wires/init.lua index c7669b3..d869cde 100644 --- a/mesecons_wires/init.lua +++ b/mesecons_wires/init.lua @@ -8,6 +8,8 @@ -- ## Update wire looks ## -- ####################### +local S = minetest.get_translator(minetest.get_current_modname()) + -- self_pos = pos of any mesecon node, from_pos = pos of conductor to getconnect for local wire_getconnect = function (from_pos, self_pos) local node = minetest.get_node(self_pos) @@ -203,7 +205,7 @@ local function register_wires() end mesecon.register_node(":mesecons:wire_"..nodeid, { - description = "Mesecon", + description = S("Mesecon"), drawtype = "nodebox", inventory_image = "mesecons_wire_inv.png", wield_image = "mesecons_wire_inv.png", diff --git a/mesecons_wires/locale/mesecons_wires.eo.tr b/mesecons_wires/locale/mesecons_wires.eo.tr new file mode 100644 index 0000000..9fee95a --- /dev/null +++ b/mesecons_wires/locale/mesecons_wires.eo.tr @@ -0,0 +1,4 @@ +# textdomain: mesecons_wires + +### init.lua ### +Mesecon=Mesekonduktilo diff --git a/mesecons_wires/locale/template.txt b/mesecons_wires/locale/template.txt new file mode 100644 index 0000000..82de311 --- /dev/null +++ b/mesecons_wires/locale/template.txt @@ -0,0 +1,4 @@ +# textdomain: mesecons_wires + +### init.lua ### +Mesecon= From c10ce2dbc5bf1ab0c2146dbd012cc8bacf2fcc09 Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Tue, 6 Dec 2022 11:54:21 -0500 Subject: [PATCH 05/10] Add automated tests for some mods (#605) Depends on mineunit from https://github.com/S-S-X/mineunit mesecons, mesecons_mvps, mesecons_fpga, and mesecons_luacontroller are now tested. --- .github/workflows/check-release.yml | 26 ++ .luacheckrc | 23 ++ .test_fixtures/mesecons.lua | 156 +++++++++ .test_fixtures/mesecons_fpga.lua | 59 ++++ .test_fixtures/mesecons_gamecompat.lua | 5 + .test_fixtures/mesecons_luacontroller.lua | 12 + .test_fixtures/mesecons_mvps.lua | 45 +++ .test_fixtures/screwdriver.lua | 6 + mesecons/spec/action_spec.lua | 62 ++++ mesecons/spec/mineunit.conf | 1 + mesecons/spec/service_spec.lua | 192 +++++++++++ mesecons/spec/state_spec.lua | 147 +++++++++ mesecons_fpga/spec/helper_spec.lua | 107 +++++++ mesecons_fpga/spec/logic_spec.lua | 235 ++++++++++++++ mesecons_fpga/spec/mineunit.conf | 1 + .../spec/lightweight_interrupt_spec.lua | 38 +++ mesecons_luacontroller/spec/luac_spec.lua | 176 +++++++++++ mesecons_luacontroller/spec/mineunit.conf | 1 + mesecons_mvps/spec/mineunit.conf | 1 + mesecons_mvps/spec/node_spec.lua | 297 ++++++++++++++++++ mesecons_mvps/spec/object_spec.lua | 3 + 21 files changed, 1593 insertions(+) create mode 100644 .test_fixtures/mesecons.lua create mode 100644 .test_fixtures/mesecons_fpga.lua create mode 100644 .test_fixtures/mesecons_gamecompat.lua create mode 100644 .test_fixtures/mesecons_luacontroller.lua create mode 100644 .test_fixtures/mesecons_mvps.lua create mode 100644 .test_fixtures/screwdriver.lua create mode 100644 mesecons/spec/action_spec.lua create mode 100644 mesecons/spec/mineunit.conf create mode 100644 mesecons/spec/service_spec.lua create mode 100644 mesecons/spec/state_spec.lua create mode 100644 mesecons_fpga/spec/helper_spec.lua create mode 100644 mesecons_fpga/spec/logic_spec.lua create mode 100644 mesecons_fpga/spec/mineunit.conf create mode 100644 mesecons_luacontroller/spec/lightweight_interrupt_spec.lua create mode 100644 mesecons_luacontroller/spec/luac_spec.lua create mode 100644 mesecons_luacontroller/spec/mineunit.conf create mode 100644 mesecons_mvps/spec/mineunit.conf create mode 100644 mesecons_mvps/spec/node_spec.lua create mode 100644 mesecons_mvps/spec/object_spec.lua diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index d43987a..616adb3 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -13,3 +13,29 @@ jobs: run: luarocks install --local luacheck - name: luacheck run run: $HOME/.luarocks/bin/luacheck ./ + + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@main + - name: apt + run: sudo apt-get install -y luarocks + - name: busted install + run: luarocks install --local busted + - name: luacov install + run: luarocks install --local luacov + - name: mineunit install + run: luarocks install --server=https://luarocks.org/dev --local mineunit + - name: run mesecons tests + working-directory: ./mesecons/ + run: $HOME/.luarocks/bin/mineunit -q + - name: run mesecons_mvps tests + working-directory: ./mesecons_mvps/ + run: $HOME/.luarocks/bin/mineunit -q + - name: run mesecons_fpga tests + working-directory: ./mesecons_fpga/ + run: $HOME/.luarocks/bin/mineunit -q + - name: run mesecons_luacontroller tests + working-directory: ./mesecons_luacontroller/ + run: $HOME/.luarocks/bin/mineunit -q diff --git a/.luacheckrc b/.luacheckrc index c8cccda..f2445a0 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -34,3 +34,26 @@ globals = {"mesecon"} files["mesecons/actionqueue.lua"] = { globals = {"minetest.registered_globalsteps"}, } + +-- Test-specific stuff follows. + +local test_conf = { + read_globals = { + "assert", + "fixture", + "mineunit", + "Player", + "sourcefile", + "world", + }, +} +files["*/spec/*.lua"] = test_conf +files[".test_fixtures/*.lua"] = test_conf + +files[".test_fixtures/screwdriver.lua"] = { + globals = {"screwdriver"}, +} + +files[".test_fixtures/mesecons_fpga.lua"] = { + globals = {"minetest.register_on_player_receive_fields"}, +} diff --git a/.test_fixtures/mesecons.lua b/.test_fixtures/mesecons.lua new file mode 100644 index 0000000..2acd6f6 --- /dev/null +++ b/.test_fixtures/mesecons.lua @@ -0,0 +1,156 @@ +mineunit("core") +mineunit("server") +mineunit("voxelmanip") + +mineunit:set_current_modname("mesecons") +mineunit:set_modpath("mesecons", "../mesecons") +sourcefile("../mesecons/init") + +-- Utility node: this conductor is used to test the connectivity and state of adjacent wires. +do + local off_spec = {conductor = { + state = mesecon.state.off, + rules = mesecon.rules.alldirs, + onstate = "mesecons:test_conductor_on", + }} + local on_spec = {conductor = { + state = mesecon.state.on, + rules = mesecon.rules.alldirs, + offstate = "mesecons:test_conductor_off", + }} + mesecon.register_node("mesecons:test_conductor", { + description = "Test Conductor", + }, {mesecons = off_spec}, {mesecons = on_spec}) +end + +-- Utility node: this receptor is used to test power sources. +do + local off_spec = {receptor = { + state = mesecon.state.off, + rules = mesecon.rules.alldirs, + }} + local on_spec = {receptor = { + state = mesecon.state.on, + rules = mesecon.rules.alldirs, + }} + mesecon.register_node("mesecons:test_receptor", { + description = "Test Receptor", + }, {mesecons = off_spec}, {mesecons = on_spec}) +end + +-- Utility node: this effector is used to test circuit outputs. +do + -- This is a list of actions in the form {, }, + -- where is "on", "off", or "overheat". + mesecon._test_effector_events = {} + local function action_on(pos, node) + table.insert(mesecon._test_effector_events, {"on", pos}) + node.param2 = node.param2 % 64 + 128 -- Turn on bit 7 + minetest.swap_node(pos, node) + end + local function action_off(pos, node) + table.insert(mesecon._test_effector_events, {"off", pos}) + node.param2 = node.param2 % 64 -- Turn off bit 7 + minetest.swap_node(pos, node) + end + local function action_change(pos, node, rule_name, new_state) + if mesecon.do_overheat(pos) then + table.insert(mesecon._test_effector_events, {"overheat", pos}) + minetest.remove_node(pos) + return + end + -- Set the value of a bit in param2 according to the rule name and new state. + local bit = tonumber(rule_name.name, 2) + local bits_above = node.param2 - node.param2 % (bit * 2) + local bits_below = node.param2 % bit + local bits_flipped = new_state == mesecon.state.on and bit or 0 + node.param2 = bits_above + bits_flipped + bits_below + minetest.swap_node(pos, node) + end + minetest.register_node("mesecons:test_effector", { + description = "Test Effector", + mesecons = {effector = { + action_on = action_on, + action_off = action_off, + action_change = action_change, + rules = { + {x = 1, y = 0, z = 0, name = "000001"}, + {x = -1, y = 0, z = 0, name = "000010"}, + {x = 0, y = 1, z = 0, name = "000100"}, + {x = 0, y = -1, z = 0, name = "001000"}, + {x = 0, y = 0, z = 1, name = "010000"}, + {x = 0, y = 0, z = -1, name = "100000"}, + } + }}, + }) +end + +-- Utility node: this conductor is used to test rotation. +do + local get_rules = mesecon.horiz_rules_getter({{x = 1, y = 0, z = 0}, {x = -1, y = 0, z = 0}}) + local off_spec = {conductor = { + state = mesecon.state.off, + rules = get_rules, + onstate = "mesecons:test_conductor_rot_on", + }} + local on_spec = {conductor = { + state = mesecon.state.on, + rules = get_rules, + offstate = "mesecons:test_conductor_rot_off", + }} + mesecon.register_node("mesecons:test_conductor_rot", { + description = "Rotatable Test Conductor", + on_rotate = mesecon.on_rotate_horiz, + }, {mesecons = off_spec}, {mesecons = on_spec}) +end + +-- Utility node: this is used to test multiple conductors within a single node. +do + local mesecons_spec = {conductor = { + rules = { + {{x = 1, y = 0, z = 0}, {x = 0, y = -1, z = 0}}, + {{x = 0, y = 1, z = 0}, {x = 0, y = 0, z = -1}}, + {{x = 0, y = 0, z = 1}, {x = -1, y = 0, z = 0}}, + }, + states = { + "mesecons:test_multiconductor_off", "mesecons:test_multiconductor_001", + "mesecons:test_multiconductor_010", "mesecons:test_multiconductor_011", + "mesecons:test_multiconductor_100", "mesecons:test_multiconductor_101", + "mesecons:test_multiconductor_110", "mesecons:test_multiconductor_on", + }, + }} + for _, state in ipairs(mesecons_spec.conductor.states) do + minetest.register_node(state, { + description = "Test Multiconductor", + mesecons = mesecons_spec, + }) + end +end + +mesecon._test_autoconnects = {} +mesecon.register_autoconnect_hook("test", function(pos, node) + table.insert(mesecon._test_autoconnects, {pos, node}) +end) + +function mesecon._test_dig(pos) + local node = minetest.get_node(pos) + minetest.remove_node(pos) + mesecon.on_dignode(pos, node) +end + +function mesecon._test_place(pos, node) + world.set_node(pos, node) + mesecon.on_placenode(pos, minetest.get_node(pos)) +end + +function mesecon._test_reset() + -- First let circuits settle by simulating many globalsteps. + for i = 1, 10 do + mineunit:execute_globalstep(60) + end + mesecon.queue.actions = {} + mesecon._test_effector_events = {} + mesecon._test_autoconnects = {} +end + +mineunit:execute_globalstep(mesecon.setting("resumetime", 4) + 1) diff --git a/.test_fixtures/mesecons_fpga.lua b/.test_fixtures/mesecons_fpga.lua new file mode 100644 index 0000000..ba4440a --- /dev/null +++ b/.test_fixtures/mesecons_fpga.lua @@ -0,0 +1,59 @@ +mineunit("player") + +fixture("mesecons") +fixture("mesecons_gamecompat") + +local registered_on_player_receive_fields = {} +local old_register_on_player_receive_fields = minetest.register_on_player_receive_fields +function minetest.register_on_player_receive_fields(func) + old_register_on_player_receive_fields(func) + table.insert(registered_on_player_receive_fields, func) +end + +mineunit:set_current_modname("mesecons_fpga") +mineunit:set_modpath("mesecons_fpga", "../mesecons_fpga") +sourcefile("../mesecons_fpga/init") + +local fpga_user = Player("mesecons_fpga_user") + +function mesecon._test_program_fpga(pos, program) + local node = minetest.get_node(pos) + assert.equal("mesecons_fpga:fpga", node.name:sub(1, 18)) + + local fields = {program = true} + for i, instr in ipairs(program) do + -- Translate the instruction into formspec fields. + local op1, act, op2, dst + if #instr == 3 then + act, op2, dst = unpack(instr) + else + assert.equal(4, #instr) + op1, act, op2, dst = unpack(instr) + end + fields[i .. "op1"] = op1 + fields[i .. "act"] = (" "):rep(4 - #act) .. act + fields[i .. "op2"] = op2 + fields[i .. "dst"] = dst + end + + minetest.registered_nodes[node.name].on_rightclick(pos, node, fpga_user) + + for _, func in ipairs(registered_on_player_receive_fields) do + if func(fpga_user, "mesecons:fpga", fields) then + break + end + end +end + +function mesecon._test_copy_fpga_program(pos) + fpga_user:get_inventory():set_stack("main", 1, "mesecons_fpga:programmer") + local pt = {type = "node", under = vector.new(pos), above = vector.offset(pos, 0, 1, 0)} + fpga_user:do_place(pt) + return fpga_user:get_wielded_item() +end + +function mesecon._test_paste_fpga_program(pos, tool) + fpga_user:get_inventory():set_stack("main", 1, tool) + local pt = {type = "node", under = vector.new(pos), above = vector.offset(pos, 0, 1, 0)} + fpga_user:do_use(pt) +end diff --git a/.test_fixtures/mesecons_gamecompat.lua b/.test_fixtures/mesecons_gamecompat.lua new file mode 100644 index 0000000..07bffd0 --- /dev/null +++ b/.test_fixtures/mesecons_gamecompat.lua @@ -0,0 +1,5 @@ +fixture("mesecons") + +mineunit:set_current_modname("mesecons_gamecompat") +mineunit:set_modpath("mesecons_gamecompat", "../mesecons_gamecompat") +sourcefile("../mesecons_gamecompat/init") diff --git a/.test_fixtures/mesecons_luacontroller.lua b/.test_fixtures/mesecons_luacontroller.lua new file mode 100644 index 0000000..9090b47 --- /dev/null +++ b/.test_fixtures/mesecons_luacontroller.lua @@ -0,0 +1,12 @@ +fixture("mesecons") +fixture("mesecons_gamecompat") + +mineunit:set_current_modname("mesecons_luacontroller") +mineunit:set_modpath("mesecons_luacontroller", "../mesecons_luacontroller") +sourcefile("../mesecons_luacontroller/init") + +function mesecon._test_program_luac(pos, code) + local node = minetest.get_node(pos) + assert.equal("mesecons_luacontroller:luacontroller", node.name:sub(1, 36)) + return minetest.registered_nodes[node.name].mesecons.luacontroller.set_program(pos, code) +end diff --git a/.test_fixtures/mesecons_mvps.lua b/.test_fixtures/mesecons_mvps.lua new file mode 100644 index 0000000..b15774c --- /dev/null +++ b/.test_fixtures/mesecons_mvps.lua @@ -0,0 +1,45 @@ +mineunit("protection") + +fixture("mesecons") + +mineunit:set_current_modname("mesecons_mvps") +mineunit:set_modpath("mesecons_mvps", "../mesecons_mvps") +sourcefile("../mesecons_mvps/init") + +minetest.register_node("mesecons_mvps:test_stopper", { + description = "Test Stopper", +}) +mesecon.register_mvps_stopper("mesecons_mvps:test_stopper") + +minetest.register_node("mesecons_mvps:test_stopper_cond", { + description = "Test Stopper (Conditional)", +}) +mesecon.register_mvps_stopper("mesecons_mvps:test_stopper_cond", function(node) + return node.param2 == 0 +end) + +minetest.register_node("mesecons_mvps:test_sticky", { + description = "Test Sticky", + mvps_sticky = function(pos) + local connected = {} + for i, rule in ipairs(mesecon.rules.alldirs) do + connected[i] = vector.add(pos, rule) + end + return connected + end, +}) + +mesecon._test_moves = {} +minetest.register_node("mesecons_mvps:test_on_move", { + description = "Test Moveable", + mesecon = { + on_mvps_move = function(pos, node, oldpos, meta) + table.insert(mesecon._test_moves, {pos, node, oldpos, meta}) + end + }, +}) +local old_reset = mesecon._test_reset +function mesecon._test_reset() + mesecon._test_moves = {} + old_reset() +end diff --git a/.test_fixtures/screwdriver.lua b/.test_fixtures/screwdriver.lua new file mode 100644 index 0000000..1a98de0 --- /dev/null +++ b/.test_fixtures/screwdriver.lua @@ -0,0 +1,6 @@ +mineunit:set_current_modname("screwdriver") + +screwdriver = {} + +screwdriver.ROTATE_FACE = 1 +screwdriver.ROTATE_AXIS = 2 diff --git a/mesecons/spec/action_spec.lua b/mesecons/spec/action_spec.lua new file mode 100644 index 0000000..55f75d8 --- /dev/null +++ b/mesecons/spec/action_spec.lua @@ -0,0 +1,62 @@ +require("mineunit") + +fixture("mesecons") + +describe("action queue", function() + local layout = { + {{x = 1, y = 0, z = 0}, "mesecons:test_receptor_off"}, + {{x = 0, y = 0, z = 0}, "mesecons:test_conductor_off"}, + {{x = -1, y = 0, z = 0}, "mesecons:test_conductor_off"}, + {{x = 0, y = 1, z = 0}, "mesecons:test_effector"}, + {{x = -1, y = 1, z = 0}, "mesecons:test_effector"}, + } + + before_each(function() + world.layout(layout) + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("executes in order", function() + world.set_node(layout[1][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[1][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal(2, #mesecon._test_effector_events) + assert.same({"on", layout[4][1]}, mesecon._test_effector_events[1]) + assert.same({"on", layout[5][1]}, mesecon._test_effector_events[2]) + + world.set_node(layout[1][1], "mesecons:test_receptor_off") + mesecon.receptor_off(layout[1][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_off action + mineunit:execute_globalstep() -- Execute deactivate/change actions + assert.equal(4, #mesecon._test_effector_events) + assert.same({"off", layout[4][1]}, mesecon._test_effector_events[3]) + assert.same({"off", layout[5][1]}, mesecon._test_effector_events[4]) + end) + + it("discards outdated/overwritten node events", function() + world.set_node(layout[1][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[1][1], mesecon.rules.alldirs) + world.set_node(layout[1][1], "mesecons:test_receptor_off") + mesecon.receptor_off(layout[1][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_off action + mineunit:execute_globalstep() -- Execute deactivate/change actions + assert.equal(0, #mesecon._test_effector_events) + end) + + it("delays actions", function() + world.set_node(layout[1][1], "mesecons:test_receptor_on") + mesecon.queue:add_action(layout[1][1], "receptor_on", {mesecon.rules.alldirs}, 1, nil) + mineunit:execute_globalstep(0.1) + mineunit:execute_globalstep(1) + assert.equal(0, #mesecon._test_effector_events) + mineunit:execute_globalstep() -- Execute receptor_on action + assert.equal(0, #mesecon._test_effector_events) + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal(2, #mesecon._test_effector_events) + end) +end) diff --git a/mesecons/spec/mineunit.conf b/mesecons/spec/mineunit.conf new file mode 100644 index 0000000..81bd36c --- /dev/null +++ b/mesecons/spec/mineunit.conf @@ -0,0 +1 @@ +fixture_paths = {"../.test_fixtures"} diff --git a/mesecons/spec/service_spec.lua b/mesecons/spec/service_spec.lua new file mode 100644 index 0000000..7b6fa0a --- /dev/null +++ b/mesecons/spec/service_spec.lua @@ -0,0 +1,192 @@ +require("mineunit") + +fixture("mesecons") +fixture("screwdriver") + +describe("placement/digging service", function() + local layout = { + {{x = 1, y = 0, z = 0}, "mesecons:test_receptor_on"}, + {{x = 0, y = 0, z = 0}, "mesecons:test_conductor_on"}, + {{x = -1, y = 0, z = 0}, "mesecons:test_conductor_on"}, + {{x = 0, y = 1, z = 0}, "mesecons:test_effector"}, + {{x = -2, y = 0, z = 0}, "mesecons:test_effector"}, + {{x = 2, y = 0, z = 0}, "mesecons:test_effector"}, + } + + before_each(function() + world.layout(layout) + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("updates components when a receptor changes", function() + -- Dig then replace a receptor and check that the connected effectors changed. + + mesecon._test_dig(layout[1][1]) + mineunit:execute_globalstep() -- Execute receptor_off action + assert.equal("mesecons:test_conductor_off", world.get_node(layout[2][1]).name) + mineunit:execute_globalstep() -- Execute deactivate/change actions + assert.equal(3, #mesecon._test_effector_events) + + mesecon._test_place(layout[1][1], "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on action + assert.equal("mesecons:test_conductor_on", world.get_node(layout[2][1]).name) + mineunit:execute_globalstep() -- Execute activate/change action + assert.equal(6, #mesecon._test_effector_events) + end) + + it("updates components when a conductor changes", function() + -- Dig then replace a powered conductor and check that the connected effectors changed. + + mesecon._test_dig(layout[2][1]) + mineunit:execute_globalstep() -- Execute receptor_off action + assert.equal("mesecons:test_conductor_off", world.get_node(layout[3][1]).name) + mineunit:execute_globalstep() -- Execute deactivate/change actions + assert.equal(2, #mesecon._test_effector_events) + + mesecon._test_place(layout[2][1], "mesecons:test_conductor_off") + assert.equal("mesecons:test_conductor_on", world.get_node(layout[2][1]).name) + assert.equal("mesecons:test_conductor_on", world.get_node(layout[3][1]).name) + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal(4, #mesecon._test_effector_events) + end) + + it("updates effectors on placement", function() + local pos = {x = 0, y = 0, z = 1} + mesecon._test_place(pos, "mesecons:test_effector") + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal(tonumber("10100000", 2), world.get_node(pos).param2) + end) + + it("updates multiconductors on placement", function() + local pos = {x = 0, y = 0, z = 1} + mesecon._test_place(pos, "mesecons:test_multiconductor_off") + assert.equal("mesecons:test_multiconductor_010", world.get_node(pos).name) + end) + + it("turns off conductors on placement", function() + local pos = {x = 3, y = 0, z = 0} + mesecon._test_place(pos, "mesecons:test_conductor_on") + assert.equal("mesecons:test_conductor_off", world.get_node(pos).name) + end) + + it("turns off multiconductors on placement", function() + local pos = {x = 3, y = 0, z = 0} + mesecon._test_place(pos, "mesecons:test_multiconductor_on") + assert.equal("mesecons:test_multiconductor_off", world.get_node(pos).name) + end) + + it("triggers autoconnect hooks", function() + mesecon._test_dig(layout[2][1]) + mineunit:execute_globalstep() -- Execute delayed hook + assert.equal(1, #mesecon._test_autoconnects) + + mesecon._test_place(layout[2][1], layout[2][2]) + assert.equal(2, #mesecon._test_autoconnects) + end) +end) + +describe("overheating service", function() + local layout = { + {{x = 0, y = 0, z = 0}, "mesecons:test_receptor_off"}, + {{x = 1, y = 0, z = 0}, "mesecons:test_effector"}, + {{x = 2, y = 0, z = 0}, "mesecons:test_receptor_on"}, + } + + before_each(function() + world.layout(layout) + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("tracks heat", function() + mesecon.do_overheat(layout[2][1]) + assert.equal(1, mesecon.get_heat(layout[2][1])) + mesecon.do_cooldown(layout[2][1]) + assert.equal(0, mesecon.get_heat(layout[2][1])) + end) + + it("cools over time", function() + mesecon.do_overheat(layout[2][1]) + assert.equal(1, mesecon.get_heat(layout[2][1])) + mineunit:execute_globalstep(60) + mineunit:execute_globalstep(60) + mineunit:execute_globalstep(60) + assert.equal(0, mesecon.get_heat(layout[2][1])) + end) + + it("tracks movement", function() + local oldpos = layout[2][1] + local pos = vector.offset(oldpos, 0, 1, 0) + mesecon.do_overheat(oldpos) + mesecon.move_hot_nodes({{pos = pos, oldpos = oldpos}}) + assert.equal(0, mesecon.get_heat(oldpos)) + assert.equal(1, mesecon.get_heat(pos)) + end) + + it("causes overheating", function() + -- Switch the first receptor on and off until it overheats/breaks a receptor. + repeat + if mesecon.flipstate(layout[1][1], minetest.get_node(layout[1][1])) == "on" then + mesecon.receptor_on(layout[1][1], mesecon.rules.alldirs) + else + mesecon.receptor_off(layout[1][1], mesecon.rules.alldirs) + end + mineunit:execute_globalstep(0) -- Execute receptor_on/receptor_off/activate/deactivate/change actions + until minetest.get_node(layout[2][1]).name ~= "mesecons:test_effector" + assert.same({"overheat", layout[2][1]}, mesecon._test_effector_events[#mesecon._test_effector_events]) + assert.equal(0, mesecon.get_heat(layout[2][1])) + end) +end) + +describe("screwdriver service", function() + local layout = { + {{x = 0, y = 0, z = 0}, "mesecons:test_conductor_rot_on"}, + {{x = 1, y = 0, z = 0}, "mesecons:test_receptor_on"}, + {{x = -1, y = 0, z = 0}, "mesecons:test_conductor_on"}, + {{x = 0, y = 0, z = 1}, "mesecons:test_receptor_on"}, + {{x = 0, y = 0, z = -1}, "mesecons:test_conductor_off"}, + } + + local function rotate(new_param2) + local pos = layout[1][1] + local node = world.get_node(pos) + local on_rotate = minetest.registered_nodes[node.name].on_rotate + on_rotate(pos, node, nil, screwdriver.ROTATE_FACE, new_param2) + end + + before_each(function() + world.layout(layout) + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("updates conductors", function() + -- Rotate a conductor and see that the circuit state changes. + rotate(1) + mineunit:execute_globalstep() -- Execute receptor_off action + assert.equal("mesecons:test_conductor_off", world.get_node(layout[3][1]).name) + assert.equal("mesecons:test_conductor_on", world.get_node(layout[5][1]).name) + rotate(2) + mineunit:execute_globalstep() -- Execute receptor_off action + assert.equal("mesecons:test_conductor_on", world.get_node(layout[3][1]).name) + assert.equal("mesecons:test_conductor_off", world.get_node(layout[5][1]).name) + rotate(3) + mineunit:execute_globalstep() -- Execute receptor_off action + assert.equal("mesecons:test_conductor_off", world.get_node(layout[3][1]).name) + assert.equal("mesecons:test_conductor_on", world.get_node(layout[5][1]).name) + rotate(0) + mineunit:execute_globalstep() -- Execute receptor_off action + assert.equal("mesecons:test_conductor_on", world.get_node(layout[3][1]).name) + assert.equal("mesecons:test_conductor_off", world.get_node(layout[5][1]).name) + end) +end) diff --git a/mesecons/spec/state_spec.lua b/mesecons/spec/state_spec.lua new file mode 100644 index 0000000..c66871b --- /dev/null +++ b/mesecons/spec/state_spec.lua @@ -0,0 +1,147 @@ +require("mineunit") + +fixture("mesecons") + +describe("state", function() + local layout = { + {{x = 1, y = 0, z = 0}, "mesecons:test_receptor_off"}, + {{x = 0, y = 1, z = 0}, "mesecons:test_receptor_off"}, + {{x = 0, y = 0, z = 0}, "mesecons:test_conductor_off"}, + {{x = -1, y = 0, z = 0}, "mesecons:test_effector"}, + {{x = 2, y = 0, z = 0}, "mesecons:test_effector"}, + {{x = 0, y = -1, z = 0}, "mesecons:test_effector"}, + } + + before_each(function() + world.layout(layout) + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("turns on", function() + world.set_node(layout[1][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[1][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal("mesecons:test_conductor_on", world.get_node(layout[3][1]).name) + assert.equal(tonumber("10000001", 2), world.get_node(layout[4][1]).param2) + assert.equal(tonumber("10000010", 2), world.get_node(layout[5][1]).param2) + assert.equal(tonumber("10000100", 2), world.get_node(layout[6][1]).param2) + + world.set_node(layout[2][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[2][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal("mesecons:test_conductor_on", world.get_node(layout[3][1]).name) + assert.equal(tonumber("10000001", 2), world.get_node(layout[4][1]).param2) + assert.equal(tonumber("10000010", 2), world.get_node(layout[5][1]).param2) + assert.equal(tonumber("10000100", 2), world.get_node(layout[6][1]).param2) + end) + + it("turns off", function() + world.set_node(layout[1][1], "mesecons:test_receptor_on") + world.set_node(layout[2][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[1][1], mesecon.rules.alldirs) + mesecon.receptor_on(layout[2][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_on actions + + world.set_node(layout[1][1], "mesecons:test_receptor_off") + mesecon.receptor_off(layout[1][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_off and activate/change actions + mineunit:execute_globalstep() -- Execute deactivate/change actions + assert.equal("mesecons:test_conductor_on", world.get_node(layout[3][1]).name) + assert.equal(tonumber("10000001", 2), world.get_node(layout[4][1]).param2) + assert.equal(tonumber("00000000", 2), world.get_node(layout[5][1]).param2) + assert.equal(tonumber("10000100", 2), world.get_node(layout[6][1]).param2) + + world.set_node(layout[2][1], "mesecons:test_receptor_off") + mesecon.receptor_off(layout[2][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_off action + mineunit:execute_globalstep() -- Execute deactivate/change actions + assert.equal("mesecons:test_conductor_off", world.get_node(layout[3][1]).name) + assert.equal(tonumber("00000000", 2), world.get_node(layout[4][1]).param2) + assert.equal(tonumber("00000000", 2), world.get_node(layout[5][1]).param2) + assert.equal(tonumber("00000000", 2), world.get_node(layout[6][1]).param2) + end) +end) + +describe("rotation", function() + local layout = { + {{x = 0, y = 0, z = 0}, "mesecons:test_receptor_off"}, + {{x = 1, y = 0, z = 0}, {name = "mesecons:test_conductor_rot_off", param2 = 0}}, + {{x = 0, y = 0, z = 1}, {name = "mesecons:test_conductor_rot_off", param2 = 1}}, + {{x = -1, y = 0, z = 0}, {name = "mesecons:test_conductor_rot_off", param2 = 2}}, + {{x = 0, y = 0, z = -1}, {name = "mesecons:test_conductor_rot_off", param2 = 3}}, + } + + before_each(function() + for _, entry in ipairs(layout) do + world.set_node(entry[1], entry[2]) + end + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("works", function() + world.set_node(layout[1][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[1][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_on action + assert.equal("mesecons:test_conductor_rot_on", world.get_node(layout[2][1]).name) + assert.equal("mesecons:test_conductor_rot_on", world.get_node(layout[3][1]).name) + assert.equal("mesecons:test_conductor_rot_on", world.get_node(layout[4][1]).name) + assert.equal("mesecons:test_conductor_rot_on", world.get_node(layout[5][1]).name) + end) +end) + +describe("multiconductor", function() + local layout = { + {{x = 1, y = 0, z = 0}, "mesecons:test_receptor_off"}, + {{x = 0, y = 1, z = 0}, "mesecons:test_receptor_off"}, + {{x = 0, y = 0, z = 1}, "mesecons:test_receptor_off"}, + {{x = 0, y = 0, z = 0}, "mesecons:test_multiconductor_off"}, + } + + before_each(function() + world.layout(layout) + end) + + after_each(function() + world.clear() + mesecon._test_reset() + end) + + it("separates its subparts", function() + world.set_node(layout[1][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[1][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_on action + assert.equal("mesecons:test_multiconductor_001", world.get_node(layout[4][1]).name) + + world.set_node(layout[2][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[2][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_on action + assert.equal("mesecons:test_multiconductor_011", world.get_node(layout[4][1]).name) + + world.set_node(layout[3][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[3][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_on action + assert.equal("mesecons:test_multiconductor_on", world.get_node(layout[4][1]).name) + end) + + it("loops through itself", function() + -- Make a loop. + world.set_node({x = 0, y = -1, z = 0}, "mesecons:test_conductor_off") + world.set_node({x = -1, y = -1, z = 0}, "mesecons:test_conductor_off") + world.set_node({x = -1, y = 0, z = 0}, "mesecons:test_conductor_off") + + world.set_node(layout[1][1], "mesecons:test_receptor_on") + mesecon.receptor_on(layout[1][1], mesecon.rules.alldirs) + mineunit:execute_globalstep() -- Execute receptor_on action + assert.equal("mesecons:test_multiconductor_101", world.get_node(layout[4][1]).name) + end) +end) diff --git a/mesecons_fpga/spec/helper_spec.lua b/mesecons_fpga/spec/helper_spec.lua new file mode 100644 index 0000000..3b5b4c9 --- /dev/null +++ b/mesecons_fpga/spec/helper_spec.lua @@ -0,0 +1,107 @@ +require("mineunit") + +fixture("mesecons_fpga") +fixture("screwdriver") + +local pos = {x = 0, y = 0, z = 0} +local pos_a = {x = -1, y = 0, z = 0} +local pos_b = {x = 0, y = 0, z = 1} +local pos_c = {x = 1, y = 0, z = 0} +local pos_d = {x = 0, y = 0, z = -1} + +describe("FPGA rotation", function() + before_each(function() + world.set_node(pos, "mesecons_fpga:fpga0000") + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("rotates I/O operands clockwise", function() + mesecon._test_program_fpga(pos, {{"A", "OR", "B", "C"}}) + + local node = world.get_node(pos) + minetest.registered_nodes[node.name].on_rotate(pos, node, nil, screwdriver.ROTATE_FACE) + + mesecon._test_place(pos_b, "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal("mesecons_fpga:fpga1000", world.get_node(pos).name) + + mesecon._test_dig(pos_b) + mesecon._test_place(pos_c, "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on/receptor_off actions + mineunit:execute_globalstep() -- Execute activate/deactivate/change actions + assert.equal("mesecons_fpga:fpga1000", world.get_node(pos).name) + end) + + it("rotates I/O operands counterclockwise", function() + mesecon._test_program_fpga(pos, {{"A", "OR", "B", "C"}}) + + local node = world.get_node(pos) + minetest.registered_nodes[node.name].on_rotate(pos, node, nil, screwdriver.ROTATE_AXIS) + + mesecon._test_place(pos_d, "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal("mesecons_fpga:fpga0010", world.get_node(pos).name) + + mesecon._test_dig(pos_d) + mesecon._test_place(pos_a, "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on/receptor_off actions + mineunit:execute_globalstep() -- Execute activate/deactivate/change actions + assert.equal("mesecons_fpga:fpga0010", world.get_node(pos).name) + end) + + it("updates ports", function() + mesecon._test_program_fpga(pos, {{"NOT", "A", "B"}}) + assert.equal("mesecons_fpga:fpga0010", world.get_node(pos).name) + + local node = world.get_node(pos) + minetest.registered_nodes[node.name].on_rotate(pos, node, nil, screwdriver.ROTATE_AXIS) + assert.equal("mesecons_fpga:fpga0001", world.get_node(pos).name) + end) +end) + +-- mineunit does not support deprecated ItemStack:get_metadata() +pending("FPGA programmer", function() + local pos2 = {x = 10, y = 0, z = 0} + + before_each(function() + world.set_node(pos, "mesecons_fpga:fpga0000") + world.set_node(pos2, "mesecons_fpga:fpga0000") + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("transfers instructions", function() + mesecon._test_program_fpga(pos2, {{"NOT", "A", "B"}}) + mesecon._test_paste_fpga_program(pos, mesecon._test_copy_fpga_program(pos2)) + assert.equal("mesecons_fpga:fpga0010", world.get_node(pos).name) + end) + + it("does not copy from new FPGAs", function() + mesecon._test_program_fpga(pos, {{"NOT", "A", "B"}}) + mesecon._test_paste_fpga_program(pos, mesecon._test_copy_fpga_program(pos2)) + assert.equal("mesecons_fpga:fpga0010", world.get_node(pos).name) + end) + + it("does not copy from cleared FPGAs", function() + mesecon._test_program_fpga(pos, {{"NOT", "A", "B"}}) + mesecon._test_program_fpga(pos2, {{"=", "A", "B"}}) + mesecon._test_program_fpga(pos2, {}) + mesecon._test_paste_fpga_program(pos, mesecon._test_copy_fpga_program(pos2)) + assert.equal("mesecons_fpga:fpga0010", world.get_node(pos).name) + end) + + it("does not copy from non-FPGA nodes", function() + mesecon._test_program_fpga(pos, {{"NOT", "A", "B"}}) + mesecon._test_paste_fpga_program(pos, mesecon._test_copy_fpga_program(vector.add(pos2, 1))) + assert.equal("mesecons_fpga:fpga0010", world.get_node(pos).name) + end) +end) diff --git a/mesecons_fpga/spec/logic_spec.lua b/mesecons_fpga/spec/logic_spec.lua new file mode 100644 index 0000000..73882dd --- /dev/null +++ b/mesecons_fpga/spec/logic_spec.lua @@ -0,0 +1,235 @@ +require("mineunit") + +fixture("mesecons_fpga") + +describe("FPGA logic", function() + local pos = {x = 0, y = 0, z = 0} + local pos_a = {x = -1, y = 0, z = 0} + local pos_b = {x = 0, y = 0, z = 1} + local pos_c = {x = 1, y = 0, z = 0} + local pos_d = {x = 0, y = 0, z = -1} + + local fpga_set = false + + local function set_fpga() + if not fpga_set then + world.set_node(pos, "mesecons_fpga:fpga0000") + fpga_set = true + end + end + before_each(set_fpga) + + local function reset_world() + if fpga_set then + mesecon._test_reset() + world.clear() + fpga_set = false + end + end + after_each(reset_world) + + local function test_program(inputs, outputs, program) + set_fpga() + + mesecon._test_program_fpga(pos, program) + + if inputs.a then mesecon._test_place(pos_a, "mesecons:test_receptor_on") end + if inputs.b then mesecon._test_place(pos_b, "mesecons:test_receptor_on") end + if inputs.c then mesecon._test_place(pos_c, "mesecons:test_receptor_on") end + if inputs.d then mesecon._test_place(pos_d, "mesecons:test_receptor_on") end + mineunit:execute_globalstep() -- Execute receptor_on actions + mineunit:execute_globalstep() -- Execute activate/change actions + + local expected_name = "mesecons_fpga:fpga" + .. (outputs.d and 1 or 0) .. (outputs.c and 1 or 0) + .. (outputs.b and 1 or 0) .. (outputs.a and 1 or 0) + assert.equal(expected_name, world.get_node(pos).name) + + reset_world() + end + + it("operator and", function() + local prog = {{"A", "AND", "B", "C"}} + test_program({}, {}, prog) + test_program({a = true}, {}, prog) + test_program({b = true}, {}, prog) + test_program({a = true, b = true}, {c = true}, prog) + end) + + it("operator or", function() + local prog = {{"A", "OR", "B", "C"}} + test_program({}, {}, prog) + test_program({a = true}, {c = true}, prog) + test_program({b = true}, {c = true}, prog) + test_program({a = true, b = true}, {c = true}, prog) + end) + + it("operator not", function() + local prog = {{"NOT", "A", "B"}} + test_program({}, {b = true}, prog) + test_program({a = true}, {}, prog) + end) + + it("operator xor", function() + local prog = {{"A", "XOR", "B", "C"}} + test_program({}, {}, prog) + test_program({a = true}, {c = true}, prog) + test_program({b = true}, {c = true}, prog) + test_program({a = true, b = true}, {}, prog) + end) + + it("operator nand", function() + local prog = {{"A", "NAND", "B", "C"}} + test_program({}, {c = true}, prog) + test_program({a = true}, {c = true}, prog) + test_program({b = true}, {c = true}, prog) + test_program({a = true, b = true}, {}, prog) + end) + + it("operator buf", function() + local prog = {{"=", "A", "B"}} + test_program({}, {}, prog) + test_program({a = true}, {b = true}, prog) + end) + + it("operator xnor", function() + local prog = {{"A", "XNOR", "B", "C"}} + test_program({}, {c = true}, prog) + test_program({a = true}, {}, prog) + test_program({b = true}, {}, prog) + test_program({a = true, b = true}, {c = true}, prog) + end) + + it("operator nor", function() + local prog = {{"A", "NOR", "B", "C"}} + test_program({}, {c = true}, prog) + test_program({a = true}, {}, prog) + test_program({b = true}, {}, prog) + test_program({a = true, b = true}, {}, prog) + end) + + it("rejects duplicate operands", function() + test_program({a = true}, {}, {{"A", "OR", "A", "B"}}) + test_program({a = true}, {}, {{"=", "A", "0"}, {"0", "OR", "0", "B"}}) + end) + + it("rejects unassigned memory operands", function() + test_program({a = true}, {}, {{"A", "OR", "0", "B"}}) + test_program({a = true}, {}, {{"0", "OR", "A", "B"}}) + end) + + it("rejects double memory assignment", function() + test_program({a = true}, {}, {{"=", "A", "0"}, {"=", "A", "0"}, {"=", "0", "B"}}) + end) + + it("rejects assignment to memory operand", function() + test_program({a = true}, {}, {{"=", "A", "0"}, {"A", "OR", "0", "0"}, {"=", "0", "B"}}) + end) + + it("allows double port assignment", function() + test_program({a = true}, {b = true}, {{"NOT", "A", "B"}, {"=", "A", "B"}}) + end) + + it("allows assignment to port operand", function() + test_program({a = true}, {b = true}, {{"A", "OR", "B", "B"}}) + end) + + it("preserves initial pin states", function() + test_program({a = true}, {b = true}, {{"=", "A", "B"}, {"=", "B", "C"}}) + end) + + it("rejects binary operations with single operands", function() + test_program({a = true}, {}, {{"=", "A", "B"}, {" ", "OR", "A", "C"}}) + test_program({a = true}, {}, {{"=", "A", "B"}, {"A", "OR", " ", "C"}}) + end) + + it("rejects unary operations with first operands", function() + test_program({a = true}, {}, {{"=", "A", "B"}, {"A", "=", " ", "C"}}) + end) + + it("rejects operations without destinations", function() + test_program({a = true}, {}, {{"=", "A", "B"}, {"=", "A", " "}}) + end) + + it("allows blank statements", function() + test_program({a = true}, {b = true, c = true}, { + {" ", " ", " ", " "}, + {"=", "A", "B"}, + {" ", " ", " ", " "}, + {" ", " ", " ", " "}, + {"=", "A", "C"}, + }) + end) + + it("transmits output signals to adjacent nodes", function() + mesecon._test_program_fpga(pos, { + {"=", "A", "B"}, + {"=", "A", "C"}, + {"NOT", "A", "D"}, + }) + mesecon._test_place(pos_b, "mesecons:test_effector") + mesecon._test_place(pos_c, "mesecons:test_effector") + mesecon._test_place(pos_d, "mesecons:test_effector") + mineunit:execute_globalstep() -- Execute receptor_on actions + mineunit:execute_globalstep() -- Execute activate/change actions + + -- Makes an object from the last three effector events in the list for use with assert.same. + -- This is necessary to ignore the ordering of events. + local function event_tester(list) + local o = {list[#list - 2], list[#list - 1], list[#list - 0]} + table.sort(o, function(a, b) + local fmt = "%s %d %d %d" + return fmt:format(a[1], a[2].x, a[2].y, a[2].z) < fmt:format(b[1], b[2].x, b[2].y, b[2].z) + end) + return o + end + + mesecon._test_place(pos_a, "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + mineunit:execute_globalstep() -- Execute receptor_on/receptor_off actions + mineunit:execute_globalstep() -- Execute activate/deactivate/change actions + assert.equal("mesecons_fpga:fpga0110", world.get_node(pos).name) + assert.same(event_tester({{"on", pos_b}, {"on", pos_c}, {"off", pos_d}}), event_tester(mesecon._test_effector_events)) + + mesecon._test_dig(pos_a) + mineunit:execute_globalstep() -- Execute receptor_off action + mineunit:execute_globalstep() -- Execute deactivate/change actions + mineunit:execute_globalstep() -- Execute receptor_on/receptor_off actions + mineunit:execute_globalstep() -- Execute activate/deactivate/change actions + assert.equal("mesecons_fpga:fpga1000", world.get_node(pos).name) + assert.same(event_tester({{"off", pos_b}, {"off", pos_c}, {"on", pos_d}}), event_tester(mesecon._test_effector_events)) + end) + + it("considers past outputs in determining inputs", function() + -- Memory cell: Turning on A turns on C; turning on B turns off C. + mesecon._test_program_fpga(pos, { + {"A", "OR", "C", "0"}, + {"B", "OR", "D", "1"}, + {"NOT", "A", "2"}, + {"NOT", "B", "3"}, + {"0", "AND", "3", "C"}, + {"1", "AND", "2", "D"}, + }) + + mesecon._test_place(pos_a, "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on actions + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal("mesecons_fpga:fpga0100", world.get_node(pos).name) + + mesecon._test_dig(pos_a) + mineunit:execute_globalstep() -- Execute receptor_off actions + mineunit:execute_globalstep() -- Execute deactivate/change actions + assert.equal("mesecons_fpga:fpga0100", world.get_node(pos).name) + + mesecon._test_place(pos_b, "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on actions + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal("mesecons_fpga:fpga1000", world.get_node(pos).name) + + mesecon._test_dig(pos_b) + mineunit:execute_globalstep() -- Execute receptor_off actions + mineunit:execute_globalstep() -- Execute deactivate/change actions + assert.equal("mesecons_fpga:fpga1000", world.get_node(pos).name) + end) +end) diff --git a/mesecons_fpga/spec/mineunit.conf b/mesecons_fpga/spec/mineunit.conf new file mode 100644 index 0000000..81bd36c --- /dev/null +++ b/mesecons_fpga/spec/mineunit.conf @@ -0,0 +1 @@ +fixture_paths = {"../.test_fixtures"} diff --git a/mesecons_luacontroller/spec/lightweight_interrupt_spec.lua b/mesecons_luacontroller/spec/lightweight_interrupt_spec.lua new file mode 100644 index 0000000..045c9b4 --- /dev/null +++ b/mesecons_luacontroller/spec/lightweight_interrupt_spec.lua @@ -0,0 +1,38 @@ +require("mineunit") + +-- This test is done in a separate file since it requires different configuration at startup. +mineunit("core") +minetest.settings:set("mesecon.luacontroller_lightweight_interrupts", "true") + +fixture("mesecons_luacontroller") + +describe("LuaController lightweight interrupt", function() + local pos = {x = 0, y = 0, z = 0} + + before_each(function() + mesecon._test_place(pos, "mesecons_luacontroller:luacontroller0000") + mineunit:execute_globalstep() -- Execute receptor_on action + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("works", function() + mesecon._test_program_luac(pos, [[ + if event.type == "program" then + interrupt(5) + interrupt(10) + elseif event.type == "interrupt" then + port.a = not pin.a + end + ]]) + mineunit:execute_globalstep(0.1) + mineunit:execute_globalstep(9) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + mineunit:execute_globalstep(1) + mineunit:execute_globalstep(0.1) + assert.equal("mesecons_luacontroller:luacontroller0001", world.get_node(pos).name) + end) +end) diff --git a/mesecons_luacontroller/spec/luac_spec.lua b/mesecons_luacontroller/spec/luac_spec.lua new file mode 100644 index 0000000..351b2e4 --- /dev/null +++ b/mesecons_luacontroller/spec/luac_spec.lua @@ -0,0 +1,176 @@ +require("mineunit") + +fixture("mesecons_luacontroller") + +-- Digiline is not tested, since that would require the digiline mod. + +describe("LuaController", function() + local pos = {x = 0, y = 0, z = 0} + local pos_a = {x = -1, y = 0, z = 0} + + before_each(function() + mesecon._test_place(pos, "mesecons_luacontroller:luacontroller0000") + mineunit:execute_globalstep() -- Execute receptor_on action + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("rejects binary code", function() + local ok = mesecon._test_program_luac(pos, string.dump(function() end)) + assert.is_false(ok) + end) + + it("I/O", function() + mesecon._test_place(pos_a, "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + mesecon._test_program_luac(pos, [[ + port.a = not pin.a + port.b = not pin.b + port.c = not pin.c + port.d = not pin.d + ]]) + assert.equal("mesecons_luacontroller:luacontroller1110", world.get_node(pos).name) + mesecon._test_dig(pos_a) + mineunit:execute_globalstep() -- Execute receptor_off action + mineunit:execute_globalstep() -- Execute deactivate/change actions + assert.equal("mesecons_luacontroller:luacontroller0001", world.get_node(pos).name) + end) + + it("memory", function() + mesecon._test_program_luac(pos, [[ + if not mem.x then + mem.x = {} + mem.x[mem.x] = {true, "", 1.2} + else + local b, s, n = unpack(mem.x[mem.x]) + if b == true and s == "" and n == 1.2 then + port.d = true + end + end + ]]) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + mesecon._test_place(pos_a, "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + assert.equal("mesecons_luacontroller:luacontroller1000", world.get_node(pos).name) + end) + + it("interrupts without IDs", function() + mesecon._test_program_luac(pos, [[ + if event.type == "program" then + interrupt(4) + interrupt(8) + elseif event.type == "interrupt" then + port.a = not pin.a + end + ]]) + mineunit:execute_globalstep(0.1) + mineunit:execute_globalstep(3) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + mineunit:execute_globalstep(1) + mineunit:execute_globalstep(0.1) + assert.equal("mesecons_luacontroller:luacontroller0001", world.get_node(pos).name) + mineunit:execute_globalstep(3) + assert.equal("mesecons_luacontroller:luacontroller0001", world.get_node(pos).name) + mineunit:execute_globalstep(1) + mineunit:execute_globalstep(0.1) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + end) + + it("interrupts with IDs", function() + mesecon._test_program_luac(pos, [[ + if event.type == "program" then + interrupt(2, "a") + interrupt(4, "a") + interrupt(16, "b") + elseif event.type == "interrupt" then + if event.iid == "a" then + interrupt(5, "b") + interrupt(4, "b") + end + port.a = not pin.a + end + ]]) + mineunit:execute_globalstep(0.1) + mineunit:execute_globalstep(3) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + mineunit:execute_globalstep(1) + mineunit:execute_globalstep(0.1) + assert.equal("mesecons_luacontroller:luacontroller0001", world.get_node(pos).name) + mineunit:execute_globalstep(3) + assert.equal("mesecons_luacontroller:luacontroller0001", world.get_node(pos).name) + mineunit:execute_globalstep(1) + mineunit:execute_globalstep(0.1) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + end) + + it("limits interrupt ID size", function() + mesecon._test_program_luac(pos, [[ + if event.type == "program" then + interrupt(0, (" "):rep(257)) + elseif event.type == "interrupt" then + port.a = not pin.a + end + ]]) + mineunit:execute_globalstep(3) + mineunit:execute_globalstep(3) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + end) + + it("string.rep", function() + mesecon._test_program_luac(pos, [[ + (" "):rep(64000) + port.a = true + ]]) + assert.equal("mesecons_luacontroller:luacontroller0001", world.get_node(pos).name) + mesecon._test_program_luac(pos, [[ + (" "):rep(64001) + port.b = true + ]]) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + end) + + it("string.find", function() + mesecon._test_program_luac(pos, [[ + port.a = (" a"):find("a", nil, true) == 2 + ]]) + assert.equal("mesecons_luacontroller:luacontroller0001", world.get_node(pos).name) + mesecon._test_program_luac(pos, [[ + (" a"):find("a", nil) + port.b = true + ]]) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + end) + + it("overheats", function() + mesecon._test_program_luac(pos, [[ + interrupt(0) + interrupt(0) + ]]) + mineunit:execute_globalstep() -- Execute 2 interrupts + mineunit:execute_globalstep() -- Execute 4 interrupts + mineunit:execute_globalstep() -- Execute 8 interrupts + mineunit:execute_globalstep() -- Execute 16 interrupts + assert.equal("mesecons_luacontroller:luacontroller_burnt", world.get_node(pos).name) + end) + + it("limits memory", function() + mesecon._test_program_luac(pos, [[ + port.a = true + mem.x = (" "):rep(50000) .. (" "):rep(50000) + ]]) + assert.equal("mesecons_luacontroller:luacontroller_burnt", world.get_node(pos).name) + end) + + it("limits run time", function() + mesecon._test_program_luac(pos, [[ + port.a = true + for i = 1, 1000000 do end + ]]) + assert.equal("mesecons_luacontroller:luacontroller0000", world.get_node(pos).name) + end) +end) diff --git a/mesecons_luacontroller/spec/mineunit.conf b/mesecons_luacontroller/spec/mineunit.conf new file mode 100644 index 0000000..81bd36c --- /dev/null +++ b/mesecons_luacontroller/spec/mineunit.conf @@ -0,0 +1 @@ +fixture_paths = {"../.test_fixtures"} diff --git a/mesecons_mvps/spec/mineunit.conf b/mesecons_mvps/spec/mineunit.conf new file mode 100644 index 0000000..81bd36c --- /dev/null +++ b/mesecons_mvps/spec/mineunit.conf @@ -0,0 +1 @@ +fixture_paths = {"../.test_fixtures"} diff --git a/mesecons_mvps/spec/node_spec.lua b/mesecons_mvps/spec/node_spec.lua new file mode 100644 index 0000000..3f16718 --- /dev/null +++ b/mesecons_mvps/spec/node_spec.lua @@ -0,0 +1,297 @@ +require("mineunit") + +fixture("mesecons_mvps") + +world.set_default_node("air") + +describe("node movement", function() + after_each(function() + mesecon._test_reset() + world.clear() + end) + + it("works with no moved nodes", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + + assert.same({true, {}, {}}, {mesecon.mvps_push(pos, dir, 1, "")}) + assert.same({true, {}, {}}, {mesecon.mvps_pull_all(pos, dir, 1, "")}) + assert.same({true, {}, {}}, {mesecon.mvps_pull_single(pos, dir, 1, "")}) + end) + + it("works with simple stack", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + world.set_node(vector.add(pos, dir), "mesecons:test_conductor_off") + + assert.is_true((mesecon.mvps_push(pos, dir, 2, ""))) + assert.equal("air", world.get_node(pos).name) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.add(pos, dir)).name) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.add(pos, vector.multiply(dir, 2))).name) + + assert.is_true((mesecon.mvps_pull_all(vector.add(pos, dir), vector.multiply(dir, -1), 2, ""))) + assert.equal("mesecons:test_conductor_off", world.get_node(pos).name) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.add(pos, dir)).name) + assert.equal("air", world.get_node(vector.add(pos, vector.multiply(dir, 2))).name) + + assert.is_true((mesecon.mvps_pull_single(pos, vector.multiply(dir, -1), 1, ""))) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.subtract(pos, dir)).name) + assert.equal("air", world.get_node(pos).name) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.add(pos, dir)).name) + end) + + it("works with sticky nodes", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 0, y = 1, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + world.set_node(vector.offset(pos, 0, 1, 0), "mesecons_mvps:test_sticky") + world.set_node(vector.offset(pos, 1, 1, 0), "mesecons:test_conductor_off") + world.set_node(vector.offset(pos, 1, 2, 0), "mesecons:test_conductor_off") + + assert.is_true((mesecon.mvps_push(pos, dir, 4, ""))) + assert.equal("air", world.get_node(vector.offset(pos, 1, 1, 0)).name) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.offset(pos, 1, 2, 0)).name) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.offset(pos, 1, 3, 0)).name) + + assert.is_true((mesecon.mvps_pull_all(vector.add(pos, dir), vector.multiply(dir, -1), 4, ""))) + assert.equal("air", world.get_node(vector.offset(pos, 1, 0, 0)).name) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.offset(pos, 1, 1, 0)).name) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.offset(pos, 1, 2, 0)).name) + + assert.is_true((mesecon.mvps_pull_single(pos, vector.multiply(dir, -1), 3, ""))) + assert.equal("air", world.get_node(vector.offset(pos, 1, -1, 0)).name) + assert.equal("mesecons:test_conductor_off", world.get_node(vector.offset(pos, 1, 0, 0)).name) + assert.equal("air", world.get_node(vector.offset(pos, 1, 1, 0)).name) + end) + + it("respects maximum", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + world.set_node(vector.add(pos, dir), "mesecons:test_conductor_off") + + assert.is_true(not mesecon.mvps_push(pos, dir, 1, "")) + end) + + it("is blocked by basic stopper", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + world.set_node(pos, "mesecons_mvps:test_stopper") + + assert.is_true(not mesecon.mvps_push(pos, dir, 1, "")) + end) + + it("is blocked by conditional stopper", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + + world.set_node(pos, {name = "mesecons_mvps:test_stopper_cond", param2 = 0}) + assert.is_true(not mesecon.mvps_push(pos, dir, 1, "")) + + world.set_node(pos, {name = "mesecons_mvps:test_stopper_cond", param2 = 1}) + assert.is_true((mesecon.mvps_push(pos, dir, 1, ""))) + end) + + -- TODO: I think this is supposed to work? + pending("is blocked by ignore", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + world.set_node(vector.add(pos, dir), "ignore") + + assert.is_true(not mesecon.mvps_push(pos, dir, 1, "")) + end) + + it("moves metadata", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + minetest.get_meta(pos):set_string("foo", "bar") + minetest.get_node_timer(pos):set(12, 34) + + mesecon.mvps_push(pos, dir, 1, "") + assert.equal("bar", minetest.get_meta(vector.add(pos, dir)):get("foo")) + local moved_timer = minetest.get_node_timer(vector.add(pos, dir)) + assert.equal(12, moved_timer:get_timeout()) + assert.equal(34, moved_timer:get_elapsed()) + moved_timer:stop() + assert.same({}, minetest.get_meta(pos):to_table().fields) + assert.is_false(minetest.get_node_timer(pos):is_started()) + end) + + it("calls move callbacks", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + world.set_node(pos, {name = "mesecons_mvps:test_on_move", param2 = 123}) + minetest.get_meta(pos):set_string("foo", "bar") + local move_info = {vector.add(pos, dir), world.get_node(pos), pos, minetest.get_meta(pos):to_table()} + + mesecon.mvps_push(pos, dir, 1, "") + assert.equal(1, #mesecon._test_moves) + assert.same(move_info, mesecon._test_moves[1]) + end) + + it("executes autoconnect hooks", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + + mesecon.mvps_push(pos, dir, 1, "") + mineunit:execute_globalstep() -- Execute delayed autoconnect hook + assert.equal(2, #mesecon._test_autoconnects) + end) + + it("updates moved receptors", function() + local pos1 = {x = 0, y = 0, z = 0} + local pos2 = vector.offset(pos1, 0, 1, 0) + local pos3 = vector.offset(pos1, 2, 0, 0) + local pos4 = vector.offset(pos1, 0, 0, 1) + local dir = {x = 1, y = 0, z = 0} + mesecon._test_place(pos1, "mesecons:test_receptor_on") + mesecon._test_place(pos2, "mesecons:test_conductor_off") + mesecon._test_place(pos3, "mesecons:test_conductor_off") + mesecon._test_place(pos4, "mesecons:test_conductor_off") + mesecon._test_place(vector.add(pos4, dir), "mesecons:test_conductor_off") + mineunit:execute_globalstep() -- Execute receptor_on action + + mesecon.mvps_push(pos1, dir, 1, "") + mineunit:execute_globalstep() -- Execute receptor_on/receptor_off actions + assert.equal("mesecons:test_conductor_off", world.get_node(pos2).name) + assert.equal("mesecons:test_conductor_on", world.get_node(pos3).name) + assert.equal("mesecons:test_conductor_on", world.get_node(pos4).name) + end) + + it("updates moved conductors", function() + local pos1 = {x = 0, y = 0, z = 0} + local pos2 = vector.offset(pos1, 0, 1, 0) + local pos3 = vector.offset(pos1, 0, -1, 0) + local dir = {x = 1, y = 0, z = 0} + mesecon._test_place(pos1, "mesecons:test_conductor_off") + mesecon._test_place(pos2, "mesecons:test_receptor_on") + mesecon._test_place(pos3, "mesecons:test_conductor_off") + mineunit:execute_globalstep() -- Execute receptor_on action + + mesecon.mvps_push(pos1, dir, 1, "") + mineunit:execute_globalstep() -- Execute receptor_off action + assert.equal("mesecons:test_conductor_off", world.get_node(vector.add(pos1, dir)).name) + assert.equal("mesecons:test_conductor_off", world.get_node(pos3).name) + + mesecon.mvps_pull_all(vector.add(pos1, dir), vector.multiply(dir, -1), 1, "") + mineunit:execute_globalstep() -- Execute receptor_on action + assert.equal("mesecons:test_conductor_on", world.get_node(pos1).name) + assert.equal("mesecons:test_conductor_on", world.get_node(pos3).name) + end) + + it("updates moved effectors", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + mesecon._test_place(pos, "mesecons:test_effector") + mesecon._test_place(vector.offset(pos, 0, 1, 0), "mesecons:test_receptor_on") + mesecon._test_place(vector.add(pos, dir), "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + + mesecon.mvps_push(pos, dir, 2, "") + mineunit:execute_globalstep() -- Execute receptor_on/receptor_off actions + mineunit:execute_globalstep() -- Execute activate/deactivate/change actions + assert.equal(tonumber("10000001", 2), world.get_node(vector.add(pos, dir)).param2) + + mineunit:execute_globalstep() -- Let the component cool down + + mesecon.mvps_pull_single(vector.add(pos, dir), vector.multiply(dir, -1), 1, "") + mineunit:execute_globalstep() -- Execute receptor_on/receptor_off actions + mineunit:execute_globalstep() -- Execute activate/deactivate/change actions + assert.equal(tonumber("10000100", 2), world.get_node(pos).param2) + end) + + -- Since turnon is called before turnoff when pushing, effectors may be incorrectly turned off. + pending("does not overwrite turnon with receptor_off", function() + local pos = {x = 0, y = 0, z = 0} + local dir = {x = 1, y = 0, z = 0} + mesecon._test_place(pos, "mesecons:test_effector") + mesecon._test_place(vector.add(pos, dir), "mesecons:test_conductor_off") + mesecon._test_place(vector.add(pos, vector.multiply(dir, 2)), "mesecons:test_receptor_on") + mineunit:execute_globalstep() -- Execute receptor_on action + mineunit:execute_globalstep() -- Execute activate/change actions + + mesecon.mvps_push(pos, dir, 3, "") + mineunit:execute_globalstep() -- Execute receptor_on/receptor_off actions + mineunit:execute_globalstep() -- Execute activate/deactivate/change actions + assert.equal(tonumber("10000001", 2), world.get_node(vector.add(pos, dir)).param2) + end) + + -- mineunit doesn't yet implement minetest.check_for_falling. + pending("causes nodes to fall", function() + end) +end) + +describe("protection", function() + teardown(function() + minetest.settings:remove("mesecon.mvps_protection_mode") + end) + + after_each(function() + mesecon._test_reset() + world.clear() + end) + + local protected_pos = {x = 1, y = 0, z = 0} + mineunit:protect(protected_pos, "Joe") + + it("blocks movement", function() + minetest.settings:set("mesecon.mvps_protection_mode", "restrict") + + local pos = {x = 0, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + + assert.same({false, "protected"}, {mesecon.mvps_push(pos, {x = 1, y = 0, z = 0}, 1, "Bob")}) + end) + + it("allows owner's movement", function() + minetest.settings:set("mesecon.mvps_protection_mode", "restrict") + + local pos = {x = 0, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + + assert.is_true((mesecon.mvps_push(pos, {x = 1, y = 0, z = 0}, 1, "Joe"))) + end) + + it("'ignore'", function() + minetest.settings:set("mesecon.mvps_protection_mode", "ignore") + + local pos = {x = 0, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + + assert.is_true((mesecon.mvps_push(pos, {x = 1, y = 0, z = 0}, 1, "Bob"))) + end) + + it("'normal'", function() + minetest.settings:set("mesecon.mvps_protection_mode", "normal") + + local pos = {x = 0, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + + assert.same({false, "protected"}, {mesecon.mvps_push(pos, {x = 1, y = 0, z = 0}, 1, "")}) + + assert.is_true((mesecon.mvps_push(pos, {x = 0, y = 1, z = 0}, 1, ""))) + end) + + it("'compat'", function() + minetest.settings:set("mesecon.mvps_protection_mode", "compat") + + local pos = {x = 0, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + + assert.is_true((mesecon.mvps_push(pos, {x = 1, y = 0, z = 0}, 1, ""))) + end) + + it("'restrict'", function() + minetest.settings:set("mesecon.mvps_protection_mode", "restrict") + + local pos = {x = 0, y = 0, z = 0} + world.set_node(pos, "mesecons:test_conductor_off") + + assert.same({false, "protected"}, {mesecon.mvps_push(pos, {x = 0, y = 1, z = 0}, 1, "")}) + end) +end) diff --git a/mesecons_mvps/spec/object_spec.lua b/mesecons_mvps/spec/object_spec.lua new file mode 100644 index 0000000..cc2ca8d --- /dev/null +++ b/mesecons_mvps/spec/object_spec.lua @@ -0,0 +1,3 @@ +-- mineunit doesn't yet implement minetest.get_objects_inside_radius +pending("object movement", function() +end) From 60240ba268357d4ef38574ac0ced596e684628bc Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Wed, 7 Dec 2022 07:15:23 -0500 Subject: [PATCH 06/10] Fix mvps receptor_off issue (#627) --- mesecons/init.lua | 1 - mesecons/internal.lua | 9 ++++++--- mesecons_mvps/spec/node_spec.lua | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mesecons/init.lua b/mesecons/init.lua index 83e611b..c11e480 100644 --- a/mesecons/init.lua +++ b/mesecons/init.lua @@ -99,7 +99,6 @@ mesecon.queue:add_function("receptor_off", function (pos, rules) local rulenames = mesecon.rules_link_rule_all(pos, rule) for _, rulename in ipairs(rulenames) do mesecon.vm_begin() - mesecon.changesignal(np, minetest.get_node(np), rulename, mesecon.state.off, 2) -- Turnoff returns true if turnoff process was successful, no onstate receptor -- was found along the way. Commit changes that were made in voxelmanip. If turnoff diff --git a/mesecons/internal.lua b/mesecons/internal.lua index 6f52b44..a28c430 100644 --- a/mesecons/internal.lua +++ b/mesecons/internal.lua @@ -542,9 +542,12 @@ function mesecon.turnoff(pos, link) end for _, sig in ipairs(signals) do - mesecon.changesignal(sig.pos, sig.node, sig.link, mesecon.state.off, sig.depth) - if mesecon.is_effector_on(sig.node.name) and not mesecon.is_powered(sig.pos) then - mesecon.deactivate(sig.pos, sig.node, sig.link, sig.depth) + -- If sig.depth is 1, it has not yet been checked that the power source is actually off. + if sig.depth > 1 or not mesecon.is_powered(sig.pos, sig.link) then + mesecon.changesignal(sig.pos, sig.node, sig.link, mesecon.state.off, sig.depth) + if mesecon.is_effector_on(sig.node.name) and not mesecon.is_powered(sig.pos) then + mesecon.deactivate(sig.pos, sig.node, sig.link, sig.depth) + end end end diff --git a/mesecons_mvps/spec/node_spec.lua b/mesecons_mvps/spec/node_spec.lua index 3f16718..2a9d170 100644 --- a/mesecons_mvps/spec/node_spec.lua +++ b/mesecons_mvps/spec/node_spec.lua @@ -206,7 +206,7 @@ describe("node movement", function() end) -- Since turnon is called before turnoff when pushing, effectors may be incorrectly turned off. - pending("does not overwrite turnon with receptor_off", function() + it("does not overwrite turnon with receptor_off", function() local pos = {x = 0, y = 0, z = 0} local dir = {x = 1, y = 0, z = 0} mesecon._test_place(pos, "mesecons:test_effector") From edcdc6817ec7bfa12eb1a9799af18a613c7e55ef Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Thu, 15 Dec 2022 12:43:08 -0500 Subject: [PATCH 07/10] Avoid deprecated item metadata accessors (#630) --- mesecons_fpga/spec/helper_spec.lua | 3 +-- mesecons_fpga/tool.lua | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mesecons_fpga/spec/helper_spec.lua b/mesecons_fpga/spec/helper_spec.lua index 3b5b4c9..5f71c03 100644 --- a/mesecons_fpga/spec/helper_spec.lua +++ b/mesecons_fpga/spec/helper_spec.lua @@ -65,8 +65,7 @@ describe("FPGA rotation", function() end) end) --- mineunit does not support deprecated ItemStack:get_metadata() -pending("FPGA programmer", function() +describe("FPGA programmer", function() local pos2 = {x = 10, y = 0, z = 0} before_each(function() diff --git a/mesecons_fpga/tool.lua b/mesecons_fpga/tool.lua index fa708b3..e0aff3b 100644 --- a/mesecons_fpga/tool.lua +++ b/mesecons_fpga/tool.lua @@ -23,7 +23,7 @@ minetest.register_tool("mesecons_fpga:programmer", { minetest.sound_play("mesecons_fpga_fail", { pos = placer:get_pos(), gain = 0.1, max_hear_distance = 4 }, true) return itemstack end - itemstack:set_metadata(meta:get_string("instr")) + itemstack:get_meta():set_string("", meta:get_string("instr")) minetest.chat_send_player(placer:get_player_name(), "FPGA gate configuration was successfully copied!") minetest.sound_play("mesecons_fpga_copy", { pos = placer:get_pos(), gain = 0.1, max_hear_distance = 4 }, true) @@ -44,7 +44,7 @@ minetest.register_tool("mesecons_fpga:programmer", { return itemstack end - local imeta = itemstack:get_metadata() + local imeta = itemstack:get_meta():get_string("") if imeta == "" then minetest.chat_send_player(player_name, "Use shift+right-click to copy a gate configuration first.") minetest.sound_play("mesecons_fpga_fail", { pos = user:get_pos(), gain = 0.1, max_hear_distance = 4 }, true) From 2589b391e5f0c684a2da96e1c82da256f172d445 Mon Sep 17 00:00:00 2001 From: HybridDog <3192173+HybridDog@users.noreply.github.com> Date: Thu, 22 Dec 2022 21:02:36 +0100 Subject: [PATCH 08/10] Do not print the mesecons OK message --- mesecons/init.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/mesecons/init.lua b/mesecons/init.lua index c11e480..3446004 100644 --- a/mesecons/init.lua +++ b/mesecons/init.lua @@ -117,8 +117,6 @@ function mesecon.receptor_off(pos, rules) end -print("[OK] Mesecons") - -- Deprecated stuff -- To be removed in future releases dofile(minetest.get_modpath("mesecons").."/legacy.lua"); From 6890624f3db04b95be1ba10e83cf433d62a8df75 Mon Sep 17 00:00:00 2001 From: fluxionary <25628292+fluxionary@users.noreply.github.com> Date: Thu, 23 Feb 2023 08:16:35 -0800 Subject: [PATCH 09/10] Add option to disable printing inside a luacontroller (#633) Co-authored-by: DS --- mesecons_luacontroller/init.lua | 12 +++++++----- settingtypes.txt | 4 ++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mesecons_luacontroller/init.lua b/mesecons_luacontroller/init.lua index 2eba817..72bee24 100644 --- a/mesecons_luacontroller/init.lua +++ b/mesecons_luacontroller/init.lua @@ -203,11 +203,13 @@ end ------------------------- local function safe_print(param) - local string_meta = getmetatable("") - local sandbox = string_meta.__index - string_meta.__index = string -- Leave string sandbox temporarily - print(dump(param)) - string_meta.__index = sandbox -- Restore string sandbox + if mesecon.setting("luacontroller_print_behavior", "log") == "log" then + local string_meta = getmetatable("") + local sandbox = string_meta.__index + string_meta.__index = string -- Leave string sandbox temporarily + minetest.log("action", string.format("[mesecons_luacontroller] print(%s)", dump(param))) + string_meta.__index = sandbox -- Restore string sandbox + end end local function safe_date() diff --git a/settingtypes.txt b/settingtypes.txt index 5627440..688baf2 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -28,6 +28,10 @@ mesecon.luacontroller_memsize (Controller memory limit) int 100000 10000 1000000 # IID is ignored and at most one interrupt may be queued if this setting is enabled. mesecon.luacontroller_lightweight_interrupts (Lightweight interrupts) bool false +# Behavior of print() inside a luacontroller. By default, this emits a message into actionstream. +# Set it to noop if you wish to disable that behavior. +mesecon.luacontroller_print_behavior (Behavior of print) enum log log,noop + [mesecons_mvps] # In pre-existing world, MVPS may not be labelled with the owner. From 54de66b3e195a68cc28c1be6a01be3853c5d30aa Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Sat, 18 Mar 2023 14:42:20 +0100 Subject: [PATCH 10/10] Add pitch variations for most noteblock sounds (#535) --- mesecons_noteblock/README.txt | 15 +++++++++++++ .../doc/noteblock/description.html | 4 ++-- mesecons_noteblock/init.lua | 20 ++++++++++++++++-- .../sounds/mesecons_noteblock_litecrash.ogg | Bin 30453 -> 27627 bytes 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 mesecons_noteblock/README.txt diff --git a/mesecons_noteblock/README.txt b/mesecons_noteblock/README.txt new file mode 100644 index 0000000..e0ad34c --- /dev/null +++ b/mesecons_noteblock/README.txt @@ -0,0 +1,15 @@ +Credits of sound files: + +Note: Most sounds have not been used verbatim, but tweaked a little to be more suitable for the noteblock mod. + +* mesecons_noteblock_litecrash.ogg + * License: CC BY 3.0 + * by freesound.org user ani_music + * Source: https://freesound.org/people/ani_music/sounds/219612/ + +Everything else: +Created by Mesecons authors, licensed CC BY 3.0. + +-------------------- +License links: +* CC BY 3.0: http://creativecommons.org/licenses/by/3.0/ diff --git a/mesecons_noteblock/doc/noteblock/description.html b/mesecons_noteblock/doc/noteblock/description.html index e11724d..a98c0f9 100644 --- a/mesecons_noteblock/doc/noteblock/description.html +++ b/mesecons_noteblock/doc/noteblock/description.html @@ -6,8 +6,8 @@ This effector makes a sound if powered and can be used for making music. Normall Chest or Locked ChestSnare Any treeCrash Any wooden planksLite Crash -Coal BlockExplosion sound -Lava SourceFire sound +Coal BlockExplosion sound (fixed pitch) +Lava SourceFire sound (fixed pitch) Steel BlockPiano (high pitch, one octave higher than normal) Any other blockPiano (low pitch) diff --git a/mesecons_noteblock/init.lua b/mesecons_noteblock/init.lua index 4080478..847c40b 100644 --- a/mesecons_noteblock/init.lua +++ b/mesecons_noteblock/init.lua @@ -20,6 +20,7 @@ minetest.register_node("mesecons_noteblock:noteblock", { mesecon.noteblock_play(pos, node.param2) end }}, + place_param2 = 11, -- initialize at C note on_blast = mesecon.on_blastnode, }) @@ -45,7 +46,7 @@ local soundnames = { "mesecons_noteblock_a", "mesecons_noteblock_asharp", "mesecons_noteblock_b", - "mesecons_noteblock_c" + "mesecons_noteblock_c" -- << noteblock is initialized here } local node_sounds = {} @@ -74,6 +75,9 @@ mesecon.noteblock_play = function(pos, param2) pos.y = pos.y-1 local nodeunder = minetest.get_node(pos).name local soundname = node_sounds[nodeunder] + local use_pitch = true + local pitch + -- Special sounds if not soundname then for k,v in pairs(node_sounds_group) do local g = minetest.get_item_group(nodeunder, k) @@ -83,6 +87,7 @@ mesecon.noteblock_play = function(pos, param2) end end end + -- Piano if not soundname then soundname = soundnames[param2] if not soundname then @@ -92,6 +97,17 @@ mesecon.noteblock_play = function(pos, param2) if nodeunder == steelblock_nodename then soundname = soundname.. 2 end + use_pitch = false + end + -- Disable pitch for fire and explode because they'd sound too odd + if soundname == "fire_fire" or soundname == "tnt_explode" then + use_pitch = false + end + if use_pitch then + -- Calculate pitch + -- Adding 1 to param2 because param2=11 is *lowest* pitch sound + local val = (param2+1)%12 + pitch = 2^((val-6)/12) end pos.y = pos.y+1 if soundname == "fire_fire" then @@ -99,6 +115,6 @@ mesecon.noteblock_play = function(pos, param2) local handle = minetest.sound_play(soundname, {pos = pos, loop = true}) minetest.after(3.0, minetest.sound_fade, handle, -1.5, 0.0) else - minetest.sound_play(soundname, {pos = pos}, true) + minetest.sound_play(soundname, {pos = pos, pitch = pitch}, true) end end diff --git a/mesecons_noteblock/sounds/mesecons_noteblock_litecrash.ogg b/mesecons_noteblock/sounds/mesecons_noteblock_litecrash.ogg index 79ab256a7cda06e01eae8a12e3f0d5eb2cab82db..36d83f39e7caea577034eace088e6173ee0841e0 100644 GIT binary patch literal 27627 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 L;S+P<>i`Z8<0bVr literal 30453 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