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 ba54d47..329e6a5 100644 Binary files a/sounds/Dtmf0.ogg and b/sounds/Dtmf0.ogg differ diff --git a/sounds/piano.ogg b/sounds/piano.ogg new file mode 100644 index 0000000..331fc1c Binary files /dev/null and b/sounds/piano.ogg differ