basic_robot.commands = {}; -- set up nodes for planting (for example seeds -> plant) : [nodename] = plant_name basic_robot.plant_table = {["farming:seed_barley"]="farming:barley_1",["farming:beans"]="farming:beanpole_1", -- so it works with farming redo mod ["farming:blueberries"]="farming:blueberry_1",["farming:carrot"]="farming:carrot_1",["farming:cocoa_beans"]="farming:cocoa_1", ["farming:coffee_beans"]="farming:coffee_1",["farming:corn"]="farming:corn_1", ["farming:seed_cotton"]="farming:cotton_1",["farming:cucumber"]="farming:cucumber_1",["farming:grapes"]="farming:grapes_1", ["farming:melon_slice"]="farming:melon_1",["farming:potato"]="farming:potato_1",["farming:pumpkin_slice"]="farming:pumpkin_1", ["farming:raspberries"]="farming:raspberry_1",["farming:rhubarb"]="farming:rhubarb_1",["farming:tomato"]="farming:tomato_1", ["farming:seed_wheat"]="farming:wheat_1"} local function tick(pos) -- needed for plants to start growing: minetest 0.4.14 farming minetest.get_node_timer(pos):start(math.random(166, 286)) end local pi = math.pi; local function pos_in_dir(obj, dir) -- position after we move in specified direction local yaw = obj:getyaw(); local pos = obj:getpos(); if dir == 1 then -- left yaw = yaw + pi/2; elseif dir == 2 then --right yaw = yaw - pi/2; elseif dir == 3 then -- forward elseif dir == 4 then -- backward yaw = yaw+pi; elseif dir == 5 then -- up pos.y=pos.y+1 elseif dir == 6 then -- down pos.y=pos.y-1 elseif dir == 7 then -- left_down yaw = yaw + pi/2;pos.y=pos.y-1 elseif dir == 8 then -- right_down yaw = yaw - pi/2;pos.y=pos.y-1 elseif dir == 9 then -- forward_down pos.y=pos.y-1 elseif dir == 10 then -- backward_down yaw = yaw + pi;pos.y=pos.y-1 elseif dir == 11 then -- left_up yaw = yaw + pi/2;pos.y=pos.y+1 elseif dir == 12 then -- right_up yaw = yaw - pi/2;pos.y=pos.y+1 elseif dir == 13 then -- forward_up pos.y=pos.y+1 elseif dir == 14 then -- backward_up yaw = yaw + pi;pos.y=pos.y+1 end if dir ~= 5 and dir ~= 6 then pos.x = pos.x - math.sin(yaw) -- math.cos(yaw+pi/2) pos.z = pos.z + math.cos(yaw) -- math.sin(yaw+pi/2) end return pos end local check_operations = function(name, amount, quit) if basic_robot.maxoperations~=0 then local data = basic_robot.data[name]; local operations = data.operations-amount; if operations >= 0 then data.operations = operations else if quit then error("Robot out of available operations in one step (1s). View available operations with self.operations() and check help to see how much operations each action requires."); return false end return false end end end basic_robot.commands.move = function(name,dir) check_operations(name,2,true) local obj = basic_robot.data[name].obj; local pos = pos_in_dir(obj, dir) -- can move through walkable nodes if minetest.registered_nodes[minetest.get_node(pos).name].walkable then return end -- up; no levitation! if minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name == "air" and minetest.get_node({x=pos.x,y=pos.y-2,z=pos.z}).name == "air" then return false end obj:moveto(pos, true) -- sit and stand up for model - doesnt work for overwriten obj export -- if dir == 5 then-- up -- obj:set_animation({x=0,y=0}) -- elseif dir == 6 then -- down -- obj:set_animation({x=81,y=160}) -- end return true end basic_robot.commands.turn = function (name, angle) local obj = basic_robot.data[name].obj; local yaw; -- more precise turns by 1 degree resolution local mult = math.pi/180; local yaw = obj:getyaw(); yaw = math.floor((yaw+angle)/mult+0.5)*mult; obj:setyaw(yaw); end basic_robot.digcosts = { -- 1 energy = 1 coal ["default:stone"] = 1/25, ["default:cloud"] = 10^8, } basic_robot.commands.dig = function(name,dir) local energy = 0; check_operations(name,6,true) local obj = basic_robot.data[name].obj; local pos = pos_in_dir(obj, dir) local luaent = obj:get_luaentity(); if minetest.is_protected(pos,luaent.owner) then return false end local nodename = minetest.get_node(pos).name; if nodename == "air" or nodename=="ignore" then return false end local spos = obj:get_luaentity().spawnpos; local inv = minetest.get_meta(spos):get_inventory(); --require energy to dig if basic_robot.dig_require_energy then local digcost = basic_robot.digcosts[nodename]; 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."); end data.menergy = energy; end end if not inv then return end --inv:add_item("main",ItemStack( nodename )); basic_robot.give_drops(nodename, inv); minetest.set_node(pos,{name = "air"}) --DS: sounds local sounds = minetest.registered_nodes[nodename].sounds if sounds then local sound = sounds.dug if sound then minetest.sound_play(sound,{pos=pos, max_hear_distance = 10}) end end return true end basic_robot.commands.insert_item = function(name,item, inventory,dir) check_operations(name,2,true) local obj = basic_robot.data[name].obj; local tpos = pos_in_dir(obj, dir); -- position of target block local luaent = obj:get_luaentity(); if minetest.is_protected(tpos,luaent.owner ) then return false end local pos = basic_robot.data[name].spawnpos; -- position of spawner block local meta = minetest.get_meta(pos); local tmeta = minetest.get_meta(tpos); 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 --if not inv then return end local stack = ItemStack(item); if (not inv:contains_item("main", stack) or not tinv:room_for_item(inventory, stack)) and meta:get_int("admin")~=1 then return false end tinv:add_item(inventory,stack); inv:remove_item("main", stack); return true end basic_robot.commands.take_item = function(name,item, inventory,dir) check_operations(name,2,true) local obj = basic_robot.data[name].obj; local tpos = pos_in_dir(obj, dir); -- position of target block local luaent = obj:get_luaentity(); if minetest.is_protected(tpos,luaent.owner ) then return false end local pos = basic_robot.data[name].spawnpos; -- position of spawner block for i, bad_inventory in pairs(basic_robot.bad_inventory_blocks) do -- dont allow take from if minetest.get_node(tpos).name:find("^" .. bad_inventory) ~= nil then return false end end local meta = minetest.get_meta(pos); local tmeta = minetest.get_meta(tpos); local inv = minetest.get_meta(pos):get_inventory(); local tinv = minetest.get_meta(tpos):get_inventory(); if not inventory then inventory = "main"; end --if not inv then return end local stack = ItemStack(item); local contains = tinv:contains_item(inventory, stack); if (not contains or not inv:room_for_item("main", stack)) and meta:get_int("admin")~=1 then return false end inv:add_item("main",stack); tinv:remove_item(inventory, stack); return true end -- check_inventory(item, inventory, position) --if position>0 then it returns name of item at that position basic_robot.commands.check_inventory = function(name,itemname, inventory, position, dir) local obj = basic_robot.data[name].obj; local tpos; if dir~=0 then tpos = pos_in_dir(obj, dir); -- position of target block in front else tpos = obj:get_luaentity().spawnpos; end local tinv = minetest.get_meta(tpos):get_inventory(); if not position then position = -1 end if position>0 then return tinv:get_stack(inventory, position):to_string() end if itemname == "" then return tinv:is_empty(inventory) end local stack = ItemStack(itemname); if not inventory then inventory = "main"; end return tinv:contains_item(inventory, stack); end basic_robot.no_teleport_table = { ["itemframes:item"] = true, ["signs:text"] = true, ["basic_robot:robot"] = true, ["robot"] = true, } basic_robot.commands.pickup = function(r,name) if r>8 then return false end check_operations(name,4,true) local pos = basic_robot.data[name].obj:getpos(); local spos = basic_robot.data[name].spawnpos; -- position of spawner block local meta = minetest.get_meta(spos); local inv = minetest.get_meta(spos):get_inventory(); local picklist = {}; for _,obj in pairs(minetest.get_objects_inside_radius({x=pos.x,y=pos.y,z=pos.z}, r)) do local lua_entity = obj:get_luaentity() if not obj:is_player() and lua_entity and lua_entity.itemstring then local detected_obj = lua_entity.itemstring or "" if not basic_robot.no_teleport_table[detected_obj] then -- object on no teleport list -- put item in chest local stack = ItemStack(lua_entity.itemstring) picklist[#picklist+1]=detected_obj; if inv:room_for_item("main", stack) then inv:add_item("main", stack); obj:setpos({x=0,y=0,z=0}) -- no dupe end obj:remove(); end end end if not picklist[1] then return nil end return picklist end basic_robot.commands.read_node = function(name,dir) local obj = basic_robot.data[name].obj; local pos = pos_in_dir(obj, dir) return minetest.get_node(pos).name or "" end basic_robot.commands.read_text = function(name,mode,dir,stringname) if not mode then mode = 0 end local obj = basic_robot.data[name].obj; local pos = pos_in_dir(obj, dir) if stringname == nil then stringname = "infotext" end if mode == 1 then return minetest.get_meta(pos):get_int(stringname) else return minetest.get_meta(pos):get_string(stringname) or "" end end basic_robot.commands.write_text = function(name,dir,text) local obj = basic_robot.data[name].obj; local pos = pos_in_dir(obj, dir) local luaent = obj:get_luaentity(); if minetest.is_protected(pos,luaent.owner ) then return false end minetest.get_meta(pos):set_string("infotext",text or "") return true end basic_robot.commands.place = function(name,nodename, param2,dir) check_operations(name,2,true) local obj = basic_robot.data[name].obj; local pos = pos_in_dir(obj, dir) local luaent = obj:get_luaentity(); if minetest.is_protected(pos,luaent.owner ) then return false end if minetest.get_node(pos).name~="air" then return false end local spos = obj:get_luaentity().spawnpos; local meta = minetest.get_meta(spos); local inv = meta:get_inventory(); if not inv then return false end if not inv:contains_item("main", ItemStack(nodename)) and meta:get_int("admin")~=1 then return false end inv:remove_item("main", ItemStack(nodename)); --DS local registered_node = minetest.registered_nodes[nodename]; if registered_node then local sounds = registered_node.sounds if sounds then local sound = sounds.place if sound then minetest.sound_play(sound,{pos=pos, max_hear_distance = 10}) end end end local placename = basic_robot.plant_table[nodename]; if placename then minetest.set_node(pos,{name = placename}) tick(pos); -- needed for seeds to grow else -- normal place if param2 then minetest.set_node(pos,{name = nodename, param2 = param2}) else minetest.set_node(pos,{name = nodename}) end end return true end basic_robot.commands.attack = function(name, target) -- attack range 4, damage 5 local energy = 0; check_operations(name,4,true); local reach = 4; local damage = 5; local tplayer = minetest.get_player_by_name(target); if not tplayer then return false end local obj = basic_robot.data[name].obj; local pos = obj:getpos(); local tpos = tplayer:getpos(); if math.abs(pos.x-tpos.x)> reach or math.abs(pos.y-tpos.y)> reach or math.abs(pos.z-tpos.z)> reach then return false end tplayer:set_hp(tplayer:get_hp()-damage) return true end basic_robot.commands.grab = function(name,target) local reach = 4; local tplayer = minetest.get_player_by_name(target); if not tplayer then return false end local obj = basic_robot.data[name].obj; local pos = obj:getpos(); local tpos = tplayer:getpos(); if math.abs(pos.x-tpos.x)> reach or math.abs(pos.y-tpos.y)> reach or math.abs(pos.z-tpos.z)> reach then return false end if tplayer:get_attach() then tplayer:set_detach() return false else tplayer:set_attach(obj, "", {x=0,y=5,z=0}, {x=0,y=0,z=0}) end return true end --local minetest_version = minetest.get_version().string; basic_robot.commands.read_book = function (itemstack) -- itemstack should contain book local data; --if minetest_version == "0.4.16" then data = itemstack:get_meta():to_table().fields -- 0.4.16 if data and data.text then data.text = data.text:gsub(string.char(13),string.char(10)) --for unknown reason books sometime? convert \n (10) to CR (13) end -- else -- local data = minetest.deserialize(itemstack:get_metadata()) -- pre 0.4.16 -- end if data then return data.title,data.text; else return nil end end basic_robot.commands.write_book = function(name,title,text) -- returns itemstack containing book local lpp = 14; local new_stack = ItemStack("default:book_written") local data = {} if title == "" or not title then title = "program book "..minetest.get_gametime() end data.title = title or "" data.text = text or "" data.text_len = #data.text data.page = 1 data.description = title or "" data.page_max = math.ceil((#data.text:gsub("[^\n]", "") + 1) / lpp) data.owner = name --local data_str = minetest.serialize(data) -- pre 0.4.16 --new_stack:set_metadata(data_str); new_stack:get_meta():from_table({fields = data}) -- 0.4.16 return new_stack; end basic_robot.give_drops = function(nodename, inv) -- gives apropriate drops when node is dug local table = minetest.registered_items[nodename]; local dropname; if table~=nil then --put in chest if table.drop~= nil then -- drop handling if table.drop.items then --handle drops better, emulation of drop code local max_items = table.drop.max_items or 0; if max_items==0 then -- just drop all the items (taking the rarity into consideration) max_items = #table.drop.items or 0; end max_items = math.random(max_items) -- return random number of items local drop = table.drop; local i = 0; for k,v in pairs(drop.items) do if i > max_items then break end; i=i+1; local rare = v.rarity or 1; if rare>0 and math.random(1, rare)==1 then dropname = v.items[math.random(1,#v.items)]; -- pick item randomly from list inv:add_item("main",dropname); end end else inv:add_item("main",table.drop); end else inv:add_item("main",nodename); end end end local render_text = function(text,linesize) local count = math.floor(string.len(text)/linesize)+1; local width = 18; local height = 24; local tex = ""; local ret = {}; local y = 0; local x=0; for i=1,string.len(text) do local cb = string.byte(text,i); local c = ""; if cb == 10 or cb == 13 then y=y+1; x=0 else c = string.format("%03d",cb)..".png" ret[#ret+1] = ":" .. (x*width) .. "," .. (y*height) .. "=" .. c; --tex = tex .. ":" .. (x*width) .. "," .. (y*height) .. "=" .. c; if x==linesize-1 then y=y+1 x=0 else x=x+1 end end end local background = "(black_screen.png^[resize:"..(linesize*width).. "x".. (linesize*height) ..")"; --tex = "([combine:"..(linesize*width).."x"..(linesize*height)..tex..")"; return background .. "^" .."([combine:"..(linesize*width).."x"..(linesize*height)..table.concat(ret,"")..")"; end basic_robot.commands.display_text = function(obj,text,linesize,size) if not linesize or linesize<1 then linesize = 20 elseif linesize>40 then linesize = 40 end if size and size<=0 then size = 1 end if string.len(text)>linesize*linesize then text = string.sub(text,1,linesize*linesize) end local tex = render_text(text,linesize); if not size then return tex end if string.len(tex)<=1600 then obj:set_properties({ textures = {"topface.png","legs.png",tex,"face-back.png","left-hand.png","right-hand.png"}, --textures={"arrow.png","basic_machine_side.png",tex,"basic_machine_side.png","basic_machine_side.png","basic_machine_side.png"}, visual_size = {x=size,y=size}}) else self.label("error: string too long") end end local robot_activate_furnace = minetest.registered_nodes["default:furnace"].on_metadata_inventory_put; -- this function will activate furnace basic_robot.commands.activate = function(name,mode, dir) check_operations(name,2,true); local obj = basic_robot.data[name].obj; local tpos = pos_in_dir(obj, dir); -- position of target block in front local node = minetest.get_node(tpos); if node.name == "default:furnace" or node.name == "default:furnace_active" then if mode>0 then robot_activate_furnace(tpos) end return true end local table = minetest.registered_nodes[node.name]; if table and table.mesecons and table.mesecons.effector then else return false end -- error local effector=table.mesecons.effector; if not mode then mode = 1 end if mode > 0 then if not effector.action_on then return false end effector.action_on(tpos,node,16) elseif mode<0 then if not effector.action_off then return false end effector.action_off(tpos,node,16) end return true end local register_robot_button = function(R,G,B,type) minetest.register_node("basic_robot:button"..R..G..B, { description = "robot button", tiles = {"robot_button.png^[colorize:#"..R..G..B..":180"}, inventory_image = "robot_button.png^[colorize:#"..R..G..B..":180", wield_image = "robot_button.png^[colorize:#"..R..G..B..":180", is_ground_content = false, groups = {cracky=3,not_in_craft_guide=1}, on_punch = function(pos, node, player) local name = player:get_player_name(); if name==nil then return end local round = math.floor; local r = basic_robot.radius; local ry = 2*r; -- note: this is skyblock adjusted local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; -- just on top of basic_protect:protector! local meta = minetest.get_meta(ppos); local name = meta:get_string("name"); local data = basic_robot.data[name]; if data then data.keyboard = {x=pos.x,y=pos.y,z=pos.z, puncher = player:get_player_name(), type = type} end end }) end local register_robot_button_number = function(number,type) minetest.register_node("basic_robot:button"..number, { description = "robot button", tiles = {"robot_button".. number .. ".png"}, inventory_image = "robot_button".. number .. ".png", wield_image = "robot_button".. number .. ".png", is_ground_content = false, groups = {cracky=3,not_in_craft_guide=1}, on_punch = function(pos, node, player) local name = player:get_player_name(); if name==nil then return end local round = math.floor; local r = basic_robot.radius; local ry = 2*r; local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; local meta = minetest.get_meta(ppos); local name = meta:get_string("name"); local data = basic_robot.data[name]; if data then data.keyboard = {x=pos.x,y=pos.y,z=pos.z, puncher = player:get_player_name(), type = type} end end }) end local register_robot_button_char = function(number,type) minetest.register_node("basic_robot:button_"..number, { description = "robot button", tiles = {string.format("%03d",number).. ".png"}, inventory_image = string.format("%03d",number).. ".png", wield_image = string.format("%03d",number).. ".png", is_ground_content = false, groups = {cracky=3,not_in_craft_guide=1}, on_punch = function(pos, node, player) local name = player:get_player_name(); if name==nil then return end local round = math.floor; local r = basic_robot.radius; local ry = 2*r; local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; local meta = minetest.get_meta(ppos); local name = meta:get_string("name"); local data = basic_robot.data[name]; if data then data.keyboard = {x=pos.x,y=pos.y,z=pos.z, puncher = player:get_player_name(), type = type} end end }) end local register_robot_button_custom = function(number,texture) minetest.register_node("basic_robot:button_"..number, { description = "robot button", tiles = {texture .. ".png"}, inventory_image = texture .. ".png", wield_image = texture .. ".png", is_ground_content = false, groups = {cracky=3,not_in_craft_guide=1}, on_punch = function(pos, node, player) local name = player:get_player_name(); if name==nil then return end local round = math.floor; local r = basic_robot.radius; local ry = 2*r; local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; local meta = minetest.get_meta(ppos); local name = meta:get_string("name"); local data = basic_robot.data[name]; if data then data.keyboard = {x=pos.x,y=pos.y,z=pos.z, puncher = player:get_player_name(), type = number} end end }) end register_robot_button("FF","FF","FF",1); register_robot_button("80","80","80",2); register_robot_button("FF","80","80",3); register_robot_button("80","FF","80",4); register_robot_button("80","80","FF",5); register_robot_button("FF","FF","80",6); for i = 0,9 do register_robot_button_number(i,i+7) end for i = 0,255 do register_robot_button_char(i,i+17) end register_robot_button_custom(273,"puzzle_switch_off") register_robot_button_custom(274,"puzzle_switch_on") register_robot_button_custom(275,"puzzle_button_off") register_robot_button_custom(276,"puzzle_button_on") register_robot_button_custom(277,"puzzle_equalizer") register_robot_button_custom(278,"puzzle_setter") register_robot_button_custom(279,"puzzle_piston") register_robot_button_custom(280,"puzzle_diode") register_robot_button_custom(281,"puzzle_NOT") register_robot_button_custom(282,"puzzle_delayer") register_robot_button_custom(283,"puzzle_platform") register_robot_button_custom(284,"puzzle_giver") register_robot_button_custom(285,"puzzle_checker") -- interactive button for robot: place robot on top of protector to intercept events basic_robot.commands.keyboard = { get = function(name) local data = basic_robot.data[name]; if data.keyboard then local keyboard = data.keyboard; local event = {x=keyboard.x,y=keyboard.y,z=keyboard.z, puncher = keyboard.puncher, type = keyboard.type}; data.keyboard = nil; return event else return nil end end, set = function(data,pos,type) local owner = data.owner; local spos = data.spawnpos; local dist = math.max(math.abs(spos.x-pos.x),math.abs(spos.y-pos.y),math.abs(spos.z-pos.z)); if dist>10 then return false end if minetest.is_protected(pos,owner) then return false end -- with fast protect checks this shouldnt be problem! local nodename; if type == 0 then nodename = "air" elseif type == 1 then nodename = "basic_robot:buttonFFFFFF"; elseif type == 2 then nodename = "basic_robot:button808080"; elseif type == 3 then nodename = "basic_robot:buttonFF8080"; elseif type == 4 then nodename = "basic_robot:button80FF80"; elseif type == 5 then nodename = "basic_robot:button8080FF"; elseif type == 6 then nodename = "basic_robot:buttonFFFF80"; elseif type>=7 and type <= 16 then nodename = "basic_robot:button"..(type-7); else nodename = "basic_robot:button_"..(type-17); end minetest.swap_node(pos, {name = nodename}) return true end, } basic_robot.commands.craftcache = {}; basic_robot.commands.craft = function(item, mode, idx,amount, name) amount = amount and tonumber(amount) or 1; if amount<0 then amount = 1 end if not item then return false end local cache = basic_robot.commands.craftcache[name]; if not cache then basic_robot.commands.craftcache[name] = {}; cache = basic_robot.commands.craftcache[name] end local itemlist = {}; local output = ""; if cache.item == item and cache.idx == idx then -- read cache itemlist = cache.itemlist; output = cache.output; else local craft; if not idx then craft = minetest.get_craft_recipe(item); else craft = minetest.get_all_craft_recipes(item)[idx] end if craft and craft.type == "normal" and craft.items then else return false end output = craft.output; local items = craft.items; for _,item in pairs(items) do itemlist[item]=(itemlist[item] or 0)+1; end cache.item = item; cache.idx = idx; cache.itemlist = itemlist; cache.output = output; -- loop through robot inventory for those "group" items and see if anything in inventory matches group - then replace -- group name with that item local pos = basic_robot.data[name].spawnpos; -- position of spawner block local inv = minetest.get_meta(pos):get_inventory(); for item,v in pairs(itemlist) do local k = string.find(item,"group:"); if k then local group = string.sub(item,k+6); -- do we have that in inventory? local size = inv:get_size("main"); for i=1,size do local itemname = inv:get_stack("main", i):get_name(); local groups = minetest.registered_items[itemname].groups or {}; if groups[group] then cache.itemlist[item] = nil; cache.itemlist[itemname] = v break end end end end end --minetest.chat_send_all(item) --minetest.chat_send_all(dump(itemlist)) if mode == 1 then return itemlist end -- check if all items from itemlist.. -- craft item local pos = basic_robot.data[name].spawnpos; -- position of spawner block local inv = minetest.get_meta(pos):get_inventory(); for item,quantity in pairs(itemlist) do local stack = ItemStack(item .. " " .. quantity*amount); if not inv:contains_item("main",stack) then return false end end for item,quantity in pairs(itemlist) do local stack = ItemStack(item .. " " .. quantity*amount); inv:remove_item("main",stack); end inv:add_item("main",ItemStack(output)) return true end --FORMS basic_robot.commands.show_form = function(name, playername, form) minetest.show_formspec(playername, "robot_form".. name, form) end -- handle robots receiving fields minetest.register_on_player_receive_fields(function(player, formname, fields) if not string.sub(formname,1,10) == "robot_form" then return end local name = string.sub(formname,11); -- robot name if not basic_robot.data[name] then return end basic_robot.data[name].read_form = fields; basic_robot.data[name].form_sender = player:get_player_name() or ""; end) -- ROBOT TECHNIC -- amount parameter in generate_power, smelt,... is determined by upgrade level -- it specifies how much energy will be generated : basic_robot.technic = { -- data cache fuels = {}, --[fuel] = value smelts = {}, -- [item] = [cooktime, cookeditem, aftercookeditem] grinder_recipes = { --[in] ={fuel cost, out, quantity of material required for processing} ["default:stone"] = {2,"default:sand",1}, ["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:ice"] = {1, "default:snow", 1}, ["moreores:tin_lump"] = {4,"basic_machines:tin_dust_33 2",1}, ["default:obsidian_shard"] = {199, "default:lava_source",1}, ["default:mese_crystal"] = {8, "basic_machines:mese_dust_33 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:mithril_lump"] = {16, "basic_machines:mithril_dust_33 2",1}, ["default:steel_ingot"] = {4, "basic_machines:iron_dust_33 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}, }, compressor_recipes = { --[in] ={fuel cost, out, quantity of material required for processing} ["default:snow"] = {1,"default:ice"}, ["default:coalblock"] = {41,"default:diamond"}, -- to smelt diamond dust to diamond need 25 coal + 16 for grinder }, } local chk_machine_level = function(inv,level) -- does machine have upgrade to be classified with at least "level" if level < 1 then level = 1 end local upg = {"default:diamondblock","default:mese","default:goldblock"}; for i = 1,#upg do if not inv:contains_item("main",ItemStack(upg[i].. " " .. level)) then return false end end return true end basic_robot.commands.machine = { -- convert fuel into energy generate_power = function(name,input, amount) -- fuel used, if no fuel then amount specifies how much energy builtin generator should produce check_operations(name,6, true) if amount and amount>0 then -- attempt to generate power from builtin generator local pos = basic_robot.data[name].spawnpos; -- position of spawner block local inv = minetest.get_meta(pos):get_inventory(); local level = amount*40; -- to generate 1 unit ( coal lump per second ) we need at least upgrade 40 if not chk_machine_level(inv, level) then error("generate_power : tried to generate " .. amount .. " energy requires upgrade level at least " .. level .. " (blocks of mese, diamond, gold )") return end local data = basic_robot.data[name]; local energy = (data.menergy or 0)+amount; data.menergy = energy; return energy; end local energy = 0; -- can only do one step at a run time if string.find(input," ") then return nil, "1: can convert only one item at once" end local pos = basic_robot.data[name].spawnpos; -- position of spawner block local inv = minetest.get_meta(pos):get_inventory(); local stack = ItemStack(input); if not inv:contains_item("main",stack) then return nil,"2: no input material" end -- read energy value of input ( coal lump = 1) local add_energy = basic_robot.technic.fuels[input]; if not add_energy then -- lookup fuel value local fueladd, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = {stack}}) if fueladd.time > 0 then add_energy = fueladd.time/40; -- fix by kurik else return nil, "3: material can not be used as a fuel" end if add_energy>0 then basic_robot.technic.fuels[input] = add_energy end end inv:remove_item("main", stack); --add energy local data = basic_robot.data[name]; energy = data.menergy or 0; energy = energy+ add_energy;data.menergy = energy return energy; end, -- smelting smelt = function(name,input,amount) -- input material, amount of energy used for smelt local energy = 0; -- can only do one step at a run time check_operations(name,6,true) if string.find(input," ") then return nil, "0: only one item per smelt" end local pos = basic_robot.data[name].spawnpos; -- position of spawner block local meta = minetest.get_meta(pos); local inv = minetest.get_meta(pos):get_inventory(); --read robot energy local cost = 1/40; local smelttimeboost = 1; local level = 1; if amount and amount>0 then level = amount*10; -- 10 level required for 1 of amount if not chk_machine_level(inv,level) then error("3 smelting: need at least level " .. level .. " upgrade for required power " .. amount); return end cost = cost*(1+amount); smelttimeboost = smelttimeboost + amount; -- double speed with amount 1 end local data = basic_robot.data[name] energy = data.menergy or 0; -- machine energy if energy0 then basic_robot.technic.smelts[input] = {cooked.time, cooked.item, aftercooked.items[1]}; smelts = basic_robot.technic.smelts[input]; else return nil, "3: material can not be smelted" end end local cooktime = smelts[1]; local cookeditem = smelts[2]; local aftercookeditem = smelts[3] -- is smelting done? data.menergy = energy-cost; if src_time >= cooktime then inv:remove_item("main",stack); inv:add_item("main", ItemStack(aftercookeditem)); inv:add_item("main", ItemStack(cookeditem)); data.src_time = 0 return true else data.src_time = src_time return math.floor(src_time/cooktime*100*100)/100 end end, -- grind grind = function(name,input) --[in] ={fuel cost, out, quantity of material required for processing} check_operations(name,6,true) local recipe = basic_robot.technic.grinder_recipes[input]; if not recipe then return nil, "unknown recipe" end local cost = recipe[1]; local output = recipe[2]; local pos = basic_robot.data[name].spawnpos; -- position of spawner block local meta = minetest.get_meta(pos); local inv = minetest.get_meta(pos):get_inventory(); --level requirement local level = math.floor((cost-1)/3); if not chk_machine_level(inv,level) then error("0: tried to grind " .. input .. " requires upgrade level at least " .. level) return end local stack = ItemStack(input); if not inv:contains_item("main",stack) then return nil, "1: missing input material" end local data = basic_robot.data[name]; local energy = data.menergy or 0; if energyenergy then return nil,"energy too low" end if not tdata.menergy then tdata.menergy = 0 end tdata.menergy = tdata.menergy + amount data.menergy = energy - amount; return true end, } -- CRPYTOGRAPHY -- rnd 2017 -- nonlinear block stream cypher encryption with scrambling local scramble = function(input,password,sgn) -- permutes text randomly, nice after touch to stream cypher to prevent block analysis _G.math.randomseed(password); local n = #input; local permute = {} for i = 1, n do permute[i] = i end --input:sub(i, i) for i = n,2,-1 do local j = math.random(i-1); local tmp = permute[j]; permute[j] = permute[i]; permute[i] = tmp; end local out = {}; if sgn>0 then -- unscramble for i = 1,n do out[permute[i]] = string.sub(input,i,i) end else -- scramble for i = 1,n do out[i] = string.sub(input,permute[i],permute[i]) end end return table.concat(out,"") end local scramble_test = function() local text = "testing scrambling 1 2 3"; local enc = scramble(text,10,1); -- scramble local dec = scramble(enc,10,-1); -- descramble say("SCRAMBLED--> ".. enc .. " DESCRAMBLED--> ".. dec) end --scramble_test() local get_hash = function(s,p) -- basic modular hash, first convert string into 4*8=32 bit int if not s then return end local h = 0; local n = string.len(s);local m = 4; -- put 4 characters together local r = 0;local i = 0; while i50 then block_offset = block_offset*math.random(n*(1+block_offset)) end -- extra fun, why not end offset = (offset + block_offset)%n; local c = string.byte(input,i)-m; c = m+((c+offset*sgn) % n); ret = ret .. string.char(c) end return ret end local ascii2hex = function(input) -- compress 256 char set to 16 charset range local ret = "" for i = 1, string.len(input) do local c = string.byte(input,i); -- c = c2*16+c1 local c1 = c % 16; local c2 = (c-c1)/16; ret = ret .. (string.char(c1+65)..string.char(c2+65)) end return ret; end local hex2ascii = function(input) local ret = "" if string.len(input)%2 == 1 then input = input .. "A" end -- padding for i = 1, string.len(input),2 do local c1 = string.byte(input,i)-65; -- c = c2*16+c1 local c2 = string.byte(input,i+1)-65; ret = ret .. string.char(c2*16+c1) end return ret; end -- scheme: encrypt: (stream cypher) encrypt+scramble, decrypt: unscramble+decrypt local encrypt = function(input, password) local ret = encrypt_(ascii2hex(input),password,1); local scrambleseed = get_hash(_G.minetest.get_password_hash("",password),10^30); -- strong hash from password, 10^30 possible permutes return scramble(ret, scrambleseed, 1); end local decrypt = function(input, password) local scrambleseed = get_hash(_G.minetest.get_password_hash("",password),10^30); input = scramble(input, scrambleseed, -1); -- descramble return hex2ascii(encrypt_(input,password,-1)) end -- just test local encrypt_test = function() local text = "testing encryption 1 2 3"; local password = "hello encryptions"; local enc = encrypt(text, password);local dec = decrypt(enc, password); say("INPUT: " .. text .. " ENC: " .. enc .. " DEC: " .. dec) end basic_robot.commands.crypto = {encrypt = encrypt, decrypt = decrypt, scramble = scramble, basic_hash = get_hash} -- PUZZLE GAMEPLAY - need puzzle privs local is_same_block = function(pos1,pos2) local round = math.floor; local r = basic_robot.radius; local ry = 2*r; -- note: this is skyblock adjusted local ppos1 = {round(pos1.x/r+0.5)*r,round(pos1.y/ry+0.5)*ry,round(pos1.z/r+0.5)*r}; local ppos2 = {round(pos2.x/r+0.5)*r,round(pos2.y/ry+0.5)*ry,round(pos2.z/r+0.5)*r}; return ppos1[1]==ppos2[1] and ppos1[2]==ppos2[2] and ppos1[3] == ppos2[3] end local cmd_set_node = function(data,pos,node) if minetest.is_protected(pos,data.owner) then return end local spos = data.spawnpos; if not is_same_block(pos,spos) then return end -- only allow to edit same block as spawner is in minetest.swap_node(pos,node) end local cmd_get_node_inv = function(data,pos) local spos = data.spawnpos; if minetest.is_protected(pos,data.owner) then return end if not is_same_block(pos,spos) then return end return minetest.get_meta(pos):get_inventory() end local cmd_get_player = function(data,pname) -- return player for further manipulation local player = minetest.get_player_by_name(pname) if not player then error("player does not exist"); return end local spos = data.spawnpos; local ppos = player:getpos(); if not is_same_block(ppos,spos) then error("can not get player in another protection zone") return end return player end local cmd_get_player_inv = function(data,pname) local player = minetest.get_player_by_name(pname) if not player then return end local spos = data.spawnpos; local ppos = player:getpos(); if not is_same_block(ppos,spos) then error("can not get player in another protection zone") return end return player:get_inventory(); end -- spatial triggers with hashing local trigger_range = 5 -- how close player must be to "activate" - also size/2 of cells local round = math.floor local cmd_get_pos_id = function(data,pos, no_neighbors) -- return 4 nearby block ids local r = trigger_range*2; local range = 1000*2; -- coordinates from -1000 to +1000 allowed local n = range/r; local x1 = round(pos.x/r+0.5) local z1 = round(pos.z/r+0.5) local y1 = round(pos.y/r+0.5) local baseid = x1 + z1*n + y1*n^2 -- hash value if no_neighbors then return baseid end --check 4 nearby closest squares: 2D local x0 = round(pos.x/r); local z0 = round(pos.z/r); if x00 then robot_activate_furnace(tpos) end return true end local table = minetest.registered_nodes[node.name]; if table and table.mesecons and table.mesecons.effector then else return false end -- error local effector=table.mesecons.effector; if not mode then mode = 1 end if mode > 0 then if not effector.action_on then return false end effector.action_on(tpos,node,16) elseif mode<0 then if not effector.action_off then return false end effector.action_off(tpos,node,16) end return true end, } -- VIRTUAL PLAYER -- local Vplayer = {}; function Vplayer:new(name) -- constructor if not basic_robot.data[name].obj then return end -- only make it for existing robot if basic_robot.virtual_players[name] then return end -- already exists local o = {} setmetatable(o, self) self.__index = self o.obj = basic_robot.data[name].obj; o.data = basic_robot.data[name]; local spawnpos = o.data.spawnpos; local meta = minetest.get_meta(spawnpos); if not meta then return end o.inv = meta:get_inventory(); basic_robot.virtual_players[name] = o; end -- functions function Vplayer:getpos() return self.obj:getpos() end function Vplayer:remove() end function Vplayer:setpos() end function Vplayer:move_to() end function Vplayer:punch() end function Vplayer:rightlick() end function Vplayer:get_hp() return 20 end function Vplayer:set_hp() return 20 end function Vplayer:get_inventory() return self.inv end function Vplayer:get_wield_list() return "main" end function Vplayer:get_wield_index() return 1 end function Vplayer:get_wielded_item() return self.inv:get_stack("main", 1) end function Vplayer:set_wielded_item() end function Vplayer:set_armor_groups() end function Vplayer:get_armor_groups() return {fleshy = 100} end function Vplayer:set_animation() end function Vplayer:get_animation() end function Vplayer:set_attach() end function Vplayer:get_attach() end function Vplayer:set_detach() end function Vplayer:set_bone_position() end function Vplayer:get_bone_position() end function Vplayer:set_properties() end function Vplayer:get_properties() end function Vplayer:is_player() return true end function Vplayer:get_nametag_attributes() end function Vplayer:set_nametag_attributes() end function Vplayer:set_velocity() end function Vplayer:get_velocity() end function Vplayer:set_acceleration() end function Vplayer:get_acceleration() end function Vplayer:set_yaw() end function Vplayer:get_yaw() end function Vplayer:set_texture_mod() end function Vplayer:get_luaentity() end function Vplayer:get_player_name() return self.data.name end function Vplayer:get_player_velocity() return {x=0,y=0,z=0} end function Vplayer:get_look_dir() return {x=1,y=0,z=0} end function Vplayer:get_look_vertical() return 0 end function Vplayer:get_look_horizontal() return 0 end function Vplayer:set_look_vertical() end function Vplayer:set_look_horizontal() end function Vplayer:get_breath() return 1 end function Vplayer:set_breath() end function Vplayer:set_attribute() end function Vplayer:get_attribute() end function Vplayer:set_inventory_formspec() end function Vplayer:get_inventory_formspec() return "" end function Vplayer:get_player_control() return {} end function Vplayer:get_player_control_bits() return 0 end function Vplayer:set_physics_override() end function Vplayer:get_physics_override() return {} end function Vplayer:hud_add() end function Vplayer:hud_remove() end function Vplayer:hud_change() end function Vplayer:hud_get() end function Vplayer:hud_set_flags() end function Vplayer:hud_get_flags() return {} end function Vplayer:hud_set_hotbar_itemcount() end function Vplayer:hud_get_hotbar_itemcount() return 0 end function Vplayer:hud_set_hotbar_image() end function Vplayer:hud_get_hotbar_image() return "" end function Vplayer:hud_set_hotbar_selected_image() end function Vplayer:hud_get_hotbar_selected_image() return "" end function Vplayer:set_sky() end function Vplayer:get_sky() end function Vplayer:set_clouds() end function Vplayer:get_clouds() end function Vplayer:override_day_night_ratio() end function Vplayer:get_day_night_ratio() end function Vplayer:set_local_animation() end function Vplayer:get_local_animation() end function Vplayer:set_eye_offset() end function Vplayer:get_eye_offset() end -- code for act borrowed from: https://github.com/minetest-mods/pipeworks/blob/fa4817136c8d1e62dafd6ab694821cba255b5206/wielder.lua, line 372