From 8db6acb80166eb865bb94ac7e01204a95d1b61f1 Mon Sep 17 00:00:00 2001 From: rnd Date: Tue, 5 Mar 2019 14:15:59 +0100 Subject: [PATCH] code preprocessor fix various demo scripts added --- commands.lua | 41 +-- init.lua | 75 +++--- misc_commands.lua | 54 ++++ scripts/games/simple_box_push.lua | 56 ++++ scripts/games/sokoban3d.lua | 103 ++++++++ scripts/gui/scrolling_inventory.lua | 23 ++ scripts/http/webcommands/webcommands.lua | 14 +- scripts/misc/activity_generator.lua | 91 +++++++ scripts/misc/chat_board.lua | 23 ++ scripts/misc/compact_inventory.lua | 16 ++ scripts/programming/block_save.lua | 297 ++++++++++++++++++++++ scripts/programming/robot2irc.lua | 9 + scripts/programming/serialize.lua | 12 + scripts/programming/turtle_with_loops.lua | 46 ++++ scripts/shop/digital_money.lua | 89 +++++++ scripts/simulators/horse_race.lua | 42 +++ scripts/simulators/pathfinder.lua | 25 ++ scripts/simulators/room_walker.lua | 40 +++ scripts/simulators/turtle_with_loops.lua | 46 ++++ scripts/utils/3d printer.lua | 73 ++++++ sounds/Dtmf0.ogg | Bin 6531 -> 5619 bytes sounds/piano.ogg | Bin 0 -> 10808 bytes 22 files changed, 1088 insertions(+), 87 deletions(-) create mode 100644 misc_commands.lua create mode 100644 scripts/games/simple_box_push.lua create mode 100644 scripts/games/sokoban3d.lua create mode 100644 scripts/gui/scrolling_inventory.lua create mode 100644 scripts/misc/activity_generator.lua create mode 100644 scripts/misc/chat_board.lua create mode 100644 scripts/misc/compact_inventory.lua create mode 100644 scripts/programming/block_save.lua create mode 100644 scripts/programming/robot2irc.lua create mode 100644 scripts/programming/serialize.lua create mode 100644 scripts/programming/turtle_with_loops.lua create mode 100644 scripts/shop/digital_money.lua create mode 100644 scripts/simulators/horse_race.lua create mode 100644 scripts/simulators/pathfinder.lua create mode 100644 scripts/simulators/room_walker.lua create mode 100644 scripts/simulators/turtle_with_loops.lua create mode 100644 scripts/utils/3d printer.lua create mode 100644 sounds/piano.ogg diff --git a/commands.lua b/commands.lua index be43b0d..cff46ab 100644 --- a/commands.lua +++ b/commands.lua @@ -136,12 +136,12 @@ basic_robot.commands.dig = function(name,dir) --require energy to dig if basic_robot.dig_require_energy then - local digcost = basic_robot.digcosts[nodename]; + local digcost = basic_robot.digcosts[nodename] or 1/(5*25); -- default 1/5th of stone dig if digcost then local data = basic_robot.data[name]; local energy = (data.menergy or 0) - digcost; if energy<0 then - error("need " .. digcost .. " energy to dig " .. nodename .. ". Use machine.generate(...) to get some energy."); + error("need " .. digcost .. " energy to dig " .. nodename .. ". Use machine.generate to get some energy."); end data.menergy = energy; end @@ -183,19 +183,6 @@ basic_robot.commands.insert_item = function(name,item, inventory,dir) local inv = minetest.get_meta(pos):get_inventory(); - -- fertilize if soil - if item == "farming:fertilizer" then - local stack = ItemStack(item); - if minetest.get_node(tpos).name == "farming:soil_wet" and (meta:get_int("admin")==1 or inv:contains_item("main", stack)) then - inv:remove_item("main", stack); - local nutrient = tmeta:get_int("nutrient"); nutrient = nutrient + 10; if nutrient>20 then nutrient = 20 end - tmeta:set_int("nutrient",nutrient); - minetest.set_node({x=tpos.x,y=tpos.y+1,z=tpos.z},{name = "air"}) - return true - end - end - - local tinv = minetest.get_meta(tpos):get_inventory(); if not inventory then inventory = "main"; end @@ -866,30 +853,26 @@ basic_robot.technic = { -- data cache ["default:cobble"] = {1,"default:gravel",1}, ["default:gravel"] = {0.5,"default:dirt",1}, ["default:dirt"] = {0.5,"default:clay_lump 4",1}, - ["es:aikerum_crystal"] ={16,"es:aikerum_dust 2",1}, -- added for es mod - ["es:ruby_crystal"] = {16,"es:ruby_dust 2",1}, - ["es:emerald_crystal"] = {16,"es:emerald_dust 2",1}, - ["es:purpellium_lump"] = {16,"es:purpellium_dust 2",1}, ["default:obsidian_shard"] = {199,"default:lava_source",1}, ["gloopblocks:basalt"] = {1, "default:cobble",1}, -- enable coble farms with gloopblocks mod ["default:ice"] = {1, "default:snow 4",1}, ["darkage:silt_lump"]={1,"darkage:chalk_powder",1}, - ["default:diamond"] = {16, "basic_machines:diamond_dust_33 2", 1}, + ["default:diamond"] = {16, "basic_machines:diamond_dust_00 2", 1}, ["default:ice"] = {1, "default:snow", 1}, - ["moreores:tin_lump"] = {4,"basic_machines:tin_dust_33 2",1}, + ["moreores:tin_lump"] = {4,"basic_machines:tin_dust_00 2",1}, ["default:obsidian_shard"] = {199, "default:lava_source",1}, - ["default:mese_crystal"] = {8, "basic_machines:mese_dust_33 2",1}, + ["default:mese_crystal"] = {8, "basic_machines:mese_dust_00 2",1}, ["moreores:mithril_ingot"] = {16, "basic_machines:mithril_dust_33 2",1}, ["moreores:silver_ingot"] = {5, "basic_machines:silver_dust_33 2",1}, - ["moreores:tin_ingot"] = {4,"basic_machines:tin_dust_33 2",1}, + ["moreores:tin_ingot"] = {4,"basic_machines:tin_dust_00 2",1}, ["moreores:mithril_lump"] = {16, "basic_machines:mithril_dust_33 2",1}, - ["default:steel_ingot"] = {4, "basic_machines:iron_dust_33 2",1}, + ["default:steel_ingot"] = {4, "basic_machines:iron_dust_00 2",1}, ["moreores:silver_lump"] = {5, "basic_machines:silver_dust_33 2",1}, - ["default:gold_ingot"] = {6, "basic_machines:gold_dust_33 2", 1}, - ["default:copper_ingot"] = {4, "basic_machines:copper_dust_33 2",1}, - ["default:gold_lump"] = {6, "basic_machines:gold_dust_33 2", 1}, - ["default:iron_lump"] = {4, "basic_machines:iron_dust_33 2",1}, - ["default:copper_lump"] = {4, "basic_machines:copper_dust_33 2",1}, + ["default:gold_ingot"] = {6, "basic_machines:gold_dust_00 2", 1}, + ["default:copper_ingot"] = {4, "basic_machines:copper_dust_00 2",1}, + ["default:gold_lump"] = {6, "basic_machines:gold_dust_00 2", 1}, + ["default:iron_lump"] = {4, "basic_machines:iron_dust_00 2",1}, + ["default:copper_lump"] = {4, "basic_machines:copper_dust_00 2",1}, }, compressor_recipes = { --[in] ={fuel cost, out, quantity of material required for processing} diff --git a/init.lua b/init.lua index 67ccb69..6aca69f 100644 --- a/init.lua +++ b/init.lua @@ -4,8 +4,8 @@ basic_robot = {}; ------ SETTINGS -------- basic_robot.call_limit = {50,200,1500,10^9}; -- how many execution calls per script run allowed, for auth levels 0,1,2 (normal, robot, puzzle, admin) -basic_robot.entry_count = 2 -- how many robots ordinary player can have -basic_robot.advanced_count = 16 -- how many robots player with robot privs can have +basic_robot.count = {2,4,16,128} -- how many robots player can have + basic_robot.radius = 32; -- divide whole world into blocks of this size - used for managing events like keyboard punches basic_robot.password = "raN___dOM_ p4S"; -- IMPORTANT: change it before running mod, password used for authentifications @@ -25,7 +25,7 @@ basic_robot.bad_inventory_blocks = { -- disallow taking from these nodes invento basic_robot.http_api = minetest.request_http_api(); -basic_robot.version = "2019/01/13a"; +basic_robot.version = "2019/02/16a"; basic_robot.gui = {}; local robogui = basic_robot.gui -- gui management basic_robot.data = {}; -- stores all robot related data @@ -217,29 +217,6 @@ function getSandboxEnv (name) return commands.display_text(obj,text,linesize,size) end, - sound = function(sample,volume, pos) - if pos then - return minetest.sound_play( sample, - { - pos = pos, - gain = volume or 1, - max_hear_distance = 32, -- default, uses an euclidean metric - }) - end - - local obj = basic_robot.data[name].obj; - return minetest.sound_play( sample, - { - object = obj, - gain = volume or 1, - max_hear_distance = 32, -- default, uses an euclidean metric - }) - end, - - sound_stop = function(handle) - minetest.sound_stop(handle) - end, - }, machine = {-- adds technic like functionality to robots: power generation, smelting, grinding, compressing @@ -478,7 +455,9 @@ function getSandboxEnv (name) if authlevel>=1 then -- robot privs - + env.self.sound = minetest.sound_play + env.self.sound_stop = minetest.sound_stop + env.table = { concat = table.concat, insert = table.insert, @@ -512,7 +491,6 @@ function getSandboxEnv (name) end return true end - env.self.read_form = function() local fields = basic_robot.data[name].read_form; @@ -590,9 +568,9 @@ local identify_strings = function(code) -- returns list of positions {start,end} local i = 0; local j; local _; local length = string.len(code); local mode = 0; -- 0: not in string, 1: in '...' string, 2: in "..." string, 3. in [==[ ... ]==] string local modes = { - {"'","'"}, - {"\"","\""}, - {"%[=*%[","%]=*%]"} + {"'","'"}, -- inside ' ' + {"\"","\""}, -- inside " " + {"%[=*%[","%]=*%]"}, -- inside [=[ ]=] } local ret = {} while i < length do @@ -615,7 +593,7 @@ local identify_strings = function(code) -- returns list of positions {start,end} else _,j = string.find(code,modes[mode][2],i); -- search for closing pair if not j then break end - if (mode~=2 or string.sub(code,j-1,j-1) ~= "\\") then -- not (" and \") + if (mode~=2 or (string.sub(code,j-1,j-1) ~= "\\") or string.sub(code,j-2,j-1) == "\\\\") then -- not (" and not \" - but "\\" is allowed) ret[#ret][2] = j mode = 0 end @@ -626,6 +604,7 @@ local identify_strings = function(code) -- returns list of positions {start,end} return ret end + is_inside_string = function(strings,pos) -- is position inside one of the strings? local low = 1; local high = #strings; if high == 0 then return false end @@ -805,10 +784,24 @@ end -- note: to see memory used by lua in kbytes: collectgarbage("count") +local get_authlevel = function(name) -- given player name return auth level + local privs = minetest.get_player_privs(name); + local authlevel = 0; + if privs.privs then -- set auth level depending on privs + authlevel = 3 + elseif privs.puzzle then + authlevel = 2 + elseif privs.robot then + authlevel = 1 + else + authlevel = 0 + end + return authlevel +end + local function setupid(owner) local privs = minetest.get_player_privs(owner); if not privs then return end - local maxid = basic_robot.entry_count; - if privs.robot or privs.puzzle then maxid = basic_robot.advanced_count end -- max id's per user + local maxid = basic_robot.count[get_authlevel(owner)+1] or 2; basic_robot.ids[owner] = {id = 1, maxid = maxid}; --active id for remove control end @@ -1584,6 +1577,7 @@ minetest.register_on_player_receive_fields( data.text = text or "" data.title = fields.title or "" data.text_len = #data.text + data.description = fields.title or "" data.page = 1 data.owner = data.owner or "" local lpp = 14 @@ -1645,17 +1639,7 @@ minetest.register_node("basic_robot:spawner", { local owner = placer:get_player_name(); meta:set_string("owner", owner); - local privs = minetest.get_player_privs(placer:get_player_name()); - local authlevel = 0; - if privs.privs then -- set auth level depending on privs - authlevel = 3 - elseif privs.puzzle then - authlevel = 2 - elseif privs.robot then - authlevel = 1 - else - authlevel = 0 - end + local authlevel = get_authlevel(placer:get_player_name()); meta:set_int("authlevel",authlevel) local sec_hash = minetest.get_password_hash("",authlevel .. owner .. basic_robot.password) -- 'digitally sign' authlevel using password @@ -1749,6 +1733,7 @@ minetest.register_craftitem("basic_robot:control", { local ids = basic_robot.ids[owner]; if not ids then setupid(owner) end local id = basic_robot.ids[owner].id or 1; -- read active id for player local name = owner..id; + local form = "size[9.5,1.25]" .. -- width, height "textarea[1.25,-0.25;8.75,2.25;code;;".. code.."]".. diff --git a/misc_commands.lua b/misc_commands.lua new file mode 100644 index 0000000..12cce7b --- /dev/null +++ b/misc_commands.lua @@ -0,0 +1,54 @@ +//lua minetest.get_player_by_name("rnd"):set_properties({visual_size = {x=1,y=1}}) + +local name = "rnd"; local player = minetest.get_player_by_name(name); player:set_properties({visual = "upright_sprite"});player:set_properties({textures={"default_tool_diamondpick.png"}}) + + +// change to robot +local name = "rnd"; local player = minetest.get_player_by_name(name); player:set_properties({visual = "cube"});player:set_properties({textures={"arrow.png^[transformR90","basic_machine_side.png","basic_machine_side.png","basic_machine_side.png","face.png","basic_machine_side.png"}});player:set_properties({collisionbox={-0.5,-0.5,-0.5,0.5,0.5,0.5}}) + +//LawnGreen + +local name = "rnd"; local player = minetest.get_player_by_name(name); player:set_properties({visual = "sprite"});player:set_properties({textures={"farming_bottle_ethanol.png"}});player:set_properties({collisionbox={-0.5,-0.5,-0.5,0.5,0.5,0.5}});player:set_properties({visual_size = {x=2,y=2}}) + +//farming_blueberry_muffin +local name = "rnd"; local player = minetest.get_player_by_name(name); player:set_properties({visual = "cube"});player:set_properties({textures={"farming_pumpkin_face_off.png","farming_pumpkin_face_off.png","farming_pumpkin_face_off.png","farming_pumpkin_face_off.png","farming_pumpkin_face_off.png","farming_pumpkin_face_off.png"}});player:set_properties({collisionbox={-0.5,-0.5,-0.5,0.5,0.5,0.5}}) + +--nyan cat +//lua local name = "rnd"; local player = minetest.get_player_by_name(name); player:set_properties({visual = "cube"});player:set_properties({textures = {"nyancat_side.png", "nyancat_side.png", "nyancat_side.png","nyancat_side.png", "nyancat_front.png", "nyancat_back.png"}});player:set_properties({collisionbox={-0.5,-0.5,-0.5,0.5,0.5,0.5}}) + + + +local name = "rnd1"; local player = minetest.get_player_by_name(name); player:set_nametag_attributes({text = "Friend of Giorge"}); + +//lua local player = minetest.get_player_by_name("pro2");minetest.sound_play("nyan",{object = player,gain = 1.0,max_hear_distance = 8,loop = false}) + + +//lua local player = minetest.get_player_by_name("rnd");player:set_properties({visual = "mesh",textures = {"mobs_spider.png"},mesh = "mobs_spider.x",visual_size = {x=7,y=7}}) + + +//lua local player = minetest.get_player_by_name("rnd");player:set_properties({visual = "mesh",textures = {"mobs_dungeon_master.png"},mesh = "mobs_dungeon_master.b3d",visual_size = {x=1,y=1}}) + +//lua local player = minetest.get_player_by_name("best");player:set_properties({visual = "mesh",textures = {"zmobs_mese_monster.png"},mesh = "zmobs_mese_monster.x",visual_size = {x=1,y=1}}) + +//lua local player = minetest.get_player_by_name("rnd1");player:set_properties({visual = "mesh",textures = {"mobs_oerkki.png"},mesh = "mobs_oerkki.b3d",visual_size = {x=1,y=1}}) + +//lua local player = minetest.get_player_by_name("rnd1");player:set_properties({visual = "mesh",textures = {"mobs_stone_monster.png"},mesh = "mobs_stone_monster.b3d",visual_size = {x=1,y=1}}) + + +mesh = "zmobs_lava_flan.x", + textures = { + {"zmobs_lava_flan.png"}, + {"zmobs_lava_flan2.png"}, + {"zmobs_lava_flan3.png"}, + }, +---------------------------------------------- + + + +//lua local player = minetest.get_player_by_name("towner");player:set_physics_override({speed=0.05}) + + + + + + diff --git a/scripts/games/simple_box_push.lua b/scripts/games/simple_box_push.lua new file mode 100644 index 0000000..4a737ec --- /dev/null +++ b/scripts/games/simple_box_push.lua @@ -0,0 +1,56 @@ +-- simple box pushing game, rnd + +if not init then + spos = self.spawnpos(); spos.x = spos.x +5; spos.z = spos.z +5; + + for i = 1, 2 do + for j = 1,2 do + puzzle.set_node({x=spos.x+i,y=spos.y,z=spos.z+j}, {name = "basic_robot:buttonFFFFFF"}) + end + end + + init = true + players = find_player(5); + if not players then say("no players nearby") self.remove() end + say("BOX PUSH demo. punch the white box to move it around ") + + pushables = {[1] = true} -- button types + canpushnodes = {["air"] = 1, ["basic_robot:button8080FF"] = 2} -- 1 push node, 2 absorb node +end + +event = keyboard.get() +if event then + local boxtype = event.type + if pushables[boxtype] then + player = puzzle.get_player(event.puncher) + local pos = player:getpos(); + local boxpos = {x = event.x, y = event.y, z = event.z}; + local diff = { pos.x-boxpos.x, pos.z-boxpos.z}; + + local newx,newz + if math.abs(diff[1])>math.abs(diff[2]) then -- punch in x-direction + newx = boxpos.x - (diff[1]>0 and 1 or -1) + newz = boxpos.z + else + newx = boxpos.x + newz = boxpos.z - (diff[2]>0 and 1 or -1) + end + + local newnode = puzzle.get_node({x=newx, y= boxpos.y, z= newz}).name + + + local canpush = canpushnodes[newnode] + if canpush then + local oldnode = puzzle.get_node(boxpos).name + puzzle.set_node(boxpos,{name= "air"}) -- remove node + if canpush == 1 then -- simply move the box + newnode = oldnode + elseif canpush == 2 then -- absorb the box + newnode = newnode + end + + puzzle.set_node({x=newx, y= boxpos.y, z= newz}, {name = newnode}) + end + end + --say(serialize(event)) +end \ No newline at end of file diff --git a/scripts/games/sokoban3d.lua b/scripts/games/sokoban3d.lua new file mode 100644 index 0000000..2e391e0 --- /dev/null +++ b/scripts/games/sokoban3d.lua @@ -0,0 +1,103 @@ +-- sokoban 3D, rnd + +if not init then + spos = self.spawnpos(); spos.x = spos.x +5; spos.z = spos.z +5; + + for i = 1, 2 do + for j = 1,2 do + puzzle.set_node({x=spos.x+i,y=spos.y,z=spos.z+j}, {name = "basic_robot:buttonFFFFFF"}) + end + end + + init = true + players = find_player(5); + if not players then say("no players nearby") self.remove() end + + puzzle.get_player(players[1]):set_physics_override({jump = 0.85}) -- just allow jump on 1 block up + say("BOX PUSH demo. punch the white box to move it around ") + + self.label( + "SOKOBAN 3D. RULES:\n1. pushable blocks: white,gray,yellow. you can not push block if another block is on top of it,\n2. elevator block: yellow - can push other blocks on top of it,".. + "\n3. if block falls it breaks, unless it falls less than 1 deep onto green block\n".. + "4. if you push block into blue it dissapears\n".. + "5. you can only push block by standing close in front of it, not too low below it or too high above it\n".. + "LEVEL 1: push white block on top of red block") + + pushables = {[1] = true,[2] = true,[6] = true} -- button types: white,gray, yellow + canpushnodes = {-- you can push into these nodes, 1 push node, 2 absorb node, 3 = elevator + ["air"] = 1, + ["basic_robot:button8080FF"] = 2, + ["basic_robot:buttonFFFF80"] = 3, + } +end + +event = keyboard.get() +if event then + local boxtype = event.type + if pushables[boxtype] then + player = puzzle.get_player(event.puncher) + local pos = player:getpos(); + local boxpos = {x = event.x, y = event.y, z = event.z}; + local diff = { pos.x-boxpos.x, pos.z-boxpos.z, pos.y - boxpos.y}; -- x,z,y + + local newx,newy,newz + newy = boxpos.y + local allowpush = true + + --self.label(diff[3]) + if diff[3]<-1.5 or diff[3]>0.5 then allowpush = false end -- dont allow to push if height difference to large + + if math.abs(diff[1])>math.abs(diff[2]) then -- punch in x-direction + newx = boxpos.x - (diff[1]>0 and 1 or -1) + newz = boxpos.z + if math.abs(diff[1])<0.7 or math.abs(diff[1])>1 then allowpush = false end -- dont allow push if too close + if math.abs(diff[2]) > 0.25 then allowpush = false end -- must stand in front to push, not from side + else + newx = boxpos.x + newz = boxpos.z - (diff[2]>0 and 1 or -1) + if math.abs(diff[2])<0.7 or math.abs(diff[2])>1 then allowpush = false end -- dont allow push if too close + if math.abs(diff[1]) > 0.25 then allowpush = false end -- must stand in front to push, not from side + end + + --self.label(diff[1] .. " " .. diff[2] .. " " .. diff[3]) + + local newnode = puzzle.get_node({x=newx, y= boxpos.y, z= newz}).name + + + local canpush = canpushnodes[newnode] + if allowpush and canpush then + local oldnode = puzzle.get_node(boxpos).name + + if canpush == 1 then -- simply move the box + newnode = oldnode + elseif canpush == 2 then -- absorb the box + newnode = newnode + elseif canpush == 3 then + newnode = oldnode + newy = newy+1 + end + + + + local nodeabove = puzzle.get_node({x=boxpos.x, y=boxpos.y+1, z= boxpos.z}).name + if nodeabove ~="air" then allowpush = false end -- no floating nodes allowed + + local nodebelow = puzzle.get_node({x=newx, y=newy-1, z= newz}).name + if nodebelow == "air" then + if puzzle.get_node({x=newx, y=newy-2, z= newz}).name ~= "basic_robot:button80FF80" then + newnode = "air" + else + newy=newy-1 + end + end -- fall down + + + + if allowpush then + puzzle.set_node(boxpos,{name= "air"}) -- remove node + puzzle.set_node({x=newx, y= newy, z= newz}, {name = newnode}) + end + end + end + --say(serialize(event)) +end \ No newline at end of file diff --git a/scripts/gui/scrolling_inventory.lua b/scripts/gui/scrolling_inventory.lua new file mode 100644 index 0000000..6112866 --- /dev/null +++ b/scripts/gui/scrolling_inventory.lua @@ -0,0 +1,23 @@ +if not init then +text = " hello world " +name = "rnd" +m = 8; +idx = 0; +n = string.len(text) + +player = puzzle.get_player(name) +inv = player:get_inventory() +inv:set_list("main",{}) +init = true + +end + +for i = 1, m do + local j = (idx+i)%n + 1 + local c = string.byte(text,j)-97; + if c<0 or c>30 then c = -97 end + inv:set_stack("main", i,puzzle.ItemStack("basic_robot:button_" ..(97+c))) +end + +idx = (idx + 1) % n +--self.remove() \ No newline at end of file diff --git a/scripts/http/webcommands/webcommands.lua b/scripts/http/webcommands/webcommands.lua index 2916306..fe067ad 100644 --- a/scripts/http/webcommands/webcommands.lua +++ b/scripts/http/webcommands/webcommands.lua @@ -25,18 +25,8 @@ if not fetch then self.label(os.date("%X") ..', cmd : ' .. req) local i = string.find(req," !") if i then - local cmd = string.sub(req,i+2) - if cmd == "players" then - local players = minetest.get_connected_players(); - out = {}; - for i = 1,#players do out[i] = players[i]:get_player_name() end - MT2web("online players : " .. table.concat(out,", ")) - else - run_commmand(cmd) - end + run_commmand(string.sub(req,i+2)) end - - end end @@ -59,8 +49,6 @@ if not fetch then fetch({url = "http://".. address .. "/mtmsg/"..message, timeout = 5}, result) end MT2web("minetest robot started and listening.") - - self.listen(1) end diff --git a/scripts/misc/activity_generator.lua b/scripts/misc/activity_generator.lua new file mode 100644 index 0000000..c9d4961 --- /dev/null +++ b/scripts/misc/activity_generator.lua @@ -0,0 +1,91 @@ +-- ACTIVITY GENERATOR + modified /status +-- makes server appear more active: virtual players join/leave, talk + +if not init then + minetest.forceload_block(self.pos(),true) + self.label("activity generator") + local chatc = _G.core.registered_chatcommands["status"]; + if chatc then + + if not rom.chatc then rom.chatc = chatc end + --self.label(serialize(chatc)) + hidden_players = { + rnd = false, + } + + extra_players = { + "Piojoblanco","Bryan0911","EmmaBTS","atmaca","pausc05","bobo","marquez","Maike-008","odiseu","jere700182","_z","erick07","elvergalarga01","follow","Mantano10","AW","0987654321","lavraimeriem","formless","kanekii","cuchita","X_Pro_X","Tron","MGPe","Budrow42","lahina","shaahin18","dolphin","Stickman301","Galves58","Appelbaum747jdjxi","agy","E23","Utsler26","Rafael_Aaron_PROOO","cloe","Athans82","Love_Girl","jklu","Marne485hv","xXNicoXx","Dootson22","squad","fatima","Cucuzza62" + } + extra_joined = {}; + + greetings = {"hi","hello","pls help","help","how to play?", "i have only 2 blocks?","cool",":(","someone help","HI"} + + _G.core.registered_chatcommands["status"] = + { + description = "Print server status", + func = function(name, param) + local connected = minetest.get_connected_players(); + local ret = {}; + for i = 1,#connected do + local pname = connected[i]:get_player_name(); + if not hidden_players[pname] then ret[#ret+1] = pname end + end + local clients = table.concat(ret,", "); + local extras = {}; + for name,_ in pairs(extra_joined) do extras[#extras+1] = name end + if #extras>0 then clients = clients ..", " .. table.concat(extras, ", ") end + return true, "# Server: version=0.4.17.1, uptime = ".. math.floor(minetest.get_server_uptime()*10)/10 ..", max_lag = 0.1, clients = {".. clients .. "}" + + end, + } + + --_G.core.registered_chatcommands["status"] = rom.chatc -- uncomment this to restore + + end + t=0 + init = true +end +t=t+1; + +if t%5 == 0 then + local r = math.random(10) + if r <= 2 then -- add random new player + --say(t) + local idx = math.random(#extra_players) + local pname = extra_players[idx]; + if pname and not extra_joined[pname] then + extra_joined[pname] = true + minetest.chat_send_all("*** " .. pname .. " joined the game.") + end + elseif r<=4 then -- disconnect random extra + local count = 0; + for pname,_ in pairs(extra_joined) do count = count + 1 end + local idx = math.random(count) + count = 0; + for pname,_ in pairs(extra_joined) do + count = count + 1 + if count == idx then + minetest.chat_send_all("*** " .. pname .. " left the game.") + extra_joined[pname] = nil + break + end + end + elseif r<=6 then -- chat + if math.random(5) == 1 then + local count = 0 + for pname,_ in pairs(extra_joined) do count = count + 1 end + local idx = math.random(count) + count = 0; + for pname,_ in pairs(extra_joined) do + count = count + 1 + if count == idx then + r = math.random(#greetings); + minetest.chat_send_all("<" .. pname .. "> " ..greetings[r]) + break + end + end + end + end +end + +--self.remove() \ No newline at end of file diff --git a/scripts/misc/chat_board.lua b/scripts/misc/chat_board.lua new file mode 100644 index 0000000..f2e0081 --- /dev/null +++ b/scripts/misc/chat_board.lua @@ -0,0 +1,23 @@ +-- simple chat board by rnd +if not init then init = true + +pos = self.pos() + + +write_text = function(text,size) +for x=0,size-1 do + for y = size-1,0,-1 do + local c = (string.byte(text, (size-1-y)*size+(x+1)) or 32)-97 + puzzle.set_node({x=pos.x+x+1,y=pos.y+y,z=pos.z},{name = "basic_robot:button_"..(97+c)}) + end +end +end + +self.listen(1) + +end + +speaker,msg= self.listen_msg() +if msg then + write_text(msg,15) +end \ No newline at end of file diff --git a/scripts/misc/compact_inventory.lua b/scripts/misc/compact_inventory.lua new file mode 100644 index 0000000..9f8ee17 --- /dev/null +++ b/scripts/misc/compact_inventory.lua @@ -0,0 +1,16 @@ +compact_inventory = function(item) + local size = 32 + local count = 0 + + for i = 1,size do + local stringname = check_inventory.self("","main",i); + local itemname, j = stringname:match("(%S+) (%d+)") + if itemname == item then + count = count + tonumber(j) + end + end + + say(string.format("total count of %s is %s",item,count)) + + insert.forward(string.format("%s %s",item,count)) -- will join all items together +end \ No newline at end of file diff --git a/scripts/programming/block_save.lua b/scripts/programming/block_save.lua new file mode 100644 index 0000000..bd95b90 --- /dev/null +++ b/scripts/programming/block_save.lua @@ -0,0 +1,297 @@ +-- subdivide rectangular area made from various node types into union of disjoint contigious boxes, +-- box count should be small, by rnd 2018 + +if not init then + init = true -- map saver + local dout = function(msg) minetest.chat_send_player("rnd", msg) end + + minidx = 1 -- minimal position index not yet in box collection + + blockdata = {}; --[1] = {p1,p2} [2] = list of nodenames, [3] = list [x][y][z] = nodename idx + + + table_copy = function(tab) + local out = {}; + for k,v in pairs(tab) do + out[k] = v + end + return out + end + + read_block = function(p1,p2,data) + if p1.x>p2.x or p1.y>p2.y or p1.z>p2.z then return end -- p1 coords must be smaller than p2 coords + + data[1] = {{x=p1.x,y=p1.y,z=p1.z},{x=p2.x-p1.x+1,y=p2.y-p1.y+1,z=p2.z-p1.z+1}} -- startpos, dimensions + + local nodedb = {} -- [nodename] = node idx + local nodelist = {} -- list of nodes: nodename1, nodename2,... + local nodes = {} -- table containing nodes [x][y][z] = node idx + local ncount = 0; + local idx = 0 + for x = p1.x, p2.x do + nodes[x-p1.x+1] = {} + for y = p1.y, p2.y do + nodes[x-p1.x+1][y-p1.y+1] = {} + for z = p1.z, p2.z do + idx=idx+1 + local nodename = minetest.get_node({x=x,y=y,z=z}).name + local nidx = nodedb[nodename] + if not nidx then + ncount = ncount + 1 + nodedb[nodename] = ncount; + nodelist[ncount] = nodename + nidx = ncount + end + --nodes[idx] = nidx + nodes[x-p1.x+1][y-p1.y+1][z-p1.z+1 ] = nidx + end + end + end + + data[2] = nodelist + data[3] = nodes + end + + id2pos = function(idx,blockdata) -- idx starts with 0 + local dx = blockdata[1][2].x + local dy = blockdata[1][2].y + local dz = blockdata[1][2].z + + -- idx = z*dx*dy + y*dx + dx + local x = idx % dx; + idx = (idx - x)/dx + local y = idx % dy; -- y = y % (dy) + local z = (idx - y)/dy + return {x=x, y = y, z=z} + end + + pos2id = function(pos,blockdata) + local dx = blockdata[1][2].x + local dy = blockdata[1][2].y + local dz = blockdata[1][2].z + + local x = pos.x - blockdata[1][2].x; + local y = pos.y - blockdata[1][2].y; + local z = pos.z - blockdata[1][2].z; + return z*dx*dy + y*dx + dx + end + + get_box = function(pos, blockdata, boxdata) -- return p1,p2, nodeidx defining largest contiguos box containing pos (all relative coordinates) + + local nodeidx = blockdata[3][pos.x][pos.y][pos.z]; + + local active_dir = {[1]=true,[2]=true,[3]=true,[4]=true,[5]=true,[6]=true}; -- which dirs to search: x-,x+ y-,y+ z-,z+ + local p1 = {x=pos.x,y=pos.y,z=pos.z} -- search limits + local p2 = {x=pos.x,y=pos.y,z=pos.z} + local stop = false; + + local steps = 0 + while not stop and steps < 10000 do -- steps 'safety' + steps = steps + 1 + --dout("step " .. steps .. " p1 " .. serialize(p1) .. " p2 " .. serialize(p2) .. " dirs " .. serialize(active_dir)) + stop = true + for idx,_ in pairs(active_dir) do -- try expansion in different directions + stop = false -- still something to do + if idx<=2 then + local x + if idx == 1 then x = p1.x-1 else x=p2.x+1 end + if blockdata[3][x] then + local bdata = blockdata[3][x]; + for y = p1.y,p2.y do + for z = p1.z,p2.z do + if bdata[y][z]~= nodeidx or (boxdata and boxdata[x] and boxdata[x][y] and boxdata[x][y][z]) then + active_dir[idx] = nil; goto ex; -- not contiguous anymore + end + end + end + ::ex:: + else + active_dir[idx] = nil -- out of bounds, remove direction + end + if active_dir[idx] then -- expansion succesful + if idx == 1 then p1.x = p1.x -1 else p2.x = p2.x+1 end + end + + elseif idx>=5 then + local z + if idx == 5 then z = p1.z-1 else z=p2.z+1 end + local bdata = blockdata[3] + if bdata[1][1][z] then + for x = p1.x,p2.x do + for y = p1.y,p2.y do + if bdata[x][y][z]~= nodeidx or (boxdata and boxdata[x] and boxdata[x][y] and boxdata[x][y][z]) then + active_dir[idx] = nil;goto ex; -- lua only breaks out of 1 loop :( + end + end + end + ::ex:: + else + active_dir[idx] = nil -- out of bounds, remove direction + end + + if active_dir[idx] then -- expansion succesful + if idx == 5 then p1.z = p1.z -1 else p2.z = p2.z+1 end + end + else + local y + if idx == 3 then y = p1.y-1 else y=p2.y+1 end + local bdata = blockdata[3] + if bdata[1][y] then + for x = p1.x,p2.x do + for z = p1.z,p2.z do + if bdata[x][y][z]~= nodeidx or (boxdata and boxdata[x] and boxdata[x][y] and boxdata[x][y][z]) then + active_dir[idx] = nil; goto ex; -- not contiguous anymore + end + end + end + ::ex:: + else + active_dir[idx] = nil -- out of bounds, remove direction + end + + if active_dir[idx] then -- expansion succesful + if idx == 3 then p1.y = p1.y-1 else p2.y = p2.y+1 end + end + + end + + end --try to expand: -x +x -y +y -z +z -x +x -y ... when not possible remove direction from list + end + + return {p1,p2, nodeidx} + end + + get_boxes = function(blockdata,boxdata) + + local dx = blockdata[1][2].x + local dy = blockdata[1][2].y + local dz = blockdata[1][2].z + + for k,v in pairs(boxdata) do boxdata[k] = nil end + local res = {}; -- list of boxes + + + for x=1,dx do + boxdata[x] = {} + for y = 1,dy do + boxdata[x][y] = {} + end + end + + + local xc, yc,zc; + xc = 1 ; yc = 1; zc = 1; -- current 'working' coordinates in block + + local steps = 0; + local stop = false; + + while not stop and steps < 1000 do + steps = steps + 1 + stop = true + + -- find 'next' coordinate thats not yet marked: todo - fix bugs here, skipping/unnecessary ... + + xc = 1; -- set 1st index ( inner loop) to start + --dout("step " .. steps ..", search start xc yc zc ".. xc .. " " .. yc .. " " .. zc) + + local x,y,z;x= xc; y=yc; z = zc; + while (z<=dz) do + while (y<=dy) do + while (x<=dx) do + if not boxdata[x][y][z] then + --dout("step " .. steps .. ", next point: " .. x .. " " .. y .. " " .. z) + stop = false; xc = x; yc=y; zc = z; goto ex + end + x=x+1 + end + y=y+1; x=1; -- reset x-loop + end + z=z+1; y=1; -- reset y-loop + end + + --dout("no unmarked point left, dim dx dy dz : " .. dx .. " " .. dy .. " " .. dz) + + ::ex:: + if stop then break end + + --if box non air add it + + local box = get_box({x=xc,y=yc,z=zc},blockdata, boxdata); + if blockdata[2][box[3]]~= "air" then + res[#res+1] = box + end + -- mark box area as done + + for x = box[1].x, box[2].x do + for y = box[1].y, box[2].y do + for z = box[1].z, box[2].z do + boxdata[x][y][z] = true + end + end + end + + --dout("boxdata " .. serialize(boxdata)) + end + + return res + end + + render_boxes = function(blockdata,boxes) + local dx = blockdata[1][2].x + local dy = blockdata[1][2].y + local dz = blockdata[1][2].z + + local x0 = blockdata[1][1].x + local y0 = blockdata[1][1].y + local z0 = blockdata[1][1].z + + + local nodelist = { + "wool:white","wool:red","wool:green","wool:blue","wool:yellow", "wool:cyan","wool:pink", + "wool:brown","wool:magenta","wool:orange","wool:violet" + } + local nodelen = #nodelist + + for x = 1, dx do + for y = 1, dy do + for z = 1, dz do + minetest.set_node({x = x0+x-1, y= y0+y-1+ (dy + 1), z = z0+z-1},{name = "air"}) + end + end + end + + + for i = 1,#boxes do + for x = boxes[i][1].x, boxes[i][2].x do + for y = boxes[i][1].y, boxes[i][2].y do + for z = boxes[i][1].z, boxes[i][2].z do + --minetest.set_node({x = x0+x-1, y= y0+y-1+ (dy + 4), z = z0+z-1},{name = blockdata[2][boxes[i][3] ] }) + minetest.set_node({x = x0+x-1, y= y0+y-1+ (dy + 4), z = z0+z-1},{name = nodelist[1+((i-1) % nodelen)]}) + end + end + end + end + end + + + --p1 = {x=-57,y=6,z=14};p2 = {x=-54,y=6,z=17} + p1 = {x=-45,y=502,z=-45};p2 = {x=-30,y=504,z=-30} + + read_block(p1,p2,blockdata) -- careful, all p1 coords must be smaller than p2 coords coordinatewise + --self.label(serialize(blockdata)) + + -- 7 numbers per box: pos1, pos2, nodeidx. There are n^2 numbers for nxn grid, so for boxes to be efficient + -- 7*boxcount on skyblock + init = true +end + diff --git a/scripts/programming/serialize.lua b/scripts/programming/serialize.lua new file mode 100644 index 0000000..1ad306a --- /dev/null +++ b/scripts/programming/serialize.lua @@ -0,0 +1,12 @@ +local serialize = function(tab) -- helper function + local out = {}; + for k,v in pairs(tab) do + if type(v)~= "table" then + out[#out+1] = ""..k .." = " ..v + else + out[#out+1] = ""..k .. " = " .. serialize(v) + end + end + + return "{"..table.concat(out,", ").."}" +end diff --git a/scripts/programming/turtle_with_loops.lua b/scripts/programming/turtle_with_loops.lua new file mode 100644 index 0000000..9e87e81 --- /dev/null +++ b/scripts/programming/turtle_with_loops.lua @@ -0,0 +1,46 @@ +-- simple turtlebot with loops, rnd, 30 mins + +if not init then +init = true + commands = { + ["f"] = function() move.forward() end, + ["l"] = function() move.left() end, + ["r"] = function() move.right() end, + ["u"] = function() move.up() end, + ["d"] = function() move.down() end, + [">"] = function() turn.right() end, + ["<"] = function() turn.left() end, + ["p"] = function() place.down("default:dirt") end, + ["P"] = function() place.forward_down("default:dirt") end, +} + + program = "R3[Pfu]<" + loop = {start = 1, quit = 1, count = 1}; + + step = 1 +end + +c = string.sub(program,step,step) +if c == "R" then -- loop + local i = string.find(program,"%[",step+1); + loop.count = tonumber(string.sub(program,step+1, i-1)) or 1; + loop.start = i+1; + i = string.find(program,"]",i+1); + loop.quit = i-1 + step = loop.start-1 +else -- normal command + command = commands[c]; + if command then + command() + elseif step>string.len(program) then + step = 0 + end +end + + +self.label(step) +step = step +1 +if loop.count>0 then -- are we in loop? + if step>loop.quit then loop.count = loop.count - 1; step = loop.start end + if loop.count == 0 then step = loop.quit + 2 end +end \ No newline at end of file diff --git a/scripts/shop/digital_money.lua b/scripts/shop/digital_money.lua new file mode 100644 index 0000000..be112d6 --- /dev/null +++ b/scripts/shop/digital_money.lua @@ -0,0 +1,89 @@ +-- digital money: transform items into key (stand on chest and write \buy) and later get them back (\sell code) + +--[[ + instructions: put items in chest, stand on top of it and say: \buy. this gives you code, you can later write in + '\sell code' and get stuff back. +--]] + +if not init then + self.label("digital banker bot") + password = "super secret password" + buydb = {}; + starttime = minetest.get_gametime() + + _G.minetest.forceload_block(self.pos(),true) + + import_inventory = function(pos) -- read inventory and output digital money + local meta = minetest.get_meta(pos); if not meta then return end + local inv = meta:get_inventory(); if not inv then return end + if not inv:get_size("main") then return end + if inv:is_empty("main") then return end + + local invlist = {}; + for i = 1, inv:get_size("main") do + local stack = inv:get_stack("main", i):to_string(); + local i = string.find(stack, " "); + local itemname = stack; + local count = 0; + if i then + itemname = string.sub(stack,1,i-1) + count = count + (tonumber(string.sub(stack,i+1)) or 1) + end + if count == 0 then count = 1 end + if itemname ~= "" then invlist[itemname] = (invlist[itemname] or 0) + count end + end + + local t = minetest.get_gametime() + local out = string.sub(serialize(invlist),7) + inv:set_list("main",{}) + return minetest.get_password_hash("",t .. out .. password) .. " " .. t .. " " .. out + + end + + export_inventory = function(speaker,msg) -- import digital money and give items + local i1 = string.find(msg," "); if not i1 then return end + local i2 = string.find(msg," ",i1+1); if not i2 then return end + local sig = string.sub(msg,1,i1-1); + local t = tonumber(string.sub(msg,i1+1,i2-1)) or 0; + local out = string.sub(msg,i2+1); + local sigm = minetest.get_password_hash("",t .. out .. password); + if sigm~=sig then return end + + if t< starttime then return end -- invalid time, before bot start + + if buydb[t] then -- already used, prevent double spending + return + else + buydb[t] = true; + end + + local p = minetest.get_player_by_name(speaker); + local inv = p:get_inventory(); + local invlist = deserialize("return " ..out); + for item, count in pairs(invlist) do + inv:add_item("main", item .. (count==1 and "" or " " .. count)) + end + end + + round = function(x) if x>0 then return math.floor(x+0.5) else return -math.floor(-x+0.5) end end + + init = true + self.listen(1) +end + +speaker,msg = self.listen_msg() +if msg then + if msg == "buy" then + local pos = minetest.get_player_by_name(speaker):getpos(); + pos.x = round(pos.x);pos.y = round(pos.y);pos.z = round(pos.z) + if pos.y>0 then pos.y = pos.y-1 end + local out = import_inventory(pos); + if out then + local text = "Your code is between BEGIN and END:\nBEGIN\n" .. out.."\nEND\nSave it for future use, to reclaim items say: /sell code. code is valid until next restart." + local form = "size[8,8]".. "textarea[0.,0;11.,9.5;text;digital money;".. minetest.formspec_escape(text) .. "]" + self.show_form(speaker, form) + end + elseif string.sub(msg,1,4) == "sell" then + export_inventory(speaker,string.sub(msg,6)) + end +end \ No newline at end of file diff --git a/scripts/simulators/horse_race.lua b/scripts/simulators/horse_race.lua new file mode 100644 index 0000000..a5bf3a5 --- /dev/null +++ b/scripts/simulators/horse_race.lua @@ -0,0 +1,42 @@ +if not init then init = true + horses = {10,5,5,5,5,5}; -- ratings, probability that horse wins is proportional to its rating + + + + local rndseed = 1; + local random = function(n) + rndseed = (48271*rndseed)% 2147483647; + return rndseed % n + end + + -- race simulation: output is winner, 2nd, 3rd, ... + -- algorithm sketch: first choose 1st, then from remainder select 2nd, then ... + + race = function(horses) + + rndseed = os.time() + local n = #horses; local sum = 0 + local res = {}; + for i = 1,n do sum = sum + horses[i]; res[i]=i end + + for i = 1,n do + -- select random idx from i..n as winner of i-th round + local sel = random(sum); + --find first j such that partial sums i,..,j exceed sel ( probability that horse will be selected is proportional to its rating) + local psum = 0; local j = n + for k = i,n do + psum = psum + horses[res[k]] + if psum>= sel then j = k break end + end + --dout(i..". j= " ..j .. ", sel " .. sel ) + + --swap j-th and i-th to put selected horse on i-th position + local tmp = res[j]; res[j] = res[i]; res[i] = tmp + sum = sum - horses[ tmp ] -- remove winner from sum + end + return res + end + + self.label( serialize(race(horses)) ) + +end \ No newline at end of file diff --git a/scripts/simulators/pathfinder.lua b/scripts/simulators/pathfinder.lua new file mode 100644 index 0000000..affb265 --- /dev/null +++ b/scripts/simulators/pathfinder.lua @@ -0,0 +1,25 @@ +-- pathfinder robot + +if not init then + max_jump = 1 + max_drop = 1 + searchdistance = 10 + move.forward(); move.down() + pos1 = self.pos() + pos2 = {x = -70 , y = -1, z = -123 } + path = minetest.find_path(pos1,pos2,searchdistance,max_jump,max_drop,"Dijkstra") + if not path then say("i dont know how to get there :(") self.remove() end + + say("im going to " .. pos2.x .. " " .. pos2.y .. " " .. pos2.z .. ", will be there in " .. #path .. " seconds") + -- self.label(serialize(path)) + step = 0 + obj = _G.basic_robot.data[self.name()].obj + init = true +end + +if step<#path then +step = step + 1 +pos = path[step] +obj:setpos(pos) +if step == #path then say("arrived at destination.") end +end \ No newline at end of file diff --git a/scripts/simulators/room_walker.lua b/scripts/simulators/room_walker.lua new file mode 100644 index 0000000..b545fdb --- /dev/null +++ b/scripts/simulators/room_walker.lua @@ -0,0 +1,40 @@ +--TODO: unfinished +if not init then + state = 0 + init = true + walkdb = {}; + spos = self.spawnpos(); + step = 0 + startstep = 0; -- mark the step when next walk around begins + + local get_dir = function() + local dir = self.viewdir() + end + + rot_left = function(dir) local tmp = dir.x;dir.x = -dir.z; dir.z = tmp end + + rot_right = function(dir) local tmp = dir.x;dir.x = dir.z; dir.z = -tmp end + +end + +if state == 0 then + if not move.forward() then state = 1; turn.right(); startstep = 1 end +elseif state == 1 then + step = step + 1 + local pos = self.pos(); + local x = pos.x-spos.x; local z = pos.z-spos.z; + if not walkdb[x] then walkdb[x] = {} end walkdb[x][z] = step; -- add position + local dir = self.viewdir(); + + local node = read_node.left(); + + + + rot_left(dir) -- rotate left + local xr = x + dir.x; local zr = z + dir.z + + if node == "air" and (not dir[xr] or not dir[xr][zr]) then turn.left() end + if not move.forward() and (not dir[xn] or not dir[xn][zn]) then turn.right() move.forward() end +end + +self.label(state) \ No newline at end of file diff --git a/scripts/simulators/turtle_with_loops.lua b/scripts/simulators/turtle_with_loops.lua new file mode 100644 index 0000000..9e87e81 --- /dev/null +++ b/scripts/simulators/turtle_with_loops.lua @@ -0,0 +1,46 @@ +-- simple turtlebot with loops, rnd, 30 mins + +if not init then +init = true + commands = { + ["f"] = function() move.forward() end, + ["l"] = function() move.left() end, + ["r"] = function() move.right() end, + ["u"] = function() move.up() end, + ["d"] = function() move.down() end, + [">"] = function() turn.right() end, + ["<"] = function() turn.left() end, + ["p"] = function() place.down("default:dirt") end, + ["P"] = function() place.forward_down("default:dirt") end, +} + + program = "R3[Pfu]<" + loop = {start = 1, quit = 1, count = 1}; + + step = 1 +end + +c = string.sub(program,step,step) +if c == "R" then -- loop + local i = string.find(program,"%[",step+1); + loop.count = tonumber(string.sub(program,step+1, i-1)) or 1; + loop.start = i+1; + i = string.find(program,"]",i+1); + loop.quit = i-1 + step = loop.start-1 +else -- normal command + command = commands[c]; + if command then + command() + elseif step>string.len(program) then + step = 0 + end +end + + +self.label(step) +step = step +1 +if loop.count>0 then -- are we in loop? + if step>loop.quit then loop.count = loop.count - 1; step = loop.start end + if loop.count == 0 then step = loop.quit + 2 end +end \ No newline at end of file diff --git a/scripts/utils/3d printer.lua b/scripts/utils/3d printer.lua new file mode 100644 index 0000000..704ce1a --- /dev/null +++ b/scripts/utils/3d printer.lua @@ -0,0 +1,73 @@ +-- 3d printer bot, made by rnd in 30 mins +if not init then + + nx = 5; ny = 5; nz = 5 + data = {}; -- [nx][nz][ny] + + rndseed = 1; + random = function(n) + rndseed = (48271*rndseed)% 2147483647; + return rndseed % n + end + + for i = 1,nx do + data[i]={} + for j = 1,nz do + data[i][j] = {} + for k = 1,ny do + if random(2) == 1 then data[i][j][k] = true end -- make random structure + end + end + end + + local spos = self.spawnpos(); spos.y = spos.y+1; spos.z = spos.z+1 + state = 1; -- walk in x way, state = 1: build up + x=1; z = 1; y = 1; angle = -90 + zc = 1; worky = 1; + + get_worky = function() + worky = 0 + local dataxz = data[x][zc] + if not dataxz then return end + for k = ny,1,-1 do if dataxz[k] then worky = k break end end + end + get_worky() + + move.forward(); move.right() + init = true +end + + + +if state == 0 then -- walk around + if z>=nz then + x = x+1; z=0; + turn.angle(angle); move.forward(); turn.angle(angle) + angle = -angle; + if x>nx then self.remove() end + else + move.forward() + end + z=z+1 + if angle<0 then zc = z else zc = nz-z+1 end + get_worky() -- is there anything to print at x,z? + if worky>0 then state = 1 y = 1 end + + self.label("walking at " .. x .. " " .. zc .. ", worky = " .. worky) +elseif state == 1 then -- make coloumn + + if y>=worky+1 then + state = 2 -- go down ladder + else + place.down("default:ladder") + y=y+1; move.up() + end + self.label("going up " .. x .. " " .. zc .. ", y = " .. (y-1)) +elseif state == 2 then -- go down and build + dig.down();move.down(); + + if data[x][zc][y-1] then place.up("default:dirt") end + y=y-1 + self.label("going down at " .. x .. " " .. zc .. ", y = " .. (y-1)) + if y<2 then state = 0; y = 1 end +end \ No newline at end of file diff --git a/sounds/Dtmf0.ogg b/sounds/Dtmf0.ogg index ba54d47937c52a7f78c7250bc41f327d23ecb486..329e6a55f8900859151a74d43d682d3c15ebd858 100644 GIT binary patch delta 1796 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 m)&+v& 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 Z=%@xpRedT)f9-oErkhb(p#8t){{S(UEAIdR literal 0 HcmV?d00001