diff --git a/README.txt b/README.txt index 5950b38..a247eec 100644 --- a/README.txt +++ b/README.txt @@ -18,4 +18,12 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ----------------------------------------------------------------------- \ No newline at end of file +---------------------------------------------------------------------- + + +GAMEPLAY: + +- robot has limited operations available every run ( 1 run per 1 second). +- while using for loops, while loops or function calls it is limited to default 48 such code executions per run +- while using 'physical' operations like move/dig robot has (default) 10 operations available per run. Default costs are + move=2, dig = 6, insert = 2, place = 2, machine.generate = 6, machine.smelt = 6, machine.grind = 6, \ No newline at end of file diff --git a/commands.lua b/commands.lua index 5b85e1e..771a54f 100644 --- a/commands.lua +++ b/commands.lua @@ -21,18 +21,17 @@ local function pos_in_dir(obj, dir) -- position after we move in specified direc local yaw = obj:getyaw(); local pos = obj:getpos(); - if dir == 1 then -- left + 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 - yaw = yaw+pi; -- backward + 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 @@ -40,8 +39,7 @@ local function pos_in_dir(obj, dir) -- position after we move in specified direc 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 - + 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 @@ -49,12 +47,12 @@ local function pos_in_dir(obj, dir) -- position after we move in specified direc 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 + yaw = yaw + pi;pos.y=pos.y+1 end if dir ~= 5 and dir ~= 6 then - pos.x = pos.x+math.cos(yaw) - pos.z = pos.z+math.sin(yaw) + 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 @@ -68,7 +66,7 @@ local check_operations = function(name, amount, quit) data.operations = operations else if quit then - error("robot out of available operations in one step."); return false + 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 @@ -77,9 +75,11 @@ 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! @@ -90,7 +90,7 @@ basic_robot.commands.move = function(name,dir) obj:moveto(pos, true) - -- sit and stand up for model - doenst work for overwriten obj export + -- 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 @@ -113,14 +113,14 @@ 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,2,true) + check_operations(name,6,true) local obj = basic_robot.data[name].obj; local pos = pos_in_dir(obj, dir) @@ -168,6 +168,8 @@ 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(); @@ -210,6 +212,8 @@ basic_robot.commands.insert_item = function(name,item, inventory,dir) 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(); @@ -280,7 +284,8 @@ basic_robot.no_teleport_table = { 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); @@ -337,6 +342,8 @@ basic_robot.commands.write_text = function(name,dir,text) 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(); @@ -380,7 +387,7 @@ end basic_robot.commands.attack = function(name, target) -- attack range 4, damage 5 local energy = 0; - check_operations(name,2,true); + check_operations(name,4,true); local reach = 4; local damage = 5; @@ -424,9 +431,18 @@ basic_robot.commands.grab = function(name,target) end +--local minetest_version = minetest.get_version().string; basic_robot.commands.read_book = function (itemstack) -- itemstack should contain book - local data = itemstack:get_meta():to_table().fields -- 0.4.16 - --local data = minetest.deserialize(itemstack:get_metadata()) -- pre 0.4.16 + 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 @@ -441,10 +457,11 @@ basic_robot.commands.write_book = function(name,title,text) -- returns itemstack local data = {} if title == "" or not title then title = "program book "..minetest.get_gametime() end - data.title = title + 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 @@ -468,15 +485,16 @@ basic_robot.give_drops = function(nodename, inv) -- gives apropriate drops when 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; + if i > max_items then break end; + i=i+1; local rare = v.rarity or 1; - if math.random(1, rare)==1 then + 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 @@ -493,6 +511,7 @@ 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); @@ -501,27 +520,30 @@ local render_text = function(text,linesize) y=y+1; x=0 else c = string.format("%03d",cb)..".png" - tex = tex .. ":" .. (x*width) .. "," .. (y*height) .. "=" .. c; + 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..")"; - tex = background .. "^" .. tex; - return tex; + --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 end + 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)<60000 then - obj:set_properties({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}}) + 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 @@ -529,6 +551,8 @@ 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 @@ -734,13 +758,16 @@ basic_robot.commands.keyboard = { basic_robot.commands.craftcache = {}; -basic_robot.commands.craft = function(item, mode, idx, name) +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 then-- read cache + if cache.item == item and cache.idx == idx then -- read cache itemlist = cache.itemlist; output = cache.output; else @@ -759,6 +786,7 @@ basic_robot.commands.craft = function(item, mode, idx, name) itemlist[item]=(itemlist[item] or 0)+1; end cache.item = item; + cache.idx = idx; cache.itemlist = itemlist; cache.output = output; @@ -797,12 +825,12 @@ basic_robot.commands.craft = function(item, mode, idx, name) local inv = minetest.get_meta(pos):get_inventory(); for item,quantity in pairs(itemlist) do - local stack = ItemStack(item .. " " .. quantity); + 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); + local stack = ItemStack(item .. " " .. quantity*amount); inv:remove_item("main",stack); end @@ -866,7 +894,7 @@ basic_robot.technic = { -- data cache compressor_recipes = { --[in] ={fuel cost, out, quantity of material required for processing} ["default:snow"] = {1,"default:ice"}, - ["default:coalblock"] = {16,"default:diamond"}, + ["default:coalblock"] = {41,"default:diamond"}, -- to smelt diamond dust to diamond need 25 coal + 16 for grinder }, } @@ -885,7 +913,7 @@ 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,1.5, true) + 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 @@ -932,7 +960,7 @@ basic_robot.commands.machine = { 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,2,true) + check_operations(name,6,true) if string.find(input," ") then return nil, "0: only one item per smelt" end @@ -994,6 +1022,7 @@ basic_robot.commands.machine = { -- 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]; @@ -1024,6 +1053,7 @@ basic_robot.commands.machine = { -- compress compress = function(name,input) --[in] ={fuel cost, out, quantity of material required for processing} + check_operations(name,6,true) local recipe = basic_robot.technic.compressor_recipes[input]; if not recipe then return nil, "unknown recipe" end local cost = recipe[1]; local output = recipe[2]; @@ -1050,13 +1080,15 @@ basic_robot.commands.machine = { end, transfer_power = function(name,amount,target) + + check_operations(name,2, true); local pos = basic_robot.data[name].spawnpos; local data = basic_robot.data[name]; local tdata = basic_robot.data[target]; if not tdata then return nil, "target inactive" end local energy = 0; -- can only do one step at a run time - check_operations(name,0.5, true); + energy = data.menergy or 0; if amount>energy then return nil,"energy too low" end @@ -1209,10 +1241,10 @@ 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 return end + 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 return end + if not is_same_block(ppos,spos) then error("can not get player in another protection zone") return end return player end @@ -1221,7 +1253,7 @@ local cmd_get_player_inv = function(data,pname) if not player then return end local spos = data.spawnpos; local ppos = player:getpos(); - if not is_same_block(ppos,spos) then return end + if not is_same_block(ppos,spos) then error("can not get player in another protection zone") return end return player:get_inventory(); end @@ -1320,7 +1352,9 @@ basic_robot.commands.puzzle = { if minetest.is_protected(pos,data.owner) then return end if not is_same_block(pos,spos) then return end if minetest.get_node(pos).name == "basic_robot:spawner" then return end - return minetest.get_meta(pos) + local meta = minetest.get_meta(pos); + if not meta then error("get_meta in puzzle returned nil"); return end + return meta end, get_gametime = function() return minetest.get_gametime() end, @@ -1354,3 +1388,106 @@ basic_robot.commands.puzzle = { 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 diff --git a/init.lua b/init.lua index c925578..67ccb69 100644 --- a/init.lua +++ b/init.lua @@ -2,33 +2,45 @@ basic_robot = {}; ----- SETTINGS ------ -basic_robot.call_limit = 48; -- how many execution calls per script run allowed +------ 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.radius = 32; -- divide whole world into blocks of this size - used for managing events like keyboard punches -basic_robot.password = "password"; -- IMPORTANT: change it before running mod, password used for authentifications +basic_robot.password = "raN___dOM_ p4S"; -- IMPORTANT: change it before running mod, password used for authentifications + +basic_robot.admin_bot_pos = {x=0,y=1,z=0} -- position of admin robot spawner that will be run automatically on server start + +basic_robot.maxoperations = 10; -- how many operations (dig, place,move,...,generate energy,..) available per run, 0 = unlimited +basic_robot.dig_require_energy = true; -- does robot require energy to dig stone? basic_robot.bad_inventory_blocks = { -- disallow taking from these nodes inventories to prevent player abuses ["craft_guide:sign_wall"] = true, + ["basic_machines:battery_0"] = true, + ["basic_machines:battery_1"] = true, + ["basic_machines:battery_2"] = true, + ["basic_machines:generator"] = true, } -basic_robot.maxoperations = 2; -- how many operations (dig, generate energy,..) available per run, 0 = unlimited -basic_robot.dig_require_energy = true; -- does robot require energy to dig? ----------------------- +----- END OF SETTINGS ------ basic_robot.http_api = minetest.request_http_api(); -basic_robot.version = "2017/12/18a"; +basic_robot.version = "2019/01/13a"; +basic_robot.gui = {}; local robogui = basic_robot.gui -- gui management basic_robot.data = {}; -- stores all robot related data --[[ -[name] = { sandbox= .., bytecode = ..., ram = ..., obj = robot object, spawnpos= ..., authlevel = ...} +[name] = { sandbox= .., bytecode = ..., ram = ..., obj = robot object, spawnpos= ..., authlevel = ... , t = code execution time} robot object = object of entity, used to manipulate movements and more --]] basic_robot.ids = {}; -- stores maxid for each player --[name] = {id = .., maxid = .. }, current id for robot controller, how many robot ids player can use +basic_robot.virtual_players = {}; -- this way robot can interact with the world as "player" TODO + basic_robot.data.listening = {}; -- which robots listen to chat + +dofile(minetest.get_modpath("basic_robot").."/robogui.lua") -- gui stuff dofile(minetest.get_modpath("basic_robot").."/commands.lua") local check_code, preprocess_code,is_inside_string; @@ -47,6 +59,7 @@ function getSandboxEnv (name) left_up = 11, right_up = 12, forward_up = 13, backward_up = 14 } + if not basic_robot.data[name].rom then basic_robot.data[name].rom = {} end -- create rom if not yet existing local env = { pcall=pcall, @@ -73,14 +86,20 @@ function getSandboxEnv (name) return commands.pickup(r, name); end, - craft = function(item, idx,mode) - return commands.craft(item, mode, idx, name) + craft = function(item, idx,mode, amount) + return commands.craft(item, mode, idx, amount, name) + end, + + pause = function() -- pause coroutine + if not basic_robot.data[name].cor then error("you must start program with '--coroutine' to use pause()") return end + coroutine.yield() end, self = { pos = function() return basic_robot.data[name].obj:getpos() end, spawnpos = function() local pos = basic_robot.data[name].spawnpos; return {x=pos.x,y=pos.y,z=pos.z} end, name = function() return name end, + operations = function() return basic_robot.data[name].operations end, viewdir = function() local yaw = basic_robot.data[name].obj:getyaw(); return {x=math.cos(yaw), y = 0, z=math.sin(yaw)} end, set_properties = function(properties) @@ -152,7 +171,7 @@ function getSandboxEnv (name) fire = function(speed, pitch,gravity, texture, is_entity) -- experimental: fires an projectile local obj = basic_robot.data[name].obj; local pos = obj:getpos(); - local yaw = obj:getyaw(); + local yaw = obj:getyaw()+ math.pi/2; pitch = pitch*math.pi/180 local velocity = {x=speed*math.cos(yaw)*math.cos(pitch), y=speed*math.sin(pitch),z=speed*math.sin(yaw)*math.cos(pitch)}; -- fire particle @@ -174,6 +193,7 @@ function getSandboxEnv (name) local obj = minetest.add_entity(pos, "basic_robot:projectile"); if not obj then return end obj:setvelocity(velocity); + obj:set_properties({textures = {texture or "default_furnace_fire_fg.png"}}) obj:setacceleration({x=0,y=-gravity,z=0}); local luaent = obj:get_luaentity(); luaent.name = name; @@ -189,7 +209,7 @@ function getSandboxEnv (name) label = function(text) local obj = basic_robot.data[name].obj; - obj:set_properties({nametag = text}); -- "[" .. name .. "] " .. + obj:set_properties({nametag = text or ""}); -- "[" .. name .. "] " .. end, display_text = function(text,linesize,size) @@ -290,14 +310,15 @@ function getSandboxEnv (name) grab = function(target) return basic_robot.commands.grab(name,target) end, - say = function(text, owneronly) - if not basic_robot.data[name].quiet_mode and not owneronly then + say = function(text, pname) + if not basic_robot.data[name].quiet_mode and not pname then minetest.chat_send_all(" " .. text) if not basic_robot.data[name].allow_spam then basic_robot.data[name].quiet_mode=true end else - minetest.chat_send_player(basic_robot.data[name].owner," " .. text) + if not pname then pname = basic_robot.data[name].owner end + minetest.chat_send_player(pname," " .. text) -- send chat only to player pname end end, @@ -336,44 +357,39 @@ function getSandboxEnv (name) return end end, - - run = function(script) - if basic_robot.data[name].authlevel < 3 then - local err = check_code(script); - script = preprocess_code(script); - if err then - minetest.chat_send_player(name,"#ROBOT CODE CHECK ERROR : " .. err) - return - end - end - - local ScriptFunc, CompileError = loadstring( script ) - if CompileError then - minetest.chat_send_player(name, "#code.run: compile error " .. CompileError ) - return false - end - - setfenv( ScriptFunc, basic_robot.data[name].sandbox ) - - local Result, RuntimeError = pcall( ScriptFunc ); - if RuntimeError then - minetest.chat_send_player(name, "#code.run: run error " .. RuntimeError ) - return false - end - return true - end }, - + rom = basic_robot.data[name].rom, string = { byte = string.byte, char = string.char, find = string.find, - format = string.format, gsub = string.gsub, + gsub = string.gsub, gmatch = string.gmatch, len = string.len, lower = string.lower, upper = string.upper, rep = string.rep, reverse = string.reverse, sub = string.sub, + + format = function(...) + local out = string.format(...) + if string.len(out) > 1024 then + error("result string longer than 1024") + return + end + return out + end, + + concat = function(strings, sep) + local length = 0; + for i = 1,#strings do + length = length + string.len(strings[i]) + if length > 1024 then + error("result string longer than 1024") + return + end + end + return table.concat(strings,sep or "") + end, }, math = { abs = math.abs, acos = math.acos, @@ -392,13 +408,6 @@ function getSandboxEnv (name) sqrt = math.sqrt, tan = math.tan, tanh = math.tanh, }, - table = { - concat = table.concat, - insert = table.insert, - maxn = table.maxn, - remove = table.remove, - sort = table.sort, - }, os = { clock = os.clock, difftime = os.difftime, @@ -412,16 +421,6 @@ function getSandboxEnv (name) tonumber = tonumber, pairs = pairs, ipairs = ipairs, error = error, type=type, - --_ccounter = basic_robot.data[name].ccounter, -- counts how many executions of critical spots in script - - increase_ccounter = - function() - local _ccounter = basic_robot.data[name].ccounter; - if _ccounter > basic_robot.call_limit then - error("Execution limit " .. basic_robot.call_limit .. " exceeded"); - end - basic_robot.data[name].ccounter = _ccounter + 1; - end, }; -- ROBOT FUNCTIONS: move,dig, place,insert,take,check_inventory,activate,read_node,read_text,write_text @@ -478,6 +477,43 @@ function getSandboxEnv (name) end if authlevel>=1 then -- robot privs + + + env.table = { + concat = table.concat, + insert = table.insert, + maxn = table.maxn, + remove = table.remove, + sort = table.sort, + } + + env.code.run = function(script) + if basic_robot.data[name].authlevel < 3 then + local err = check_code(script); + script = preprocess_code(script, basic_robot.call_limit[basic_robot.data[name].authlevel+1]); + if err then + minetest.chat_send_player(name,"#ROBOT CODE CHECK ERROR : " .. err) + return + end + end + + local ScriptFunc, CompileError = loadstring( script ) + if CompileError then + minetest.chat_send_player(name, "#code.run: compile error " .. CompileError ) + return false + end + + setfenv( ScriptFunc, basic_robot.data[name].sandbox ) + + local Result, RuntimeError = pcall( ScriptFunc ); + if RuntimeError then + minetest.chat_send_player(name, "#code.run: run error " .. RuntimeError ) + return false + end + return true + end + + env.self.read_form = function() local fields = basic_robot.data[name].read_form; local sender = basic_robot.data[name].form_sender; @@ -524,8 +560,6 @@ function getSandboxEnv (name) end - - --special sandbox for admin if authlevel<3 then -- is admin? env._G = env; @@ -541,131 +575,162 @@ end -- code checker check_code = function(code) - --"while ", "for ", "do ","goto ", - local bad_code = {"repeat", "until", "_ccounter", "_G", "while%(", "while{", "pcall","\\\""} - + --"while ", "for ", "do ","goto ", + local bad_code = {"repeat", "until", "_c_", "_G", "while%(", "while{", "pcall","%.%."} --,"\\\"", "%[=*%[","--[["} for _, v in pairs(bad_code) do if string.find(code, v) then return v .. " is not allowed!"; end end - end -is_inside_string = function(pos,script) - local i1=string.find (script, "\"", 1); - if not i1 then - return false - end - local i2=0; - local par = 1; +local identify_strings = function(code) -- returns list of positions {start,end} of literal strings in lua code - if poslow+1 do + mid = math.floor((low+high)/2) + if posstrings[low][2] then mid = high else mid = low end + return strings[mid][1]<=pos and pos<=strings[mid][2] +end + +local find_outside_string = function(script, pattern, pos, strings) + local length = string.len(script) + local found = true; + local i1 = pos; + while found do + found = false + local i2 = string.find(script,pattern,i1); + if i2 then + if not is_inside_string(strings,i2) then return i2 end + found = true; + i1 = i2+1; + end + end + return nil end -- COMPILATION -preprocess_code = function(script) +--todo: 2018/12 this suddenly stopped working, wtf?? + +preprocess_code = function(script, call_limit) -- version 07/24/2018 + --[[ idea: in each local a = function (args) ... end insert counter like: - local a = function (args) counter() ... end + local a = function (args) counter_check_code ... end when counter exceeds limit exit with error --]] - script="_ccounter = 0; " .. script; + script = script:gsub("%-%-%[%[.*%-%-%]%]",""):gsub("%-%-[^\n]*\n","\n") -- strip comments + script="_c_ = 0; " .. script; + + -- process script to insert call counter in every function + local _increase_ccounter = " _c_ = _c_ + 1; if _c_ > " .. call_limit .. + " then _G.error(\"Execution count \".. _c_ .. \" exceeded ".. call_limit .. "\") end; " - local i1 -- process script to insert call counter in every function - local _increase_ccounter = " if _ccounter > " .. basic_robot.call_limit .. - " then error(\"Execution count \".. _ccounter .. \" exceeded ".. basic_robot.call_limit .. "\") end _ccounter = _ccounter + 1; " - - - local i1=0; local i2 = 0; + local i1=0; local i2 = 0; local found = true; - while (found) do -- PROCESS SCRIPT AND INSERT COUNTER AT PROBLEMATIC SPOTS - - found = false; - i2 = nil; + local strings = identify_strings(script); - -- i1 = where its looking + local inserts = {}; + + local constructs = { + {"while%s", "%sdo%s", 2, 6}, -- numbers: insertion pos = i2+2, after skip to i1 = i12+6 + {"function", ")", 0, 8}, + {"for%s", "%sdo%s", 2, 4}, + {"goto%s", nil , -1, 5}, + } + + for i = 1,#constructs do + i1 = 0; found = true + while (found) do -- PROCESS SCRIPT AND INSERT COUNTER AT PROBLEMATIC SPOTS - i2=string.find (script, "while ", i1) -- fix while OK - if i2 then - if not is_inside_string(i2,script) then + found = false; + + i2=find_outside_string(script, constructs[i][1], i1, strings) -- first part of construct + if i2 then local i21 = i2; - i2=string.find(script, "do", i2); - if i2 then - script = script.sub(script,1, i2+1) .. _increase_ccounter .. script.sub(script, i2+2); - i1=i21+6; -- after while - found = true; + if constructs[i][2] then + i2 = find_outside_string(script, constructs[i][2], i2, strings); -- second part of construct ( if any ) + if i2 then + inserts[#inserts+1]= i2+constructs[i][3]; -- move to last position of construct[i][2] + found = true; + end + else + inserts[#inserts+1]= i2+constructs[i][3] + found = true -- 1 part construct + end + + if found then + i1=i21+constructs[i][4]; -- skip to after constructs[i][1] end end + end - - i2=string.find (script, "function", i1) -- fix functions - if i2 then - --minetest.chat_send_all("func0") - if not is_inside_string(i2,script) then - i2=string.find(script, ")", i2); - if i2 then - script = script.sub(script,1, i2) .. _increase_ccounter .. script.sub(script, i2+1); - i1=i2+string.len(_increase_ccounter); - found = true; - end - end - - end - - i2=string.find (script, "for ", i1) -- fix for OK - if i2 then - if not is_inside_string(i2,script) then - i2=string.find(script, "do", i2); - if i2 then - script = script.sub(script,1, i2+1) .. _increase_ccounter .. script.sub(script, i2+2); - i1=i2+string.len(_increase_ccounter); - found = true; - end - end - end - - i2=string.find (script, "goto ", i1) -- fix goto OK - if i2 then - if not is_inside_string(i2,script) then - script = script.sub(script,1, i2-1) .. _increase_ccounter .. script.sub(script, i2); - i1=i2+string.len(_increase_ccounter)+5; -- insert + skip goto - found = true; - end - end - --minetest.chat_send_all("code rem " .. string.sub(script,i1)) - end - return script + table.sort(inserts) + + -- add inserts + local ret = {}; i1=1; + for i = 1, #inserts do + i2 = inserts[i]; + ret[#ret+1] = string.sub(script,i1,i2); + i1 = i2+1; + end + ret[#ret+1] = string.sub(script,i1); + + script = table.concat(ret,_increase_ccounter) + return script:gsub("pause%(%)", "_c_ = 0; pause()") -- reset ccounter at pause end local function CompileCode ( script ) - - --minetest.chat_send_all(script) - --if true then return nil, "" end - local ScriptFunc, CompileError = loadstring( script ) if CompileError then return nil, CompileError @@ -678,17 +743,30 @@ local function initSandbox (name) end local function setCode( name, script ) -- to run script: 1. initSandbox 2. setCode 3. runSandbox - local err; - if basic_robot.data[name].authlevel<3 then -- not admin + local cor = false; + if string.sub(script,1,11) == "--coroutine" then cor = true end + + local authlevel = basic_robot.data[name].authlevel; + + if authlevel<3 then -- not admin err = check_code(script); - script = preprocess_code(script); + script = preprocess_code(script,basic_robot.call_limit[authlevel+1]); + elseif cor then + script = preprocess_code(script, basic_robot.call_limit[authlevel+1]); -- coroutines need ccounter reset or 'infinite loops' fail after limit end if err then return err end + local bytecode, err = CompileCode ( script ); if err then return err end basic_robot.data[name].bytecode = bytecode; + + if cor then -- create coroutine if requested + basic_robot.data[name].cor = coroutine.create(bytecode) + else + basic_robot.data[name].cor = nil + end return nil end @@ -702,16 +780,26 @@ local function runSandbox( name) return "Bytecode missing." end - data.ccounter = 0; data.operations = basic_robot.maxoperations; + data.t = os.clock() setfenv( ScriptFunc, data.sandbox ) + local cor = data.cor; + if cor then -- coroutine! + local err,ret + ret,err = coroutine.resume(cor) + data.t = os.clock()-data.t + if err then return err end + return nil + end + local Result, RuntimeError = pcall( ScriptFunc ) + data.t = os.clock()-data.t if RuntimeError then return RuntimeError end - + return nil end @@ -720,7 +808,7 @@ 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 then maxid = basic_robot.advanced_count end -- max id's per user + if privs.robot or privs.puzzle then maxid = basic_robot.advanced_count end -- max id's per user basic_robot.ids[owner] = {id = 1, maxid = maxid}; --active id for remove control end @@ -836,7 +924,7 @@ minetest.register_entity("basic_robot:robot",{ --textures={"character.png"}, visual="cube", - textures={"arrow.png","basic_machine_side.png","face.png","basic_machine_side.png","basic_machine_side.png","basic_machine_side.png"}, + textures={"topface.png","legs.png","left-hand.png","right-hand.png","face.png","face-back.png"}, visual_size={x=1,y=1}, running = 0, -- does it run code or is it idle? @@ -904,16 +992,13 @@ minetest.register_entity("basic_robot:robot",{ end self.running = 0; -- stop execution - if string.find(err,"stack overflow") then -- remove stupid player privs and spawner, ban player ip + if string.find(err,"stack overflow") then local name = self.name; local pos = basic_robot.data[name].spawnpos; minetest.set_node(pos, {name = "air"}); - - local privs = core.get_player_privs(self.owner);privs.interact = false; - - core.set_player_privs(self.owner, privs); minetest.auth_reload() - minetest.ban_player(self.owner) - + --local privs = core.get_player_privs(self.owner);privs.interact = false; + --core.set_player_privs(self.owner, privs); minetest.auth_reload() + minetest.kick_player(self.owner, "#basic_robot: stack overflow") end local name = self.name; @@ -994,7 +1079,7 @@ local spawn_robot = function(pos,node,ttl) local sec_hash = minetest.get_password_hash("",data.authlevel.. owner .. basic_robot.password) if meta:get_string("sec_hash")~= sec_hash then - minetest.chat_send_all("#ROBOT: " .. name .. " is using fake auth level. dig and place again.") + minetest.chat_send_player(owner,"#ROBOT: " .. name .. " is using fake auth level. dig and place again.") return end @@ -1021,7 +1106,7 @@ local spawn_robot = function(pos,node,ttl) if data.authlevel<3 then -- not admin err = check_code(script); - script = preprocess_code(script); + script = preprocess_code(script, basic_robot.call_limit[data.authlevel+1]); end if err then meta:set_string("infotext","#CODE CHECK ERROR : " .. err); @@ -1040,7 +1125,7 @@ local spawn_robot = function(pos,node,ttl) if not data.sandbox then data.sandbox = getSandboxEnv (name) end -- actual code run process - data.ccounter = 0;data.operations = basic_robot.maxoperations; + data.operations = basic_robot.maxoperations; setfenv(data.bytecode, data.sandbox ) @@ -1074,7 +1159,7 @@ local spawn_robot = function(pos,node,ttl) local sec_hash = minetest.get_password_hash("",luaent.authlevel.. owner .. basic_robot.password) if meta:get_string("sec_hash")~= sec_hash then - minetest.chat_send_all("#ROBOT: " .. name .. " is using fake auth level. dig and place again.") + minetest.chat_send_player(owner,"#ROBOT: " .. name .. " is using fake auth level. dig and place again.") obj:remove(); return end @@ -1083,7 +1168,7 @@ local spawn_robot = function(pos,node,ttl) if data == nil then basic_robot.data[name] = {}; data = basic_robot.data[name]; - data.rom = {}; + --data.rom = {}; end data.owner = owner; @@ -1105,6 +1190,16 @@ local spawn_robot = function(pos,node,ttl) self.running = 1 end + +--admin robot that starts automatically after server start +minetest.after(10, function() + local admin_bot_pos = basic_robot.admin_bot_pos; + minetest.forceload_block(admin_bot_pos,true) -- load map position + spawn_robot(admin_bot_pos,nil,1) + print("[BASIC_ROBOT] admin robot at " .. admin_bot_pos.x .. " " .. admin_bot_pos.y .. " " .. admin_bot_pos.z .. " started.") +end) + + local despawn_robot = function(pos) local meta = minetest.get_meta(pos); @@ -1150,113 +1245,6 @@ local despawn_robot = function(pos) end --- GUI - --- robogui GUI START ================================================== -robogui = {}; -- a simple table of entries: [guiName] = {getForm = ... , show = ... , response = ... , guidata = ...} -robogui.register = function(def) - robogui[def.guiName] = {getForm = def.getForm, show = def.show, response = def.response, guidata = def.guidata or {}} -end -minetest.register_on_player_receive_fields( - function(player, formname, fields) - local gui = robogui[formname]; - if gui then gui.response(player,formname,fields) end - end -) --- robogui GUI END ==================================================== - - - ---- DEMO of simple form registration, all in one place, clean and tidy --- adapted for use with basic_robot - - --- if not basic_gui then - -- basic_gui = _G.basic_gui; minetest = _G.minetest; - -- basic_gui.register({ - -- guiName = "mainWindow", -- formname - - -- getForm = function(form_id, update) -- actual form design - -- local gui = basic_gui["mainWindow"]; - -- local formdata = gui.guidata[form_id] - - -- if not formdata then -- init - -- gui.guidata[form_id] = {}; formdata = gui.guidata[form_id] - -- formdata.sel_tab = 1; - -- formdata.text = "default"; - -- formdata.form = ""; - -- end - -- if not update then return formdata.form end - - -- local sel_tab = formdata.sel_tab; - -- local text = formdata.text; - - -- formdata.form = "size[8,9]".. - -- "label[0,0;basic_gui_DEMO, form_id " .. form_id .. ", tab " .. sel_tab .. "]".. - -- "button[0,1;2,1;gui_button;CLICK ME]".. - -- "textarea[0.25,2;2,1;gui_textarea;text;" .. text .. "]".. - -- "tabheader[0,0;tabs;tab1,table demo,tab3;".. sel_tab .. ";true;true]".. - -- "list[current_player;main;0,5;8,4;]"; - - -- if sel_tab == 2 then - -- formdata.form = "size[12,6.5;true]" .. - -- "tablecolumns[color;tree;text,width=32;text]" .. - -- "tableoptions[background=#00000000;border=false]" .. - -- "field[0.3,0.1;10.2,1;search_string;;" .. minetest.formspec_escape(text) .. "]" .. - -- "field_close_on_enter[search_string;false]" .. - -- "button[10.2,-0.2;2,1;search;" .. "Search" .. "]" .. - -- "table[0,0.8;12,4.5;list_settings;".. - -- "#FFFF00,1,TEST A,,".. - -- "#FFFF00,2,TEST A,,".. - -- ",3,test a,value A,".. - -- "#FFFF00,1,TEST B,,".. - -- ",2,test b,value B," - -- end - - -- formdata.info = "This information comes with the form post"; -- extra data set - -- end, - - -- show = function(player_name,update) -- this is used to show form to user - -- local formname = "mainWindow"; - -- local form_id = player_name; -- each player has his own window! - -- local gui = basic_gui[formname]; - -- local formdata = gui.guidata[form_id]; -- all form data for this id gets stored here - -- if update then gui.getForm(form_id,true); formdata = gui.guidata[form_id]; end - -- minetest.show_formspec(player_name, "mainWindow", formdata.form) - -- end, - - -- response = function(player,formname, fields) -- this handles response - - -- local player_name = player:get_player_name(); - -- local form_id = player_name; - -- local gui = basic_gui[formname]; - -- local formdata = gui.guidata[form_id]; --gui.guidata[form_id]; - -- if not formdata then say("err") return end --error! - - -- if fields.gui_textarea then - -- formdata.text = fields.gui_textarea or "" - -- end - - - -- if fields.tabs then - -- formdata.sel_tab = tonumber(fields.tabs) or 1; - - -- gui.show(player_name,true) -- update and show form - -- else - - -- local form = "size[5,5]" .. - -- "label[0,0;you interacted with demo form, fields : " .. - -- _G.minetest.formspec_escape(_G.dump(fields)) .. "]".. - -- "label[0,4;" .. formdata.info .. "]" - -- _G.minetest.show_formspec(player_name,"basic_response", form); - -- end - -- end, - - --- }) --- end - - --process forms from spawner local on_receive_robot_form = function(pos, formname, fields, sender) @@ -1265,13 +1253,15 @@ local on_receive_robot_form = function(pos, formname, fields, sender) if minetest.is_protected(pos,name) then return end if fields.OK then - local meta = minetest.get_meta(pos); if fields.code then local code = fields.code or ""; + if string.len(code) > 64000 then + minetest.chat_send_all("#ROBOT: " .. name .. " is spamming with long text.") return + end - if meta:get_int("admin") == 1 then + if meta:get_int("authlevel") > 1 and name ~= meta:get_string("owner")then local privs = minetest.get_player_privs(name); -- only admin can edit admin robot code if not privs.privs then return @@ -1317,103 +1307,7 @@ local on_receive_robot_form = function(pos, formname, fields, sender) end if fields.help then ----- INGAME HELP ------ - - local text = "BASIC LUA SYNTAX\n \nif x==1 then A else B end".. - "\n for i = 1, 5 do something end \nwhile i<6 do A; i=i+1; end\n".. - "\n arrays: myTable1 = {1,2,3}, myTable2 = {[\"entry1\"]=5, [\"entry2\"]=1}\n".. - " access table entries with myTable1[1] or myTable2.entry1 or myTable2[\"entry1\"]\n \n".. - "ROBOT COMMANDS\n \n".. - "**MOVEMENT,DIGGING, PLACING, INVENTORY TAKE/INSERT\n move.direction(), where direction is forward, backward, left,right, up, down)\n".. - " left_down, ..., backward_down, left_up, ..., backward_up\n".. - " boost(v) sets robot velocity, -60 it returns itemname. if itemname == \"\" it checks if inventory empty\n".. - " activate.direction(mode) activates target block\n".. - " pickup(r) picks up all items around robot in radius r<8 and returns list or nil\n".. - " craft(item,idx,mode) crafts item if required materials are present in inventory. mode = 1 returns recipe, optional recipe idx\n".. - " take.direction(item, inventory) takes item from target inventory into robot inventory\n".. - " read_text.direction(stringname,mode) reads text of signs, chests and other blocks, optional stringname for other meta,\n mode 1 read number\n".. - " write_text.direction(text,mode) writes text to target block as infotext\n".. - "**BOOKS/CODE\n title,text=book.read(i) returns title,contents of book at i-th position in library \n book.write(i,title,text) writes book at i-th position at spawner library\n".. - " code.run(text) compiles and runs the code in sandbox\n".. - " code.set(text) replaces current bytecode of robot\n".. - " find_nodes(\"default:dirt\",3) returns distance to node in radius 3 around robot, or false if none\n".. - "**PLAYERS\n".. - " find_player(3,pos) finds players in radius 3 around robot(position) and returns list, if none returns nil\n".. - " attack(target) attempts to attack target player if nearby \n".. - " grab(target) attempt to grab target player if nearby and returns true if succesful \n".. - " player.getpos(name) return position of player, player.connected() returns list of players\n".. - "**ROBOT\n".. - " say(\"hello\") will speak\n".. - " self.listen(0/1) (de)attaches chat listener to robot\n".. - " speaker, msg = self.listen_msg() retrieves last chat message if robot listens\n".. - " self.send_mail(target,mail) sends mail to target robot\n".. - " sender,mail = self.read_mail() reads mail, if any\n" .. - " self.pos() returns table {x=pos.x,y=pos.y,z=pos.z}\n".. - " self.name() returns robot name\n".. - " self.set_properties({textures=.., visual=..,visual_size=.., , ) sets visual appearance\n".. - " set_animation(anim_start,anim_end,anim_speed,anim_stand_start) set mesh animation \n".. - " self.spam(0/1) (dis)enable message repeat to all\n".. - " self.remove() stops program and removes robot object\n".. - " self.reset() resets robot position\n".. - " self.spawnpos() returns position of spawner block\n".. - " self.viewdir() returns vector of view for robot\n".. - " self.fire(speed, pitch,gravity, texture, is_entity) fires a projectile from robot\n".. - " self.fire_pos() returns last hit position\n".. - " self.label(text) changes robot label\n".. - " self.display_text(text,linesize,size) displays text instead of robot face, if no size return tex\n".. - " self.sound(sample,volume, opt. pos) plays sound named 'sample' at robot location (opt. pos)\n".. - " rom is aditional table that can store persistent data, like rom.x=1\n".. - "**KEYBOARD : place spawner at coordinates (20i,40j+1,20k) to monitor events\n".. - " keyboard.get() returns table {x=..,y=..,z=..,puncher = .. , type = .. } for keyboard event\n".. - " keyboard.set(pos,type) set key at pos of type 0=air,1-6,7-15,16-271, limited to range 10 around\n".. - " keyboard.read(pos) return node name at pos\n".. - "**TECHNIC FUNCTIONALITY: namespace 'machine'. most functions return true or nil, error\n" .. - " energy() displays available energy\n".. - " generate_power(fuel, amount) = energy, attempt to generate power from fuel material,\n" .. - " if amount>0 try generate amount of power using builtin generator - this requires\n" .. - " 40 gold/mese/diamonblock upgrades for each 1 amount\n".. - " smelt(input,amount) = progress/true. works as a furnace, if amount>0 try to\n" .. - " use power to smelt - requires 10 upgrades for each 1 amount, energy cost is:\n".. - " 1/40*(1+amount)\n".. - " grind(input) - grinds input material, requires upgrades for harder material\n".. - " compress(input) - requires upgrades - energy intensive process\n" .. - " transfer_power(amount,target_robot_name)\n".. - "**CRYPTOGRAPHY: namespace 'crypto'\n".. - " encrypt(input,password) returns encrypted text, password is any string \n".. - " decrypt(input,password) attempts to decrypt encrypted text\n".. - " scramble(input,randomseed,sgn) (de)permutes text randomly according to sgn = -1,1\n".. - " basic_hash(input,n) returns simple mod hash from string input within range 0...n-1\n".. - "**PUZZLE: namespace 'puzzle' - need puzzle priv\n".. - " set_triggers({trigger1, trigger2,...}) sets and initializes spatial triggers\n".. - " check_triggers(pname) check if player is close to any trigger and run that trigger\n".. - " set_node(pos,node) - set any node, limited to current protector mapblock & get_node(pos)\n".. - " get_player(pname) return player objRef in current mapblock\n".. - " chat_send_player(pname, text) \n".. - " get_node_inv(pos) / get_player_inv(pname) - return inventories of nodes/players in current mapblock\n".. - " get_meta(pos) - return meta of target position\n".. - " get_gametime() - return current gametime\n".. - " ItemStack(itemname) returns ItemRef to be used with inventory\n".. - " count_objects(pos,radius)\n".. - " pdata contains puzzle data like .triggers and .gamedata\n".. - " add_particle(def)\n" - - - text = minetest.formspec_escape(text); - - --local form = "size [8,7] textarea[0,0;8.5,8.5;help;HELP;".. text.."]" - - --textlist[X,Y;W,H;name;listelem 1,listelem 2,...,listelem n] - local list = ""; - for word in string.gmatch(text, "(.-)\r?\n+") do list = list .. word .. ", " end - local form = "size [10,8] textlist[-0.25,-0.25;10.25,8.5;help;" .. list .. "]" - minetest.show_formspec(sender:get_player_name(), "robot_help", form); - + robogui["robot_help"].show(name) return end @@ -1505,8 +1399,10 @@ local on_receive_robot_form = function(pos, formname, fields, sender) end end - + -- handle form: when rightclicking robot entity, remote controller + + minetest.register_on_player_receive_fields( function(player, formname, fields) @@ -1537,6 +1433,9 @@ minetest.register_on_player_receive_fields( local name = string.sub(formname, string.len(robot_formname)+1); -- robot name if fields.OK and fields.code then local item = player:get_wielded_item(); --set_wielded_item(item) + if string.len(fields.code) > 1000 then + minetest.chat_send_player(player:get_player_name(),"#ROBOT: text too long") return + end item:set_metadata(fields.code); player:set_wielded_item(item); if fields.id then @@ -1678,6 +1577,10 @@ minetest.register_on_player_receive_fields( local data = itemstack:get_meta():to_table().fields -- 0.4.16, old minetest.deserialize(itemstack:get_metadata()) if not data then data = {} end local text = fields.book or ""; + if string.len(text) > 64000 then + local sender = player:get_player_name(); + minetest.chat_send_all("#ROBOT: " .. sender .. " is spamming with long text.") return + end data.text = text or "" data.title = fields.title or "" data.text_len = #data.text @@ -1715,13 +1618,15 @@ end -- handle chats minetest.register_on_chat_message( function(name, message) + local hidden = false; + if string.sub(message,1,1) == "\\" then hidden = true; message = string.sub(message,2) end local listeners = basic_robot.data.listening; for pname,_ in pairs(listeners) do local data = basic_robot.data[pname]; data.listen_msg = message; data.listen_speaker = name; end - return false + return hidden end ) @@ -1834,7 +1739,7 @@ end minetest.register_craftitem("basic_robot:control", { description = "Robot remote control", inventory_image = "control.png", - groups = {book = 1, not_in_creative_inventory = 1}, + groups = {book = 1}, --not_in_creative_inventory = 1 stack_max = 1, on_secondary_use = function(itemstack, user, pointed_thing) @@ -1951,7 +1856,7 @@ minetest.register_entity( on_step = function(self, dtime) local vel = self.object:getvelocity(); - if (self.oldvel.x~=0 and vel.x==0) or (self.oldvel.y~=0 and vel.y==0) or (self.oldvel.z~=0 and vel.z==0) then + if (self.oldvel.x~=0 and vel.x==0) or (self.oldvel.y~=0 and vel.y==0) or (self.oldvel.z~=0 and vel.z==0) then -- hit local data = basic_robot.data[self.name]; if data then data.fire_pos = self.object:getpos(); @@ -1997,4 +1902,6 @@ minetest.register_craft({ minetest.register_privilege("robot", "increased number of allowed active robots") -minetest.register_privilege("puzzle", "allow player to use puzzle. namespace in robots") \ No newline at end of file +minetest.register_privilege("puzzle", "allow player to use puzzle. namespace in robots") + +print('[MOD]'.. " basic_robot " .. basic_robot.version .. " loaded.") \ No newline at end of file diff --git a/robogui.lua b/robogui.lua new file mode 100644 index 0000000..6d802c7 --- /dev/null +++ b/robogui.lua @@ -0,0 +1,350 @@ +local robogui = basic_robot.gui; + +-- GUI + +-- robogui GUI START ================================================== +-- a simple table of entries: [guiName] = {getForm = ... , show = ... , response = ... , guidata = ...} +robogui.register = function(def) + robogui[def.guiName] = {getForm = def.getForm, show = def.show, response = def.response, guidata = def.guidata or {}} +end +minetest.register_on_player_receive_fields( + function(player, formname, fields) + local gui = robogui[formname]; + if gui then gui.response(player,formname,fields) end + end +) +-- robogui GUI END ==================================================== + + +--- DEMO of simple form registration, all in one place, clean and tidy +-- adapted for use with basic_robot + + +-- if not basic_gui then + -- basic_gui = _G.basic_gui; minetest = _G.minetest; + -- basic_gui.register({ + -- guiName = "mainWindow", -- formname + + -- getForm = function(form_id, update) -- actual form design + -- local gui = basic_gui["mainWindow"]; + -- local formdata = gui.guidata[form_id] + + -- if not formdata then -- init + -- gui.guidata[form_id] = {}; formdata = gui.guidata[form_id] + -- formdata.sel_tab = 1; + -- formdata.text = "default"; + -- formdata.form = ""; + -- end + -- if not update then return formdata.form end + + -- local sel_tab = formdata.sel_tab; + -- local text = formdata.text; + + -- formdata.form = "size[8,9]".. + -- "label[0,0;basic_gui_DEMO, form_id " .. form_id .. ", tab " .. sel_tab .. "]".. + -- "button[0,1;2,1;gui_button;CLICK ME]".. + -- "textarea[0.25,2;2,1;gui_textarea;text;" .. text .. "]".. + -- "tabheader[0,0;tabs;tab1,table demo,tab3;".. sel_tab .. ";true;true]".. + -- "list[current_player;main;0,5;8,4;]"; + + -- if sel_tab == 2 then + -- formdata.form = "size[12,6.5;true]" .. + -- "tablecolumns[color;tree;text,width=32;text]" .. + -- "tableoptions[background=#00000000;border=false]" .. + -- "field[0.3,0.1;10.2,1;search_string;;" .. minetest.formspec_escape(text) .. "]" .. + -- "field_close_on_enter[search_string;false]" .. + -- "button[10.2,-0.2;2,1;search;" .. "Search" .. "]" .. + -- "table[0,0.8;12,4.5;list_settings;".. + -- "#FFFF00,1,TEST A,,".. + -- "#FFFF00,2,TEST A,,".. + -- ",3,test a,value A,".. + -- "#FFFF00,1,TEST B,,".. + -- ",2,test b,value B," + -- end + + -- formdata.info = "This information comes with the form post"; -- extra data set + -- end, + + -- show = function(player_name,update) -- this is used to show form to user + -- local formname = "mainWindow"; + -- local form_id = player_name; -- each player has his own window! + -- local gui = basic_gui[formname]; + -- local formdata = gui.guidata[form_id]; -- all form data for this id gets stored here + -- if update then gui.getForm(form_id,true); formdata = gui.guidata[form_id]; end + -- minetest.show_formspec(player_name, "mainWindow", formdata.form) + -- end, + + -- response = function(player,formname, fields) -- this handles response + + -- local player_name = player:get_player_name(); + -- local form_id = player_name; + -- local gui = basic_gui[formname]; + -- local formdata = gui.guidata[form_id]; --gui.guidata[form_id]; + -- if not formdata then say("err") return end --error! + + -- if fields.gui_textarea then + -- formdata.text = fields.gui_textarea or "" + -- end + + + -- if fields.tabs then + -- formdata.sel_tab = tonumber(fields.tabs) or 1; + + -- gui.show(player_name,true) -- update and show form + -- else + + -- local form = "size[5,5]" .. + -- "label[0,0;you interacted with demo form, fields : " .. + -- _G.minetest.formspec_escape(_G.dump(fields)) .. "]".. + -- "label[0,4;" .. formdata.info .. "]" + -- _G.minetest.show_formspec(player_name,"basic_response", form); + -- end + -- end, + + +-- }) +-- end + +local help_address = {}; -- array containing current page name for player +local help_pages = { + ["main"] = { + " === ROBOT HELP - MAIN SCREEN === ","", + "[Commands reference] display list of robot commands", + "[Lua basics] short introduction to lua","", + "INSTRUCTIONS: double click links marked with []", + "------------------------------------------","", + "basic_robot version " .. basic_robot.version, + "(c) 2016 rnd", + }, + + ["Lua basics"] = { + "back to [main] menu", + "BASIC LUA SYNTAX","", + " IF CONDITIONAL: if x==1 then A else B end", + " FOR LOOP: for i = 1, 5 do something end", + " WHILE LOOP: while i<6 do A; i=i+1; end", + ' ARRAYS: myTable1 = {1,2,3}, myTable2 = {["entry1"]=5, ["entry2"]=1}', + ' access table entries with myTable1[1] or myTable2.entry1 or', + ' myTable2["entry1"]', + " FUNCTIONS: f = function(x) return 2*x end, call like f(3)", + " STRINGS: name = \"rnd\" or name = [[rnd ]] (multiline string)", + " string.concat({string1,string2,...}, separator) returns", + " concatenated string with maxlength is 1024", + }, + + ["Commands reference"] = { + "back to [main] menu", + + "ROBOT COMMANDS","", + " 1. [MOVEMENT DIGGING PLACING NODE SENSING]", + " 2. [TAKE INSERT AND INVENTORY]", + " 3. [BOOKS CODE TEXT WRITE OR READ]", + " 4. [PLAYERS]", + " 5. [ROBOT SPEAK LABEL APPEARANCE OTHER]", + " 6. [KEYBOARD AND USER INTERACTIONS]", + " 7. [TECHNIC FUNCTIONALITY]", + " 8. [CRYPTOGRAPHY]", + " 9. [PUZZLE]", + }, + + ["MOVEMENT DIGGING PLACING NODE SENSING"] = { + "back to [Commands reference]", + "MOVEMENT,DIGGING, PLACING, NODE SENSING","", + " move.direction(), where direction is: left, right, forward, backward,", + " up, down, left_down, right_down, forward_down, backward_down,", + " left_up, right_up, forward_up, backward_up", + " boost(v) sets robot velocity, -60 it returns itemname.", + " if itemname == \"\" it checks if inventory empty", + " activate.direction(mode) activates target block", + " pickup(r) picks up all items around robot in radius r<8 and returns list", + " or nil", + " craft(item,idx,mode) crafts item if required materials are present in", + " inventory, mode = 1 returns recipe, optional recipe idx", + " take.direction(item, inventory) takes item from target inventory into", + " robot inventory", + }, + + ["BOOKS CODE TEXT WRITE OR READ"] = { + "back to [Commands reference]", + "BOOKS CODE TEXT WRITE OR READ","", + " title,text=book.read(i) returns title,contents of book at i-th position in", + " library", + " book.write(i,title,text) writes book at i-th position at spawner library", + " code.run(text) compiles and runs the code in sandbox (privs only)", + " code.set(text) replaces current bytecode of robot", + " find_nodes(\"default:dirt\",3) returns distance to node in radius 3 around", + " robot, or false if none found", + " read_text.direction(stringname,mode) reads text of signs, chests and", + " other blocks, optional stringname for other meta, mode 1 to read number", + " write_text.direction(text,mode) writes text to target block as infotext", + }, + + ["PLAYERS"] = { + "back to [Commands reference]", + "PLAYERS","", + " find_player(3,pos) finds players in radius 3 around robot(position) and", + " returns list of found player names, if none returns nil", + " attack(target) attempts to attack target player if nearby", + " grab(target) attempt to grab target player if nearby and returns", + " true if succesful", + " player.getpos(name) return position of player, player.connected()", + " returns list of connected players names", + }, + + ["ROBOT SPEAK LABEL APPEARANCE OTHER"] = { + "back to [Commands reference]", + "ROBOT","", + " say(\"hello\") will speak", + " self.listen(0/1) (de)attaches chat listener to robot", + " speaker, msg = self.listen_msg() retrieves last chat message if robot", + " has listener attached", + " self.send_mail(target,mail) sends mail to target robot", + " sender,mail = self.read_mail() reads mail, if any", + " self.pos() returns table {x=pos.x,y=pos.y,z=pos.z}", + " self.name() returns robot name", + " self.operations() returns remaining robot operations", + " self.set_properties({textures=.., visual=..,visual_size=.., , ) sets visual", + " appearance", + " self.set_animation(anim_start,anim_end,anim_speed,anim_stand_start)", + " set mesh,animation", + " self.spam(0/1) (dis)enable message repeat to all", + " self.remove() stops program and removes robot object", + " self.reset() resets robot position", + " self.spawnpos() returns position of spawner block", + " self.viewdir() returns vector of view for robot", + " self.fire(speed, pitch,gravity, texture, is_entity) fires a projectile", + " from robot. if is_entity false (default) it fires particle.", + " self.fire_pos() returns last hit position", + " self.label(text) changes robot label", + " self.display_text(text,linesize,size) displays text instead of robot face,", + " if no size just return texture string", + " self.sound(sample,volume, opt. pos) plays sound named 'sample' at", + " robot, location (optional pos)", + " rom is aditional table that can store persistent data, like rom.x=1", + }, + + ["KEYBOARD AND USER INTERACTIONS"] = { + "back to [Commands reference]", + "KEYBOARD","", + " EVENTS : place spawner at coordinates (r*i,2*r*j+1,r*k) to monitor", + " events. value of r is ".. basic_robot.radius, + " keyboard.get() returns table {x=..,y=..,z=..,puncher = .. , type = .. }", + " for keyboard event", + " keyboard.set(pos,type) set key at pos of type 0=air,1-6,7-15,16-271,", + " limited to range 10 around spawner", + " keyboard.read(pos) return node name at pos", + }, + + ["TECHNIC FUNCTIONALITY"] = { + "back to [Commands reference]", + "TECHNIC FUNCTIONALITY","", + " All commands are in namespace 'machine', for example machine.energy()", + " most functions return: ok, error = true or nil, error", + " To use some commands fully robot must be upgraded. 1 upgrade is", + " goldblock+meseblock+diamonblock.", + " energy() displays available energy", + " generate_power(fuel, amount) = energy, attempt to generate power", + " from fuel material. If amount>0 try generate amount of power", + " using builtin generator - this requires 40 upgrades for each", + " 1 amount", + " smelt(input,amount) = progress/true. works as a furnace, if amount>0", + " try to use power to smelt - requires 10 upgrades for each 1 amount,", + " energy cost of smelt is: 1/40*(1+amount)", + " grind(input) - grinds input material, requires upgrades for harder", + " materials", + " compress(input) - requires upgrades - energy intensive process", + " transfer_power(amount,target_robot_name)", + }, + + ["CRYPTOGRAPHY"] = { + "back to [Commands reference]", + "CRYPTOGRAPHY","", + " namespace 'crypto'", + " encrypt(input,password) returns encrypted text, password is any string", + " decrypt(input,password) attempts to decrypt encrypted text", + " scramble(input,randomseed,sgn) (de)permutes text randomly according", + " to sgn = -1,1", + " basic_hash(input,n) returns simple mod hash from string input within", + " range 0...n-1", + }, + + ["PUZZLE"] = { + "back to [Commands reference]", + "PUZZLE","", + " namespace 'puzzle' - need puzzle priv", + " set_triggers({trigger1, trigger2,...}) sets and initializes spatial triggers", + " check_triggers(pname) check if player is close to any trigger and run", + " that trigger", + " set_node(pos,node) - set any node, limited to current protector", + " region", + " get_player(pname) return player objRef in current protector region", + " chat_send_player(pname, text)", + " get_node_inv(pos) / get_player_inv(pname) - return inventories of nodes", + " /players in current mapblock", + " get_meta(pos) - return meta of target position", + " get_gametime() - return current gametime", + " ItemStack(itemname) returns ItemRef to be used with inventory", + " count_objects(pos,radius)", + " pdata contains puzzle data like .triggers and .gamedata", + " add_particle(def)" + } + +} +for k,v in pairs(help_pages) do + local pages = help_pages[k]; for i = 1,#pages do pages[i] = minetest.formspec_escape(pages[i]) end +end + + +local robot_show_help = function(pname) --formname: robot_help + local address = help_address[pname] or "main"; + + --minetest.chat_send_all("D DISPLAY HELP for ".. address ) + local pages = help_pages[address]; + + local content = table.concat(pages,",") + local size = 9; local vsize = 8.75; + + local form = "size[" .. size .. "," .. size .. "] textlist[-0.25,-0.25;" .. (size+1) .. "," .. (vsize+1) .. ";wiki;".. content .. ";1]"; + --minetest.chat_send_all("D " .. form) + minetest.show_formspec(pname, "robot_help", form) + return +end + + +robogui["robot_help"] = { + response = function(player,formname,fields) + local name = player:get_player_name() + + local fsel = fields.wiki; + if fsel and string.sub(fsel,1,3) == "DCL" then + local sel = tonumber(string.sub(fsel,5)) or 1; -- selected line + local address = help_address[name] or "main"; + local pages = help_pages[address]; + + local link = string.match(pages[sel] or "", "\\%[([%w%s]+)\\%]") + if help_pages[link] then + help_address[name] = link; + robot_show_help(name) + end + end + end, + + getForm = function(player_name) + + end, + + show = robot_show_help, +}; diff --git a/scripts/command_robot.lua b/scripts/command_robot.lua new file mode 100644 index 0000000..1ebc33d --- /dev/null +++ b/scripts/command_robot.lua @@ -0,0 +1,242 @@ +--COMMAND ROBOT by rnd, v2, adapted for skyblock +if not s then + self.listen(1) + s=1;_G.minetest.forceload_block(self.pos(),true) + self.spam(1) + users = {["rnd"]=3,["rnd1"]=3,["Giorge"]=1,["quater"]=1,["UltimateNoob"]=1,["reandh3"]=1,["karo"]=1,["Fedon"]=1,["DS"]=2, + ["Arcelmi"]=1,["Gregorro"]=1,Mokk = 1, Evandro010 = 1} + cmdlvl = {["kill"]=2,["tp"]=3,["heal"]=1, ["rename"]=1,["jump"]=1,["givediamond"]=3, ["msg"]=1,["calc"]=0, ["acalc"]=3,["run"]=3, ["shutdown"] = 3,["sayhi"]=0, ["day"] = 1}; + + tpr = {}; + + cmds = { + help = + { + level = 0, + run = function(param) + local arg = param[2]; + if not arg then + say(colorize("red","OPEN INVENTORY AND READ 'Quests'. YOU GET REWARD AND BETTER STUFF FOR COMPLETING QUESTS. Say /spawn to get to spawn area.")) + return + end + + if arg and cmds[arg] then + local text = cmds[arg]["docs"]; + if text then say(text) end + else + --say(colorize("red","commands") .. colorize("LawnGreen",": 0 status, 2 kill $name, 3 tp $name1 $name2, 1 heal $name, 1 day, 1 rename $name $newname, 3 givediamond $name, 1 msg $name $message, 0 sayhi, 0 calc $formula, 3 acalc $formula, 3 run $expr")) + local msg = "" + for k,v in pairs(cmds) do + msg = msg .. v.level .. " " .. k .. ", " + end + say(colorize("red","commands") .. " " .. colorize("LawnGreen", msg)) + end + end, + docs = "Displays available commands. 'help command' shows help about particular command" + }, + + status = + { + level = 0, + run = function(param) + local usr = param[2] or speaker; + local id = _G.skyblock.players[usr].id; + local pos = _G.skyblock.get_island_pos(id) + minetest.chat_send_all(minetest.colorize("yellow", + usr .. " data : permission level " .. (users[usr] or 0).. ", island " .. id .." (at " .. pos.x .. " " .. pos.z .. "), skyblock level " .. _G.skyblock.players[usr].level + )) + end, + docs = "status name, Show target(speaker if none) level, which determines access privilege." + }, + + kill = + { + level = 2, + run = function(param) + local name = param[2]; if not name then return end + local player = _G.minetest.get_player_by_name(name); + if player then + if (users[name] or 0)<=(users[speaker] or 0) then player:set_hp(0) end + end + end, + docs = "kill name; kills target player", + }, + + tp = + { + level = 2, + run = function(param) + local player1 = _G.minetest.get_player_by_name(param[2] or ""); + local player2 = _G.minetest.get_player_by_name(param[3] or ""); + if player1 and player2 then if (users[param[2]] or 0)<=(users[speaker] or 0) then player1:setpos(player2:getpos()) end end + end, + docs = "tp name1 name2; teleports player name2 to name2", + }, + + tpr = + { + level = 0, + run = function(param) + local name = param[2] or ""; + local player1 = _G.minetest.get_player_by_name(name); + if player1 then tpr = {speaker, name} else return end + _G.minetest.chat_send_player(name,minetest.colorize("yellow","#TELEPORT REQUEST: say tpy to teleport " .. speaker .. " to you.")) + + end, + docs = "tpr name; request teleport to target player", + }, + + tpy = + { + level = 0, + run = function(param) + if speaker == tpr[2] then + local player1 = _G.minetest.get_player_by_name(tpr[1] or ""); + local player2 = _G.minetest.get_player_by_name(tpr[2] or ""); + if player1 and player2 then else return end + player1:setpos(player2:getpos()) + _G.minetest.chat_send_player(tpr[2],minetest.colorize("yellow","#teleporting " .. tpr[1] .. " to you.")) + tpr = {} + end + + end, + docs = "tpy; allow player who sent teleport request to teleport to you.", + }, + + calc = + { + level = 0, + run = function(param) + + local formula = param[2] or ""; + if not string.find(formula,"%a") then + result = 0; + code.run("result = "..formula); + result = tonumber(result) + if result then say(result) else say("error in formula") end + else + say("dont use any letters in formula") + end + end, + docs = "calculate expression", + }, + + day = + { + level = 1, + run = function() minetest.set_timeofday(0.25) end, + docs = "set time to day" + }, + + sayhi = + { + level = 0, + run = function() + local players = _G.minetest.get_connected_players();local msg = ""; + for _,player in pairs(players) do + local name = player:get_player_name(); + local color = string.format("#%x",math.random(2^24)-1) + if name~=speaker then msg = msg..colorize(color , " hi " .. name) .."," end + end + _G.minetest.chat_send_all("<"..speaker..">" .. string.sub(msg,1,-2)) + end, + docs = "say hi to all the other players" + }, + + msg = { + level = 2, + run = function(param) + local text = string.sub(msg, string.len(param[1] or "")+string.len(param[2] or "") + 3) + local form = "size [8,2] textarea[0.,0;8.75,3.75;book;MESSAGE from " .. speaker .. ";" .. _G.minetest.formspec_escape(text or "") .. "]" + _G.minetest.show_formspec(param[2], "robot_msg", form); + end, + docs = "msg name message, displays message to target player", + + }, + + plist = { + level = 0, + run = function() + local p = {}; + for k,v in pairs(minetest.get_connected_players()) do + local name = v:get_player_name() + local pdata = _G.skyblock.players[name] + p[#p+1] = name..", level " .. pdata.level .. "+" .. pdata.completed .. "/" .. pdata.total + end + local text = table.concat(p,"\n") + local form = "size [8,5] textarea[0.,0;8.75,6.75;book;PLAYERS;" .. _G.minetest.formspec_escape(text or "") .. "]" + _G.minetest.show_formspec(speaker, "robot_msg", form); + end, + docs = "plist, displays player list and their levels", + + }, + + run = { + level = 3, + run = function(param) + local expr = string.sub(msg,5); + --say("running " .. expr) + code.run(expr); + end, + docs = "run lua code", + }, + } + + self.label(colorize("red","\nCMD ROBOT")) +end +speaker, msg = self.listen_msg(); + +if msg then + local words = {}; + for word in string.gmatch(msg,"%S+") do words[#words+1]=word end -- extract words + + local level = users[speaker] or 0; + local cmd = words[1]; + + cmdlevel = cmdlvl[cmd] or 0; + if level < cmdlevel then + say("You need to be level " .. cmdlevel .. " to use " .. words[1]) + else + if cmds[cmd] then + cmds[cmd].run(words) + elseif words[1]=="heal" and words[2] then + local player = _G.minetest.get_player_by_name(words[2]); + if player then player:set_hp(20) end + elseif words[1]=="rename" then + local player = _G.minetest.get_player_by_name(words[2]); + if player then if ((users[words[2]] or 0)<=level) and (level>=3 or words[2]~=speaker) then player:set_nametag_attributes({text = words[3] or words[2]}) end end + elseif words[1]=="robot"then + local player = _G.minetest.get_player_by_name(words[2]) + if player then + 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}}) + end + elseif words[1] == "mese" then + local player = _G.minetest.get_player_by_name(words[2]) + if player then + player:set_properties({visual = "mesh",textures = {"zmobs_mese_monster.png"},mesh = "zmobs_mese_monster.x",visual_size = {x=1,y=1}}) + end + + elseif words[1] == "givediamond" then + local player = _G.minetest.get_player_by_name(words[2]) + local pos = player:getpos(); + _G.minetest.add_item(pos,_G.ItemStack("default:diamond")) + elseif words[1] == "acalc" then + local formula = words[2] or ""; + if not string.find(formula,"_G") then + result = 0; + code.run("result = "..formula); + result = tonumber(result) + if result then say(result) else say("error in formula") end + end + elseif words[1] == "shutdown" then + _G.minetest.request_shutdown("maintenance, come back",true) + elseif words[1] == "web" then + local text = string.sub(msg,5); + local f = _G.io.open("H:\\sfk\\files\\index.html", "w") + f:write(text);f:close() + + end + end +end \ No newline at end of file diff --git a/scripts/copy_paste.lua b/scripts/copy_paste.lua new file mode 100644 index 0000000..471a509 --- /dev/null +++ b/scripts/copy_paste.lua @@ -0,0 +1,90 @@ +-- COPY PASTE ROBOT by rnd: c1 c2 r = markers, c = copy p = paste + +if not paste then + _G.minetest.forceload_block(self.pos(),true) + paste = {}; + round = function(x) + if x>0 then + return math.floor(x+0.5) + else + return -math.floor(-x+0.5) + end + end + data = {}; + + display_marker = function(pos,label) + minetest.add_particle( + { + pos = pos, + expirationtime = 10, + velocity = {x=0, y=0,z=0}, + size = 18, + texture = label..".png", + acceleration = {x=0,y=0,z=0}, + collisiondetection = true, + collision_removal = true, + }) + + end + + self.listen(1) + self.label("COPY-PASTE MASTER v1.2 gold edition. commands: c1 c2 r c p") +end + + +speaker, msg = self.listen_msg() + +if speaker == "rnd" then + local player = _G.minetest.get_player_by_name(speaker); + local p = player:getpos(); p.x = round(p.x); p.y=round(p.y); p.z = round(p.z); + if p.y<0 then p.y = p.y +1 end -- needed cause of minetest bug + if msg == "c1" then + paste.src1 = {x=p.x,y=p.y,z=p.z};say("marker 1 set at " .. p.x .. " " .. p.y .. " " .. p.z) + display_marker(p,"099") -- c + elseif msg == "c2" then + paste.src2 = {x=p.x,y=p.y,z=p.z};say("marker 2 set at " .. p.x .. " " .. p.y .. " " .. p.z) + display_marker(p,"099") -- c + elseif msg == "r" then + paste.ref = {x=p.x,y=p.y,z=p.z};say("reference set at " .. p.x .. " " .. p.y .. " " .. p.z) + display_marker(p,"114") -- r + elseif msg == "c" then -- copy + local x1 = math.min(paste.src1.x,paste.src2.x);local y1 = math.min(paste.src1.y,paste.src2.y);local z1 = math.min(paste.src1.z,paste.src2.z); + local x2 = math.max(paste.src1.x,paste.src2.x);local y2 = math.max(paste.src1.y,paste.src2.y);local z2 = math.max(paste.src1.z,paste.src2.z); + local count = 0; data = {}; + for i = x1,x2 do + for j = y1,y2 do + for k = z1,z2 do + local node = _G.minetest.get_node({x=i,y=j,z=k}); + if node.name ~= "air" then + if not data[i] then data[i]= {} end + if not data[i][j] then data[i][j]= {} end + data[i][j][k] = {node, _G.minetest.get_meta({x=i,y=j,z=k}):to_table()} + count = count +1; + end + end + end + end + say(count .. " nodes copied "); + elseif msg == "p" then -- paste + local x1 = math.min(paste.src1.x,paste.src2.x);local y1 = math.min(paste.src1.y,paste.src2.y);local z1 = math.min(paste.src1.z,paste.src2.z); + local x2 = math.max(paste.src1.x,paste.src2.x);local y2 = math.max(paste.src1.y,paste.src2.y);local z2 = math.max(paste.src1.z,paste.src2.z); + local count = 0; p.x = p.x-paste.ref.x; p.y = p.y-paste.ref.y; p.z = p.z-paste.ref.z; + for i = x1,x2 do + for j = y1,y2 do + for k = z1,z2 do + local pdata; + if data[i] and data[i][j] and data[i][j][k] then + pdata = data[i][j][k] + end + if pdata then + count = count + 1 + _G.minetest.set_node({x=i+p.x,y=j+p.y,z=k+p.z}, pdata[1]); + _G.minetest.get_meta({x=i+p.x,y=j+p.y,z=k+p.z}):from_table(pdata[2]) + end + + end + end + end + say(count .. " nodes pasted "); + end +end \ No newline at end of file diff --git a/scripts/craft_guide.lua b/scripts/craft_guide.lua new file mode 100644 index 0000000..fc088e1 --- /dev/null +++ b/scripts/craft_guide.lua @@ -0,0 +1,108 @@ +-- ROBOT craft guide by rnd, 2017 +if not list then + + tname = "rnd"; + list = {}; + tmplist = _G.minetest.registered_items; + for k,v in pairs(tmplist) do + local texture = v.inventory_image or ""; + if texture=="" and v.tiles then texture = v.tiles[1] or "" end + if (not v.groups.not_in_craft_guide or v.groups.not_in_craft_guide == 0) and type(texture)=="string" and texture~="" then + list[#list+1] = {_G.minetest.formspec_escape(k),_G.minetest.formspec_escape(v.description),_G.minetest.formspec_escape(texture)}; -- v.inventory_image, k, v.description + end + end + + + idx = 1; n = 35; row = 6; size = 1.25; + filter = "" item = "" recipeid = 1 + filterlist = {}; for i = 1,#list do filterlist[i] = i end + + get_texture = function(ritem) + local v = _G.minetest.registered_items[ritem]; if not v then return "" end + local texture = v.inventory_image or ""; + if texture=="" and v.tiles then texture = v.tiles[1] or "" end + if type(texture)~="string" then return "" end + return texture + end + + get_form = function() + local form = "size[7.5,8.5]"; + local x,y,i; local idxt = idx+n; if idxt > #filterlist then idxt = #filterlist end + for i = idx, idxt do + local id = filterlist[i]; + if list[id] and list[id][3] then + x = ((i-idx) % row) + y = (i-idx-x)/row; + form = form .. "image_button[".. x*size ..",".. y*size+0.75 .. ";"..size.."," .. size .. ";" .. list[id][3] ..";".."item"..";".. list[id][1] .."]" + end + end + form = form .. "textarea[0.25,0;2,0.75;filter;filter;"..filter .. "]" .. "button[2.,0;1,0.5;search;search]".. + "button[5.5,0;1,0.5;prev;PREV]" .. "button[6.5,0;1,0.5;next;NEXT]" .. "label[4,0;".. idx .. "-"..idxt .. "/" .. #filterlist.."]"; + return form + end + + get_recipe = function() + local form = "size[7.5,8.5]"; + local recipes = _G.minetest.get_all_craft_recipes(item); if not recipes then return end; + local recipe = recipes[recipeid]; if not recipe then return end + local items = recipe.items + local x,y,i; + for i = 0, 8 do + local ritem = items[i+1] or ""; local sritem = ""; + local j = string.find(ritem,":"); if j then sritem = string.sub(ritem,j+1) end; --ritem = _G.minetest.formspec_escape(ritem); + x = (i % 3) + y = (i-x)/3; + form = form .. "image_button[".. x*size ..",".. y*size+0.75 .. ";"..size.."," .. size .. ";" .. get_texture(ritem) ..";".."item"..";".. sritem .."]" + end + form = form .. "textarea[0.25,0;2,0.75;recipeid;recipeid ".. #recipes .. ";"..recipeid .. "]" .. "button[2.,0;1,0.5;go;go]".. + "label[3,0;" .. item .. "]" .. "button[6.5,0;1,0.5;back;BACK]" ; + return form + end + + s=0 +end + +if s==0 then + local p = find_player(4); s = 1 + if p then + self.show_form(p[1],get_form()) + else + self.remove() + end +end + + +sender,fields = self.read_form() +if sender then + + if fields.search then + filter = fields.filter or "" + filterlist = {}; + for i = 1,#list do + if string.find(list[i][1],filter) then filterlist[#filterlist+1] = i end + end + idx=1;self.show_form(sender,get_form()) + + elseif fields.prev then + idx = idx - n; if idx<1 then idx =#filterlist-n end + self.show_form(sender,get_form()) + elseif fields.next then + idx = idx+n; if idx > #filterlist then idx = 1 end + self.show_form(sender,get_form()) + elseif fields.back then + self.show_form(sender,get_form()) + elseif fields.recipeid then + recipeid = tonumber(fields.recipeid) or 1; + self.show_form(sender,get_recipe()) + elseif fields.item then + item = fields.item; + local recipes = _G.minetest.get_all_craft_recipes(item); + local count = 0; if recipes then count = #recipes end + if count>0 then + recipeid = 1 + self.show_form(sender,get_recipe() or "") + end + elseif fields.quit then + self.remove() + end +end \ No newline at end of file diff --git a/scripts/farm_walker.lua b/scripts/farm_walker.lua new file mode 100644 index 0000000..b0c8b00 --- /dev/null +++ b/scripts/farm_walker.lua @@ -0,0 +1,15 @@ +if not init then + init = true + angle = 90 + walk = {["default:dirt"] = 1} + stop = {["wool:white"] = 1} +end + +node = read_node.forward_down() +if walk[node] then + move.forward() +elseif stop[node] then + self.reset(); angle = 90 +else + turn.angle(angle);move.forward(); turn.angle(angle); angle = - angle +end \ No newline at end of file diff --git a/scripts/games/CTF_bot.lua b/scripts/games/CTF_bot.lua new file mode 100644 index 0000000..742b1d5 --- /dev/null +++ b/scripts/games/CTF_bot.lua @@ -0,0 +1,146 @@ + -- simple ctf robot, rnd + --instructions: build game arena and place blue/red buttons as flags. edit flag positions below + --you must register 'keyboard' events by placing robot with same 'id' as robot running this code at 'event register' positions - default (32*i,64*j+1, 32*k) + + if not ctf then + _G.minetest.forceload_block(self.pos(),true) + ctf = { + [1] = {state = 1, flagnode = "basic_robot:button8080FF", pos = {x=-18,y=502,z=110}, name = "blue", owner = "", score = 0}, -- team[1] + [2] = {state = 1, flagnode = "basic_robot:buttonFF8080", pos = {x=-18,y=502,z=80}, name = "red", owner = "", score = 0}, -- team[2] + } + + teams = {} -- example : {["rnd"] = {1,0, player, health points at start}}; -- team, ownership of flag + maxscore = 3; + t = 0 + teamid = 1; -- team selector when joining + + gamestate = 0; + self.listen(1) + self.spam(1) + + get_id = function(pos) + local range = 1000; + return pos.x + range*pos.y+range^2*pos.z + end + + flag_id = {}; for i = 1,#ctf do flag_id[get_id(ctf[i].pos)] = i end + + render_flags = function() + for i = 1,#ctf do minetest.set_node(ctf[i].pos, {name = ctf[i].flagnode}) end + end + + end + + if gamestate == 0 then -- welcome + say(colorize("red","#CAPTURE THE FLAG GAME. say '\\join' to join game. to start game one of joined players says '\\start'")) + gamestate = 1 + elseif gamestate == 1 then + speaker,msg = self.listen_msg() + if msg == "join" then + local pl = minetest.get_player_by_name(speaker); + teams[speaker] = {teamid, 0, pl,20};pl:set_hp(20) + local msg1 = ""; local msg2 = "" + for k,v in pairs(teams) do + if v[1] == 1 then msg1 = msg1 .. k .. " " elseif v[1] == 2 then msg2 = msg2 .. k .. " " end + end + + say(colorize("yellow","#CTF : " .. speaker .. " joined team " .. ctf[teamid].name .. ". TEAM " .. ctf[1].name .. ": " .. msg1 .. ", TEAM " .. ctf[2].name .. ": " .. msg2)) + teamid = 3-teamid; -- 1,2 + elseif msg == "start" then -- game start + if teams[speaker] then + gamestate = 2 + keyboard.get() -- clear keyboard buffer + say(colorize("red","#CTF GAME STARTED. GET ENEMY FLAG AND BRING IT BACK TO YOUR FLAG. DONT LET YOUR HEALTH GO BELOW 3 HEARTS OR YOU ARE OUT.")) + for k,_ in pairs(teams) do -- teleport players + local data = teams[k];data[3]:setpos( ctf[data[1]].pos ) + end + render_flags() + end + end + + elseif gamestate == 2 then + speaker,msg = self.listen_msg() + if msg == "score" then + local msg1 = ""; local msg2 = "" + for k,v in pairs(teams) do + if v[1] == 1 then msg1 = msg1 .. k .. " " elseif v[1] == 2 then msg2 = msg2 .. k .. " " end + end + say(colorize("yellow","SCORE " .. ctf[1].score .. "/" .. ctf[2].score) .."\n" .. colorize("yellow","TEAM " .. ctf[1].name .. ": " .. msg1 .. ", TEAM " .. ctf[2].name .. ": " .. msg2)) + end + + -- check player health + for k,v in pairs(teams) do + local hp = teams[k][3]:get_hp(); + if not hp or hp<6 then -- teams[k][4] + + local cflag = teams[k][2]; + if cflag>0 then -- drop flag + ctf[cflag].state = 1 + ctf[cflag].owner = "" + minetest.set_node(ctf[cflag].pos, {name = ctf[cflag].flagnode}) + say(colorize("red", "#CTF " .. k .. " dropped " .. ctf[cflag].name .. " flag!")) + end + if not hp then -- player left + say(colorize("yellow", "#CTF " .. k .. " left the game!")) + teams[k] = nil + else -- reset player + say(colorize("yellow", "#CTF " .. k .. " resetted!")) + v[2] = 0 -- player has no flag + v[3]:set_hp(20) + v[3]:setpos( ctf[v[1]].pos ) + end + + + + end + end + + + event = keyboard.get() + if event and teams[event.puncher] then + --say(serialize(event)) + local punch_id = get_id({x=event.x,y=event.y,z=event.z}); + local flag = flag_id[punch_id]; + if flag then + local state = ctf[flag].state + local puncher = event.puncher; + if state == 1 then -- flag is here, ready to be taken or capture of enemy flag + if teams[puncher][1] ~= flag then -- take + say(colorize("red","#CTF " .. puncher .. " has taken " .. ctf[flag].name .. " flag !")) + ctf[flag].state = 2; + ctf[flag].owner = puncher; + teams[puncher][2] = flag; + minetest.set_node(ctf[flag].pos, {name = "basic_robot:buttonFFFF80"}) + else -- capture? + if teams[puncher][2] > 0 then + local cflag = teams[puncher][2] -- puncher has this flag + local data = ctf[cflag]; + + local team = teams[puncher][1]; + ctf[team].score = ctf[team].score + 1 + ctf[team].owner = "" + ctf[cflag].state = 1; -- reset captured flag state + minetest.set_node(ctf[cflag].pos, {name = ctf[cflag].flagnode}) + teams[puncher][2] = 0 + say(colorize("orange","#CTF " .. puncher .. " has captured " .. data.name .. " flag! Team " .. ctf[team].name .. " has score " .. ctf[team].score )) + if ctf[team].score == maxscore then + say(colorize("yellow","#CTF: TEAM " .. ctf[team].name .. " WINS! ")) + gamestate = 3;t=5; -- intermission, duration 5 + + --reset + teams = {} + for i=1,#ctf do ctf[i].state = 1 ctf[i].score = 0 ctf[i].owner = "" end + end + + end + + end + + + end + end + --say(serialize(event)) + end + elseif gamestate == 3 then -- intermission + if t>0 then t=t-1 else gamestate = 0 end + end \ No newline at end of file diff --git a/scripts/games/battle_bot_arena.lua b/scripts/games/battle_bot_arena.lua new file mode 100644 index 0000000..a9ceb57 --- /dev/null +++ b/scripts/games/battle_bot_arena.lua @@ -0,0 +1,91 @@ +if not s then +-- init +bots = {[4] = {}, [5] = {}}; -- [type] = {{1,1,10}, {3,2,10}}; -- {x,y,hp} +arena = {}; --[x][z] = {type, idx} +for i = -10,10 do arena[i] = {} for j=-10,10 do arena[i][j] = {0,0} end end +centerpos = self.spawnpos(); centerpos.y = centerpos.y+2 +TYPE = 4; -- 4,5 defines which bots are on the move/attack +DIR = 1 +s=0 +t=0 +-- load user progs +_,script1 = book.read(1);_,script2 = book.read(2); +prog1, _ = _G.loadstring( script1 ); prog2, _ = _G.loadstring( script2 ); + + spawn_bot = function (x,z,type) + if arena[x] and arena[x][z] and arena[x][z][1] == 0 then + keyboard.set({x=centerpos.x+x,y=centerpos.y,z=centerpos.z+z},type) + table.insert(bots[type],{x,z,10}) + arena[x][z] = {type,#bots[type]} + else + return false + end + end + + move_bot = function (i,dx,dz) + local bot = bots[TYPE][i];if not bot then return false end + if math.abs(dx)>1 or math.abs(dz)>1 then return false end + local x1=bot[1]+dx; local z1=bot[2]+dz; + if math.abs(x1)>10 or math.abs(z1)>10 then return false end + if arena[x1] and arena[x1][z1] and arena[x1][z1][1] == 0 then else return false end + + keyboard.set({x=centerpos.x+bot[1],y=centerpos.y,z=centerpos.z+bot[2]},0); + keyboard.set({x=centerpos.x+x1,y=centerpos.y,z=centerpos.z+z1},TYPE); + arena[bot[1]][bot[2]] = {0,0} + arena[x1][z1] = {TYPE,i} + + bot[1]=x1;bot[2]=z1; + end + + attack_bot = function(i,dx,dz) + local bot = bots[TYPE][i];if not bot then return false end + if math.abs(dx)>1 or math.abs(dz)>1 then return false end + local x1=bot[1]+dx; local z1=bot[2]+dz; + if math.abs(x1)>10 or math.abs(z1)>10 then return false end + if arena[x1] and arena[x1][z1] and arena[x1][z1][1] == 0 then return false end + local type = arena[x1][z1][1]; local idx = arena[x1][z1][2]; + local tbot = bots[type][idx]; + if not tbot then return false end + tbot[3]=tbot[3]-5; + if tbot[3]<=0 then + keyboard.set({x=centerpos.x+tbot[1],y=centerpos.y,z=centerpos.z+tbot[2]},0); + table.remove(bots[type],idx); + arena[x1][z1] = {0,0} + end + end + + read_arena = function(x,z) + local data = arena[x][z]; + if not data then return end + return {data[1],data[2]}; + end + + read_bots = function (type, idx) + local data = bots[type][idx]; + if not data then return end + return {data[1],data[2],data[3]} + end +end + +if t%10 == 0 then + spawn_bot(0,-10,4) + spawn_bot(0,10,5) +end +t=t+1 +self.label(#bots[4] .. " " .. #bots[5]) + +-- PROGRAM RULES: +-- not allowed to modify api code: TYPE, bots,t,s, spawn_bot, move_bot, attack_bot, read_arena, read_bots +-- only allowed to move bot or attack, but not to dig/place + +TYPE = 4+(t%2); +DIR = - DIR + +if TYPE == 5 then + _G.setfenv(prog1, _G.basic_robot.data[self.name()].sandbox ) + _,err = pcall(prog1) +else + _G.setfenv(prog2, _G.basic_robot.data[self.name()].sandbox ) + _,err = pcall(prog2) +end +if err then say(err) self.remove() end \ No newline at end of file diff --git a/scripts/games/battle_minesweeper_game.lua b/scripts/games/battle_minesweeper_game.lua new file mode 100644 index 0000000..5eae232 --- /dev/null +++ b/scripts/games/battle_minesweeper_game.lua @@ -0,0 +1,189 @@ +if not data then + m=10;n=10; + players = {}; + paused = true + + turn = 2; + turntimeout = 5 + shorttimeout = 1 + shortround = false + turnmessage = "" + t = 0; + SIGNUP = 0; GAME = 1; INTERMISSION = 2 + state = SIGNUP + + t0 = _G.minetest.get_gametime();spawnpos = self.spawnpos() -- place mines + data = {}; + + init_game = function() + abilitypoints = {0,0}; -- points to use ability, step outside ring to use them in following round + shortround = false + data = {}; minescount = 32 + for i = 1, minescount do local i = math.random(m); local j = math.random(n); if not data[i] then data[i] = {} end; data[i][j] = 1; end + if not data[1] then data[1] = {} end if not data[2] then data[2] = {} end -- create 2x2 safe area + data[1][1] = 0;data[1][2] = 0;data[2][1] = 0;data[2][2] = 0; + + minescount = 0; + for i = 1,m do for j = 1,n do -- render game + if data[i] and data[i][j] == 1 then minescount = minescount + 1 end + if keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j})~="basic_robot:button808080" then + keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},2) + end + end end + keyboard.set({x=spawnpos.x+1,y=spawnpos.y,z=spawnpos.z+1},4) -- safe start spot + end + + get_mine_count = function(i,j) + if i<0 or i>m+1 or j<0 or j>n+1 then return 0 end; count = 0 + for k = -1,1 do for l = -1,1 do + if data[i+k] and data[i+k][j+l] == 1 then count = count +1 end + end end + return count + end + chk_mines = function() + local count = minescount; + for i=1,m do for j=1,n do + if keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j})=="basic_robot:buttonFF8080" and data[i] and data[i][j]==1 then + count=count-1 + end + end end + return count + end + + near_chat = function(msg) + + end + + greeting = function() + _G.minetest.chat_send_all(colorize("red", + "#BATTLE MINESWEEPER : two player battle in minesweeper. say join to play.\nRules: ".. + "1. each player has 5 second turn to make a move 2. if you dont make move you lose\n".. + "3. if you make move in other player turn you lose. 4. if you hit bomb or mark bomb falsely you lose\n".. + "5. by playing fast you get ability charge. when you collect 10 you can step out of ring when your round ends and opponent will only have 2s to play his turn." + )) + end + + player_lost = function () + for i=1,#players do + local player = _G.minetest.get_player_by_name(players[i]); + if player then player:setpos({x=spawnpos.x,y=spawnpos.y+10,z=spawnpos.z}) end + end + _G.minetest.sound_play("electric_zap",{pos=spawnpos, max_hear_distance = 100}) + state = INTERMISSION; t = 0 + end + + function change_turn() + shortround = false -- reset ability if activated + if turn == 1 then + _G.minetest.sound_play("default_break_glass",{pos=spawnpos, max_hear_distance = 100}) + else + _G.minetest.sound_play("default_cool_lava",{pos=spawnpos, max_hear_distance = 100}) + end + + if paused == false then + say(players[turn] .. " lost : didn't make a move fast enough "); + player_lost() + else + local player = minetest.get_player_by_name(players[turn]) + local ppos = player:getpos() + local x = ppos.x - spawnpos.x;local y = ppos.y - spawnpos.y;local z = ppos.z - spawnpos.z; + local points = abilitypoints[turn] + if x<1 or x>m or z<1 or z>n then -- outside area + local points = abilitypoints[turn] + if points>=10 then -- do we have enough points? + shortround = true -- next round will be shorter, 2s + _G.minetest.sound_play("grinder",{pos=spawnpos, max_hear_distance = 100}) + abilitypoints[turn] = abilitypoints[turn]-10 + end + end + + if turn == 1 then turn = 2 else turn = 1 end + turnmessage = "turn " .. turn .. " " .. players[turn] .. ",charge " .. abilitypoints[turn] .. "\n" + self.label(turnmessage) + t=0 + paused = false + end + end + + init_game() + greeting() + self.listen(1) +end + +if state == SIGNUP then + speaker,msg = self.listen_msg() + if speaker then + if msg == "join" then + players[#players+1] = speaker; + local plist = ""; for i=1,#players do plist = plist .. players[i] .. ", " end + _G.minetest.chat_send_all("BATTLE MINESWEEPER, current players : " .. plist) + + if #players >= 2 then + state = GAME + change_turn(); + keyboard.get(); t=0; + for i = 1, #players do + local player = _G.minetest.get_player_by_name(players[i]); + if player then player:setpos({x=spawnpos.x,y=spawnpos.y+1,z=spawnpos.z}) end + end + _G.minetest.chat_send_all(colorize("red","BATTLE MINESWEEPER " .. m .. "x" ..n .. " with " .. minescount .. " mines.\n" .. players[turn] .. " its your move!")) + init_game() + end + + end + end + +elseif state == GAME then + + t = t + 1; + if (t>turntimeout) or (shortround and t>shorttimeout) then -- change of turn + change_turn() + else + self.label(turnmessage .. " " .. (turntimeout-t+1)) + end + + event = keyboard.get(); + if event and event.type == 2 and not paused then + if event.puncher == players[turn] then + local x = event.x - spawnpos.x;local y = event.y - spawnpos.y;local z = event.z - spawnpos.z; + local points = abilitypoints[turn] + if x<1 or x>m or z<1 or z>n then -- outside area + else + local ppos = player.getpos(event.puncher) + + points = points + math.max(turntimeout-t-2,0); if points>40 then points = 40 end + abilitypoints[turn] = points + + if ppos and math.abs(ppos.x-event.x)<0.5 and math.abs(ppos.z-event.z)<0.5 then -- just mark mine + if data[x] and data[x][z] == 1 then + if keyboard.read({x=event.x,y=event.y,z=event.z})~="basic_robot:button808080" then + keyboard.set({x=event.x,y=event.y,z=event.z},2) + else + keyboard.set({x=event.x,y=event.y,z=event.z},3) + end + else + say(event.puncher .. " lost : marked a bomb where it was none! "); + player_lost() + end + else + if data[x] and data[x][z]==1 then + _G.minetest.sound_play("tnt_boom",{pos=spawnpos, max_hear_distance = 100}) + keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},3) + say(event.puncher .. " lost : punched a bomb! "); + player_lost() + else + local count = get_mine_count(x,z); + if count == 0 then keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},4) + else keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},7+count) end + end + end + end + paused = true + else + say(event.puncher .. " lost : played out of his/her turn"); player_lost() + end + end + +elseif state == INTERMISSION then + t=t+1; if t> 15 then state = SIGNUP;players = {}; paused = true; greeting() end +end \ No newline at end of file diff --git a/scripts/games/blackbox_game.lua b/scripts/games/blackbox_game.lua new file mode 100644 index 0000000..9ac9753 --- /dev/null +++ b/scripts/games/blackbox_game.lua @@ -0,0 +1,208 @@ +--black box by rnd, 03/18/2017 +--https://en.wikipedia.org/wiki/Black_Box_(game) + +if not data then + m=16;n=16; + atoms = 32 + attempts = 1;turn = 0; + spawnpos = self.spawnpos(); spawnpos.x = spawnpos.x-m/2; spawnpos.y = spawnpos.y+2; spawnpos.z = spawnpos.z-n/2 + + local players = find_player(5,spawnpos); + if not player then self.remove() else pname = players[1] end + + self.spam(1);t0 = _G.minetest.get_gametime(); + data = {}; + for i = 1,m do data[i]={}; for j = 1,n do data[i][j]=0 end end + + for i=1,atoms do -- put in atoms randomly + data[math.random(m)][math.random(n)] = 1 + end + + atoms = 0 + for i = 1,m do for j = 1,n do if data[i][j]==1 then atoms = atoms + 1 end end end + + render_board = function(mode) -- mode 0 : render without solution, 1: render solution + for i = 1,m do for j = 1,n do -- render game + if mode == 0 or data[i][j] == 0 then + if keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j})~="basic_robot:button808080" then + keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},2) + end + else + keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},3) + end + end end + end + + get_dirl = function(dir) + local dirl; -- direction left + if dir[1] > 0.5 then dirl = {0,-1} + elseif dir[1] < -0.5 then dirl = {0,1} + elseif dir[2] > 0.5 then dirl = {-1,0} + elseif dir[2] < -0.5 then dirl = {1,0} + end + return dirl + end + + read_pos = function(x,z) + if x<1 or x>m or z<1 or z>n then return nil end + return data[x][z] + end + + newdir = function(x,z,dir) -- where will ray go next + local retdir = {dir[1],dir[2]}; + local xf = x+dir[1]; local zf = z+dir[2] -- forward + local dirl = get_dirl(dir) + + local nodef = read_pos(xf,zf) + local nodel = read_pos(xf + dirl[1],zf + dirl[2]) + local noder = read_pos(xf - dirl[1],zf - dirl[2]) + if nodef == 1 then + retdir = {0,0} -- ray hit something + elseif nodel == 1 and noder ~= 1 then + retdir = {-dirl[1],-dirl[2]} + elseif nodel ~= 1 and noder == 1 then + retdir = {dirl[1],dirl[2]} + elseif nodel == 1 and noder == 1 then + retdir = {-dir[1],-dir[2]} + end + return retdir + end + + shootray = function(x,z,dir) + --say("ray starts " .. x .. " " .. z .. " dir " .. dir[1] .. " " .. dir[2]) + local xp = x; local zp = z; + local dirp = {dir[1],dir[2]}; + local maxstep = m*n; + + for i = 1,maxstep do + dirp = newdir(xp,zp,dirp); + if dirp[1]==0 and dirp[2]==0 then return -i end -- hit + xp=xp+dirp[1];zp=zp+dirp[2]; + if xp<1 or xp>m or zp<1 or zp>n then return i,{xp,zp} end -- out + end + return 0 -- hit + end + + count = 0; -- how many letters were used up + border_start_ray = function(x,z) + local rdir + if x==0 then rdir = {1,0} + elseif x == m+1 then rdir = {-1,0} + elseif z == 0 then rdir = {0,1} + elseif z == n+1 then rdir = {0,-1} + end + if rdir then + local result,out = shootray(x,z,rdir); + if result >= 0 then + + if out then + if out[1]==x and out[2]==z then -- got back where it originated, reflection + keyboard.set({x=spawnpos.x+out[1],y=spawnpos.y,z=spawnpos.z+out[2]},1); + else + if result<=1 then + keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},6); -- immediate bounce off + else + local nodename = "basic_robot:button_"..(65+count); + _G.minetest.set_node( + {x=spawnpos.x+out[1],y=spawnpos.y+1,z=spawnpos.z+out[2]}, + {name = nodename, param2 = 1}) + _G.minetest.set_node( + {x=spawnpos.x+x,y=spawnpos.y+1,z=spawnpos.z+z}, + {name = nodename, param2 = 1}) + count = count + 1; + keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},4); + keyboard.set({x=spawnpos.x+out[1],y=spawnpos.y,z=spawnpos.z+out[2]},4); + end + end + end + elseif result<0 then + keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},3); -- hit + end + end + end + + -- initial border loop and marking + + --render blue border + for i = 1,m do keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+0},5) keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+n+1},5) end + for j = 1,n do keyboard.set({x=spawnpos.x+0,y=spawnpos.y,z=spawnpos.z+j},5) keyboard.set({x=spawnpos.x+m+1,y=spawnpos.y,z=spawnpos.z+j},5) end + + for i = 1,m do keyboard.set({x=spawnpos.x+i,y=spawnpos.y+1,z=spawnpos.z+0},0) keyboard.set({x=spawnpos.x+i,y=spawnpos.y+1,z=spawnpos.z+n+1},0) end + for j = 1,n do keyboard.set({x=spawnpos.x+0,y=spawnpos.y+1,z=spawnpos.z+j},0) keyboard.set({x=spawnpos.x+m+1,y=spawnpos.y+1,z=spawnpos.z+j},0) end + + + z=0 -- bottom + for x = 1,m do + if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button8080FF" then + border_start_ray(x,z) + end + end + + x=m+1 -- right + for z = 1,n do + if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button8080FF" then + border_start_ray(x,z) + end + end + + z=n+1 -- top + for x = m,1,-1 do + if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button8080FF" then + border_start_ray(x,z) + end + end + + x=0 -- left + for z = n,1,-1 do + if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button8080FF" then + border_start_ray(x,z) + end + end + + check_solution = function() + for i = 1,m do + for j = 1,n do + if keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j}) == "basic_robot:buttonFF8080" then -- red + if data[i][j]~=1 then return false end + else + if data[i][j]~=0 then return false end + end + end + end + return true + end + + --render board + render_board(0) + keyboard.set({x=spawnpos.x,y=spawnpos.y,z=spawnpos.z-1},4) + keyboard.set({x=spawnpos.x+1,y=spawnpos.y,z=spawnpos.z-1},5) + self.label("BLACKBOX with " .. atoms .. " atoms") + +end + +event = keyboard.get(); +if event then + local x = event.x - spawnpos.x;local y = event.y - spawnpos.y;local z = event.z - spawnpos.z; + if x<1 or x>m or z<1 or z>n then + if event.type == 4 then + if check_solution() then + say("#BLACKBOX : CONGRATULATIONS! " .. event.puncher .. " found all atoms after " .. attempts .. " tries."); self.remove() + else + say("#BLACKBOX : " .. event.puncher .. " failed to detect atoms after " .. attempts .. " attempts.") + attempts = attempts+1 + end + elseif event.type == 5 then + say("#BLACKBOX : DISPLAYING SOLUTION",pname) + render_board(1) + self.remove() + end + else -- interior punch + nodetype = 2; + if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button808080" then + nodetype = 3 + end + keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},nodetype); + end + +end +::END:: \ No newline at end of file diff --git a/scripts/games/casino_bot.lua b/scripts/games/casino_bot.lua new file mode 100644 index 0000000..657cef4 --- /dev/null +++ b/scripts/games/casino_bot.lua @@ -0,0 +1,38 @@ +-- rnd 2017 +if not s then + s=0 + player0 =""; + reward = "default:gold_ingot 6" + price = "default:gold_ingot"; + self.spam(1) +end +if s==0 then + local player = find_player(5); + if player then + player=player[1] + if player~=player0 then + self.label("Hello " .. player .. ". Please insert one gold ingot in chest to play.\nYou need to roll 6 on dice to win 6 gold.") + player0 = player + end + else + self.label(colorize("red","Come and win 6 gold!")) + end + if check_inventory.forward(price) then + take.forward("default:gold_ingot"); + self.label("Thank you for your gold. rolling the dice!") + s=1 + end +elseif s==1 then + roll = math.random(6); + if roll == 6 then + self.label("#YOU WIN!") + say("#WE HAVE A WINNER! get 6 gold in chest!") + insert.forward(reward) + s=2 + else + self.label(":( you rolled " .. roll.. ". Put gold in to try again.") + s=0 + end +elseif s==2 then + if not check_inventory.forward(reward) then s=0 self.label("Please insert one gold to continue playing") end +end \ No newline at end of file diff --git a/scripts/games/connect4.lua b/scripts/games/connect4.lua new file mode 100644 index 0000000..fd3ebd6 --- /dev/null +++ b/scripts/games/connect4.lua @@ -0,0 +1,60 @@ +-- CONNECT, coded in 20 minutes by rnd +if not data then + m=8;n=8;turn = 0; num = 4; + self.spam(1);t0 = _G.minetest.get_gametime(); + spawnpos = self.spawnpos() -- place mines + state = 0; -- 0 signup 1 game + players = {}; + data = {}; + for i = 1,m do data[i]={}; for j = 1,n do data[i][j]=0 end end + for i = 1,m do for j = 1,n do -- render game + if keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j})~="basic_robot:button808080" then + keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},2) + end + end end + + get_count_in_dir = function(dir,x,y) + local r = num; -- num=4? in a row + local snode = data[x][y];local count = 1; + for j = 1,2 do + for i = 1,r-1 do + local x1 = x + dir[1]*i;local y1 = y + dir[2]*i; + if not data[x1] or not data[x1][y1] then break end; if data[x1][y1]~= snode then break end + count = count +1 + end + dir[1]=-dir[1];dir[2]=-dir[2]; + end + return count + end + + get_count = function(x,y) + local c1 = get_count_in_dir({0,1},x,y); local c2 = get_count_in_dir({1,0},x,y) + local c3 = get_count_in_dir({1,1},x,y); local c4 = get_count_in_dir({1,-1},x,y) + if c2>c1 then c1 = c2 end; if c3>c1 then c1 = c3 end; if c4>c1 then c1 = c4 end + return c1 + end + + self.label("CONNECT 4 : GREEN starts play. 2 players punch to join game.") +end + +event = keyboard.get(); +if event then + local x = event.x - spawnpos.x;local y = event.y - spawnpos.y;local z = event.z - spawnpos.z; + if x<1 or x>m or z<1 or z>n then + elseif event.type == 2 then --if event.type == 2 then + if state == 0 then + if #players<2 then players[#players+1] = event.puncher + else state = 1 end + if #players==2 then state = 1 end + end + keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},4+turn); + data[x][z] = 4+turn; + if get_count(x,z) == num then say("CONGRATULATIONS! " .. event.puncher .. " has "..num .. " in a row"); self.remove(); goto END end + turn = 1-turn + if state == 1 then + local msg = ""; if turn == 0 then msg = "GREEN " else msg = "BLUE" end + self.label(msg .. " : " .. players[turn+1]) + end + end +end +::END:: \ No newline at end of file diff --git a/scripts/games/fallout_hacking.lua b/scripts/games/fallout_hacking.lua new file mode 100644 index 0000000..54f1c37 --- /dev/null +++ b/scripts/games/fallout_hacking.lua @@ -0,0 +1,95 @@ +if not init then + init = true + + generate_random_string = function(n,m) + local ret = {}; + for i = 1,n do ret[i]=string.char(math.random(m)+96) end --24 + return table.concat(ret) + end + + get_similiarity = function(text1,text2) + local n = string.len(text1); + if string.len(text2)~=n then return 0 end + local ret = 0; + for i = 1,n do + if string.sub(text1,i,i) == string.sub(text2,i,i) then ret = ret + 1 end + end + return ret + end + + get_form = function() + + local n = #passlist; + + local frm = "label[0,0;" .. intro_msg .. "] " .. "label[0,8.5;" .. msg .. "] " + + for i = 1,10 do + frm = frm .. "button[0,".. (i)*0.75 ..";2,1;" .. i .. ";".. passlist[i] .. "] " + end + + for i = 11,n do + frm = frm .. "button[2,".. (i-11+1)*0.75 ..";2,1;" .. i .. ";".. passlist[i] .. "] " + end + + local form = "size[6," .. 9 .. "]" .. frm; + return form + end + + _G.math.randomseed(os.time()) + intro_msg = minetest.colorize("lawngreen","FALLOUT PASSWORD HACKING GAME\nmatch is both position and character.") + msg = "" --TEST\nTEST\nTEST"; + passlist = {}; passdict = {} + + n = 20; -- how many options + count = 0; + while count< n do + local pass = generate_random_string(5,5); -- password length, charset size + if not passdict[pass] then passlist[#passlist+1] = pass; passdict[pass] = true; count = count + 1 end + end + correct = math.random(n) + guesses = 0 + max_guesses = 4 + + rom.data = {}; + if not rom.data then rom.data = {} end + self.spam(1) + + local players = find_player(4); + if not players then say("#fallout hacking game: no players") self.remove() end + pname = players[1]; + say("#fallout hacking game, player " .. pname) + + --if rom.data[pname] then say("password is locked out!") self.remove() end + + self.show_form(pname,get_form()) + self.read_form() + +end + +sender,fields = self.read_form() + if sender and sender == pname then -- form event + local pl = _G.minetest.get_player_by_name(pname); + if pl then + local selected = 0 + for k,_ in pairs(fields) do if k~="quit" then selected = tonumber(k) break end end + + if selected>0 then + guesses = guesses + 1 + if selected == correct then + say("password " .. passlist[correct] .. " is correct! " .. guesses .. " guesses.") + self.show_form(pname, "size[1,1] label[0,0.5;" .. minetest.colorize("lawngreen", "ACCESS GRANTED") .. "]") + self.remove() + --correct: do something with player + else + msg = msg .. " " .. minetest.colorize("yellow",guesses .. ". " .. passlist[selected]) .. " (" .. get_similiarity(passlist[correct], passlist[selected]) .. " match)" + self.show_form(pname, get_form()) + end + if guesses>=max_guesses then + msg = minetest.colorize("red","A C C E S S D E N I E D!") + self.show_form(pname, get_form()) + say("too many false guesses. password locked out!") rom.data[pname] = 1; self.remove() + end + end + if fields.quit then self.remove() end + end + end \ No newline at end of file diff --git a/scripts/games/hacking_game.lua b/scripts/games/hacking_game.lua new file mode 100644 index 0000000..10d2aaa --- /dev/null +++ b/scripts/games/hacking_game.lua @@ -0,0 +1,97 @@ +-- 'hacking' game from Fallout, by rnd +if not init then + init = true + + generate_random_string = function(n,m) + local ret = {}; + for i = 1,n do ret[i]=string.char(math.random(m)+96) end --24 + return table.concat(ret) + end + + get_similiarity = function(text1,text2) + local n = string.len(text1); + if string.len(text2)~=n then return 0 end + local ret = 0; + for i = 1,n do + if string.sub(text1,i,i) == string.sub(text2,i,i) then ret = ret + 1 end + end + return ret + end + + get_form = function() + + local n = #passlist; + + local frm = "label[0,0;" .. intro_msg .. "] " .. "label[0,8.5;" .. msg .. "] " + + for i = 1,10 do + frm = frm .. "button[0,".. (i)*0.75 ..";2,1;" .. i .. ";".. passlist[i] .. "] " + end + + for i = 11,n do + frm = frm .. "button[2,".. (i-11+1)*0.75 ..";2,1;" .. i .. ";".. passlist[i] .. "] " + end + + local form = "size[4," .. 9 .. "]" .. frm; + return form + end + + _G.math.randomseed(os.time()) + intro_msg = minetest.colorize("lawngreen","FALLOUT PASSWORD HACKING GAME\nmatch is both position and character.") + msg = "" --TEST\nTEST\nTEST"; + passlist = {}; passdict = {} + + n = 20; -- how many options + count = 0; + while count< n do + local pass = generate_random_string(4,5); -- password length, charset size + if not passdict[pass] then passlist[#passlist+1] = pass; passdict[pass] = true; count = count + 1 end + end + correct = math.random(n) + guesses = 0 + max_guesses = 4 + + rom.data = {}; + if not rom.data then rom.data = {} end + self.spam(1) + + local players = find_player(4); + if not players then say("#fallout hacking game: no players") self.remove() end + pname = players[1]; + minetest.chat_send_player(pname,"#fallout hacking game, player " .. pname) + + --if rom.data[pname] then say("password is locked out!") self.remove() end + + self.show_form(pname,get_form()) + self.read_form() + +end + +sender,fields = self.read_form() + if sender and sender == pname then -- form event + local pl = _G.minetest.get_player_by_name(pname); + if pl then + local selected = 0 + for k,_ in pairs(fields) do if k~="quit" then selected = tonumber(k) break end end + + if selected>0 then + guesses = guesses + 1 + if selected == correct then + minetest.chat_send_player(pname,"password " .. passlist[correct] .. " is correct! " .. guesses .. " guesses.") + self.show_form(pname, "size[2,1] label[0,0.5;" .. minetest.colorize("lawngreen", "ACCESS GRANTED") .. "]") + self.remove() + --correct: do something with player + else + if guesses == 3 then msg = msg .. "\n" end + msg = msg .. " " .. minetest.colorize("yellow",guesses .. ". " .. passlist[selected]) .. " (" .. get_similiarity(passlist[correct], passlist[selected]) .. " match)" + self.show_form(pname, get_form()) + end + if guesses>=max_guesses then + msg = minetest.colorize("red","A C C E S S D E N I E D!") + self.show_form(pname, get_form()) + minetest.chat_send_player(pname,"too many false guesses. password locked out!") rom.data[pname] = 1; self.remove() + end + end + if fields.quit then self.remove() end + end + end \ No newline at end of file diff --git a/scripts/games/hide_and_seek.lua b/scripts/games/hide_and_seek.lua new file mode 100644 index 0000000..43d4d9d --- /dev/null +++ b/scripts/games/hide_and_seek.lua @@ -0,0 +1,106 @@ +--HIDE AND SEEK game robot, by rnd +if not gamemaster then + timeout = 10; + gamemaster = "rnd" + player_list = {}; + _G.minetest.chat_send_all("# HIDE AND SEEK .. say #hide to join play") + s=0;t=0; count = 0; + _G.minetest.forceload_block(self.pos(),true) + self.listen(1); self.label(colorize("yellow","HIDE&SEEK")) +end + +speaker,msg = self.listen_msg(); + +if s==0 then + if msg =="#hide" then + player_list[speaker]={}; + _G.minetest.chat_send_all("# HIDE AND SEEK: " .. speaker .. " joined the game") + local player = _G.minetest.get_player_by_name(speaker); + if player then + player:setpos({x=0,y=5,z=0});player:set_properties({nametag_color = "0x0"}) + end + + end + if msg == "#start" and speaker == gamemaster then s = 0.5 _G.minetest.chat_send_all("# HIDE AND SEEK STARTS in " .. timeout .. " SECONDS!") end + +elseif s==0.5 then + t=t+1 + if t==timeout then + t=0;s = 1; count = 0; + for pname,_ in pairs(player_list) do + local player = _G.minetest.get_player_by_name(pname); + if player then + player_list[pname].hp = player:get_hp(); + player_list[pname].pos = player:getpos() + player_list[pname].t = 0; + count = count+1 + end + end + if count == 1 then + gamemaster = false; _G.minetest.chat_send_all("# HIDE AND SEEK only 1 player, aborting.") + else + _G.minetest.chat_send_all(colorize("red","# HIDE AND SEEK STARTS NOW WITH " .. count .. " PLAYERS. You are out if: 1.your health changes, 2. leave spawn. If stay in same area for too long or you will be exposed.")) + end + + end +elseif s==1 then + players = _G.minetest.get_connected_players(); + count = 0; + for _,player in pairs(players) do + local name = player:get_player_name(); + local data = player_list[name]; + if data then + count=count+1 + local pos = player:getpos(); + local dist = math.max(math.abs(pos.x),math.abs(pos.y),math.abs(pos.z)); + if dist>50 or (not _G.minetest.get_player_by_name(name)) then + _G.minetest.chat_send_all("# HIDE AND SEEK: ".. name .. " is OUT! went too far away " ) + player:set_properties({nametag_color = "white"}) + player_list[name] = nil; + end + if data.hp ~= player:get_hp() then + _G.minetest.chat_send_all("# HIDE AND SEEK: ".. name .. " is OUT! his health changed!" ) + player:set_properties({nametag_color = "white"}) + player_list[name] = nil; + end + + --expose campers + local p = data.pos; + dist = math.max(math.abs(pos.x-p.x),math.abs(pos.y-p.y),math.abs(pos.z-p.z)); + --say( name .. " dist " .. dist .. " t " .. data.t) + if dist<8 then + data.t = data.t+1; + if not data.camp then + if data.t>15 and not data.camp then + _G.minetest.chat_send_player(name, "# HIDE AND SEEK: move in 5s or be exposed") + data.camp = true + end + elseif data.t>=20 then + pos.x=math.ceil(pos.x);pos.y=math.ceil(pos.y);pos.z=math.ceil(pos.z); + _G.minetest.chat_send_all("# HIDE AND SEEK: " .. name .. " is camping at " .. pos.x .. " " .. pos.z) + data.camp = false; data.t = 0 + end + else + data.t = 0; data.pos = player:getpos(); data.camp = false + end + + end + end + + self.label(count) + + if count<=1 then + if count==1 then + for name,_ in pairs(player_list) do + player0=_G.minetest.get_player_by_name(name) + _G.minetest.chat_send_all(colorize("red","****** HIDE AND SEEK: ".. name .. " wins ******")) + player0:set_properties({nametag_color = "white"}) + gamemaster = false; + end + else + _G.minetest.chat_send_all("# HIDE AND SEEK: no players left") + gamemaster = false; + end + end + +end \ No newline at end of file diff --git a/scripts/games/hide_and_seek_1.lua b/scripts/games/hide_and_seek_1.lua new file mode 100644 index 0000000..8582446 --- /dev/null +++ b/scripts/games/hide_and_seek_1.lua @@ -0,0 +1,151 @@ +--HIDE AND SEEK game robot +if not gamemaster then + timeout = 30; + gamemaster = "rnd" + gamepos = {x=0,y=5,z=0} + player_list = {}; + s=0;t=0; count = 0; + prize = "" + + get_players = function() + local msg = ""; + for name,_ in pairs(player_list) do + msg = msg .. " " .. name + end + return msg + end + + init_game = function() + + local msg = get_players(); + _G.minetest.chat_send_all(colorize("red","# HIDE AND SEEK : hide from everyone else who is playing. Winner gets DIAMONDS\nsay join to join play. say #start to start game.".. + " players: " .. msg)) + s=0;t=0; + end + + init_game() + _G.minetest.forceload_block(self.pos(),true) + self.listen(1); self.label(colorize("yellow","HIDE&SEEK")) +end + +speaker,msg = self.listen_msg(); + +if s==0 then + + t = t +1 + if t%30 == 0 then + init_game(); + end + + if msg =="join" then + player_list[speaker]={}; + _G.minetest.chat_send_all(colorize("red","# HIDE AND SEEK: " .. speaker .. " joined the game")); + _G.minetest.chat_send_all("players: " .. get_players()) + + local player = _G.minetest.get_player_by_name(speaker); + count = count + 1 + if player then + player:setpos(gamepos);player:set_properties({nametag_color = "0x0"}) + player:set_hp(20) + local inv = player:get_inventory();inv:set_list("main",{}) + end + + end + if msg == "#start" and count>1 then s = 0.5 _G.minetest.chat_send_all(colorize("red","# HIDE AND SEEK STARTS in " .. timeout .. " SECONDS!")) end + +elseif s==0.5 then + t=t+1 + if t==timeout then + t=0;s = 1; count = 0; + for pname,_ in pairs(player_list) do + local player = _G.minetest.get_player_by_name(pname); + if player then + player_list[pname].hp = player:get_hp(); + player_list[pname].pos = player:getpos() + player_list[pname].t = 0; + count = count+1 + end + end + if count == 1 then + gamemaster = false; _G.minetest.chat_send_all("# HIDE AND SEEK only 1 player, aborting.") + else + prize = "default:diamond " .. (count-1); + _G.minetest.chat_send_all(colorize("red","# HIDE AND SEEK STARTS NOW WITH " .. count .. " PLAYERS.".. + "You are out if: 1.your health changes, 2. leave game area. If stay in same area for too long or you will be exposed.")) + _G.minetest.chat_send_all(colorize("red","# WINNER WILL GET " .. prize)) + + end + + end +elseif s==1 then + players = _G.minetest.get_connected_players(); + count = 0; + for _,player in pairs(players) do + local name = player:get_player_name(); + local data = player_list[name]; + if data then + count=count+1 + local pos = player:getpos(); + local dist = math.max(math.abs(pos.x-gamepos.x),math.abs(pos.y-gamepos.y),math.abs(pos.z-gamepos.z)); + if dist>100 or (not _G.minetest.get_player_by_name(name)) then + _G.minetest.chat_send_all("# HIDE AND SEEK: ".. name .. " is OUT! went too far away " ) + player:set_properties({nametag_color = "white"}) + player_list[name] = nil; + _G.minetest.chat_send_all("remaining players: " .. get_players()) + end + if data.hp ~= player:get_hp() then + _G.minetest.chat_send_all("# HIDE AND SEEK: ".. name .. " is OUT! his health changed!" ) + player:set_properties({nametag_color = "white"}) + player_list[name] = nil; + _G.minetest.chat_send_all("remaining players: " .. get_players()) + player:setpos({x=0,y=5,z=0}) + end + + --expose campers + local p = data.pos; + dist = math.max(math.abs(pos.x-p.x),math.abs(pos.y-p.y),math.abs(pos.z-p.z)); + --say( name .. " dist " .. dist .. " t " .. data.t) + if dist<8 then + data.t = data.t+1; + if not data.camp then + if data.t>25 and not data.camp then + _G.minetest.chat_send_player(name, "# HIDE AND SEEK: move in 5s or be exposed") + data.camp = true + end + elseif data.t>=30 then + pos.x=math.ceil(pos.x);pos.y=math.ceil(pos.y);pos.z=math.ceil(pos.z); + _G.minetest.chat_send_all("# HIDE AND SEEK: " .. name .. " is camping at " .. pos.x .. " " .. pos.z) + data.camp = false; data.t = 0 + end + else + data.t = 0; data.pos = player:getpos(); data.camp = false + end + + end + end + + self.label(count) + + if count<=1 then + if count==1 then + for name,_ in pairs(player_list) do + local player0=_G.minetest.get_player_by_name(name) + if player0 then + _G.minetest.chat_send_all(colorize("red","****** HIDE AND SEEK: ".. name .. " wins ******")) + local inv = player0:get_inventory(); + inv:add_item("main",_G.ItemStack(prize)) + player0:set_properties({nametag_color = "white"}) + player0:setpos({x=0,y=5,z=0}) + end + s=2 + end + else + _G.minetest.chat_send_all("# HIDE AND SEEK: no players left") + s=2 + end + end + +elseif s==2 then + player_list = {} + init_game() +end \ No newline at end of file diff --git a/scripts/games/maze_generator.lua b/scripts/games/maze_generator.lua new file mode 100644 index 0000000..3b377c1 --- /dev/null +++ b/scripts/games/maze_generator.lua @@ -0,0 +1,138 @@ +-- maze generation by rnd + +-- http://en.wikipedia.org/wiki/Maze_generation_algorithm#Depth-first_search, recursive backtracker +-- representation of node coordinate (row,coloumn)=(i,j) -> (i-1)*n+j, i=1..n, j=1...m +-- representation of walls: below node k --> k, left of node k --> k+m.n + +-- good overview of maze generation algorithms using javascript/html5 +-- http://www.jamisbuck.org/presentations/rubyconf2011/index.html#recursive-backtracker + +-- helper functions +--stack in lua +local stack={}; +function stack.push(s,e) s[#s+1]=e end +function stack.pop(s) local r = s[#s];s[#s]=nil;return r end +--function table2string(s) local r = ""; for i,v in pairs(s) do r = r.. " ["..i.."]=".. v ; end return r end + +function maze_deep_first_search(m,n,start,seed) -- returns a table of strings representing line renders + + local steps,maxsteps; steps= 0; maxsteps = 999999; + local maze = {} + maze.m = m; maze.n = n; + maze.unvisited = {};maze.stack = {}; maze.walls = {}; + maze.free = maze.m*maze.n; + local i,j,k + local nb,wall -- unvisited neighbbors, walls + + --init structures + for i=1,maze.m do + for j =1,maze.n do + k=(i-1)*maze.n+j;maze.unvisited[k]=true -- initially all cells unvisited + maze.walls[k]=true;maze.walls[k+maze.n*maze.m]=true; -- walls are there + end + end + + _G.math.randomseed(seed) + maze.current = start + maze.unvisited [ maze.current ] = false; + maze.free = maze.free-1; maze.stack[1+#maze.stack] = maze.current + + while maze.free>0 and steps1 then -- down + k=(i-2)*maze.n+j; if maze.unvisited[k] then wall[#wall+1]=k+maze.n;nb[#nb+1]=k end + end + if i1 then --left + k=(i-1)*maze.n+j-1; if maze.unvisited[k] then wall[#wall+1]=k+1+maze.n*maze.m;nb[#nb+1]=k end + end + + --print(" unvisited neighbors " .. table2string(nb)) + if (#nb)>0 then -- if unvisited neighbors, choose random one as next current node + stack.push(maze.stack,maze.current) -- remember previous current node + k=math.random(#nb); -- pick random unvisited neighbor + maze.walls[wall[k]]=false; -- remove wall + --print(" removed wall ".. wall[k]) + k=nb[k]; + maze.current = k; -- new current cell + maze.unvisited[k]=false; maze.free = maze.free-1 -- one less unvisited + --print("new explore " .. k); + + elseif (#maze.stack)~=0 then -- no unvisited neighbors, backtrack using stack + + maze.current = stack.pop(maze.stack) + --print("backtrack to "..maze.current) + + else -- even stack is empty, just pick random unvisited cell + k = math.random(maze.free); j=1; + for i =1,maze.m*maze.n do + if maze.unvisited[i] then + if j==k then k=i; break end -- pick node + j=j+1 + end + end + --print(" stack empty, random pick " ..k) + maze.current=k;maze.unvisited[k]=false; maze.free = maze.free -1; + end + end -- of do + + -- render maze with chars, row by row + maze.ret = {}; + local hor;local vert; + local wall = "1" + + for i=1,maze.m do + hor="";vert=""; + k= (i-1)*maze.n; + -- horizontal + for j = 1, maze.n do + k=k+1; + -- if maze.walls[k+maze.n*maze.m] then vert=vert.."X." else vert=vert.. "0." end + -- if maze.walls[k] then hor=hor.."XX" else hor=hor.."X0" end + if maze.walls[k+maze.n*maze.m] then vert=vert..wall.."0" else vert=vert.. "00" end + if maze.walls[k] then hor=hor..wall..wall else hor=hor..wall.."0" end + end + maze.ret[1+#maze.ret]=hor..wall;maze.ret[1+#maze.ret]=vert..wall; + end + maze.ret[1+#maze.ret] = string.rep(wall,2*maze.n+1) + return maze.ret + end + +-- RUN PROGRAM + local maze=maze_deep_first_search(10,30,1,2015) + --for _,v in pairs(maze) do print(v) end + + + +make_maze = function(m,n,start,seed) + local pos = self.spawnpos();pos.y=pos.y+1 + local p + local maze=maze_deep_first_search(m,n,start,seed) -- m,n,start,seed + local i,j,k;local p = {x=pos.x,y=pos.y,z=pos.z}; + for i,v in pairs(maze) do + p.x = pos.x+i + for k = 1,string.len(v) do + p.z=pos.z+k + if string.sub(v,k,k)=="1" then + minetest.set_node(p,{name="default:brick"}) + else minetest.set_node(p,{name="air"}) + end + end + end +end + +make_maze(10,10,1,1) +self.remove() \ No newline at end of file diff --git a/scripts/games/mensch_argere_dich_nicht.lua b/scripts/games/mensch_argere_dich_nicht.lua new file mode 100644 index 0000000..2c3a6cb --- /dev/null +++ b/scripts/games/mensch_argere_dich_nicht.lua @@ -0,0 +1,89 @@ + +if not init then + + msg = + "'Mensch argere Dich nicht' is a German board game (but not a German-style board game), developed by Josef Friedrich Schmidt in 1907/1908.\n".. + " The players throw a die in turn and can advance any of their pieces in the game by the thrown number of dots on the dice.\n" .. + "Throwing a six means bringing a piece into the game (by placing one from the 'out' area onto the 'start' field) and throwing the dice again. If\n" .. + "a piece is on the 'start' field and there are still pieces in the 'out' area, it must be moved as soon as possible. If a piece cannot be\n".. "brought into the game then any other piece in the game must be moved by the thrown number, if that is possible. Pay attention that throwing\n".." dice continuously without moving is forbidden and by each dice throw you have to make a move.\n" .. + "Pieces can jump over other pieces, and throw out pieces from other players (into that player's 'out' area) if they land on them. A player\n".. "cannot throw out his own pieces though, he can advance further than the last field in the 'home' row. A player can be thrown out if he is on\n".. + "his 'start' field.\n" .. + "Variation which is played by most players: A player who has no pieces in the game has 3 tries to throw a six" + self.label(msg) + + init = true; + state = 1; -- game on + step = 0; + punchstate = 1; -- first punch + punchpos = {} + pos = self.spawnpos() + dice = 0 + spawns = {{2,2,"basic_robot:buttonFF8080"},{2,11,"basic_robot:button8080FF"},{11,11,"basic_robot:button80FF80"},{11,2,"basic_robot:buttonFFFF80"}} + + for i = 1,12 do for j = 1,12 do + minetest.swap_node({x=pos.x+i,y=pos.y+1,z=pos.z+j},{name = "air"}) + end end + + for k = 1,#spawns do + for i = 0,1 do for j = 0,1 do + minetest.swap_node({x=pos.x+i+spawns[k][1],y=pos.y+1,z=pos.z+j+spawns[k][2]},{name = spawns[k][3]}) + end end + end + + keyboard.set({x=pos.x+7,y=pos.y+1,z=pos.z+7},7) + +end + +if state == 0 then +elseif state == 1 then + event = keyboard.get(); + if event then + x = event.x-pos.x; y = event.y-pos.y; z = event.z-pos.z + --say("type " .. event.type .. " pos " .. x .. " " .. y .. " " .. z) + if x == 7 and y == 1 and z == 7 then + _G.math.randomseed(os.time()) + dice = math.random(6); + keyboard.set({x=pos.x+7,y=pos.y+1,z=pos.z+7},7+dice) + step = step + 1; + msg = colorize("red","") .. " STEP " .. step .. ": " ..event.puncher .. " threw dice = " .. dice; + minetest.chat_send_all(msg) + self.label(msg) + punchstate = 1 + elseif punchstate == 1 then + if y == 1 and event.type ~= 2 and event.type<7 then + punchpos = 2; minetest.chat_send_player(event.puncher,colorize("red","") .. " punch place on board where to move ") + punchpos = {x=event.x,y=event.y,z=event.z} + punchstate = 2 + end + elseif punchstate == 2 then + if y == 0 and event.type ~= 2 then + if x<2 or x>12 or z<2 or z>12 then + else + local nodename = minetest.get_node(punchpos).name; + minetest.swap_node({x=event.x, y = event.y+1, z=event.z},{name = nodename}) + minetest.swap_node(punchpos,{name = "air"}) + punchstate = 1; dice = 0 + minetest.add_particle( + { + pos = punchpos, + expirationtime = 15, + velocity = {x=0, y=0,z=0}, + size = 18, + texture = "default_apple.png", + acceleration = {x=0,y=0,z=0}, + collisiondetection = true, + collision_removal = true, + } + ) + msg = colorize("red","") .. " " .. event.puncher .. " moved."; + minetest.chat_send_all(msg) + self.label(msg) + end + end + + + end + end + + +end \ No newline at end of file diff --git a/scripts/games/minesweeper_game.lua b/scripts/games/minesweeper_game.lua new file mode 100644 index 0000000..7f72bbe --- /dev/null +++ b/scripts/games/minesweeper_game.lua @@ -0,0 +1,83 @@ +-- minesweeper +if not data then + m=24;n=22; minescount = m*n/5; + reward = 30; + + if not find_player(4) then error("minesweeper: no players near") end + + self.spam(1) + t0 = _G.minetest.get_gametime(); + data = {}; spawnpos = self.spawnpos() -- place mines + for i = 1, minescount do local i = math.random(m); local j = math.random(n); if not data[i] then data[i] = {} end; data[i][j] = 1; end + if not data[1] then data[1] = {} end if not data[2] then data[2] = {} end -- create 2x2 safe area + data[1][1] = 0;data[1][2] = 0;data[2][1] = 0;data[2][2] = 0; + + minescount = 0; + for i = 1,m do for j = 1,n do -- render game + if data[i] and data[i][j] == 1 then minescount = minescount + 1 end + if keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j})~="basic_robot:button808080" then + puzzle.set_node({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},{name = "basic_robot:button808080"}) + end + end end + puzzle.set_node({x=spawnpos.x+1,y=spawnpos.y,z=spawnpos.z+1},{name = "basic_robot:button80FF80"}) + + get_mine_count = function(i,j) + if i<0 or i>m+1 or j<0 or j>n+1 then return 0 end; count = 0 + for k = -1,1 do for l = -1,1 do + if data[i+k] and data[i+k][j+l] == 1 then count = count +1 end + end end + return count + end + chk_mines = function() + local count = minescount; + for i=1,m do for j=1,n do + if keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j})=="basic_robot:buttonFF8080" and data[i] and data[i][j]==1 then + count=count-1 + end + end end + return count + end + say("minesweeper " .. m .. "x" ..n .. " with " .. minescount .. " mines ") + self.label("find all hidden mines! mark mine by standing on top of block and punch,\notherwise it will uncover the block (and possibly explode).") + +end + +event = keyboard.get(); +if event then + local x = event.x - spawnpos.x;local y = event.y - spawnpos.y;local z = event.z - spawnpos.z; + if x<1 or x>m or z<1 or z>n then + if x == 0 and z == 1 then + local count = chk_mines(); + if count == 0 then + t0 = _G.minetest.get_gametime() - t0; + say("congratulations! " .. event.puncher .. " discovered all mines in " .. t0 .. " s") + _G.minetest.add_item({x=spawnpos.x,y=spawnpos.y+1,z=spawnpos.z},_G.ItemStack("default:gold_ingot "..reward)) -- diamond reward + else + reward = reward*(1-(count/minescount))^(1.5); reward = math.floor(reward); + say("FAIL! " .. count .. " mines remaining. You get " .. reward .. " gold for found mines") + _G.minetest.add_item({x=spawnpos.x,y=spawnpos.y+1,z=spawnpos.z},_G.ItemStack("default:gold_ingot "..reward)) -- diamond reward + end + self.remove() + end + else --if event.type == 2 then + local ppos = player.getpos(event.puncher) + if ppos and math.abs(ppos.x-event.x)<0.5 and math.abs(ppos.z-event.z)<0.5 then -- just mark mine + if keyboard.read({x=event.x,y=event.y,z=event.z})~="basic_robot:button808080" then + puzzle.set_node({x=event.x,y=event.y,z=event.z},{name = "basic_robot:button808080"}) + else + puzzle.set_node({x=event.x,y=event.y,z=event.z},{name = "basic_robot:buttonFF8080"}) + end + else + if data[x] and data[x][z]==1 then + say("boom! "..event.puncher .. " is dead ");puzzle.set_node({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},{name = "basic_robot:buttonFF8080"}); + local player_ = puzzle.get_player(event.puncher); + player_:setpos({x=spawnpos.x-1,y=spawnpos.y+1,z=spawnpos.z-1}); + self.remove() + else + local count = get_mine_count(x,z); + if count == 0 then puzzle.set_node({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},{name = "basic_robot:button80FF80"}) + else puzzle.set_node({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},{name = "basic_robot:button"..count}) end + end + end + end +end \ No newline at end of file diff --git a/scripts/games/nonogram.lua b/scripts/games/nonogram.lua new file mode 100644 index 0000000..111766f --- /dev/null +++ b/scripts/games/nonogram.lua @@ -0,0 +1,264 @@ +-- nonogram game, created in 1hr 40 min by rnd + +-- INIT +if not grid then + n=6 + solved = false -- do we render solution or blank? +-- _G.math.randomseed(3) + + self.spam(1) + function get_score_from_string(score) + --say(score) + local scores = {}; + local j=1; --j k l + for i=0,5 do -- 0 - 999 1 - 999 2 - 999 3 - 999 4 - 999 5 - 999 + j = string.find(score," ", j+1); + local k = string.find(score," ", j+1); + local l = string.find(score," ", k+1); + if i==5 then l = string.len(score)+1 end + scores[i] = {string.sub(score,j+1,k-1),tonumber(string.sub(score,k+1,l-1))}; + j=l + end + return scores + end + + if not rom.score then _,rom.score = book.read(1) end + if not rom.score then rom.score = "0 - 999 1 - 999 2 - 999 3 - 999 4 - 999 5 - 999" end + highscore = get_score_from_string(rom.score) + --self.label(string.gsub(_G.dump(highscore), "\n","")) + + function get_score_string(scores) + local out = "" + for i = 0,5 do + out = out .. i .. " " .. + scores[i][1] .. " " .. + scores[i][2] .. " " + end + return out + end + + t0 = _G.minetest.get_gametime() + local intro ="numbers at beginning of each row (coloumn) tell how many\nred blocks are together in each row ( coloumn )." .. + "\npunch gray blocks to toggle them and reveal hidden red blocks.\npunch green to check solution. If you give up punch blue."; + self.label(intro) + + grid = {} + spawnpos = self.spawnpos(); + offsetx = 10 - math.ceil(n/2); offsetz = math.floor(n/2); + spawnpos.x = spawnpos.x - offsetx; spawnpos.z = spawnpos.z - offsetz; + spawnpos.y = spawnpos.y+3 + + for i=1,n do + grid[i]={}; + for j=1,n do + grid[i][j]=math.random(2)-1 + end + end + + getcounts = function(grid) + local rowdata = {}; + for i=1,n do + rowdata[i]={}; local data = rowdata[i]; + local s=0;local c=0; + for j = 1, n do + if s == 0 and grid[i][j]==1 then s=1;c=0 end + if s == 1 then + if grid[i][j]==1 then + c=c+1 + if j == n then data[#data+1]=c end + else + data[#data+1]=c; s=0 + end + + end + end + end + local coldata = {}; + for j=1,n do + coldata[j]={}; local data = coldata[j]; + local s=0;local c=0; + for i = 1, n do + if s == 0 and grid[i][j]==1 then s=1;c=0 end + if s == 1 then + if grid[i][j]==1 then + c=c+1 + if i == n then data[#data+1]=c end + else + data[#data+1]=c; s=0 + end + + end + end + end + return rowdata,coldata + end + + read_field = function() + local grid = {}; + for i = 1, n do + grid[i]={}; + for j = 1,n do + local typ = keyboard.read({x=spawnpos.x+j,y=spawnpos.y,z=spawnpos.z+i}); + if typ == "basic_robot:button808080" then grid[i][j] = 0 else grid[i][j] = 1 end + end + end + return grid + end + + rowdata,coldata = getcounts(grid) + + check_solution = function() + local rdata,cdata; + rdata,cdata = getcounts(read_field()) + for i = 1,#rdata do + if #rdata[i]~=#rowdata[i] then return false end + for j = 1, #rdata[i] do + if rdata[i][j]~=rowdata[i][j] then return false end + end + end + + for i = 1,#cdata do + if #cdata[i]~=#coldata[i] then return false end + for j = 1, #rdata[i] do + if cdata[i][j]~=coldata[i][j] then return false end + end + end + return true + end + + get_difficulty = function() + local easy = 0; + for k = 1, n do + local sum=0 + for i = 1,#rowdata[k]-1 do + sum = sum + rowdata[k][i]+1; + end + if #rowdata[k]>0 then sum = sum + rowdata[k][#rowdata[k]] else sum = n end + if sum == n then easy = easy + 1 end + end + + for k = 1, n do + local sum=0 + for i = 1,#coldata[k]-1 do + sum = sum + coldata[k][i]+1; + end + if #coldata[k]>0 then sum = sum + coldata[k][#coldata[k]] else sum = n end + if sum == n then easy = easy + 1 end + end + easy = 5-easy; + if easy < 0 then easy = 0 end + return easy + end + + -- render game + for i=1,n do + for j =1,n do + keyboard.set({x=spawnpos.x-n+j,y=spawnpos.y,z=spawnpos.z+i},0) -- clear + keyboard.set({x=spawnpos.x+j,y=spawnpos.y,z=spawnpos.z+2*n-i+1},0) -- clear + local typ; + if grid[j][i]==0 then typ = 2 else typ = 3 end + if not solved then typ = 2 end + keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},typ) --board + end + end + + --render counts rows + for i=1,n do + length = #rowdata[i] + for k = 1,length do + keyboard.set({x=spawnpos.x-length+k,y=spawnpos.y,z=spawnpos.z+i},rowdata[i][k]+7) + end + end + --render counts coloumns + for j=1,n do + length = #coldata[j] + for k = 1,length do + keyboard.set({x=spawnpos.x+j,y=spawnpos.y,z=spawnpos.z+k+n},coldata[j][k]+7) + end + end + keyboard.set({x=spawnpos.x+1,y=spawnpos.y,z=spawnpos.z},4) -- game check button + keyboard.set({x=spawnpos.x+2,y=spawnpos.y,z=spawnpos.z},5) -- game check button + + local players = find_player(4,spawnpos) + if not players then error("minesweeper: no players near") end + local pname = players[1]; + + + --self.label() + + --self.label(string.gsub(_G.dump(read_field()),"\n","") ) + difficulty = get_difficulty() + reward = 0; limit = 0; + + if difficulty == 5 then limit = 120 reward = 10 + elseif difficulty == 4 then limit = 115 reward = 9 -- 60s + elseif difficulty == 3 then limit = 100 reward = 8 + elseif difficulty == 2 then limit = 80 reward = 7 + elseif difficulty <= 1 then limit = 70 reward = 6 + end + minetest.chat_send_player(pname, "nonogram difficulty " .. difficulty .. ". you will get " .. reward .. " gold if you solve it in faster than " .. limit .."s" .. + ". Current record " .. highscore[difficulty][2] .. " by " .. highscore[difficulty][1]) + +end + +event = keyboard.get() +if event then + if event.y == spawnpos.y and event.z == spawnpos.z then + if event.x == spawnpos.x+1 then -- check solution + if check_solution() then + t = _G.minetest.get_gametime(); t = t- t0; + local msg = ""; + keyboard.set({x=spawnpos.x+1,y=spawnpos.y,z=spawnpos.z},2) + msg = n .. "x" .. n .. " nonogram (difficuly " .. difficulty .. ") solved by " .. event.puncher .. " in " .. t .. " seconds. " + + if t < limit then + msg = msg .. " He gets " .. reward .. " gold for quick solve."; + else + reward = reward*2*(1-2*(t-limit)/limit)/2; if reward<0 then reward = 0 end + reward = math.floor(reward); + msg = msg .. " Your time was more than " .. limit .. ", you get " .. reward .. " gold "; + end + + -- highscore + if t0 then + local player = _G.minetest.get_player_by_name(event.puncher); + if player then + local inv = player:get_inventory(); + inv:add_item("main",_G.ItemStack("default:gold_ingot " .. reward)) + end + end + minetest.chat_send_player(event.puncher,msg) + + self.remove() + + else self.label("FAIL") end + elseif event.x == spawnpos.x+2 then -- solve + minetest.chat_send_player(event.puncher,"you gave up on game, displaying solution") + for i=1,n do + for j =1,n do + local typ; + if grid[j][i]==0 then typ = 2 else typ = 3 end + keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},typ) + end + end + self.remove() + end + else + local i = event.x-spawnpos.x;local j = event.z-spawnpos.z; + if i>0 and i<=n and j>0 and j<=n then + local typ = keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j}); + local newtyp; + if typ == "basic_robot:button808080" then newtyp = 3 + else newtyp = 2 + end + keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},newtyp); + end + end +end \ No newline at end of file diff --git a/scripts/games/paint_game.lua b/scripts/games/paint_game.lua new file mode 100644 index 0000000..0b7075e --- /dev/null +++ b/scripts/games/paint_game.lua @@ -0,0 +1,112 @@ +-- paint canvas by rnd, 2018 +if not init then + colors = { + "black","blue","brown","cyan","dark_green","dark_grey","green","grey", + "magenta","orange","pink","red","violet","white","yellow" + } + invcolors = {}; for i = 1,#colors do invcolors[colors[i]] = i end + + color = 1; + size = 16; + + init = true + + local ent = _G.basic_robot.data[self.name()].obj:get_luaentity(); + ent.timestep = 0.5 + + players = find_player(5); if not players then self.remove() end + player = _G.minetest.get_player_by_name(players[1]) + self.label("-> " .. players[1]) + + spos = self.spawnpos(); spos.y=spos.y+1; + + canvasn = "wool:white" + reset_canvas = function() + for i = 1, size do + for j = 1, size do + minetest.set_node({x=spos.x +i , y = spos.y + j, z = spos.z },{name = canvasn}) + end + end + end + reset_canvas() + + save_image = function() + local ret = {}; + for i = 1, size do + for j = 1, size do + local nname = string.sub(minetest.get_node({x=spos.x +i , y = spos.y + j, z = spos.z }).name,6) + local pcolor = invcolors[nname] or 1; + ret[#ret+1]= string.char(96+pcolor) + end + end + return table.concat(ret,"") + end + + load_image = function(image) + if not image then return end + local ret = {}; local k = 0; + for i = 1, size do + for j = 1, size do + k=k+1; + local pcolor = colors[string.byte(image,k)-96] or "black"; + minetest.set_node({x=spos.x +i , y = spos.y + j, z = spos.z },{name = "wool:"..pcolor}) + end + end + end + + + --draw buttons + for i = 1,#colors do + minetest.set_node({x=spos.x +i , y = spos.y , z = spos.z },{name = "wool:"..colors[i]}) + end + + minetest.set_node({x=spos.x +1 , y = spos.y-1 , z = spos.z },{name = "basic_robot:button_83"}) + minetest.set_node({x=spos.x +2 , y = spos.y-1 , z = spos.z },{name = "basic_robot:button_76"}) + + + vn = {x=0,y=0,z=1}; + T0 = {x=spos.x+0.5,y=spos.y+0.5,z=spos.z-0.5*vn.z}; + + get_intersect = function(vn, T0, p, v) + local a = (T0.x-p.x)*vn.x + (T0.y-p.y)*vn.y + (T0.z-p.z)*vn.z; + local b = vn.x*v.x + vn.y*v.y + vn.z*v.z + if b<=0 then return nil end + if a<=0 then return nil end + local t = a / b + return {x = p.x+v.x*t, y= p.y+v.y*t, z = p.z+v.z*t} + end + +end + +if player:get_player_control().LMB then -- player interacts with 'virtual canvas gui' + local v = player:get_look_dir(); + local p = player:get_pos(); p.y = p.y + 1.5 + local c = get_intersect(vn,T0,p,v); + if c then + + local x = c.x - T0.x; local y = c.y - T0.y + if x>0 and x-2 and y0 then -- above: painting + c.z = c.z+0.5 + minetest.set_node(c, {name = "wool:" .. colors[color]}) + elseif y>-1 then -- color selection + x = 1+math.floor(x) + if colors[x] then + color = x; + self.label(colors[x]) + end + else -- save,load button row + x = 1+math.floor(x) + if x==1 then + self.label("SAVED.") + book.write(1,"ROBOT_IMAGE",save_image()) + elseif x==2 then + local _,image = book.read(1) + load_image(image); + self.label("LOADED.") + end + end + end + + end +end \ No newline at end of file diff --git a/scripts/games/sliding_puzzle_game.lua b/scripts/games/sliding_puzzle_game.lua new file mode 100644 index 0000000..46f37a9 --- /dev/null +++ b/scripts/games/sliding_puzzle_game.lua @@ -0,0 +1,101 @@ +-- sliding unscramble game by rnd, made in 20 minutes +if not init then + reward = "default:gold_ingot" + size = 3; + + init = true + spos = self.spawnpos(); spos.y = spos.y + 1 + board = {}; + local players = find_player(4); + if not players then say("#sliding puzzle game: no players") self.remove() end + name = players[1]; + + minetest.chat_send_player(name, "#SLIDING GAME: try to sort numbers in increasing order, starting from top left") + + create_board = function(n) + local k = 0; + local ret = scramble(n*n, os.time()) + for i = 1,n do + board[i]={}; + for j = 1,n do + k=k+1 + board[i][j]=7+ret[k] -- 7 numbers, 82 letters + end + end + board[math.random(n)][math.random(n)] = 0 + end + + render_board = function() + local n = #board; + for i = 1,n do + for j = 1,n do + keyboard.set({x=spos.x+i, y = spos.y, z=spos.z+j}, board[i][j]) + end + end + end + + check_score = function() -- check how many places are increasing in order, starting top left + local n = #board; + local cmax = 0; + local score = 0; + for j = n,1,-1 do + for i = 1,n do + local b = board[i][j]; + if b==0 or b1 and board[i-1][j] == 0 then return i-1,j end + if i<#board and board[i+1][j] == 0 then return i+1,j end + if j>1 and board[i][j-1] == 0 then return i,j-1 end + if j<#board and board[i][j+1] == 0 then return i,j+1 end + return nil + end + + + scramble = function(n,seed) + _G.math.randomseed(seed); + local ret = {}; for i = 1,n do ret[i]=i end + for j = n,2,-1 do + local k = math.random(j); + if k~=j then + local tmp = ret[k]; ret[k] = ret[j]; ret[j] = tmp + end + end + return ret + end + + create_board(size) + render_board() + +end + +event = keyboard.get(); +if event and event.y == spos.y then + local x = event.x-spos.x; + local z = event.z-spos.z; + if x<1 or x>size or z<1 or z>size then + else + local i,j = find_hole(x,z); + if i then + local tmp = board[x][z]; + keyboard.set({x=spos.x+x, y = spos.y, z=spos.z+z}, board[i][j]) + board[x][z] = board[i][j] + board[i][j] = tmp; + keyboard.set({x=spos.x+i, y = spos.y, z=spos.z+j}, tmp) + end + local score = check_score() + self.label("score : " .. score) + if score >= size*size-2 then + minetest.chat_send_player(name, "CONGRATULATIONS! YOU SOLVED PUZZLE. REWARD WAS DROPPED ON TOP OF ROBOT.") + pos = self.pos(); pos.y = pos.y+2; + minetest.add_item(pos, _G.ItemStack(reward)) + self.remove() + end + end + +end \ No newline at end of file diff --git a/scripts/games/sokoban_game.lua b/scripts/games/sokoban_game.lua new file mode 100644 index 0000000..503d3c0 --- /dev/null +++ b/scripts/games/sokoban_game.lua @@ -0,0 +1,189 @@ + -- SOKOBAN GAME, by rnd, robots port + + + if not sokoban then + sokoban = {}; + local players = find_player(8); + if not players then error("sokoban: no player near") end + name = players[1]; + + -- self.show_form(name, + -- "size[2,1.25]".. + -- "label[0,0;SELECT LEVEL 1-90]".. + -- "field[0.25,1;1,1;LVL;LEVEL;1]".. + -- "button_exit[1.25,0.75;1,1;OK;OK]" + -- ) + state = 1 -- will wait for form receive otherwise game play + self.label("stand close to white box and punch it one time to push it. you can only push 1 box\nand cant pull. goal is to get all white boxes pushed on aspen blocks") + + player_ = puzzle.get_player(name); -- get player entity - player must be present in area + player_:set_eye_offset({x=0,y=0,z=0},{x=0,y=0,z=0});player_:set_physics_override({jump=1}) -- reset player + + + self.spam(1) + sokoban.push_time = 0 + sokoban.blocks = 0;sokoban.level = 0; sokoban.moves=0; + imax = 0; jmax = 0 + + sokoban.load=0;sokoban.playername =""; sokoban.pos = {}; + SOKOBAN_WALL = "moreblocks:cactus_brick" + SOKOBAN_FLOOR = "default:silver_sandstone" + SOKOBAN_GOAL = "default:aspen_tree" + SOKOBAN_BOX = "basic_robot:buttonFFFFFF" + + load_level = function(lvl) + + local pos = self.spawnpos(); pos.x=pos.x+1;pos.y=pos.y+1; + sokoban.pos = pos; + sokoban.playername = name + + if lvl == nil then return end + if lvl <0 or lvl >89 then return end + + local file = _G.io.open(minetest.get_modpath("basic_robot").."\\scripts\\sokoban.txt","r") + if not file then return end + local str = ""; local s; local p = {x=pos.x,y=pos.y,z=pos.z}; local i,j;i=0; + local lvl_found = false + while str~= nil do + str = file:read("*line"); + if str~=nil and str =="; "..lvl then lvl_found=true break end + end + if not lvl_found then file:close();return end + + sokoban.blocks = 0;sokoban.level = lvl+1; sokoban.moves=0; + imax=0; jmax = 0; + while str~= nil do + str = file:read("*line"); + if str~=nil then + if string.sub(str,1,1)==";" then + imax=i; + file:close(); + player_:set_physics_override({jump=0}) + player_:set_eye_offset({x=0,y=20,z=0},{x=0,y=0,z=0}); + return + end + i=i+1; + if string.len(str)>jmax then jmax = string.len(str) end -- determine max dimensions + for j = 1,string.len(str) do + p.x=pos.x+i;p.y=pos.y; p.z=pos.z+j; s=string.sub(str,j,j); + p.y=p.y-1; + if puzzle.get_node(p).name~=SOKOBAN_FLOOR then puzzle.set_node(p,{name=SOKOBAN_FLOOR}); end -- clear floor + p.y=p.y+1; + if s==" " and puzzle.get_node(p).name~="air" then puzzle.set_node(p,{name="air"}) end + if s=="#" and puzzle.get_node(p).name~=SOKOBAN_WALL then puzzle.set_node(p,{name=SOKOBAN_WALL}) end + if s=="$" then puzzle.set_node(p,{name=SOKOBAN_BOX});sokoban.blocks=sokoban.blocks+1 end + if s=="." then p.y=p.y-1;puzzle.set_node(p,{name=SOKOBAN_GOAL}); p.y=p.y+1;puzzle.set_node(p,{name="air"}) end + --starting position + if s=="@" then + player_:setpos({x=p.x,y=p.y-0.5,z=p.z}); -- move player to start position + --p.y=p.y-1;puzzle.set_node(p,{name="default:glass"}); + puzzle.set_node(p,{name="air"}) + p.y=p.y+1;puzzle.set_node(p,{name="air"}) + --p.y=p.y+2;puzzle.set_node(p,{name="default:ladder"}) + end + if s~="@" then p.y = pos.y+2;puzzle.set_node(p,{name="air"}); -- ceiling default:obsidian_glass + else --p.y=pos.y+2;puzzle.set_node(p,{name="default:ladder"}) + end -- roof above to block jumps + + end + end + end + + file:close(); + end + + clear_game = function() + local pos = self.spawnpos(); pos.x=pos.x+1;pos.y=pos.y+1; + for i = 1, 20 do + for j = 1,20 do + local node = minetest.get_node({x=pos.x+i,y=pos.y-1,z=pos.z+j}).name + if node ~= "default:silver_sandstone" then minetest.set_node({x=pos.x+i,y=pos.y-1,z=pos.z+j}, {name = "default:silver_sandstone"}) end + node = minetest.get_node({x=pos.x+i,y=pos.y,z=pos.z+j}).name + if node ~= "air" then minetest.set_node({x=pos.x+i,y=pos.y,z=pos.z+j}, {name = "air"}) end + end + end + end + + end + + +if state == 1 then + clear_game() + load_level(20) + state = 0 + self.label("stand close to white box and punch it one time to push it. you can only push 1 box\nand cant pull. goal is to get all white boxes pushed on aspen blocks") +else + + local ppos = player_:getpos() + if math.abs(ppos.y-sokoban.pos.y)~= 0.5 then minetest.chat_send_player(name,colorize("red", "SOKOBAN: " .. name .. " QUITS ! ")); + player_:set_eye_offset({x=0,y=0,z=0},{x=0,y=0,z=0});player_:set_physics_override({jump=1}); self.remove() end + + event = keyboard.get(); + + if event then + + local pname = event.puncher + if pname ~= name then goto quit end + local pos = {x=event.x, y = event.y, z = event.z}; + local p=player.getpos(pname);local q={x=pos.x,y=pos.y,z=pos.z} + p.x=p.x-q.x;p.y=p.y-q.y;p.z=p.z-q.z + if math.abs(p.y+0.5)>0 then goto quit end + if math.abs(p.x)>math.abs(p.z) then -- determine push direction + if p.z<-0.5 or p.z>0.5 or math.abs(p.x)>1.5 then goto quit end + if p.x+q.x>q.x then q.x= q.x-1 + else q.x = q.x+1 + end + else + if p.x<-0.5 or p.x>0.5 or math.abs(p.z)>1.5 then goto quit end + if p.z+q.z>q.z then q.z= q.z-1 + else q.z = q.z+1 + end + end + + + if minetest.get_node(q).name=="air" then -- push crate + sokoban.moves = sokoban.moves+1 + local old_infotext = minetest.get_meta(pos):get_string("infotext"); + minetest.set_node(pos,{name="air"}) + minetest.set_node(q,{name=SOKOBAN_BOX}) + minetest.sound_play("default_dig_dig_immediate", {pos=q,gain=1.0,max_hear_distance = 24,}) -- sound of pushing + local meta = minetest.get_meta(q); + q.y=q.y-1; + if minetest.get_node(q).name==SOKOBAN_GOAL then + if old_infotext~="GOAL REACHED" then + sokoban.blocks = sokoban.blocks -1; + end + meta:set_string("infotext", "GOAL REACHED") + else + if old_infotext=="GOAL REACHED" then + sokoban.blocks = sokoban.blocks +1 + end + --meta:set_string("infotext", "push crate on top of goal block") + end + end + + if sokoban.blocks~=0 then -- how many blocks left + --say("move " .. sokoban.moves .. " : " ..sokoban.blocks .. " crates left "); + else + say("games: ".. name .. " just solved sokoban level ".. sokoban.level .. " in " .. sokoban.moves .. " moves."); + player_:set_physics_override({jump=1}) + player_:set_eye_offset({x=0,y=0,z=0},{x=0,y=0,z=0}) + + local player = _G.minetest.get_player_by_name(event.puncher); + if player then + local inv = player:get_inventory(); + inv:add_item("main",_G.ItemStack("skyblock:sokoban 4 ")) + end + + local i,j; + for i = 1,imax do + for j=1,jmax do + minetest.set_node({x= sokoban.pos.x+i,y=sokoban.pos.y,z=sokoban.pos.z+j}, {name = "air"}); -- clear level + end + end + + sokoban.playername = ""; sokoban.level = 1 + end + ::quit:: + end +end \ No newline at end of file diff --git a/scripts/games/switching_game.lua b/scripts/games/switching_game.lua new file mode 100644 index 0000000..64c95d3 --- /dev/null +++ b/scripts/games/switching_game.lua @@ -0,0 +1,101 @@ +--[[ +SWITCHING GAME by rnd, 2018 + +lights: +0110 + +switches, each one toggles certain lights like: s1 1001 (toggles light with 1) + +PROBLEM: +hit switches in correct order to turn on all lights + +GENERATE RANDOM CHALLENGE: +start with all lights on and apply random sequence of switches + +TODO: instead of simply 0/1 switches have ones that advance +1 mod p (p can be say 3 or more) + + +REMARKS: application of 2 different switches is commutative ( obvious, since just x->x+1 mod p) +--]] +if not init then + +init = true +numlights = 2; +numswitches = 2; +states = 10; + +lights = {}; -- states of lights, initialy 1,1,...,1 +for i = 1, numlights do lights[i] = 0 end +switches = {} + +--switches = {{1,0,0,1},{1,1,1,1}}; +make_random_switches = function(lights, switches,count) + for i = 1, count do + switches[i] = {}; + local switch = switches[i]; + for j = 1, #lights do switch[j] = math.random(states)-1 end + end +end +make_random_switches(lights,switches, numswitches) + + +pos = self.spawnpos(); pos.x = pos.x + 1;-- pos.z = pos.z + 1 + +apply_switch = function(switches,lights,idx) + local switch = switches[idx]; + for i = 1, #switch do + local state = lights[i] + switch[i]; + if state >= states then state = state - states end + lights[i] = state + end +end + +randomize = function(switches, lights, steps) -- randomize lights + for i = 1, steps do + local idx = math.random(#switches); + apply_switch(switches,lights,idx); + end +end + +render_lights = function() for i = 1, #lights do keyboard.set({x=pos.x+i-1,y=pos.y+1, z=pos.z}, 7+lights[i]) end end +render_switches = function(mode) + if mode then + for i = 1, #switches do keyboard.set({x=pos.x+i-1,y=pos.y, z=pos.z}, 1+i) end + else + for i = 1, #switches do keyboard.set({x=pos.x+i-1,y=pos.y, z=pos.z}, 0) end + end +end + +check_lights = function() + for i = 1, #lights do if lights[i] ~= 0 then return false end end + return true +end +step = 0 + +randomize(switches,lights, math.min((#switches)^states,10000)) +if check_lights() then randomize(switches,lights, #switches + states) end + +render_lights(); render_switches(true) + + +self.label("GOAL OF GAME: punch buttons with numbers in correct order to turn all blocks to 0") + +--self.label(serialize(switches)) +end + + +event = keyboard.get() +if event then + local idx = event.x-pos.x+1; + if event.y==pos.y and idx>=1 and idx <= #switches then + apply_switch(switches, lights, idx) + render_lights() + step = step + 1 + if check_lights() then + self.label("DONE IN " .. step .. " STEPS !") + render_switches(false) + else + self.label("STEP " .. step) + end + end +end \ No newline at end of file diff --git a/scripts/games/tank_bot.lua b/scripts/games/tank_bot.lua new file mode 100644 index 0000000..8e2356f --- /dev/null +++ b/scripts/games/tank_bot.lua @@ -0,0 +1,85 @@ +-- rnd 2017 +-- instructions: put 7 buttons around bot(top one left empty) +-- clockwise: empty, green, yellow,blue, red, blue,yellow,green. +-- those buttons serve as controls + + if not s then + name = self.name(); + direction = 1; + s=0; + self.label("TANK ROBOT. control with colored buttons") + user=find_player(4); if user then user = user[1] end + + speed = 7 + math.random(7);turn.angle(math.random(360)); + pitch = 0 + + gravity = 1+math.random(2); + if user then + say("TANK ROBOT, ready. ".. user .. " in control") + else + say("no player found nearby. deactivating"); self.remove() + s=-1 + end + pos = self.spawnpos(); + end + + ppos = player.getpos(user); ppos.x=ppos.x-pos.x;ppos.y=ppos.y-pos.y;ppos.z=ppos.z-pos.z; + if ppos.x^2+ppos.y^2+ppos.z^2>10 then + local obj = _G.minetest.get_player_by_name(user); + if obj then say("deserter " .. user .. " killed for abandoning the tank!") obj:set_hp(0) end + self.remove() + else + local obj = _G.minetest.get_player_by_name(user); + if obj then + if obj:get_hp() == 0 then + say("TANK DESTROYED!") + self.remove() + end + end + end + + if s == 0 then + event = keyboard.get(); + if event and event.puncher==user then + --self.label(event.x-pos.x .. " " .. event.y-pos.y .. " " .. event.z-pos.z .. " T " .. event.type) + event.x = event.x-pos.x;event.y = event.y-pos.y;event.z = event.z-pos.z; + if event.x == 0 and event.y == 0 and event.z == 1 then + self.fire(speed, pitch,gravity) + s=1;self.label("BOOM") + _G.minetest.sound_play("tnt_explode",{pos = self.pos(), max_hear_distance = 256, gain = 1}) + elseif event.x == direction*1 and event.y == 0 and event.z == direction*1 then + turn.angle(2) + elseif event.x == -1*direction and event.y == 0 and event.z == 1*direction then + turn.angle(-2) + elseif event.x == 1*direction and event.y == 0 and event.z == 0 then + turn.angle(40) + elseif event.x == -1*direction and event.y == 0 and event.z == 0 then + turn.angle(-40) + elseif event.x == 1*direction and event.y == 0 and event.z == -1*direction then + pitch = pitch + 5; if pitch> 85 then pitch = 85 end + self.label("pitch " .. pitch) + elseif event.x == -1*direction and event.y == 0 and event.z == -1*direction then + pitch = pitch - 5; if pitch<-10 then pitch = -10 end + self.label("pitch " .. pitch) + end + end + end + + if s == 1 then + local pos = self.fire_pos(); + if pos then + self.label("HIT") + msg = ""; + _G.minetest.sound_play("tnt_explode",{pos = pos, max_hear_distance = 256, gain = 1}) + + local objs=_G.minetest.get_objects_inside_radius(pos, 4); + for _,obj in pairs(objs) do + if obj:is_player() then + obj:set_hp(0) + msg = msg .. obj:get_player_name() .. " is dead, " + end + end + s = 0 + if msg~="" then say(msg) end + end + end \ No newline at end of file diff --git a/scripts/graphics/painting.lua b/scripts/graphics/painting.lua new file mode 100644 index 0000000..0b7075e --- /dev/null +++ b/scripts/graphics/painting.lua @@ -0,0 +1,112 @@ +-- paint canvas by rnd, 2018 +if not init then + colors = { + "black","blue","brown","cyan","dark_green","dark_grey","green","grey", + "magenta","orange","pink","red","violet","white","yellow" + } + invcolors = {}; for i = 1,#colors do invcolors[colors[i]] = i end + + color = 1; + size = 16; + + init = true + + local ent = _G.basic_robot.data[self.name()].obj:get_luaentity(); + ent.timestep = 0.5 + + players = find_player(5); if not players then self.remove() end + player = _G.minetest.get_player_by_name(players[1]) + self.label("-> " .. players[1]) + + spos = self.spawnpos(); spos.y=spos.y+1; + + canvasn = "wool:white" + reset_canvas = function() + for i = 1, size do + for j = 1, size do + minetest.set_node({x=spos.x +i , y = spos.y + j, z = spos.z },{name = canvasn}) + end + end + end + reset_canvas() + + save_image = function() + local ret = {}; + for i = 1, size do + for j = 1, size do + local nname = string.sub(minetest.get_node({x=spos.x +i , y = spos.y + j, z = spos.z }).name,6) + local pcolor = invcolors[nname] or 1; + ret[#ret+1]= string.char(96+pcolor) + end + end + return table.concat(ret,"") + end + + load_image = function(image) + if not image then return end + local ret = {}; local k = 0; + for i = 1, size do + for j = 1, size do + k=k+1; + local pcolor = colors[string.byte(image,k)-96] or "black"; + minetest.set_node({x=spos.x +i , y = spos.y + j, z = spos.z },{name = "wool:"..pcolor}) + end + end + end + + + --draw buttons + for i = 1,#colors do + minetest.set_node({x=spos.x +i , y = spos.y , z = spos.z },{name = "wool:"..colors[i]}) + end + + minetest.set_node({x=spos.x +1 , y = spos.y-1 , z = spos.z },{name = "basic_robot:button_83"}) + minetest.set_node({x=spos.x +2 , y = spos.y-1 , z = spos.z },{name = "basic_robot:button_76"}) + + + vn = {x=0,y=0,z=1}; + T0 = {x=spos.x+0.5,y=spos.y+0.5,z=spos.z-0.5*vn.z}; + + get_intersect = function(vn, T0, p, v) + local a = (T0.x-p.x)*vn.x + (T0.y-p.y)*vn.y + (T0.z-p.z)*vn.z; + local b = vn.x*v.x + vn.y*v.y + vn.z*v.z + if b<=0 then return nil end + if a<=0 then return nil end + local t = a / b + return {x = p.x+v.x*t, y= p.y+v.y*t, z = p.z+v.z*t} + end + +end + +if player:get_player_control().LMB then -- player interacts with 'virtual canvas gui' + local v = player:get_look_dir(); + local p = player:get_pos(); p.y = p.y + 1.5 + local c = get_intersect(vn,T0,p,v); + if c then + + local x = c.x - T0.x; local y = c.y - T0.y + if x>0 and x-2 and y0 then -- above: painting + c.z = c.z+0.5 + minetest.set_node(c, {name = "wool:" .. colors[color]}) + elseif y>-1 then -- color selection + x = 1+math.floor(x) + if colors[x] then + color = x; + self.label(colors[x]) + end + else -- save,load button row + x = 1+math.floor(x) + if x==1 then + self.label("SAVED.") + book.write(1,"ROBOT_IMAGE",save_image()) + elseif x==2 then + local _,image = book.read(1) + load_image(image); + self.label("LOADED.") + end + end + end + + end +end \ No newline at end of file diff --git a/scripts/graphics/painting_import.lua b/scripts/graphics/painting_import.lua new file mode 100644 index 0000000..43e3519 --- /dev/null +++ b/scripts/graphics/painting_import.lua @@ -0,0 +1,58 @@ +-- painting import from minetest 'painting mod' to robot canvas by rnd +-- stand near image and run "get_texture()" command in remote control + +if not init then + self.label("PAINTING IMPORTER") + pname = "rnd" + player = minetest.get_player_by_name(pname) + + get_texture = function() + + + local pos = player:get_pos(); local radius = 2 + local objs = minetest.get_objects_inside_radius(pos, radius) + local obj = {}; + + local ret = {}; + for i=1,#objs do + if not objs[i]:is_player() then obj = objs[i] break end + end + + if obj then + local tex = obj:get_properties().textures + local out = tex[1] or "" + if string.sub(out,1,9) == "[combine:" then + local pcolors = {"black","blue","brown","cyan","darkgreen","darkgrey","green","grey", + "magenta","orange","pink","red","violet","white","yellow"} + local ipcolors = {}; for i = 1,#pcolors do ipcolors[pcolors[i]] = i end + + local ret = {}; + local i =0; local j = 1; local k = 0; local size = 16; + --ret[1] = {} + for word in out:gmatch("=(%a+)%.png") do + ret[#ret+1] = string.char(96 + (ipcolors[word] or 1)) + end + + local rret = {}; + for i = 1, size do rret[i] = {} for j = 1,size do rret[i][j] = 0 end end + + k = 0 -- rotate 90 right + for j = 1,size do + for i = size,1,-1 do + k = k + 1 + rret[size-i+1][size-j+1] = ret[k] + end + end + + ret = {}; for i = 1, size do for j = 1, size do ret[#ret+1]= rret[i][j] end end -- write back + + out = table.concat(ret,"") + book.write(1,"IMPORTED_PAINTING", out) + minetest.chat_send_player(pname, "PAINTING FOUND, saved in robot library in book 1.") + end + else return "empty" + end + end + + init = true +end \ No newline at end of file diff --git a/scripts/gui/craft_guide.lua b/scripts/gui/craft_guide.lua new file mode 100644 index 0000000..fc088e1 --- /dev/null +++ b/scripts/gui/craft_guide.lua @@ -0,0 +1,108 @@ +-- ROBOT craft guide by rnd, 2017 +if not list then + + tname = "rnd"; + list = {}; + tmplist = _G.minetest.registered_items; + for k,v in pairs(tmplist) do + local texture = v.inventory_image or ""; + if texture=="" and v.tiles then texture = v.tiles[1] or "" end + if (not v.groups.not_in_craft_guide or v.groups.not_in_craft_guide == 0) and type(texture)=="string" and texture~="" then + list[#list+1] = {_G.minetest.formspec_escape(k),_G.minetest.formspec_escape(v.description),_G.minetest.formspec_escape(texture)}; -- v.inventory_image, k, v.description + end + end + + + idx = 1; n = 35; row = 6; size = 1.25; + filter = "" item = "" recipeid = 1 + filterlist = {}; for i = 1,#list do filterlist[i] = i end + + get_texture = function(ritem) + local v = _G.minetest.registered_items[ritem]; if not v then return "" end + local texture = v.inventory_image or ""; + if texture=="" and v.tiles then texture = v.tiles[1] or "" end + if type(texture)~="string" then return "" end + return texture + end + + get_form = function() + local form = "size[7.5,8.5]"; + local x,y,i; local idxt = idx+n; if idxt > #filterlist then idxt = #filterlist end + for i = idx, idxt do + local id = filterlist[i]; + if list[id] and list[id][3] then + x = ((i-idx) % row) + y = (i-idx-x)/row; + form = form .. "image_button[".. x*size ..",".. y*size+0.75 .. ";"..size.."," .. size .. ";" .. list[id][3] ..";".."item"..";".. list[id][1] .."]" + end + end + form = form .. "textarea[0.25,0;2,0.75;filter;filter;"..filter .. "]" .. "button[2.,0;1,0.5;search;search]".. + "button[5.5,0;1,0.5;prev;PREV]" .. "button[6.5,0;1,0.5;next;NEXT]" .. "label[4,0;".. idx .. "-"..idxt .. "/" .. #filterlist.."]"; + return form + end + + get_recipe = function() + local form = "size[7.5,8.5]"; + local recipes = _G.minetest.get_all_craft_recipes(item); if not recipes then return end; + local recipe = recipes[recipeid]; if not recipe then return end + local items = recipe.items + local x,y,i; + for i = 0, 8 do + local ritem = items[i+1] or ""; local sritem = ""; + local j = string.find(ritem,":"); if j then sritem = string.sub(ritem,j+1) end; --ritem = _G.minetest.formspec_escape(ritem); + x = (i % 3) + y = (i-x)/3; + form = form .. "image_button[".. x*size ..",".. y*size+0.75 .. ";"..size.."," .. size .. ";" .. get_texture(ritem) ..";".."item"..";".. sritem .."]" + end + form = form .. "textarea[0.25,0;2,0.75;recipeid;recipeid ".. #recipes .. ";"..recipeid .. "]" .. "button[2.,0;1,0.5;go;go]".. + "label[3,0;" .. item .. "]" .. "button[6.5,0;1,0.5;back;BACK]" ; + return form + end + + s=0 +end + +if s==0 then + local p = find_player(4); s = 1 + if p then + self.show_form(p[1],get_form()) + else + self.remove() + end +end + + +sender,fields = self.read_form() +if sender then + + if fields.search then + filter = fields.filter or "" + filterlist = {}; + for i = 1,#list do + if string.find(list[i][1],filter) then filterlist[#filterlist+1] = i end + end + idx=1;self.show_form(sender,get_form()) + + elseif fields.prev then + idx = idx - n; if idx<1 then idx =#filterlist-n end + self.show_form(sender,get_form()) + elseif fields.next then + idx = idx+n; if idx > #filterlist then idx = 1 end + self.show_form(sender,get_form()) + elseif fields.back then + self.show_form(sender,get_form()) + elseif fields.recipeid then + recipeid = tonumber(fields.recipeid) or 1; + self.show_form(sender,get_recipe()) + elseif fields.item then + item = fields.item; + local recipes = _G.minetest.get_all_craft_recipes(item); + local count = 0; if recipes then count = #recipes end + if count>0 then + recipeid = 1 + self.show_form(sender,get_recipe() or "") + end + elseif fields.quit then + self.remove() + end +end \ No newline at end of file diff --git a/scripts/gui/file_manager.lua b/scripts/gui/file_manager.lua new file mode 100644 index 0000000..10205ba --- /dev/null +++ b/scripts/gui/file_manager.lua @@ -0,0 +1,57 @@ +-- file 'manager' by rnd + +if not init then + fmver = "2018/12/09" + local players = find_player(4); + if not players then self.remove() end + pname = players[1]; + size = 8; + vsize = 6.5; + + path = "/"; + pathlist = {} + folderlist = {}; + filelist = {}; + + render_page = function() + local foldlist = minetest.get_dir_list(path,true) -- only folders + if foldlist then folderlist = foldlist else folderlist = {} end + for i = 1,#foldlist do foldlist[i] = "*"..foldlist[i] end + foldlist[#foldlist+1] = "*.." + local fillist = minetest.get_dir_list(path,false) + if fillist then filelist = fillist else filelist = {} end + local content = table.concat(folderlist,",") .. ",-------------------," .. table.concat(filelist,",") + return "size[" .. size .. "," .. size .. "] label[0,-0.25;ROBOT FILE MANAGER " .. fmver .. " by rnd\nPATH " .. minetest.formspec_escape(path) .. "] textlist[-0.25,0.75;" .. (size+1) .. "," .. (vsize+1) .. ";wiki;".. content .. ";1]"; + end + + page = {} + self.show_form(pname,render_page()) + init = true + self.read_form() +end + +sender,fields = self.read_form() +if sender then + local fsel = fields.wiki; + if fsel and string.sub(fsel,1,3) == "DCL" then + local sel = tonumber(string.sub(fsel,5)) or 1; -- selected line + local fold = folderlist[sel]; + if fold and string.sub(fold,1,1) == "*" then + if fold == "*.." then -- go back + if #pathlist>0 then + local i = string.len(pathlist[#pathlist]); + if i>0 then + pathlist[#pathlist] = nil + path = string.sub(path,1,-i-2); + end + end + else + pathlist[#pathlist+1] = string.sub(fold,2) + path = path .. "/".. pathlist[#pathlist] + end + + self.show_form(pname,render_page()) + end + end + --self.label(fsel); +end \ No newline at end of file diff --git a/scripts/gui/gui_2player_coop_edit.lua b/scripts/gui/gui_2player_coop_edit.lua new file mode 100644 index 0000000..1b6759d --- /dev/null +++ b/scripts/gui/gui_2player_coop_edit.lua @@ -0,0 +1,57 @@ +-- gui demo by rnd +-- 2 player cooperative editing of image with 2 colors + +if not init then + _G.basic_robot.data[self.name()].obj:get_luaentity().timestep = 0.25 + init = true + name = "rnd" + otherrobotname = "rnd2" -- on other robot do name of this robot + drawcolor = 3; -- other robot has reversed colors + otherdrawcolor = 4 + + color = {"black","white","blue","green"} + data = {}; + n = 20; + + for i = 1,n do + data[i]={}; + --local y = math.floor(f(i)); + for j = 1,n do + data[i][j] = 1--(n-j>y) and 2 or 1 + end + end + + get_form = function() + local form = "size[10,10] "; ret = {}; + + for i = 1,n do + for j = 1,n do + ret[#ret+1] = "image_button["..((i-1)*0.5)..","..((j-1)*0.5)..";0.7,0.63;wool_"..color[data[i][j]]..".png;"..((i-1)*n+j-1) .. ";] " + end + end + return form .. table.concat(ret,"") + end + + self.show_form(name,get_form()) + self.read_form() +end + +sender,mail = self.read_mail() +if mail then + local x = mail[1]; local y = mail[2]; + if data[x][y]==1 then data[x][y] = otherdrawcolor else data[x][y] = 1 end + self.show_form(name,get_form()) +end + +sender,fields = self.read_form() +if fields then + if fields.quit then self.remove() end + local sel = 0; + for k,v in pairs(fields) do + if k ~="quit" then sel = tonumber(k); break end + end + local x = 1+math.floor(sel/n); local y = 1+sel % n; + if data[x][y]==1 then data[x][y] = drawcolor else data[x][y] = 1 end + self.send_mail(otherrobotname,{x,y}) + self.show_form(name,get_form()) +end \ No newline at end of file diff --git a/scripts/gui/gui_deposit_withdraw_demo.lua b/scripts/gui/gui_deposit_withdraw_demo.lua new file mode 100644 index 0000000..bd6785a --- /dev/null +++ b/scripts/gui/gui_deposit_withdraw_demo.lua @@ -0,0 +1,94 @@ + +if not init then init = true + + deposit = function(pname) + _, text = book.read(1); + local data = deserialize(text) or {}; + + local player = _G.minetest.get_player_by_name(pname) + local inv = player:get_inventory(); + local pstack = inv:get_stack("main", 1); + local iname = pstack:to_string() + + + item, count = _G.string.match(iname,"(%S+)%s?(%d*)"); + item = item or "";if item == "" then return end + + count = tonumber(count) or 1; + + --say("item " .. item .. ", count " .. count) + + data[pname] = data[pname] or {}; + local pdata = data[pname]; + + pdata[item] = pdata[item] or {}; + + local t = minetest.get_gametime() + pdata[item][1] = (pdata[item][1] or 0) + count + pdata[item][2] = t; + + inv:set_stack("main", 1, _G.ItemStack("")) + + book.write(1,"",serialize(data)) + local form = "size [5,5] label[0,0; You deposited " .. item .. "( " .. count .. " pieces)]" + self.show_form(pname, form) + --say(pname .. " deposited " .. item .. "( " .. count .. " pieces) ") + end + + check = function(pname) + _, text = book.read(1); + local data = deserialize(text) or {}; + data[pname] = data[pname] or {}; + + --say(serialize(data[pname])) + return serialize(data[pname]) + end + + withdraw = function(pname) + _, text = book.read(1); + local data = deserialize(text) or {}; + data[pname] = data[pname] or {}; + + local player = _G.minetest.get_player_by_name(pname) + local inv = player:get_inventory(); + local pdata = data[pname] + local t0 = minetest.get_gametime(); + + for k,v in pairs(pdata) do + local t = t0 - v[2]; -- time since last deposit + local a = (1+interests/100)^t; + self.label("deposited time " .. t .. ", deposited quantity " .. v[1] .. ", new quantity : " .. math.floor(a*tonumber(v[1])) ) + inv:add_item("main", _G.ItemStack(k .. " " .. math.floor(a*tonumber(v[1]))) ) + end + data[pname] = nil; + book.write(1,"",serialize(data)) + end + + + function show(pname) + local itemlist = check(pname) + local form = "size [5,5] button[0,0;2,1;DEPOSIT;DEPOSIT] button[0,1;2,1;WITHDRAW;WITHDRAW]" .. + "textarea[0,2.5;6,3;STORAGE;YOUR DEPOSITS;" .. minetest.formspec_escape(itemlist) .. "]" + self.show_form(pname, form) + end + + + interests = 5; -- interest rate per second (demo) + local players = find_player(4) + if not players then self.remove() end + show(players[1]) + +end + +sender,fields = self.read_form() +if sender then + if fields.DEPOSIT then + deposit(sender) + show(sender) + elseif fields.WITHDRAW then + withdraw(sender) + show(sender) + end + + --say(sender .. " clicked " .. serialize(fields)) +end \ No newline at end of file diff --git a/scripts/gui/gui_interact_demo_board.lua b/scripts/gui/gui_interact_demo_board.lua new file mode 100644 index 0000000..6f1d62b --- /dev/null +++ b/scripts/gui/gui_interact_demo_board.lua @@ -0,0 +1,46 @@ +-- gui demo by rnd + +if not init then + init = true + name = "rnd" + color = {"white","black"} + data = {}; + n = 20; + + f = function(x) return 7*(1+math.sin(x/2)) end + + for i = 1,n do + data[i]={}; + local y = math.floor(f(i)); + for j = 1,n do + data[i][j] = (n-j>y) and 2 or 1 + end + end + + get_form = function() + local form = "size[10,10] "; ret = {}; + + for i = 1,n do + for j = 1,n do + ret[#ret+1] = "image_button["..((i-1)*0.5)..","..((j-1)*0.5)..";0.7,0.63;wool_"..color[data[i][j]]..".png;"..((i-1)*n+j-1) .. ";] " + end + end + return form .. table.concat(ret,"") + end + + self.show_form(name,get_form()) + self.read_form() +end + +sender,fields = self.read_form() +if fields then + if fields.quit then self.remove() end + local sel = 0; + for k,v in pairs(fields) do + if k ~="quit" then sel = tonumber(k); break end + end + local x = 1+math.floor(sel/n); local y = 1+sel % n; + data[x][y] = 3 - data[x][y] + --self.label(x .. " " .. y) + self.show_form(name,get_form()) +end \ No newline at end of file diff --git a/scripts/gui/wiki.lua b/scripts/gui/wiki.lua new file mode 100644 index 0000000..187b0f3 --- /dev/null +++ b/scripts/gui/wiki.lua @@ -0,0 +1,68 @@ +-- ROBOT WIKI by rnd +-- to do: ability for multiple links in 1 line + + +if not init then + _G.basic_robot.data[self.name()].obj:get_luaentity().timestep = 0.1 + local players = find_player(4); + if not players then self.remove() end + pname = players[1]; + size = 8; + vsize = 8; + linesize = 60; -- break up longer lines + + wiki = { -- example of wiki pages + ["MAIN PAGE"] = + { + "-- WIKI CONTENTS -- ", "", + "double click link marked with [] or press enter while selected.","", + "[Viewing wiki]", + "[Editing wiki]" + }, + + ["Viewing wiki"] = { + "back to [MAIN PAGE]","", + " ** Viewing wiki", + "double click link marked with [] or press enter while selected." + }, + + ["Editing wiki"] = { + "back to [MAIN PAGE]","", + " ** Editing wiki", + "Edit wiki table and write in entries" + } + } + + for k,v in pairs(wiki) do + local pages = wiki[k]; for i = 1,#pages do pages[i] = minetest.formspec_escape(pages[i]) end + end + + + current = "MAIN PAGE"; + + render_page = function() + local content = table.concat(wiki[current],",") + return "size[" .. size .. "," .. size .. "] textlist[-0.25,-0.25;" .. (size+1) .. "," .. (vsize+1) .. ";wiki;".. content .. ";1]"; + end + + page = {} + self.show_form(pname,render_page()) + init = true +end + +sender,fields = self.read_form() +if sender then + local fsel = fields.wiki; + if fsel and string.sub(fsel,1,3) == "DCL" then + local sel = tonumber(string.sub(fsel,5)) or 1; -- selected line + local address = current or "main"; + local pages = wiki[address]; + + local link = _G.string.match(pages[sel] or "", "\\%[([%w%s]+)\\%]") + if wiki[link] then + current = link; + self.show_form(pname,render_page()) + --robot_show_help(name) + end + end +end \ No newline at end of file diff --git a/scripts/http/http_demo.lua b/scripts/http/http_demo.lua new file mode 100644 index 0000000..4cae0cb --- /dev/null +++ b/scripts/http/http_demo.lua @@ -0,0 +1,44 @@ +-- HOW TO DOWNLOAD WEBPAGES/FILES USING ROBOT + +if not fetch then +fetch = _G.basic_robot.http_api.fetch; + +-- WARNING: this is run outside pcall and can crash server if errors! +result = function(res) -- res.data is string containing result + if not res.succeeded then self.label("#ERROR: data couldn't be downloaded :\n" .. minetest.serialize(res) ) return end + if res.data then self.label(res.data) end +end + +fetch({url = "http://185.85.149.248/FILES/minetest/README.txt", timeout = 30}, result) +end + +--[[ +from https://github.com/minetest/minetest/blob/master/doc/lua_api.txt : + +`HTTPRequest` definition +------------------------ + +Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`. + + { + url = "http://example.org", + timeout = 10, + -- ^ Timeout for connection in seconds. Default is 3 seconds. + post_data = "Raw POST request data string" OR {field1 = "data1", field2 = "data2"}, + -- ^ Optional, if specified a POST request with post_data is performed. + -- ^ Accepts both a string and a table. If a table is specified, encodes + -- ^ table as x-www-form-urlencoded key-value pairs. + -- ^ If post_data ist not specified, a GET request is performed instead. + user_agent = "ExampleUserAgent", + -- ^ Optional, if specified replaces the default minetest user agent with + -- ^ given string. + extra_headers = { "Accept-Language: en-us", "Accept-Charset: utf-8" }, + -- ^ Optional, if specified adds additional headers to the HTTP request. + -- ^ You must make sure that the header strings follow HTTP specification + -- ^ ("Key: Value"). + multipart = boolean + -- ^ Optional, if true performs a multipart HTTP request. + -- ^ Default is false. +} + +--]] \ No newline at end of file diff --git a/scripts/http/webcommands/minetest_webcommands.js b/scripts/http/webcommands/minetest_webcommands.js new file mode 100644 index 0000000..600a8e7 --- /dev/null +++ b/scripts/http/webcommands/minetest_webcommands.js @@ -0,0 +1,68 @@ +// listen to web request and pass it to minetest or back to web, rnd 2018 + +// INSTRUCTIONS. url options: +// 1./mtmsg/msg will store msg as message received from minetest ( minetest_message). note that msg cant contain spaces or newlines +// 2./getwebmsg/ will reply = IP + ' ' + webmessage +// 3./webmsg/msg will store message as webmessage +// 4./getmtmsg will reply with minetest_message + + +// NOTES: 1. avoids the need to deal with POST nastyness and complications like +// https://stackoverflow.com/questions/4295782/how-do-you-extract-post-data-in-node-js + +const http = require('http'); + + +const hostname = '192.168.0.10' //write address of your router (it will be accessible from internet then if you open firewall for nodejs process) +const port = 80; + +var webreq = "" // message from web +var mtreq = "" // message from mt + +// take request from web and pass it to minetest +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + + var msg = req.url; + if (msg == '/favicon.ico') return // prevent passing this as request + + + var pos = msg.indexOf("/",1); // gets the 2nd / in /part1/part2/... + var cmd = msg.substring(1,pos); + var response = "" + var ip = req.connection.remoteAddress; + + switch(cmd) + { + case "mtmsg": + response = msg.substring(pos+1); + mtreq = response + break + case "getmtmsg": + response = mtreq; mtreq = '' + break + case "getwebmsg": + response = webreq; webreq = '' + break + case "webmsg": + webreq = ip + ' ' + msg.substring(pos+1); + response = 'request received: ' + webreq + '\nuse /getmtmsg to view response from minetest' + break + default: + response = 'INSTRUCTIONS. url options:\n'+ + '1./mtmsg/msg will store msg as message received from minetest ( minetest_message). note that msg cant contain spaces or newlines\n'+ + '2./getwebmsg/ will reply = IP + " " + webmessage\n'+ + '3./webmsg/msg will store message as webmessage\n'+ + '4./getmtmsg will reply with minetest_message\n' + } + + if (msg!='' && cmd != 'getwebmsg') console.log('ip ' + ip + ', msg ' + msg) + res.write(response); res.end() + return +}); + +// make server listen +server.listen(port, hostname, () => { + console.log(`Server running at http://${hostname}:${port}/`); +}); \ No newline at end of file diff --git a/scripts/http/webcommands/webcommands.lua b/scripts/http/webcommands/webcommands.lua new file mode 100644 index 0000000..2916306 --- /dev/null +++ b/scripts/http/webcommands/webcommands.lua @@ -0,0 +1,71 @@ +-- webcommands : access url like: 192.168.0.10/hello_world + +--[[ instructions: + 1.download nodejs server from + https://nodejs.org/dist/v10.14.2/node-v10.14.2-win-x86.zip + 2. run nodejs server using run.bat + :loop + node $path/minetest_webcommands.js + goto loop + 3. run robot and type 'http://192.168.0.10/webmsg/hello this is a test' into browser + (you need to write your router address here, i.e. ip accessible from internet OR lan address) +--]] + +if not fetch then + address = "192.168.0.10"; + fetch = _G.basic_robot.http_api.fetch; + state = 0 -- ready to fetch new command +-- WARNING: this is run outside pcall and can crash server if errors! + result = function(res) -- res.data is string containing result + state = 0 + if not res.succeeded then self.label("#ERROR: data couldn't be downloaded :\n" .. minetest.serialize(res) ) return end + if res.data == "" then return end + local req = res.data; req = string.gsub(req,"%%20"," ") + if res.data 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 + end + + + end + end + + admin = minetest.setting_get("name") + run_commmand = function(message) + local cmd, param = _G.string.match(message, "([^ ]+) *(.*)") + if not param then + param = "" + end + local cmd_def = minetest.chatcommands[cmd] + if cmd_def then + cmd_def.func(admin, param) + else + minetest.chat_send_all(admin..": "..message) + end + end + + MT2web = function(message) + message = string.gsub(message," ","%%20") -- NOTE: if you send request that has 'space' in it there will be error 400! + fetch({url = "http://".. address .. "/mtmsg/"..message, timeout = 5}, result) + end + MT2web("minetest robot started and listening.") + + self.listen(1) +end + + + +if state == 0 then + fetch({url = "http://"..address.."/getwebmsg/", timeout = 5}, result) + state = 1 +end \ No newline at end of file diff --git a/scripts/math/enigma.lua b/scripts/math/enigma.lua new file mode 100644 index 0000000..d6714be --- /dev/null +++ b/scripts/math/enigma.lua @@ -0,0 +1,207 @@ +--ENIGMA emulator by rnd +-- programming ~30 mins, total 3hrs 30 minutes with debugging - cause of youtube video with missing +-- key detail - reflector! + +-- REFERENCES: +-- 1. https://en.wikipedia.org/wiki/Enigma_machine +-- 2. http://users.telenet.be/d.rijmenants/en/enigmatech.htm#reflector +-- 3. https://www.youtube.com/watch?src_vid=V4V2bpZlqx8&v=G2_Q9FoD-oQ + +-- default settings +settings = {} +settings.set = function(reflector, plugboard, rotors) +settings.reflector = reflector or 2017 +settings.plugboard = plugboard or 2017 +settings.rotors = {} +if rotors then + table.insert(settings.rotors, rotors[1]) + table.insert(settings.rotors, rotors[2]) + table.insert(settings.rotors, rotors[3]) + table.insert(settings.rotors, rotors[4]) +else + table.insert(settings.rotors, 0) + table.insert(settings.rotors, 0) + table.insert(settings.rotors, 0) + table.insert(settings.rotors, 2018) + table.insert(settings.rotors, 2017) + table.insert(settings.rotors, 2020) +end +--if not enigma_encrypt then +if true then + 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 permutation = function(n,password,sgn) -- create random permutation of numbers 1,...,n + _G.math.randomseed(password); + local permute = {} + for i = 1, n do permute[i] = i end + for i = n,2,-1 do + local j = math.random(i-1); + local tmp = permute[j]; + permute[j] = permute[i]; permute[i] = tmp; + end + return permute; + end + + -- produces permutation U such that U^2 = 1; modified fisher-yates shuffle by rnd + local reflector = function(n,password) + _G.math.randomseed(password) + local permute = {} + local used = {}; + for i = 1, n do permute[i] = i end + local rem = n; + + for i = n,2,-1 do + if not used[i] then + local j = math.random(rem); + -- now we need to find j-th unused idx + local k = 1; local l = 0; -- k position, l how many we tried + while l < j do + if not used[k] then l=l+1; end + k=k+1 + end + j=k-1; + + used[i]=true; used[j] = true; + local tmp = permute[j]; + permute[j] = permute[i]; permute[i] = tmp; + rem = rem - 2; + end + + end + return permute; + end + + + local inverse_table = function(tbl) + local ret = {}; + for i = 1,#tbl do + ret[ tbl[i] ] = i; + end + return ret; + end + + local rotors = {}; -- { permutation, work index }} + local invrotors = {}; -- reversed tables + + -- SETUP REFLECTOR, ROTORS AND PLUGBOARD! + local enigma_charcount = 127-32+1; -- n = 96 + local enigma_charstart = 32; + local reflector = reflector(enigma_charcount,settings.reflector); -- this is permutation U such that U^2 = id -- + + local plugboard = permutation(enigma_charcount,settings.plugboard,1); -- setup plugboard for enigma machine + local invplugboard = inverse_table(plugboard); + + for i = 1,3 do rotors[i] = {rotor = permutation(enigma_charcount,settings.rotors[3 + i],1), idx = 0} end -- set up 3 rotors together with their indices + for i = 1,3 do invrotors[i] = {rotor = inverse_table(rotors[i].rotor)} end -- set up 3 rotors together with their indices + + -- how many possible setups: + --[[ + n = charcount; + rotors positions: n^3 + plugboard wiring : n! + reflector wiring: n! / (2^n * (n/2)!) + TOTAL: (n!)^2*n^3 / ( 2^n * (n/2)! ) ~ 6.4 * 10^77 + rotor positions & plugboard wiring: (n!)*n^3 ~ 4.8 * 10^57 (n=43) + --]] + + -- END OF SETUP + + local rotate_rotor = function(i) + local carry = 1; + for j = i,1,-1 do + local idx = rotors[j].idx; + idx = idx + 1; + if idx>=enigma_charcount then + carry = 1; + else + carry = 0; + end + rotors[j].idx = idx % enigma_charcount; + if carry == 0 then break end + end + end + + local enigma_encrypt_char = function(x) -- x : 1 .. enigma_charcount + -- E = P.R1.R2.R3.U.R3^-1.R2^-1.R1^-1.P^-1, P = plugboard, R = rotor, U = reflector + x = plugboard[x]; + for i = 1,3 do + local idx = rotors[i].idx; + x = rotors[i].rotor[((x+idx-1) % enigma_charcount)+1]; + end + + x = reflector[x]; + + for i = 3,1,-1 do + local idx = rotors[i].idx; + x = invrotors[i].rotor[x]; + x = ((x-1-idx) % enigma_charcount)+1 + + end + + x = invplugboard[x]; + -- apply rotation to rotor - and subsequent rotors if necessary + rotate_rotor(3) + return x; + end + + + --enigma_encrypt = function(input) + settings.encrypt = function(input) + -- rotor settings! + rotors[1].idx = settings.rotors[1] + rotors[2].idx = settings.rotors[2] + rotors[3].idx = settings.rotors[3] + + local ret = ""; + for i = 1,#input do + local c = string.byte(input,i) - enigma_charstart +1; + --say(i .. " : " .. c) + if c>=1 and c<=enigma_charcount then + c = enigma_encrypt_char(c); + end + ret = ret .. string.char(enigma_charstart+c-1); + end + return ret + + end + settings.decrypt = settings.encrypt +end + +end + +msg = self.listen_msg() + if msg then + msg = minetest.strip_colors(msg) + local mark = string.find(msg,"@e") -- delimiter in chat + if mark then + msg = string.sub(msg,mark+2); + msg = minetest.colorize("yellow",enigma_encrypt(msg)) + say(minetest.colorize("red","#decrypted : ") .. msg) + end + end + +msg = self.sent_msg() +if msg then + local msg = enigma_encrypt(msg); +say("@e" .. msg,true) +-- minetest.show_formspec("encrypted_text", "size[4,4] textarea[0,-0.25;5,5;text;;".. "@e" .. minetest.formspec_escape(msg) .. "]") +end + + diff --git a/scripts/math/fractal_bot.lua b/scripts/math/fractal_bot.lua new file mode 100644 index 0000000..7279c44 --- /dev/null +++ b/scripts/math/fractal_bot.lua @@ -0,0 +1,92 @@ +-- robot can construct classic fractals like menger sponge, jerusalem cube, sierpinski triangles,.. +-- use: build a pattern at position 1,1,1 relative to robot. when run robot will analyse pattern and construct fractal +if not init then + minetest.forceload_block(self.pos(),true) + init = true; local spos = self.spawnpos(); + + offsets = {["default:dirt"] = 0, ["default:wood"] = -1, ["default:cobble"] = 1} + + read_form = function(fractal) -- read shape from world + local form = {}; local i = 0; + local spos = self.spawnpos(); spos.x = spos.x+1;spos.y = spos.y+1;spos.z = spos.z+1; + local nx = 0; local ny = 0; local nz = 0; + fractal.form = {} + + for x = 0,fractal.nx-1 do + for y = 0,fractal.ny-1 do + for z = 0,fractal.nz-1 do + local node = _G.minetest.get_node({x=spos.x+x,y=spos.y+y,z=spos.z+z}).name; + local offset = offsets[node] or 0; + if node~= "air" then + form[i] = {x,y,z,offset}; i=i+1 + if nx0 then carry = 0 end + if sum>=base then data[i]=sum-base; carry = 1 else data[i] = sum end + end + if carry>0 then data[n+1]=1 res.size = n+1 else res.size = n end + if out then return res end + end + + number.__add = add; + + function number:set(m) + local data = self.data; + local mdata = m.data; + for i=1,#mdata do + data[i]=mdata[i]; + end + self.size = m.size; + end + + -- 'slow' long multiply + number.multiply = function (lhs, rhs, res) + local n1 = lhs.size;local n2 = rhs.size;local n = n1+n2; + --say("multiply sizes " .. n1 .. "," .. n2) + local out = false; + if not res then res = number:new({}); out = true end; + res.size = n1+n2-1; + res.data = {} -- if data not cleared it will interfere with result! + local data = res.data; + local c,prod,carry = 0; local base = lhs.base; + for i=1,n1 do + carry = 0; + c = lhs.data[i] or 0; + for j = 1,n2 do -- multiply with i-th digit and add to result + prod = (data[i+j-1] or 0)+c*(rhs.data[j] or 0)+carry; + carry = math.floor(prod / base); + prod = prod % base; + data[i+j-1] = (prod)%base; + end + if carry>0 then data[i+n2] = (data[i+n2] or 0)+ carry ;if res.sizea^2, 1 = square and multiply a-> a*a^2 + while (power>0) do + r=power%2; powerplan[#powerplan+1] = r; power = (power-r)/2 + end + + for i = #powerplan-1,1,-1 do + + number.multiply(input,input,out); + + if powerplan[i] == 1 then + input,out = out, input; + number.multiply(input,n,out); count = count + 2 + else count = count + 1; + end + + input,out = out, input; + end + + return input + end + + split = function(s,k) + local ret = ""; + local j=1,length; length = string.len(s)/k + for i = 1, length do + j = (i-1)*k+1; + ret = ret .. string.sub(s,j,j+k-1) .. "\n" + end + --say("j " .. j) + if j>1 then j = j+k end + ret = ret .. string.sub(s,j) + return ret + end + + self.spam(1) + + -- little endian ! lower bits first .. + + --n = number:new({7,1,0,2}); local power = 2017; + --self.label(split(n:tostring().."^"..power .. " = " .. number.power(n,power):tostring(),100)) + --2017^2017 = 3906... +end \ No newline at end of file diff --git a/scripts/math/perm2cycles.lua b/scripts/math/perm2cycles.lua new file mode 100644 index 0000000..0e6b405 --- /dev/null +++ b/scripts/math/perm2cycles.lua @@ -0,0 +1,83 @@ +if not perm2cycles then + + perm2cycles = function(perm) + local n = #perm; + local i = 1; -- already reached number + local ret = {}; + local visited = {}; + local step = 0; + + while (true) do + local cycle = {i} + local j=i; + + while (true) do + step = step +1 + if step > 2*n then return {} end + j=perm[j]; + visited[j] = 1; + if j and j~=cycle[1] then cycle[#cycle+1]=j else break end + end + + i=n+1; + for k=1,n do -- find smallest non visited yet + if not visited[k] and k cycles = " .. arr2string(cycles) ) + +end \ No newline at end of file diff --git a/scripts/pine_tree_harvest.lua b/scripts/pine_tree_harvest.lua new file mode 100644 index 0000000..1b69b6d --- /dev/null +++ b/scripts/pine_tree_harvest.lua @@ -0,0 +1,65 @@ +-- PINE TREE HARVEST by rnd1 +if not harvest then + harvest = {}; + harvest.s = -1 -- -1 idle, 0 expect tree + harvest.tree = "default:pine_tree";harvest.wood = "default:wood";harvest.sapling = "default:pine_sapling"; + harvest.step = function() + local s=harvest.s; + if s == 0 then -- did we bump into tree + local node = read_node.forward(); + if node == harvest.tree then + dig.forward(); move.forward(); harvest.s = 1 -- found tree, moving in + self.label("im digging up tree ") + end + elseif s == 1 then -- climbing up + dig.up(); move.up(); place.down(harvest.wood); + local node = read_node.up(); + if node ~= harvest.tree then + harvest.s = 2 -- top + self.label("i reached top of tree") + end + elseif s == 2 then -- going down + local node = read_node.down(); + if node == harvest.wood then + dig.down(); move.down() + self.label("im going back down") + else + pickup(8); + move.backward();place.forward(harvest.sapling);move.forward(); + harvest.s = -1 -- idle + self.label("i finished cutting tree") + end + end + end +end + +--harvest walk init +if not angle then + sender,mail = self.read_mail(); if sender == "rnd1" then harvest.s = tonumber(mail) or 0 end + wall = "default:cobble"; + angle = 90 +end + +if harvest.s~=-1 then + harvest.step() +elseif harvest.s==-1 then + node = read_node.forward(); + if node==harvest.tree then + harvest.s = 0; + self.label("i found tree") + else + self.label("im walking") + if not move.forward() then + if node == wall then + self.label("i hit wall") + turn.angle(angle); + if move.forward() then + move.forward();move.forward();turn.angle(angle); angle = -angle + else + turn.angle(-angle);angle = - angle + end + end + end + end +end +self.send_mail("rnd1",harvest.s) -- remember state in case of reactivation \ No newline at end of file diff --git a/scripts/programming/brainfuck generator.lua b/scripts/programming/brainfuck generator.lua new file mode 100644 index 0000000..3d910cd --- /dev/null +++ b/scripts/programming/brainfuck generator.lua @@ -0,0 +1,59 @@ +N = 30; -- length of program (without ]) + +desired_frequencies = { + [">"] = 10, + ["<"] = 10, + ["-"]=10, + ["+"]=20, + ["."]=10, + [","]=10, + ["["]=10, -- matching "]" will be inserted automatically! +} +matching_parenthesis = "]"; +routine_lower = 1; routine_higher = 5; -- specify range, how many characters in routine [.....] + +generate_selections = function(desired_frequency) + local sum = 0 + for k,v in pairs(desired_frequency) do sum = sum + v end + local isum = 0; local count = 0; local selections = {} + for k,v in pairs(desired_frequency) do count = count +1 isum = isum + desired_frequency[k]/sum; selections[count] = {isum,k} end + return selections +end + +choose = function(selections, rnd) + local low, mid, high; + low = 1; high = #selections; mid = math.floor((low+high)/2) + local step = 0; + while high-low>1 and step < 20 do + step = step + 1 + if rnd <= selections[mid][1] then high = mid else low = mid end + mid = math.floor((low+high)/2) + end + return selections[mid][2] +end + + +generate_program = function(desired_frequencies,N, routine_lower, routine_higher) + local selections = generate_selections(desired_frequencies); + + local ret = {}; + local count = 0 + local stack = {}; + + for count = 1, N do + local choice = choose(selections, math.random()); + if choice == "[" then + local i = count + math.random(routine_lower,routine_higher) + if i > N then i = N end + stack[#stack+1] = i; + end + ret[count] = choice + end + + for i = 1,#stack do local j = stack[i] ret[j]=ret[j]..matching_parenthesis end + return table.concat(ret) +end + +say(generate_program(desired_frequencies,N, routine_lower, routine_higher)) + +self.remove() \ No newline at end of file diff --git a/scripts/programming/brainfuck.lua b/scripts/programming/brainfuck.lua new file mode 100644 index 0000000..221eb08 --- /dev/null +++ b/scripts/programming/brainfuck.lua @@ -0,0 +1,66 @@ +-- BRAINFUCK interpreter by rnd, 2017 +-- https://en.wikipedia.org/wiki/Brainfuck + +if not ram then + prog = "+++.>++++++.<[->+<]>." + ramsize = 10 + maxsteps = 100; step=0; -- for RUN state only + + n = string.len(prog);ram = {};for i = 1, ramsize do ram[i]=0 end -- init ram + pointer = 1 -- ram pointer + instruction = 1 -- instruction pointer + self.spam(1) + + RUNNING = 1; END = 2; RUN = 3; + state = RUNNING + + get_ram = function() msg = "" for i = 1,ramsize do msg = msg .. ram[i] .. "," end return msg end + + cmdset = { + [">"] = function() pointer = pointer + 1; if pointer > ramsize then pointer = 1 end end, + ["<"] = function() pointer = pointer - 1; if pointer > ramsize then pointer = 1 end end, + ["+"] = function() ram[pointer]=ram[pointer]+1 end, + ["-"] = function() ram[pointer]=ram[pointer]-1 end, + ["."] = function() say(ram[pointer]) end, + [","] = function() ram[pointer] = tonumber(read_text.forward("infotext") or "") or 0 end, + ["["] = function() + if ram[pointer] == 0 then + local lvl = 0 + for j = instruction, n, 1 do + if string.sub(prog,j,j) == "]" then lvl = lvl - 1 if lvl == 0 then + self.label("JMP " .. j ) instruction = j return + end end + if string.sub(prog,j,j) == "[" then lvl = lvl + 1 end + end + end + end, + ["]"] = function() + if ram[pointer] ~= 0 then + local lvl = 0 + for j = instruction, 1, -1 do + if string.sub(prog,j,j) == "]" then lvl = lvl - 1 end + if string.sub(prog,j,j) == "[" then lvl = lvl + 1 if lvl == 0 then + self.label("JMP " .. j ) instruction = j return + end end + end + end + end, + } +end + +-- EXECUTION +if state == RUNNING then + c = string.sub(prog,instruction,instruction) or ""; + if c and cmdset[c] then cmdset[c]() end + self.label("ins ptr " .. instruction .. ", ram ptr " .. pointer .. ": " .. ram[pointer] .. "\n" .. string.sub(prog, instruction).."\n"..get_ram()) + instruction = instruction + 1; if instruction > n then state = END end + +-- RUN THROUGH +elseif state == RUN then + while (step n then self.label("ram : " .. get_ram()) step = maxsteps state = END end + end +end \ No newline at end of file diff --git a/scripts/programming/code_parser_strings_identify.lua b/scripts/programming/code_parser_strings_identify.lua new file mode 100644 index 0000000..a58b313 --- /dev/null +++ b/scripts/programming/code_parser_strings_identify.lua @@ -0,0 +1,53 @@ +function identify_strings(code) -- returns list of positions {start,end} of literal strings in lua code + + local i = 0; local j; local length = string.len(code); + local mode = 0; -- 0: not in string, 1: in '...' string, 2: in "..." string, 3. in [==[ ... ]==] string + local modes = { + {"'","'"}, + {"\"","\""}, + {"%[=*%[","%]=*%]"} + } + local ret = {} + while i < length do + i=i+1 + + local jmin = length+1; + if mode == 0 then -- not yet inside string + for k=1,#modes do + j = string.find(code,modes[k][1],i); + if j and j"] = function() turn.right() end, + }; + + build.debug =true; + build.stackdepth = 16; + + build.ignored = {["]"]=true,["["]=true}; + +-- RESERVED: Ex(y)[A] : if x == y do A end, N(node)[A] : if read_node.forward()==node then do A end +-- G(label) .. GOTO GR(label) .. CALL SUBROUTINE +a(b),-a(b),=a(b) + + set_routine = function(i) + local jr = string.find(prog,"%[",i+1) + if not jr then say("error, missing [ after position " .. i+1); self.remove() end + build.count = get_var(string.sub(prog,i+1,jr-1)) or 1 + local kr = string.find(prog,"]",jr+1); + if not kr then say("error, missing ] after position " .. jr+1); self.remove() end + return jr,kr --returns routine limits jr,kr + end + + error_msg = function(i) + local callstack = ""; + for _,v in pairs(build.callstack) do + callstack = callstack.. call_marks[v].. ","; + end + + return "ERROR: " ..string.sub(prog,i) .. "\nCALL STACK: " .. callstack + end + + run_routine = function(i,jr,kr) + local count = build.count; + if count > 0 then + if i == kr then + count = count - 1 i = jr+1 + end + --say(" i " .. i .. " kr " .. kr) + if count > 0 then + c=string.sub(prog,i,i) + if c == "G" then i=go_to(i); build.state = 0 else -- exit routine + if cmd[c] then cmd[c]() else + if not ignored[c] then + self.label("run routine: invalid instruction at position " .. i .. "\n" .. error_msg(i)); + self.debug = false; build.state = -1 + end + + end + end + end + else + i=kr-- exit,jump to next instruction + build.state = 0 + if build.debug then self.label("["..build.state .. "]" .. i .. "\nROUTINE EXIT") end + end + + build.count = count + return i -- return next execution address + end + + push_callstack = function(val) -- addresses where to continue after function ends + if #build.callstack > build.stackdepth then say("error: stack depth limit " .. build.stackdepth .. " exceeded "); self.remove() end + build.callstack[#build.callstack+1] = val; + end + + pop_callstack = function() + local val = build.callstack[#build.callstack]; + build.callstack[#build.callstack] = nil; + return val + end + + go_to = function(i) + local j = string.find(prog,"%(",i+1); local k = string.find(prog,"%)",j+1) + local call = false; + if string.sub(prog,i+1,j-1) == "R" then call = true push_callstack(k) end -- function call, save call exit address to return here + + local target = string.sub(prog,j+1,k-1); + if target == "" then -- marking end of routine + i = pop_callstack(); + if build.debug then self.label("["..build.state .. "]" .. i .. "\nEXITING ROUTINE to " .. i .. ", stack level " .. #build.callstack) end + else + i = go_to_mark[target]-1; + if call == false then + if build.debug then self.label("["..build.state .. "]" .. i .. "\nGOTO " .. target .. "=".. i ) end + else + if build.debug then self.label("["..build.state .. "]" .. i .. "\nCALL SUBROUTINE " .. target .. "=".. i .. "\ncall stack = " .. string.gsub(_G.dump(build.callstack),"\n","")) end + end + end + + return i; + end + + -- scan for go_to markers + go_to_mark = {}; + call_marks = {}; -- OPTIONAL for nicer error messages + scan_go_to = function() + local i = 0; + while ( i < string.len(prog)) do + i=i+1; + if string.sub(prog,i,i+1) == "M(" then + local j = string.find(prog,"%(",i+1) + local k = string.find(prog,"%)",j+1) + local name = string.sub(prog,j+1,k-1); + prog = string.sub(prog,1,i-1)..string.sub(prog,k+1) + go_to_mark[name] = i; + end + end + + i=0 -- OPTIONAL call marks scanning + while ( i < string.len(prog)) do + i=i+1; + if string.sub(prog,i,i+2) == "GR(" then + local j = string.find(prog,"%(",i+1) + local k = string.find(prog,"%)",j+1) + local name = string.sub(prog,j+1,k-1); + call_marks[k] = name; + end + end + end + + get_var = function(s) + local ns = tonumber(s); + if _G.tostring(ns)==s then return ns else return build.var[s] end + end + + prog = string.gsub(prog, " ", "") -- remove all spaces + prog = string.gsub(prog, "\n", "") -- remove all newlines + scan_go_to() + build.n=string.len(prog) + build.i=1; + build.count = 1; + build.state = 0; + build.callstack = {}; + build.var = {}; + self.spam(1) +end + +build.run = function() + + local i=build.i; -- get current execution address + + local jr,kr -- routine execution boundaries + local jc, kc + + --i=i+1; if i>build.n then i = 1 end end + + c=string.sub(prog,i,i) + + if build.state == 0 then + if c == "R" then + jr,kr=set_routine(i); i = jr; -- set up execution point i=jr + build.state = 1 + elseif c == "=" then + jc = string.find(prog,"%(",i+1) + kc = string.find(prog,"%)",jc+1) + local var1 = string.sub(prog,i+1,jc-1); + local var2 = get_var(string.sub(prog,jc+1,kc-1)); + build.var[var1]=var2 + i=kc + + elseif c == "+" then + jc = string.find(prog,"%(",i+1) + kc = string.find(prog,"%)",jc+1) + local var1 = string.sub(prog,i+1,jc-1); + local var2 = get_var(string.sub(prog,jc+1,kc-1)); + build.var[var1]=build.var[var1]+var2 + i=kc + elseif c == "-" then + jc = string.find(prog,"%(",i+1) + kc = string.find(prog,"%)",jc+1) + local var1 = string.sub(prog,i+1,jc-1); + local var2 = get_var(string.sub(prog,jc+1,kc-1)); + build.var[var1]=build.var[var1]-var2 + i=kc + elseif c == "E" then + jc = string.find(prog,"%(",i+1) + kc = string.find(prog,"%)",jc+1) + local INOT = string.sub(prog,i+1,i+3) == "NOT"; + local trigger; local var1; local var2; + + if INOT then + var1 = get_var(string.sub(prog,i+4,jc-1)); + var2 = get_var(string.sub(prog,jc+4,kc-1)); + else + var1 = get_var(string.sub(prog,i+1,jc-1)); + var2 = get_var(string.sub(prog,jc+1,kc-1)); + end + trigger = (var1 == var2) + + if (not INOT and trigger) or (INOT and not trigger)then + i=kc; + jr,kr=set_routine(i);i=jr + build.state = 1 + else + kc = string.find(prog,"]",kc+1) + i = kc + end + elseif c == "N" then + jc = string.find(prog,"%(",i+1) + kc = string.find(prog,"%)",jc+1) + local node = string.sub(prog,jc+1,kc-1) or "air"; + local INOT = string.sub(prog,i+1,jc-1) == "NOT"; + local trigger; + trigger = read_node.forward() == node; + + if (not INOT and trigger) or (INOT and not trigger)then + i=kc; + jr,kr=set_routine(i); i = jr + build.state = 1 + else + kc = string.find(prog,"]",kc+1) + i = kc + end + elseif c == "G" then + i=go_to(i); + elseif c == "C" then + jc = string.find(prog,"%(",i+1) + kc = string.find(prog,"%)",jc+1) + var = string.sub(prog,jc+1,kc-1); + i = kc + self.label(var .. "=" .. get_var(var)) + else + if cmd[c] then cmd[c]() else + if not build.ignored[c] then + self.label("run main: invalid instruction at position " .. i.. "\n" .. error_msg(i)); + build.state = -1; self.debug = false; + end + end + end + elseif build.state == 1 then -- routine + jr = build.jr; kr = build.kr; + i=run_routine(i,jr,kr) + end + + + i=i+1; if i>build.n then i = 1 end + + build.i = i -- update execution address + build.jr = jr; + build.kr = kr; + +end + +if build.debug then self.label("["..build.state .. "]" .. build.i .. "\n" .. string.sub(prog,build.i)) end +build.run() \ No newline at end of file diff --git a/scripts/server mods/chatlog.lua b/scripts/server mods/chatlog.lua new file mode 100644 index 0000000..2589db6 --- /dev/null +++ b/scripts/server mods/chatlog.lua @@ -0,0 +1,50 @@ +--rnd 2017 +if not logdata then + self.label("chatlog bot"); + _G.minetest.forceload_block(self.pos(),true) + n = 500; -- store so many messsages before repeating + maxresults = 100 -- display at most 'this' result + + logdata = {}; -- circular array to hold messages + idx = 1; + insert_log = function(logdata,text) -- store new message + idx = idx +1; + if idx > n then idx = 1 end + logdata[idx] = text; + end + + retrieve_log = function(logdata,count,filter) -- return last k messages, with filter only selected messages + + local k = 0; + local i=idx; local j=0; local ret = {} + + for j = 1,n do + if not logdata[i] then break end + if filter and not string.find(logdata[i], filter) then + else + ret[#ret+1] = logdata[i] + k=k+1 + if k>=count then break end -- enough results + end + i=i-1; if i < 1 then i = n end + end + return table.concat(ret,"\n") + end + + self.listen(1) +end + +speaker, msg = self.listen_msg() +if msg then + if string.sub(msg,1,4) == "?log" then + local j = string.find(msg," ",7); -- find first argument + local k;local text; + if j then k = tonumber(string.sub(msg,6,j-1)) else k = tonumber(string.sub(msg,6)) end -- if there was first argument find second + k = k or maxresults; + if j then text = retrieve_log(logdata,k,string.sub(msg,j+1)) else text = retrieve_log(logdata,k) end + local form = "size[8,8]".. "textarea[0.,0;11.,9.5;text;chatlog;".. text .. "]" + self.show_form(speaker, form) + else + insert_log(logdata, os.date("%X") .. " " .. speaker .. "> " .. msg) + end +end \ No newline at end of file diff --git a/scripts/server mods/colored chat.lua b/scripts/server mods/colored chat.lua new file mode 100644 index 0000000..d6ebb1c --- /dev/null +++ b/scripts/server mods/colored chat.lua @@ -0,0 +1,42 @@ +-- with current mods there are 4 registered chat responses so we add 5th +-- CHANGE COLOR OF CHAT FOR CERTAIN PLAYERS + +if not rom.color_chat_messages then rom.color_chat_messages = 1+#minetest.registered_on_chat_messages end + +colors = {"cyan", "LawnGreen"} +chatgroup = {}; -- players in here will see chat without colors +--say("chat " .. rom.chat_messages) + +minetest.registered_on_chat_messages[rom.color_chat_messages] = +function(name,message) + if message == "nocolor" then + chatgroup[name] = not chatgroup[name] + minetest.chat_send_all("colored chat display " .. (chatgroup[name] and "DISABLED" or "ENABLED") .. " for " .. name) + return false + else + --message = os.date("%X") .. " " .. name .." <> " .. message; + local newmessage = "["..name .."] " .. message; + local player = minetest.get_player_by_name(name); + local pos1 = player:get_pos(); + + for _,player in pairs(minetest.get_connected_players()) do + local name = player:get_player_name() + local pos2 = player:get_pos(); + local dist = math.sqrt((pos2.x-pos1.x)^2+(pos2.y-pos1.y)^2+ (pos2.z-pos1.z)^2) + local length = string.len(name); + local color = 1; -- default + if (chatgroup[name] or dist>32 or dist == 0) then color = 0 end + if string.find(message,string.lower(name)) then color = 2 end + + if color == 0 then + minetest.chat_send_player(name, newmessage) + else + minetest.chat_send_player(name, minetest.colorize(colors[color], newmessage)) + end + + end + end + return true +end + +self.remove() \ No newline at end of file diff --git a/scripts/simulators/genetic_trust.lua b/scripts/simulators/genetic_trust.lua new file mode 100644 index 0000000..2e07497 --- /dev/null +++ b/scripts/simulators/genetic_trust.lua @@ -0,0 +1,335 @@ +if not init then + rom.best_player = nil + init = true + depth = 4; -- how many moves does random player evaluate + error_rate = 0.25; -- players make wrong decision with this probability + generation = 10; -- how many times we repeat + steps = 100; -- how many steps each generation + bestc = 0; -- how many times was new best player picked + mode = 2; -- 1, evolution!, 2 play game + + -- game pay offs + rules = { + { + {2., 2.}, -- first player cooperate, second player cooperate: +2,+2 + {-1, 3}, -- first player cooperate, second player cheat: -1,+3 + }, + { + {3,-1}, -- first player cheats, second player cooperate + {0,0}, -- first player cheats, second player cheat + } + }; + + copytable = function(tab) + if type(tab)~="table" then return tab end + local ret = {}; + for k,v in pairs(tab) do ret[k] = copytable(v) end return + ret + end + + copycat = { + rules = { + {0}, -- initial: 0 = cooperate + {0,1}, -- after one move : cooperate if other cooperated last turn, cheat if other cheated last turn + }, + -- encode decision sequence in binary: 01110 = dec 8+4+2=14 + memory = 1, -- how many moves back player consideres? + moves ={}, -- opponent moves + mood = 0, -- probability that player will cheat if he had intention to cooperate + moody = 0.5, -- by how much cheat/cooperate change mood + points = 0, + name = "copycat" + } + + cheater = { + rules = { + {1}, + }, + memory = 0, -- how many moves back player consideres? + moves ={}, -- opponent moves + mood = 0, + moody = 0.5, + points = 0, + name = "cheater" + } + + realplayer = { + rules = { + {0}, + }, + memory = 0, -- how many moves back player consideres? + moves ={}, -- opponent moves + mood = 0, + moody = 0., + points = 0, + name = "real player", + real = true, + out = 0 + } + + + create_random_player = function(memory) + local rules = {}; + for i = 1, memory+1 do + rules[i] = {}; + for j = 1,2^(i-1) do + rules[i][j] = math.random(2)-1 + end + end + return {rules = rules, memory = memory, moves = {}, mood = 0, moody = math.random(), points = 0, name = "randomplayer"} + end + + + -- player makes next move according to his memory of moves so far + play = function(player) + if player.real then return player.out end -- real player + local moves = player.moves; + local n = #moves; + if n > player.memory then n = player.memory end -- there are many moves, examine only what we can + local rules = player.rules[n+1] + local state = bin2dec(player.moves,n); -- examine last n moves + --say("n " .. n .. " state " .. state) + return rules[state+1] + end + + + + group_play = function(playrs) -- each randomplayer plays with every other player in lower group + local n = #playrs; + local m = 10; -- play m games with each opponent, random pair order + for i = 1,10 do + for j = 11,20 do -- i plays with j, randomized order + playrs[i].moves = {}; playrs[j].moves = {}; -- reset remembered moves before paired match! + playrs[i].mood = 0; + for k = 1,m do + if math.random(2) == 1 then + interact(playrs[i],playrs[j]) + else + interact(playrs[j],playrs[i]) + end + end + end + end + + end + + sort_players = function(pl) + table.sort(pl, + function(p1,p2) return p1.points1 then mood = 1 elseif mood<0 then mood = 0 end + player2.mood = mood + end + + if res1 == 0 then -- mood change for player1 + mood = player1.mood; + if res2==1 then + mood = mood + player1.moody; + else + mood = mood - player1.moody; + end + if mood>1 then mood = 1 elseif mood<0 then mood = 0 end + player1.mood = mood + end + end + + dec2bin = function(input) + local ret = {}; + if input == 0 then return {0} end + while input~=0 do + local r=input%2; input = (input -r)/2 + ret[#ret+1] = r; + end + local n = #ret; + local rret = {} + for i = 1, n do + rret[i]=ret[n-i+1] + end + return rret + end + + bin2dec = function(bin, length) -- length= how many last elements we take + if length == 0 then return 0 end + if not length then length = #bin end + local offset = #bin - length; if offset<0 then offset = 0 end + local ret = 0; + for i = 1,#bin-offset do + ret = 2*ret + bin[i+offset] + end + return ret + end + + get_results = function(players) + local ret = {} for i=1,#players do ret[i] = players[i].name .. " " .. players[i].points .. "M " .. players[i].mood .. "(" .. players[i].moody .. ")" end return table.concat(ret,"\n") + end + + + players = {} -- start with 5 cheaters, 5 copycats and 10 randomplayers + + if mode == 1 then + for i = 1,5 do players[i] = copytable(cheater) end + for i = 1,5 do players[5+i] = copytable(copycat) end + + for i = 1,10 do players[10+i] = create_random_player(depth) end -- last 10 players are random + + + age = 0 + rom.best_player = nil; + elseif mode == 2 then + players = {copytable(realplayer), copytable( create_random_player(4) ) } ; + self.listen(1) + end + --players[20] = copytable(rom.best_player) or create_random_player(depth) -- add best player from before + --players[20].name = "randomplayer" + + +end + + +if mode == 1 then + + local bestpoints = 0 + for k = 1, generation do -- repeat experiment generation* + + --rom.best_player = nil + for i = 1,#players do players[i].points = 0 end + + + for j = 1, steps do -- several steps to see who is best long term on average + --for i = 1,#players do players[i].points = 0 end + group_play(players) + end + + bestpoints = 0 + + genetics(players) -- remove 5 worst randomplayers & replace them by new randoms + + + local population = {} + bestrandom = 0 + for i = 11,20 do + if players[i].points >= bestrandom then bestrandom = players[i].points end + population[#population+1] = players[i] + end + --say("randomplayer population size " .. #population) + sort_players(population); + if rom.best_player then + lastbest = rom.best_player.points + if bestrandom >= lastbest then -- PICK NEW BEST + bestc = bestc+1 + rom.best_player = copytable(population[#population]); -- remember best randomplayer for next experiment + end + else + rom.best_player = copytable(population[#population]); -- remember best randomplayer for next experiment + end + + + end + + age = age + generation + + --display results! + msg = "" + msg = msg .. "Planet Earth (galactical name Halfwits), age " .. age .. ", error_rate " .. error_rate .. ", steps " .. steps .. ", generations " .. generation .. ":\n" + msg = msg .."\nlast round\n" + --sort_players(players) + bestpoints = 0 + for i =1,#players do + if players[i].points>bestpoints then bestpoints = players[i].points end + msg = msg .. players[i].name .. ": " .. players[i].points .. " M " .. players[i].mood .. "(" .. players[i].moody .. ")\n" + end + local rules = rom.best_player.rules; + msg = msg .. "BEST: " .. bestpoints .. "\n\n## alltime best random player no. " .. bestc .. ", points " .. rom.best_player.points .. " moody " .. rom.best_player.moody .. + "\ncurrent best random player/max current score: ".. math.floor(100*bestrandom/bestpoints*100)/100 .. "% \nrules " .. serialize(rules) .. " )\n"; + + local msg1 = "{" .. rules[1][1] .. "}\n" + for i = 2,#rules do + local rule = rules[i]; + msg1 = msg1.. "{" + for j = 1,#rule do + local rule_string = table.concat(dec2bin(j-1),""); + rule_string = string.rep("0",i-string.len(rule_string)-1) .. rule_string; + msg1 = msg1 .. rule_string .."="..rule[j].."," + end + msg1 = msg1 .. "}\n" + end + + self.label(msg .. msg1) + +elseif mode == 2 then -- play vs player + + speaker,msg = self.listen_msg(); + if msg then + if msg == "0" or msg == "1" then + local nextmove = tonumber(msg); + if nextmove == 1 and players[1].out == 1 then + say("look buddy, we dont like cheaters around here. dont cheat twice in a row") + else + players[1].out = nextmove + interact(players[1], players[2]) + say("input " .. players[1].out .. ", BOT MOVES: " .. serialize(players[1].moves) .. " BOT MOOD " .. players[2].mood .. "(" .. players[2].moody .. ") SCORE: you " .. players[1].points .. " bot " .. players[2].points) + end + end + end + + +end \ No newline at end of file diff --git a/scripts/simulators/group_assembly.lua b/scripts/simulators/group_assembly.lua new file mode 100644 index 0000000..5088f48 --- /dev/null +++ b/scripts/simulators/group_assembly.lua @@ -0,0 +1,153 @@ +-- rnd's robot swarm assembly algorithm 2017 +-- https://www.youtube.com/watch?v=xK54Bu9HFRw&feature=youtu.be&list=PLC7119C2D50BEA077 +-- notes: +-- 1. limitation: there must be room for diagonal move +-- this is not big limitation: assume bots are circles of radius 1, then to allow diagonal movement +-- just spread them by factor sqrt(2)~1.4 initially +-- 2. initial random placement(not part of move algorithm): due to collision some bots may occupy same place + +if not pos then + n=50; m = 500; + stuck = m; + state = 0; + step = 0 + + pos = {}; tpos = {}; + -- INIT + for i = 1, m do + --local r = i % n;local c = (i-r)/n;pos[i]={n-c,r+1}; -- regular rectangle shape + pos[i]={math.random(n),math.random(n)}; + --tpos[i]={math.random(n),math.random(n)}; -- random shape + local r = i % n;local c = (i-r)/n;tpos[i]={c+1,r+1}; -- regular rectangle shape + end + doswap = true -- use closest swap or not? + + -- initially swap ids so that i-th bot is closest to i-th target + permute2closest = function() + -- swap bot i with one closest to i-th target + free = {}; for i = 1, m do free[i] = i end -- list of available ids for swapping + local opos = {}; + for i=1,m do opos[i] = {pos[i][1],pos[i][2]} end + closest = {}; + + for i = 1,m do + -- find closest bot to i-th point + local dmin = 2*n; + local jmin = -1; + local tp = tpos[i]; + for j = 1,#free do + local p = opos[free[j]]; + local d = math.sqrt((p[1]-tp[1])^2+(p[2]-tp[2])^2); + if d< dmin then dmin = d; jmin = j end + end + if jmin>0 then + local newj = free[jmin]; + pos[i] = {opos[newj][1], opos[newj][2]}; -- reassign id + table.remove(free,jmin); + end + end + end + + if doswap then + permute2closest() + else + for i=1,m do pos[i] = opos[i] end -- just copy positions + end + + data = {}; + + for i = 1,n do data[i]={}; for j=1,n do data[i][j] = {0,0,1} end end -- 0/1 present, id, move status? + for i = 1,#pos do data[pos[i][1]][pos[i][2]] = {1,i,1} end -- 1=present,i = id, 1=move status + + + step_move = function() + local count = 0; + for i = 1, #pos do + local p = pos[i]; + local tp = tpos[i]; + local x = tp[1]-p[1]; + local y = tp[2]-p[2]; + local d = math.sqrt(x^2+y^2); + if d~=0 then + x=x/d;y=y/d + x=p[1]+x;y=p[2]+y; + x=math.floor(x+0.5);y=math.floor(y+0.5); + if data[x][y][1]==0 then -- target is empty + data[p[1]][p[2]][1] = 0; data[x][y][1] = 1 + pos[i]={x,y}; data[x][y][2] = i; data[x][y][3] = 1; + end + else + data[p[1]][p[2]][3] = 0 -- already at position + count = count +1 + end + end + return m-count -- how many missaligned + end + + render = function() + out = ""; + for i = 1,n do + for j= 1,n do + if data[i][j][1]==1 then + local id = data[i][j][2]; id = id % 10; + if data[i][j][3] == 0 then + out = out .. id + else + out = out .. "S" -- didnt move last step + end + else + out = out .. "_" -- empty + end + end + out = out .. "\n" + end + return out + end + + s=1 + self.listen(1) +end + +speaker,msg = self.listen_msg() +if speaker == "rnd" then + if msg == "p" then + say("permute2closest()") + permute2closest() + end +end + +if s == 1 then + step = step + 1 + local c = step_move(); + --state = how many times stuck count was constant; if more than 3x then perhaps it stabilized? + -- stuck = how many robots not yet in position + if c 3 then state = 0 s = 2 end end + self.label(render().. "\nleft " .. stuck .. "="..(100*stuck/m) .. "%") + if stuck == 0 then say("*** COMPLETED! in " .. step .." steps ***") s = 3 end +elseif s == 2 then + -- do swaps of stuck ones.. + for i = 1, #pos do + local p = {pos[i][1], pos[i][2]}; + local tp = tpos[i]; + local x = tp[1]-p[1]; + local y = tp[2]-p[2]; + local d = math.sqrt(x^2+y^2); + if d~=0 then + x=x/d;y=y/d + x=p[1]+x;y=p[2]+y; + x=math.floor(x+0.5);y=math.floor(y+0.5); -- see whats going on in attempted move direction + if data[x][y][1]==1 then -- there is obstruction, do id swap + local x1,y1; + x1 = x; y1 = y; + local idb = data[x][y][2]; -- blocker id, stuck robot id is i + pos[i]={x,y}; -- new position for id-i is position of blocker + pos[idb] = {p[1],p[2]}; -- new position for blocker is position of stuck robot + -- reset stuck status + data[x][y][3]=1;data[p[1]][y][p[2]]=1; + end + end + end + + s=1 +end +--TO DO: if robots stuck do permute2closest again \ No newline at end of file diff --git a/scripts/simulators/layout_designer.lua b/scripts/simulators/layout_designer.lua new file mode 100644 index 0000000..f7e0185 --- /dev/null +++ b/scripts/simulators/layout_designer.lua @@ -0,0 +1,132 @@ +-- rnd 2017 +if not data then + m=50;n=50; minescount = m*n/14; + + t0 = _G.minetest.get_gametime(); + rom.data = {}; rom.rooms = {} -- so we dont make new tables everytime + data = rom.data; spawnpos = self.spawnpos(); + rooms = rom.rooms; + for i = 1, minescount do local i = math.random(m); local j = math.random(n); if not data[i] then data[i] = {} end; data[i][j] = 1; end + + get_mine_count = function(i,j) + if i<0 or i>m+1 or j<0 or j>n+1 then return 0 end; count = 0 + for k = -1,1 do for l = -1,1 do + if data[i+k] and data[i+k][j+l] == 1 then count = count +1 end + end end + return count + end + + -- generate level data + for i = 1,m do rooms[i]={}; for j = 1,n do + if get_mine_count(i,j) > 0 or (data[i] and data[i][j] == 1) then + rooms[i][j] = 1 + else + rooms[i][j] = 0 + end + end end + + + -- find passages + for i = 2,m-1 do for j = 2,n-1 do + if rooms[i][j] == 0 then + local A11 = rooms[i-1][j-1]; local A21 = rooms[i][j-1];local A31 = rooms[i+1][j-1]; + local A12 = rooms[i-1][j]; local A32 = rooms[i+1][j]; + local A13 = rooms[i-1][j+1]; local A23 = rooms[i][j+1];local A33 = rooms[i+1][j+1]; + + if (A12~=1 and A32~=1 and A21 == 1 and A23 == 1) or + (A12==1 and A32==1 and A21 ~= 1 and A23 ~= 1) + then + rooms[i][j] = 2; -- passage + end + end + end end + + read_room = function(i,j) + if i<1 or i > m then return nil end + if j<1 or j > n then return nil end + return rooms[i][j] + end + + render_rooms = function() + for i = 1,m do for j = 1,n do + local tile = rooms[i][j]; + if tile == 0 then + _G.minetest.swap_node({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j}, {name = "air"}) + _G.minetest.swap_node({x=spawnpos.x+i,y=spawnpos.y+1,z=spawnpos.z+j}, {name = "air"}) + elseif tile == 1 then + _G.minetest.swap_node({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j}, {name = "basic_robot:buttonFFFFFF"}) + _G.minetest.swap_node({x=spawnpos.x+i,y=spawnpos.y+1,z=spawnpos.z+j}, {name = "basic_robot:buttonFFFFFF"}) + elseif tile == 2 then -- passage, insert 1 door in it + --determine direction + local dir = {0,0} + if read_room(i+1,j) == 2 then dir = {1,0} + elseif read_room(i-1,j) == 2 then dir = {-1,0} + elseif read_room(i,j+1) == 2 then dir = {0,1} + elseif read_room(i,j-1) == 2 then dir = {0,-1} + elseif read_room(i-1,j) ~= 0 or read_room(i+1,j) ~= 0 then dir = {0,1} + else dir = {1,0} + end + local k1 = 0; local k2 = 0; + for k = 1, 10 do + if read_room(i+dir[1]*k,j+dir[2]*k)~= 2 then k1 = k-1 break + else + if rooms[i+dir[1]*k] then rooms[i+dir[1]*k][j+dir[2]*k] = 0 end + _G.minetest.swap_node({x=spawnpos.x+i+dir[1]*k,y=spawnpos.y,z=spawnpos.z+j+dir[2]*k}, {name = "air"}) + end + end + for k = 1, 10 do + if read_room(i-dir[1]*k,j-dir[2]*k)~= 2 then k2 = -(k-1) break + else + if rooms[i+dir[1]*k] then rooms[i+dir[1]*k][j+dir[2]*k] = 0 end + _G.minetest.swap_node({x=spawnpos.x+i-dir[1]*k,y=spawnpos.y,z=spawnpos.z+j-dir[2]*k}, {name = "air"}) + end + end + local k = math.floor((k1+k2)/2); + --place door + local param = 1 + if dir[1]~=0 then param = 2 end + _G.minetest.swap_node({x=spawnpos.x+i+dir[1]*k,y=spawnpos.y+1,z=spawnpos.z+j+dir[2]*k}, {name = "air"}) + if param == 1 then + _G.minetest.swap_node({x=spawnpos.x+i+dir[1]*k,y=spawnpos.y,z=spawnpos.z+j+dir[2]*k}, {name = "doors:door_wood_a", param2 = 2}) + else + _G.minetest.swap_node({x=spawnpos.x+i+dir[1]*k,y=spawnpos.y,z=spawnpos.z+j+dir[2]*k}, {name = "doors:door_wood_a", param2 = 1}) + end + + + + elseif tile == 3 then + _G.minetest.swap_node({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j}, {name = "default:stonebrick"}) + + end + end end + end + + render_rooms() + + + fill_room = function(x,y, roomIdx) -- room index: 1,2,3,... will be written as -1,-2,.. in rooms + local tile = rooms[i][j]; + if tile ~= 0 then return false end + + rooms[i][j] = -roomIdx; + local stk = {{i,j}}; -- stack to place border tiles + local tmpstk = {}; -- temporary stack + + local free = true; -- are there any free room tiles + + while free do + + -- loop all stack tiles + for i=1,#stk do + local p = stk[i]; + tile = rooms[p[1]][p[2]]; + end + + end + + + end + + +end +self.remove() \ No newline at end of file diff --git a/scripts/simulators/nuclear.lua b/scripts/simulators/nuclear.lua new file mode 100644 index 0000000..7c76aa5 --- /dev/null +++ b/scripts/simulators/nuclear.lua @@ -0,0 +1,70 @@ +-- rnd 2017 +if not data then + data = {FUEL = 1.8,TV=0,T=0,P=0,E=0;} + + generate_nuclear_power = function(CONTROL,COOLING) + if COOLING>1 then COOLING = 1 elseif COOLING<0 then COOLING = 0 end + if CONTROL>1 then CONTROL = 1 elseif CONTROL<0 then CONTROL = 0 end + --data = ... + local FUEL = data.FUEL; + local TV = data.TV; + local T = data.T; + local P = data.P; + local E = data.E; + + -- reactor specifications + local TVmax = 10000; + local FUELC = 2; -- critical mass for fuel + local DECAYRATE = 1-0.01; -- how much fuel decays per time step + local COOLINGCOEF = 1; -- how efficient is cooling per 1 unit of power (how many degrees are cooled) + local PCOEF = 1; -- how efficiently temperature is converted to power + + local TGV = FUEL/(1-(FUEL/FUELC)^2); + if TGV>TVmax then TGV = TVmax end; if FUEL>FUELC then TGV = TVmax end -- basic temperature generation speed generated in core + TV = TV + TGV* CONTROL - P*COOLING*COOLINGCOEF; -- temperature change speed + T = T + TV ; + P = 0.5*P + T*PCOEF; P = P - P*COOLING -- produced power + FUEL = FUEL*DECAYRATE; + if P<0 then P = 0 end if T<0 then T = 0 end E=E+P; + + data.FUEL = FUEL; + data.T = T; + data.TV = TV; + data.P = P; + data.E = E + return E, P, T, FUEL, TV + end + + render = function(data) -- data should be normalized [0,1] + local tname = "pix.png"; + local obj = _G.basic_robot.data[self.name()].obj; + local n = 150; local m = n; + local length = #data; if length == 0 then return end + local hsize = 1; local wsize=hsize; + local tex = "[combine:"..(wsize*m).."x"..(hsize*n); + for i = 1,length do + j=math.floor((1-data[i])*m); + local ix = math.floor((i/length)*n) + tex = tex .. ":"..(ix*wsize).."," .. (j*hsize) .. "="..tname + end + obj:set_properties({visual = "sprite",textures = {tex}}) + end + + tdata = {}; + COOLING = 0.03 + +end + +-- generate_nuclear_power(CONTROL, COOLING) +-- CONTROL = 0.20; -- control rods; 1 = no control, between 0 and 1 +-- COOLING = 0.03; -- how much power assigned for cooling, in ratio of output power P; between 0 and 1 + + +E,P,T,FUEL,TV = generate_nuclear_power(0.2,COOLING) +-- cooling strategy +if TV < 0 then COOLING = 0.0 elseif T>90 then COOLING = 0.03 else COOLING = 0 end + +tdata[#tdata+1]=math.min(T/100,1); +render(tdata) + +self.label( "T " .. T .. "\nTV " .. TV .. "\nP ".. P .. "\nFUEL " .. FUEL .. "\nTOTAL ENERGY PRODUCED " .. E ) \ No newline at end of file diff --git a/scripts/simulators/redstone_emulator.lua b/scripts/simulators/redstone_emulator.lua new file mode 100644 index 0000000..7fc4447 --- /dev/null +++ b/scripts/simulators/redstone_emulator.lua @@ -0,0 +1,507 @@ +-- REDSTONE EMULATOR & EDITOR +--v 06/28/2018a + +if not init then + local players = find_player(5); + if not players then + name = ""; + else + name = players[1] + player_ = puzzle.get_player(name) + local inv = player_:get_inventory(); + inv:set_stack("main", 8, puzzle.ItemStack("basic_robot:control 1 0 \"@\"")) -- add controller in players inventory + --add items for building + inv:set_stack("main", 1, puzzle.ItemStack("default:pick_diamond")) + inv:set_stack("main", 2, puzzle.ItemStack("basic_robot:button_273 999")) -- switch 9 = 273/274 + inv:set_stack("main", 3, puzzle.ItemStack("basic_robot:button_275 999")) -- button 7 = 275/276 + inv:set_stack("main", 4, puzzle.ItemStack("basic_robot:button_277 999")) -- equalizer 61 = 277 + inv:set_stack("main", 5, puzzle.ItemStack("basic_robot:button_278 999")) -- setter 15 = 278 + inv:set_stack("main", 6, puzzle.ItemStack("basic_robot:button_279 999")) -- piston 171 = 279 + inv:set_stack("main", 7, puzzle.ItemStack("basic_robot:button_282 999")) -- delayer 232 = 282 + inv:set_stack("main", 9, puzzle.ItemStack("basic_robot:button_281 999")) -- NOT 33 = 281 + inv:set_stack("main", 10, puzzle.ItemStack("basic_robot:button_280 999")) -- diode 175 = 280 + inv:set_stack("main", 11, puzzle.ItemStack("basic_robot:button_283 999")) -- platform 22 = 283 + inv:set_stack("main", 12, puzzle.ItemStack("basic_robot:button_284 999")) -- giver 23 150/284 + inv:set_stack("main", 13, puzzle.ItemStack("basic_robot:button_285 999")) -- checker 24 151/285 + + + local round = math.floor; protector_position = function(pos) local r = 32;local ry = 2*r; return {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry,z=round(pos.z/r+0.5)*r}; end + local spawnblock = protector_position(self.spawnpos()) + + local meta = puzzle.get_meta(spawnblock); + meta:set_string("shares", name) -- add player to protection! + puzzle.chat_send_player(name,colorize("yellow","#EDITOR: if you need any blocks get them by using 'give me' in craft guide. you can now use controller to make links from pointed at blocks. In addition hold SHIFT to display infos. Reset block links by selecting block 2x")) + end + + init = true + self.spam(1) + self.label(colorize("orange","REDSTONE EMULATOR/EDITOR")) + + + + + -- 1. EMULATOR CODE + + + TTL = 16 -- signal propagates so many steps before dissipate + --self.label(colorize("red","REDSTONE")..colorize("yellow","EMULATOR")) + opcount = 0; + + -- DEFINITIONS OF BLOCKS THAT CAN BE ACTIVATED + toggle_button_action = function(mode,pos,ttl) -- SIMPLE TOGGLE BUTTONS - SWITCH + if not ttl or ttl <=0 then return end + if mode == 1 then -- turn on + puzzle.set_node(pos,{name = "basic_robot:button_274"}) + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + for i = 1,n do activate(1,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + else -- turn off + puzzle.set_node(pos,{name = "basic_robot:button_273"}) + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + for i = 1,n do activate(0,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + end + end + + + button_action = function(mode,pos,ttl) -- SIMPLE ON BUTTON, TOGGLES BACK OFF after 1s + if not ttl or ttl <=0 then return end + if mode == 0 then return end + puzzle.set_node(pos,{name = "basic_robot:button_276"}) + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + + minetest.after(1, function() + puzzle.set_node(pos,{name = "basic_robot:button_275"}) + for i = 1,n do activate(0,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + end) + for i = 1,n do activate(1,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + end + + giver_action = function(mode,pos,ttl) -- GIVER: give block below it to player and activate targets + local nodename = puzzle.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name; + if nodename == "air" then return end + local objects = minetest.get_objects_inside_radius(pos, 5);local player1; + for _,obj in pairs(objects) do if obj:is_player() then player1 = obj; break end end + if player1 then + player1:get_inventory():add_item("main", puzzle.ItemStack(nodename)) + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + for i = 1,n do activate(1,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + end + end + + checker_action = function(mode,pos,ttl) -- CHECKER: check if player has block below it, then remove block from player and activate targets + local nodename = puzzle.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name; + if nodename == air then return end + local objects = minetest.get_objects_inside_radius(pos, 5);local player1; + for _,obj in pairs(objects) do if obj:is_player() then player1 = obj; break end end + if player1 then + local inv = player1:get_inventory(); + if inv:contains_item("main", puzzle.ItemStack(nodename)) then + inv:remove_item("main",puzzle.ItemStack(nodename)) + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + for i = 1,n do activate(1,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + end + end + end + + equalizer_action = function(mode,pos,ttl) -- CHECK NODES AT TARGET1,TARGET2. IF EQUAL ACTIVATE TARGET3,TARGET4,... + if not ttl or ttl <=0 then return end + if mode == 0 then return end + + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + local node1 = puzzle.get_node({x=meta:get_int("x1")+pos.x,y=meta:get_int("y1")+pos.y,z=meta:get_int("z1")+pos.z}).name + local node2 = puzzle.get_node({x=meta:get_int("x2")+pos.x,y=meta:get_int("y2")+pos.y,z=meta:get_int("z2")+pos.z}).name + + + if node1==node2 then + for i = 3,n do activate(1,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + else + for i = 3,n do activate(0,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + end + end + + delayer_action = function(mode,pos,ttl) -- DELAY FORWARD SIGNAL, delay determined by distance of target1 from delayer ( in seconds) + if not ttl or ttl <=0 then return end + local meta = puzzle.get_meta(pos); + if not meta then return end + + local n = meta:get_int("n"); + local pos1 = {x=meta:get_int("x1"),y=meta:get_int("y1"),z=meta:get_int("z1")} + local delay = math.sqrt(pos1.x^2+pos1.y^2+pos1.z^2); + + if delay > 0 then + minetest.after(delay, function() + if mode == 1 then + for i = 2,n do activate(1,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + else + for i = 2,n do activate(0,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + end + end) + end + end + + diode_action = function(mode,pos,ttl) -- ONLY pass through ON signal + if not ttl or ttl <=0 then return end + if mode ~= 1 then return end + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + for i = 1,n do activate(1,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + end + + not_action = function(mode,pos,ttl) -- negate signal: 0 <-> 1 + if not ttl or ttl <=0 then return end + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + for i = 1,n do activate(1-mode,{x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z},ttl) end + end + + setter_action = function(mode,pos,ttl) -- SETS NODES IN TARGET AREA TO PRESELECTED NODE + if not ttl or ttl <=0 then return end + if mode == 0 then return end + + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + if n ~= 3 then say("#setter: error, needs to be set with 3 links"); return end + local node1 = puzzle.get_node({x=meta:get_int("x1")+pos.x,y=meta:get_int("y1")+pos.y,z=meta:get_int("z1")+pos.z}) + local pos1 = {x=meta:get_int("x2")+pos.x,y=meta:get_int("y2")+pos.y,z=meta:get_int("z2")+pos.z} + local pos2 = {x=meta:get_int("x3")+pos.x,y=meta:get_int("y3")+pos.y,z=meta:get_int("z3")+pos.z} + + if pos1.x>pos2.x then pos1.x,pos2.x = pos2.x,pos1.x end + if pos1.y>pos2.y then pos1.y,pos2.y = pos2.y,pos1.y end + if pos1.z>pos2.z then pos1.z,pos2.z = pos2.z,pos1.z end + + local size = (pos2.x-pos1.x+1)*(pos2.y-pos1.y+1)*(pos2.z-pos1.z+1) + if size > 27 then say("#setter: target area too large, more than 27 blocks!"); return end + for x = pos1.x,pos2.x do + for y = pos1.y,pos2.y do + for z = pos1.z,pos2.z do + puzzle.set_node({x=x,y=y,z=z},node1) + end + end + end + end + + local piston_displaceable_nodes = {["air"] = 1,["default:water_flowing"] = 1} + + piston_action = function(mode,pos,ttl) -- PUSH NODE AT TARGET1 AWAY FROM PISTON + if not ttl or ttl <=0 then return end + --if mode == 0 then return end + + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + if n < 1 or n>2 then say("#piston: error, needs to be set with at least link and most two"); return end + local pos1 = {x=meta:get_int("x1")+pos.x,y=meta:get_int("y1")+pos.y,z=meta:get_int("z1")+pos.z} + + -- determine direction + local dir = {x=pos1.x-pos.x, y= pos1.y-pos.y, z= pos1.z-pos.z}; + + local dirabs = {x=math.abs(dir.x), y= math.abs(dir.y), z= math.abs(dir.z)}; + local dirmax = math.max(dirabs.x,dirabs.y,dirabs.z); + + if dirabs.x == dirmax then dir = { x = dir.x>0 and 1 or -1, y = 0,z = 0 } + elseif dirabs.y == dirmax then dir = { x = 0, y = dir.y>0 and 1 or -1, z=0} + else dir = {x = 0, y = 0, z = dir.z>0 and 1 or -1} + end + + local pos2 = {x=pos1.x+dir.x,y=pos1.y+dir.y,z=pos1.z+dir.z}; + + if mode == 0 then pos1,pos2 = pos2,pos1 end + + local node1 = puzzle.get_node(pos1) + if node1.name == "air" then return end + + + if piston_displaceable_nodes[puzzle.get_node(pos2).name] then + puzzle.set_node(pos2, node1) + puzzle.set_node(pos1, {name = "air"}) + minetest.check_for_falling(pos2) + self.sound("doors_door_open",1,pos) + end + end + + platform_action = function(mode,pos,ttl) -- SPAWN MOVING PLATFORM + + if mode~=1 then return end + local meta = puzzle.get_meta(pos); + if not meta then return end + local n = meta:get_int("n"); + if n ~= 2 then say("#platform: error, needs to be set with 2 targets"); return end + local pos1 = {x=meta:get_int("x1")+pos.x,y=meta:get_int("y1")+pos.y,z=meta:get_int("z1")+pos.z} + local pos2 = {x=meta:get_int("x2")+pos.x,y=meta:get_int("y2")+pos.y,z=meta:get_int("z2")+pos.z} + + -- determine direction + local dir = {x=pos2.x-pos1.x, y= pos2.y-pos1.y, z= pos2.z-pos1.z}; + + local obj = minetest.add_entity(pos1, "basic_robot:projectile"); + + if not obj then return end + obj:setvelocity(dir); + --obj:setacceleration({x=0,y=-gravity,z=0}); + local luaent = obj:get_luaentity(); + luaent.name = name; + luaent.spawnpos = pos1; + + local nodename = puzzle.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name; + local tiles = minetest.registered_nodes[nodename].tiles; tiles = tiles or {}; + local texture = tiles[1] or "default_stone"; + obj:set_properties({visual = "cube",textures = {texture,texture,texture,texture,texture,texture},visual_size = {x=1,y=1}, + collisionbox={-0.5,-0.5,-0.5,0.5,0.5,0.5}}) + end + + + -- HOW TO ACTIVATE TARGET ELEMENT - adds mesecons/basic machines compatibility + activate = function(mode, pos, ttl) + if not ttl or ttl <=0 then return end + local nodename = puzzle.get_node(pos).name; + local active_element = active_elements[nodename]; + opcount = opcount + 1 + if opcount > 64 then say("#puzzle error: opcount 64 exceeded. too many active connections."); error("#puzzle: abort") end + + if active_element then + active_element(mode,pos,ttl-1) + else -- try mesecons activate + local nodename = puzzle.get_node(pos).name + local table = minetest.registered_nodes[nodename]; + if table and table.mesecons then else return end + + local effector=table.mesecons.effector; + + if mode == 1 then + if effector.action_on then + effector.action_on(pos,node,ttl) + end + else + if effector.action_off then + effector.action_off(pos,node,ttl) + end + end + end + end + + -- THESE REACT WHEN PUNCHED + interactive_elements = { + [275] = {button_action,1}, -- BUTTON, 1 means it activates(ON) on punch + [273] = {toggle_button_action,1}, -- TOGGLE BUTTON_OFF + [274] = {toggle_button_action,0}, -- TOGGLE BUTTON_ON, 0 means it deactivates + [284] = {giver_action,0}, -- GIVER: give player item below it when activated and activate targets after that + [285] = {checker_action,0}, -- CHECKER: check if player has block below it in inventory, remove it and activate targets after that + } + + -- THESE CAN BE ACTIVATED WITH SIGNAL + active_elements = { + ["basic_robot:button_275"] = button_action, -- BUTTON, what action to do on activate + ["basic_robot:button_273"] = toggle_button_action, -- TOGGLE BUTTON_OFF + ["basic_robot:button_274"] = toggle_button_action, -- TOGGLE BUTTON_ON + ["basic_robot:button_278"] = setter_action, -- SETTER + ["basic_robot:button_277"] = equalizer_action, -- EQUALIZER + ["basic_robot:button_279"] = piston_action, -- PISTON + ["basic_robot:button_283"] = platform_action, -- PLATFORM + ["basic_robot:button_282"] = delayer_action, -- DELAYER + ["basic_robot:button_280"] = diode_action, -- DIODE + ["basic_robot:button_281"] = not_action, -- NOT + } + + + -- EDITOR CODE -- + + edit = {}; + edit.state = 1; -- tool state + edit.source = {}; edit.sourcenode = ""; -- selected source + + -- blocks that can be activated + edit.active_elements = { + ["basic_robot:button_275"] = "button: now select one or more targets", -- button + ["basic_robot:button_273"] = "switch: now select one or more targets", -- switch OFF + ["basic_robot:button_274"] = "switch: now select one or more targets", -- switch ON + ["basic_robot:button_278"] = "setter: target1 defines what material wall will use, target2/3 defines where wall will be", -- setter + ["basic_robot:button_277"] = "equalizer: target1 and target2 are for comparison, other targets are activated", -- equalizer + ["basic_robot:button_279"] = "piston: push block at target1 in direction away from piston", -- equalizer + ["basic_robot:button_283"] = "platform: select target1 to set origin, target2 for direction", -- PLATFORM + ["basic_robot:button_282"] = "delayer: distance from delayer to target1 determines delay", -- delayer + ["basic_robot:button_280"] = "diode: only pass through ON signal", -- DIODE + ["basic_robot:button_281"] = "NOT gate: negates the signal", -- NOT + + ["basic_robot:button_284"] = "GIVER: give player item below it when activated and activate targets after that", + ["basic_robot:button_285"] = "CHECKER: check if player has block below it in inventory, remove it and activate targets after that", + } + + linker_use = function(pos) + if not pos then return end + + --say(serialize(player_:get_player_control())) + if edit.state < 0 then -- link edit mode! + local meta = puzzle.get_meta(edit.source); + local i = -edit.state; + meta:set_int("x" ..i, pos.x-edit.source.x); meta:set_int("y" ..i, pos.y-edit.source.y); meta:set_int("z" ..i, pos.z-edit.source.z) + puzzle.chat_send_player(name, colorize("red", "EDIT ".. " target " .. i .. " changed")) + edit.state = 1 + goto display_particle + end + + if player_:get_player_control().sneak then -- SHOW LINKS + local meta = puzzle.get_meta(pos); + local n = meta:get_int("n"); + local nodename = puzzle.get_node(pos).name; + local active_element = edit.active_elements[nodename] + if active_element and edit.source.x == pos.x and edit.source.y == pos.y and edit.source.z == pos.z then -- gui with more info + local form = "size[4,"..(0.75*n).."] label[0,-0.25; "..active_element .."]" ; + for i = 1,n do -- add targets as lines + form = form .. + "button[0,".. (0.75*i-0.5) .. ";1,1;".."S"..i..";" .. "show " .. i .. "]".. + "button_exit[1,".. (0.75*i-0.5) .. ";1,1;".."E"..i..";" .. "edit " .. "]" .. + "button_exit[2,".. (0.75*i-0.5) .. ";1,1;".."D"..i..";" .. "delete " .. "]".. + "label[3,".. (0.75*i-0.25) .. "; " .. meta:get_int("x"..i) .. " " .. meta:get_int("y"..i) .. " " .. meta:get_int("z"..i) .."]" + end + self.show_form(name,form); + edit.state = 3; + return + end + edit.source = {x=pos.x,y=pos.y, z=pos.z}; + edit.state = 1 + if not active_element then return end + local i = string.find(active_element,":"); + if not i then return end + puzzle.chat_send_player(name,colorize("red","#INFO ".. string.sub(active_element,1,i-1) ..":") .." has " .. n .. " targets. Select again for more info.") + meta:set_string("infotext",string.sub(active_element,1,i-1)) -- write name of element on it! + + for i = 1, n do + minetest.add_particle( + { + pos = {x=meta:get_int("x"..i)+pos.x,y=meta:get_int("y"..i)+pos.y,z=meta:get_int("z"..i)+pos.z}, + expirationtime = 5, + velocity = {x=0, y=0,z=0}, + size = 18, + texture = "010.png", + acceleration = {x=0,y=0,z=0}, + collisiondetection = true, + collision_removal = true, + } + ) + end + return + end + + if edit.state == 1 then -- SET SOURCE + local nodename = puzzle.get_node(pos).name; + local active_element = edit.active_elements[nodename] + if not active_element then puzzle.chat_send_player(name,colorize("red","#ERROR linker:").. " source must be valid element like switch"); return end + edit.source = {x=pos.x,y=pos.y, z=pos.z}; + sourcenode = nodename; + puzzle.chat_send_player(name, colorize("yellow","SETUP " ..edit.state .. ": ").. active_element) + edit.state = 2 + else -- SET TARGET + local meta = puzzle.get_meta(edit.source); + local n = meta:get_int("n"); + + if edit.state == 2 and pos.x == edit.source.x and pos.y == edit.source.y and pos.z == edit.source.z then -- RESET LINK FOR SOURCE + local meta = puzzle.get_meta(pos);meta:set_int("n",0) -- reset links + puzzle.chat_send_player(name, colorize("red", "SETUP " .. edit.state .. ":") .. " resetted links for selected source.") + edit.state = 1;return + else + n=n+1; + meta:set_int("x"..n, pos.x-edit.source.x);meta:set_int("y"..n, pos.y-edit.source.y);meta:set_int("z"..n, pos.z-edit.source.z) -- relative to source! + meta:set_int("n",n) + puzzle.chat_send_player(name, colorize("red", "SETUP "..edit.state .. ":") .. " added target #" .. n) + edit.state = 1 + end + end + + -- display + ::display_particle:: + + minetest.add_particle( + { + pos = pos, + expirationtime = 5, + velocity = {x=0, y=0,z=0}, + size = 18, + texture = "009.png", + acceleration = {x=0,y=0,z=0}, + collisiondetection = true, + collision_removal = true, + } + ) + end + + tools = { + ["basic_robot:control"] = linker_use + } + + ------ END OF EDIT PROGRAM + +end + +opcount = 0 +event = keyboard.get() -- handle keyboard +if event then + if event.type == 0 then -- EDITING + if event.puncher == name then -- players in protection can edit -- not minetest.is_protected({x=event.x,y=event.y,z=event.z},event.puncher) + local wield_item = player_:get_wielded_item():get_name() + local tool = tools[wield_item] + if tool then tool({x=event.x,y=event.y,z=event.z}) end + end + else -- EMULATOR + local typ = event.type; + local interactive_element = interactive_elements[typ] + if interactive_element then + interactive_element[1](interactive_element[2],{x=event.x,y=event.y,z=event.z},TTL) + self.sound("doors_glass_door_open",1,{x=event.x,y=event.y,z=event.z}) + end + end +end + + +sender,fields = self.read_form() -- handle gui for editing +if sender then + edit.state = 1 + for k,_ in pairs(fields) do + local c = string.sub(k,1,1); + local i = tonumber(string.sub(k,2)) or 1; + if c == "S" then + + local meta = puzzle.get_meta(edit.source); + minetest.add_particle( + { + pos = {x=meta:get_int("x"..i)+edit.source.x,y=meta:get_int("y"..i)+edit.source.y,z=meta:get_int("z"..i)+edit.source.z}, + expirationtime = 5, + velocity = {x=0, y=0,z=0}, + size = 18, + texture = "010.png", + acceleration = {x=0,y=0,z=0}, + collisiondetection = true, + collision_removal = true, + } + ) + elseif c == "E" then + edit.state = -i; + puzzle.chat_send_player(name, colorize("yellow", "#EDIT: select target " .. i)); + elseif c == "D" then + local meta = puzzle.get_meta(edit.source); + local n = meta:get_int("n") + if n > 0 then + for j = i,n-1 do + meta:set_int("x"..j, meta:get_int("x"..(j+1))) + meta:set_int("y"..j, meta:get_int("y"..(j+1))) + meta:set_int("z"..j, meta:get_int("z"..(j+1))) + end + meta:set_int("n",n-1) + end + puzzle.chat_send_player(name, colorize("red", "#EDIT: target " .. i .. " deleted")); + end + --say(serialize(fields)) + end +end \ No newline at end of file diff --git a/scripts/simulators/trust_game.lua b/scripts/simulators/trust_game.lua new file mode 100644 index 0000000..c11b0e3 --- /dev/null +++ b/scripts/simulators/trust_game.lua @@ -0,0 +1,266 @@ +-- cheat trust game by rnd, made in 60 minutes +-- http://ncase.me/trust/ + +--[[ + TO DO: + hierarchies, they determine who plays with who (boss plays only with his direct underlings) + fakers: they give fake +points +--]] + + +if not init then + init = true + if not find_player(6) then error("#TRUST GAME: no players near") end + + rules = { + { + {2., 2.}, -- first player cooperate, second player cooperate: +2,+2 + {-1, 3}, -- first player cooperate, second player cheat: -1,+3 + }, + { + {3,-1}, -- first player cheats, second player cooperate + {0,0}, -- first player cheats, second player cheat + } + }; + + error_rate = 0.0; -- 10% probability we take wrong decision + + grudger = + { + name = "grudger", + opponent = {}, -- list of opponent moves from previous games, + state = 1, -- internal state, for grudger it means how he plays next move + reset = function(self) + self.state = 1; self.opponent = {}; + end, + game = function(self) -- how will we play next move + if self.state == 1 then + local opp = self.opponent; + if #opp>0 and opp[#opp] == 2 then self.state = 2 end -- you cheat me once and we are done, pardner. + end + return self.state + end, + err = error_rate, -- probability we make different decision + } + + cheater = + { + name = "cheater", + opponent = {}, -- list of opponent moves from previous games, + reset = function(self) + self.opponent = {}; + end, + game = function(self) + return 2 + end, + err = error_rate, -- probability we make different decision + } + + cooperator = + { + name = "cooperator", + opponent = {}, + reset = function(self) + self.opponent = {}; + end, + game = function(self) + return 1 + end, + err = error_rate, -- probability we make different decision + } + + copycat = + { + name = "copycat", + opponent = {}, + reset = function(self) + self.opponent = {}; + end, + game = function(self) + local opp = self.opponent; + if #opp>0 then return opp[#opp] else return 1 end -- i do to you what you did to me last move + end, + err = error_rate, -- probability we make different decision + } + + fcopycat = + { + name = "forgiving copycat", + opponent = {}, + reset = function(self) + self.opponent = {}; self.state = 0; self.cheat = 0; + end, + state = 0, + cheat = 0, + game = function(self) + local opp = self.opponent; + if #opp>0 then + if opp[#opp] == 2 then -- you cheat me + if self.state == 1 then self.cheat = self.cheat+ 1 else self.state = 1 end -- cheat in a row + else + self.state = 0 + self.cheat = 0 + end + if self.cheat >= 1 then -- fk you + return 2 + else -- you cheated me less than 2x, its still cool + return 1 + end + else -- first time + return 1 + end + end, + err = error_rate, -- probability we make different decision + } + + + detective = + { + name = "detective", + opponent = {}, + moves = {1,2,1,1}, -- starting 4 moves + step = 0, -- what move we played so far + state = 0, + reset = function(self) + self.state = 0; self.step = 0; self.opponent = {}; + end, + game = function(self) -- how will we play next move + local st = self.step+1; + self.step = st; + local opp = self.opponent; + + if st < 5 then + if self.state == 0 and opp[#opp] == 2 then self.state = 1 end -- i caught you cheating! + return self.moves[st] + end + + if self.state == 0 then -- exploiter + return 2 + else -- copycat + if #opp>0 then return opp[#opp] end -- copycat + end + return self.state + end, + err = error_rate, -- probability we make different decision + } + + -- internal functions + + Player = {} + function Player:new (o) + ret = {}; _G.setmetatable(ret, self); self.__index = self + for k,v in pairs(o) do ret[k] = v end + ret.points = 0 + return ret + end + + gamestep = function(player1,player2) + local res1 = player1:game(); if math.random(1000)<=1000*player1.err then res1 = 3-res1 end + local opponent = player2.opponent; + opponent[#opponent+1] = res1; -- player2 remembers player1 move + local res2 = player2:game(); if math.random(1000)<=1000*player2.err then res2 = 3-res2 end + opponent = player1.opponent; + opponent[#opponent+1] = res2; -- player1 remembers player2 move + local res = rules[res1][res2]; + player1.points = player1.points + res[1]; + player2.points = player2.points + res[2]; + return res1,res2 -- return what players did + end + + paired_match = function(player1,player2, rounds) + player1:reset();player2:reset() + for i = 1, rounds do + gamestep(player1,player2) + end + end + + sort_players = function(players) + table.sort(players, + function(p1,p2) return p1.points " .. players[1].points .. " VS " .. players[2].name .. " " .. res2 .. " -> " .. players[2].points); + -- end + +end \ No newline at end of file diff --git a/scripts/simulators/turtlebot.lua b/scripts/simulators/turtlebot.lua new file mode 100644 index 0000000..c486582 --- /dev/null +++ b/scripts/simulators/turtlebot.lua @@ -0,0 +1,28 @@ +if not cmd then + cmd = { + ["f"] = function() move.forward() end, + ["b"] = function() move.backward() end, + ["l"] = function() move.left() end, + ["r"] = function() move.right() end, + ["u"] = function() move.up() end, + ["d"] = function() move.down() end, + ["a"] = function() activate.forward(1) end, + ["<"] = function() turn.left() end, + [">"] = function() turn.right() end, + } + i=0; + prog = read_text.right(); s=0 + prog = string.gsub(prog,"%s",""); + --say(prog) + self.label("RUNNING PROGRAM: " .. prog);n=string.len(prog); + if string.sub(prog,1,1) == " " then self.label("WRITE A PROGRAM FIRST!") s=1 end + +end + +if s == 0 then + i=i+1; if i > n then self.label("PROGRAM ENDED");s=1 end; + if s == 0 then + c=string.sub(prog,i,i) + if cmd[c] then cmd[c]() else self.label("INVALID PROGRAM INSTRUCTION : " .. c) s=1 end + end +end \ No newline at end of file diff --git a/scripts/spawn_quiz.lua b/scripts/spawn_quiz.lua new file mode 100644 index 0000000..b3f97e3 --- /dev/null +++ b/scripts/spawn_quiz.lua @@ -0,0 +1,62 @@ +if not s then + s=0 + t=0 + option = {"A","B","C","D","E"} + generate_question = function() + local a = math.random(10)+0; + local b = math.random(10)+0; + local c = math.random(20)-10; + local d = a*b+c; + msg = "To get out solve the math problem\n"; + msg = msg .. colorize("LawnGreen",a.." * "..b.." + "..c .. " = ?\n\n") + problem = a.."*"..b.."+"..c .. " = ?"; + correct = math.random(5); + local frm = ""; + + for i =1,5 do + local offset = 0; + if i~=correct then offset = math.random(10)-5; if offset == 0 then offset = -1 end end + frm = frm .. "button_exit[".. -0.1+(i-1)*1.25 ..",0.75;1.25,1;" .. i .. ";".. d + offset .. "]" + end + + local form = "size[6,1.25]" .. "label[0.05,-0.3;".. msg.."] "..frm .. "button_exit[4.9,-0.25;1.2,1;cancel;cancel]"; + return form, correct + end + + selection = 1; + question = ""; + problem = ""; +end + + +if t%4 == 0 then + t = 0; form,selection = generate_question(); + for _,obj in pairs(_G.minetest.get_objects_inside_radius({x=2,y=2,z=0}, 1)) do + if obj:is_player() then + local pname = obj:get_player_name(); + self.show_form(pname,form) + end + end +end +t=t+1; + + +sender,fields = self.read_form() +if sender then + player = _G.minetest.get_player_by_name(sender); + if player then + + answer = 0; + for i = 1,5 do if fields[_G.tostring(i)] then answer = i end end + + if answer == correct then + player:setpos({x=0,y=2,z=3}) + --inv = player:get_inventory(); inv:add_item("main", "default:apple") + --_G.minetest.chat_send_player(sender," congratulations, here is an apple.") + elseif answer ~= 0 then + player:setpos({x=0,y=-6,z=-1}) + say(sender .. " failed to solve the problem " .. problem) + self.show_form(sender, "size[1.25,0.5] label[0,0; WRONG]") + end + end +end \ No newline at end of file diff --git a/scripts/tree_harvest.lua b/scripts/tree_harvest.lua new file mode 100644 index 0000000..242bf2f --- /dev/null +++ b/scripts/tree_harvest.lua @@ -0,0 +1,112 @@ +-- rnd, 2017 +if not tree then + tree = {}; + wood = "default:tree"; + support = "default:tree"; + leaves = "default:leaves"; + sapling = "default:sapling"; + + tree.s = 0 -- searching + tree.st = 0 +end + +tree_step = function() + + if tree.s == 0 then -- search + node = read_node.forward() + if node == wood then tree.s = 1 end + elseif tree.s==1 then -- found + dig.forward(); + move.forward(); + tree.s=2 + elseif tree.s==2 then -- dig up + node = read_node.up() + dig.up() + move.up() + place.down(support) + if node ~= wood then + tree.s=3 tree.st = 0 + end + elseif tree.s==3 then -- on top + if tree.st == 0 then + move.up(); + turn.right();place.forward_down(support); + dig.forward() + turn.angle(180); place.forward_down(support); + tree.st=1 + elseif tree.st == 1 then + dig.forward(); move.forward(); + tree.st=2 + elseif tree.st == 2 then + turn.left(); dig.forward(); turn.angle(180) + tree.st=3 + elseif tree.st == 3 then + dig.forward(); turn.right(); tree.st = 4 + elseif tree.st == 4 then + dig.down();move.forward(); move.forward(); tree.st = 5 + elseif tree.st == 5 then + turn.left(); dig.forward(); turn.angle(180) + tree.st=6 + elseif tree.st == 6 then + dig.forward(); turn.right(); tree.st = 7 + elseif tree.st == 7 then + dig.down();move.forward(); + turn.right(); + tree.st = 0; tree.s = 4 + end + elseif tree.s == 4 then -- going down + node = read_node.down() + if node == wood then + dig.down(); + move.down(); + else + pickup(8); move.forward(); + place.backward(sapling) + tree.s=5 + end + end + +end + + + + +-- walk around +if not s then +wall = "basic_robot:buttonFFFFFF"; +s=0 +if rom.tree and rom.s then + tree.s = rom.tree.s; tree.st = rom.tree.st + s = rom.s; + rom.s = nil; + else + rom.tree = {} + end; + +angle = 90 +end + +if s==0 then -- walk + + if not move.forward() then + node = read_node.forward(); + if node == wood then + s = 1 + else + turn.angle(angle); node = read_node.forward() + if node == wall then + turn.angle(180);move.forward();turn.angle(-angle) + else + move.forward(); turn.angle(angle)angle=-angle; + end + end + end +elseif s==1 then + tree_step(); + if tree.s == 5 then s = 0; tree.s = 0 end +end + +rom.s = s;rom.tree.s = tree.s; rom.tree.st = tree.st -- remember whats it doing + + +--self.label(s .. " " .. tree.s .. " " .. tree.st) \ No newline at end of file diff --git a/scripts/utils/chatlog.lua b/scripts/utils/chatlog.lua new file mode 100644 index 0000000..ee2eaa4 --- /dev/null +++ b/scripts/utils/chatlog.lua @@ -0,0 +1,46 @@ +--rnd 2017 +if not logdata then + self.label("chatlog bot"); + _G.minetest.forceload_block(self.pos(),true) + n = 250; + idx = 1; + logdata = {}; + + insert = function(text) -- insert new message + idx = idx +1; + if idx > n then idx = 1 end + logdata[idx] = text; + end + + last = function(k,filter) -- return last k messages + if k > n then k = 30 end + local i,j,ret; + i=idx;j=0; ret = "" + + for j = 1,k do + if not logdata[i] then break end + if filter and not string.find(logdata[i], filter) then + else + ret = ret .. logdata[i] .. "\n"; + end + i=i-1; if i < 1 then i = n end + end + return ret + end + + self.listen(1) +end + +speaker, msg = self.listen_msg() +if msg then + if string.sub(msg,1,4) == "?log" then + local j = string.find(msg," ",6); + local k = tonumber(string.sub(msg,6) or "") or n; + local text; + if j then text = last(k,string.sub(msg,j+1)) else text = last(k) end + local form = "size[8,8]".. "textarea[0.,0;11.,9.5;text;chatlog;".. text .. "]" + self.show_form(speaker, form) + else + insert(os.date("%X") .. " " .. speaker .. "> " .. msg) + end +end \ No newline at end of file diff --git a/scripts/utils/helper_chat_bot.lua b/scripts/utils/helper_chat_bot.lua new file mode 100644 index 0000000..e2a0704 --- /dev/null +++ b/scripts/utils/helper_chat_bot.lua @@ -0,0 +1,89 @@ +if not init then + init = true; self.listen(1); + self.spam(1); self.label("help bot") + + talk = function(msg) minetest.chat_send_all(" " .. msg) end + keywords = { + {"tp", 14}, + {"help", + {"robot",6},{"",1} + }, + {"how", + {"play",1},{"robot", 6},{"stone",4},{"tree",3},{"wood",3},{"lava",5},{"cobble",4},{"dirt",10}, + {"do i get",1},{"do i make",1}, {"to get",1} + }, + {"i need", + {"wood",3} + }, + + {"hello",2}, -- words matched must appear at beginning + {"hi",2}, + {"back",7}, + {" hard",{"",9}}, -- word matched can appear anywhere + {" died", {"",9}}, + {" die",{"",8}}, {" dead",{"",8}}, + {"rnd",{"",11}}, + {"bye",{"",12}}, + {"!!",{"",9}}, + + {"calc", 13}, + } + answers = { + "%s open inventory, click 'Quests' and do them to get more stuff", --1 + "hello %s", + "do the dirt quest to get sticks then do sapling quest", + "get pumice from lava and water. then search craft guide how to make cobble", + "you get lava as compost quest reward or with grinder", -- 5 + "you have to write a program so that robot knows what to do. for list of commands click 'help' button inside robot.", + "wb %s", + "dont die, you lose your stuff and it will reset your level on level 1", + "you suck %s!", -- 9 + "to get dirt craft composter and use it with leaves", -- 10 + "rnd is afk. in the meantime i can answer your questions", + "bye %s", + function(speaker,msg) -- 13, calc + local expr = string.sub(msg,5); if string.find(expr,"%a") then return end + local res = _G.loadstring("return " .. expr)(); say(expr .. " = " .. res) + end, + function(speaker,msg) -- 14,tp + local p1 = minetest.get_player_by_name(speaker); + local p2 = minetest.get_player_by_name(string.sub(msg,4)); + if p1 and p2 then + p1:setpos(p2:getpos()) + end + end, + } +end + +speaker,msg = self.listen_msg(); +if msg then + --msg = string.lower(msg); + sel = 0; + for i = 1, #keywords do + local k = string.find(msg,keywords[i][1]) + if k then + if type(keywords[i][2])~="table" then -- one topic only + if k == 1 then sel = keywords[i][2] break end + else + for j=2,#keywords[i] do -- category of several topics + if string.find(msg,keywords[i][j][1]) then + sel = keywords[i][j][2]; break; + end + end + end + + end + end + + if sel>0 then + local response = answers[sel]; + if type(response) == "function" then + response(speaker,msg) + elseif string.find(response,"%%s") then + talk(string.format(response,speaker)) + else + talk(response) + end + end + +end \ No newline at end of file diff --git a/scripts/utils/language translator.lua b/scripts/utils/language translator.lua new file mode 100644 index 0000000..626e0e4 --- /dev/null +++ b/scripts/utils/language translator.lua @@ -0,0 +1,123 @@ +if not dict then + lang = "german" + dict = {}; + fname = "F:\\games\\rpg\\minetest-0415server\\mods\\basic_translate\\"..lang; + local f = _G.assert(_G.io.open(fname, "r"));local dicts = f:read("*all");f:close() + + step = 0; maxwords = 10000; + i=0 + + while(step c or a b -> d, where data for a = {{[de] = c},{[b] = d} + local found = false + local defaultv = "" + + + for j=1,#data do + + for key,v in pairs(data[j]) do + local keylen = string.len(key) + local pattern = string.sub(input,i1+1,i1+keylen) + if deb then say("pattern '" .. pattern .. "' key '" .. key .. "' len " .. keylen) end + if key == "" then defaultv = v + elseif pattern == key then + found = true; + if deb then say(word .. " " .. pattern .. " -> match key " .. key) end + out = out .. " " .. v; i1 = i1+string.len(key)+1 -- skip to after key + end + end + if found then break end + end + + if not found then out = out .. " " .. defaultv end + + end + else + out = out .. " " .. word + end + i=i1 + if deb then say("next word at i " .. i1 .. " remainder " .. string.sub(input,i1)) end + end + + return out + end + + -- say(translate("hello world")) + self.listen(1) +end + + + +speaker,msg = self.listen_msg() +if msg then + if string.sub(msg,1,1) == "?" then + msg = string.sub(msg,2) + local transmsg = translate(msg); + _G.minetest.chat_send_all("TRANSLATOR> " .. transmsg) + end +end \ No newline at end of file diff --git a/scripts/utils/object_lister.lua b/scripts/utils/object_lister.lua new file mode 100644 index 0000000..7fe8ade --- /dev/null +++ b/scripts/utils/object_lister.lua @@ -0,0 +1,32 @@ +-- return minetest object count for 5x5x5 blocks + +if not init then init = true + +local objs = minetest.get_objects_inside_radius(self.pos(), 30000); +local ret = {}; + +local round = function(x) return math.floor(x/5)*5 end +local ret = {}; + +for i = 1, #objs do + local p = objs[i]:get_pos(); + local phash = round(p.x) .. " " .. round(p.y) .. " " .. round(p.z); + ret[phash] = (ret[phash] or 0) + 1 +end + +local out = {}; +for k,v in pairs(ret) do + out[#out+1] = {k,v} +end + +table.sort(out, function(a,b) return a[2]>b[2] end) +local res = {}; +for i = 1, #out do + res[#res+1] = out[i][1] .. "=" .. out[i][2] +end + +self.label("#objects " .. #objs .. "\n" .. table.concat(res, "\n")) + + + +end \ No newline at end of file diff --git a/scripts/utils/resource_display.lua b/scripts/utils/resource_display.lua new file mode 100644 index 0000000..b1fe504 --- /dev/null +++ b/scripts/utils/resource_display.lua @@ -0,0 +1,16 @@ +-- SHOWS ACTIVE ROBOTS AND STATISTICS +if not init then init = true + local data = _G.basic_robot.data; + local ret = {}; + for k,v in pairs(data) do + if k~="listening" and v.obj then + local ent = v.obj:get_luaentity(); + local t = v.t or 0; if t< 100000 then t = math.floor(t * 10000)/10 else t = 0 end + if ent then ret[#ret+1] = k .. " " .. string.len(ent.code or "") .. " " .. string.len(_G.string.dump(v.bytecode) or "") .. " ~ " .. t end + end + end + mem1 = _G.collectgarbage("count") + self.label("memory used by lua (kbytes) ".. mem1 .. " ( delta " .. mem1 - (mem0 or 0) .. ")\n\nACTIVE ROBOTS\nrobot name | source code size | bytecode size | ~ time (ms)\n" .. table.concat(ret,"\n")) + mem0 = mem1 + init = false +end \ No newline at end of file diff --git a/scripts/utils/serverbot.lua b/scripts/utils/serverbot.lua new file mode 100644 index 0000000..28c4981 --- /dev/null +++ b/scripts/utils/serverbot.lua @@ -0,0 +1,76 @@ +--SERVER ROBOT : can send various data to other robots that requested it +if not cmds then + +-- user auth data +auth = {["rnd1"]=2}; + +-- server commands +cmds = { + list = { + run = function() + local ret = ""; for i,_ in pairs(cmds) do ret = ret .. " " .. i end; return ret + end, + help = "list all commands", + level = 0 + }, + + help = { + run = function(words) + local arg = words[2]; + if not arg then return "help: missing argument" end + local cmd = cmds[arg]; + if not cmd then return "help: nonexistent command" end + return cmd.help or "" + end, + help = "display help for command", + level = 0 + }, + + chat = { + run = function(words) + words[1] = "";_G.minetest.chat_send_all("#server bot : " .. table.concat(words," ") or ""); return true; + end, + help = "prints text globally", + level = 2 + }, + + minetest = { + run = function() return minetest end, + help = "returns minetest namespace", + level = 3 + } + +}; + +LISTENING = 0; --states +state = LISTENING; -- init +_G.minetest.forceload_block(self.pos(),true) +end + + + +if state == LISTENING then + sender,mail = self.read_mail() + if mail then + if type(mail)~="string" then mail = "" end + self.label("received request " .. mail); + local words = {}; + for word in string.gmatch(mail,"%S+") do words[#words+1]=word end -- get arguments + if not words or not words[1] then + self.send_mail(sender,"error: nil request") + else + local cmd = cmds[words[1]]; + if not cmd or not cmd.run then + self.send_mail(sender,"error: illegal command") + elseif (auth[sender] or 0) < cmd.level then + self.send_mail(sender,"error: auth level " .. (auth[sender] or 0) ..", need level " .. cmd.level) + else + self.send_mail(sender,cmd.run(words)); + self.label("sending data to " .. sender .. " ...") + end + end + else + self.label("listening...") + end + +end \ No newline at end of file diff --git a/scripts/utils/simple_house_builder.lua b/scripts/utils/simple_house_builder.lua new file mode 100644 index 0000000..78f38ca --- /dev/null +++ b/scripts/utils/simple_house_builder.lua @@ -0,0 +1,34 @@ +-- rnd 2017 +if not pos then + pos = self.spawnpos(); + n = 6; -- width + m = 4; -- height + door = math.floor(n/2)+1; -- door place + + plan = {}; + build_cube = function(x,y,z) + plan[#plan+1] = {c= math.random(10)+6, pos={x=pos.x+x,y=pos.y+y,z=pos.z+z}}; + end + + --floor + y=0;for z=1,n do for x=1,n do build_cube(x,y,z) end end --bottom + + z=1;for y=1,m do for x=1,n do build_cube(x,y,z) end end --wall 1 + z=n;for y=1,m do for x=1,n do build_cube(x,y,z) end end --wall2 + + x=n;for y=1,m do for z=2,n-1 do build_cube(x,y,z) end end -- wall3 + x=1;for y=1,m do for z=2,n-1 do if z~=door then build_cube(x,y,z) end end end -- wall4 + x=1;z=door;for y=3,m do build_cube(x,y,z) end -- door hole + + + y=m;for x = 2,n-1 do for z = 2,n-1 do build_cube(x,y,z) end end -- ceiling + s=0 + --self.remove() +end + +s=s+1; +if plan[s] then + keyboard.set(plan[s].pos,plan[s].c) +else + self.remove() +end \ No newline at end of file diff --git a/textures/000.png b/textures/000.png index ca46b04..8f76139 100644 Binary files a/textures/000.png and b/textures/000.png differ diff --git a/textures/001.png b/textures/001.png index 449ca8d..8b0d87c 100644 Binary files a/textures/001.png and b/textures/001.png differ diff --git a/textures/002.png b/textures/002.png index a49d678..83fe4fd 100644 Binary files a/textures/002.png and b/textures/002.png differ diff --git a/textures/003.png b/textures/003.png index ad1fce1..d69095a 100644 Binary files a/textures/003.png and b/textures/003.png differ diff --git a/textures/004.png b/textures/004.png index f6e155b..c985cb0 100644 Binary files a/textures/004.png and b/textures/004.png differ diff --git a/textures/005.png b/textures/005.png index 3d385e9..3d62fa8 100644 Binary files a/textures/005.png and b/textures/005.png differ diff --git a/textures/006.png b/textures/006.png index 6d4b31f..ae253a2 100644 Binary files a/textures/006.png and b/textures/006.png differ diff --git a/textures/007.png b/textures/007.png index e5cc15c..8936264 100644 Binary files a/textures/007.png and b/textures/007.png differ diff --git a/textures/008.png b/textures/008.png index 0a48eed..637495e 100644 Binary files a/textures/008.png and b/textures/008.png differ diff --git a/textures/009.png b/textures/009.png index c4f6ab0..4b49dac 100644 Binary files a/textures/009.png and b/textures/009.png differ diff --git a/textures/010.png b/textures/010.png index 2ebecd9..9c318d7 100644 Binary files a/textures/010.png and b/textures/010.png differ diff --git a/textures/011.png b/textures/011.png index 973e72d..8cab3df 100644 Binary files a/textures/011.png and b/textures/011.png differ diff --git a/textures/012.png b/textures/012.png index 44464a5..2bae39e 100644 Binary files a/textures/012.png and b/textures/012.png differ diff --git a/textures/013.png b/textures/013.png index 428503f..820ed20 100644 Binary files a/textures/013.png and b/textures/013.png differ diff --git a/textures/014.png b/textures/014.png index 2662f10..3a6e985 100644 Binary files a/textures/014.png and b/textures/014.png differ diff --git a/textures/015.png b/textures/015.png index c63b46a..1f32b41 100644 Binary files a/textures/015.png and b/textures/015.png differ diff --git a/textures/016.png b/textures/016.png index c43c2ae..d4fa7de 100644 Binary files a/textures/016.png and b/textures/016.png differ diff --git a/textures/017.png b/textures/017.png index 91d0764..9f8be89 100644 Binary files a/textures/017.png and b/textures/017.png differ diff --git a/textures/018.png b/textures/018.png index bc9aed4..a725aae 100644 Binary files a/textures/018.png and b/textures/018.png differ diff --git a/textures/019.png b/textures/019.png index fe43a9c..349feb1 100644 Binary files a/textures/019.png and b/textures/019.png differ diff --git a/textures/020.png b/textures/020.png index a8e651f..fe48897 100644 Binary files a/textures/020.png and b/textures/020.png differ diff --git a/textures/021.png b/textures/021.png index 83783b3..29c9407 100644 Binary files a/textures/021.png and b/textures/021.png differ diff --git a/textures/022.png b/textures/022.png index cf539a8..f7bf76c 100644 Binary files a/textures/022.png and b/textures/022.png differ diff --git a/textures/023.png b/textures/023.png index 8ccffc0..1958a70 100644 Binary files a/textures/023.png and b/textures/023.png differ diff --git a/textures/024.png b/textures/024.png index 60c9370..b68c974 100644 Binary files a/textures/024.png and b/textures/024.png differ diff --git a/textures/025.png b/textures/025.png index 0f3caf3..99442e0 100644 Binary files a/textures/025.png and b/textures/025.png differ diff --git a/textures/026.png b/textures/026.png index 23fc769..b768f9d 100644 Binary files a/textures/026.png and b/textures/026.png differ diff --git a/textures/027.png b/textures/027.png index c63cf6c..65abf61 100644 Binary files a/textures/027.png and b/textures/027.png differ diff --git a/textures/028.png b/textures/028.png index 9fb3653..fd364d7 100644 Binary files a/textures/028.png and b/textures/028.png differ diff --git a/textures/029.png b/textures/029.png index f635fe7..ba01b59 100644 Binary files a/textures/029.png and b/textures/029.png differ diff --git a/textures/030.png b/textures/030.png index f99708f..ac3773d 100644 Binary files a/textures/030.png and b/textures/030.png differ diff --git a/textures/031.png b/textures/031.png index b868529..172325f 100644 Binary files a/textures/031.png and b/textures/031.png differ diff --git a/textures/032.png b/textures/032.png index ca46b04..a95a40c 100644 Binary files a/textures/032.png and b/textures/032.png differ diff --git a/textures/033.png b/textures/033.png index 3110214..c64aad8 100644 Binary files a/textures/033.png and b/textures/033.png differ diff --git a/textures/034.png b/textures/034.png index f5a1996..3fe9aa3 100644 Binary files a/textures/034.png and b/textures/034.png differ diff --git a/textures/035.png b/textures/035.png index 78eadff..17dec88 100644 Binary files a/textures/035.png and b/textures/035.png differ diff --git a/textures/036.png b/textures/036.png index a50d5b0..a00cc92 100644 Binary files a/textures/036.png and b/textures/036.png differ diff --git a/textures/037.png b/textures/037.png index b8f6e23..3bcc88f 100644 Binary files a/textures/037.png and b/textures/037.png differ diff --git a/textures/038.png b/textures/038.png index 9fc7313..6ac004c 100644 Binary files a/textures/038.png and b/textures/038.png differ diff --git a/textures/039.png b/textures/039.png index 37eec80..753019b 100644 Binary files a/textures/039.png and b/textures/039.png differ diff --git a/textures/040.png b/textures/040.png index db03697..734c92e 100644 Binary files a/textures/040.png and b/textures/040.png differ diff --git a/textures/041.png b/textures/041.png index 781247c..bd4ed1a 100644 Binary files a/textures/041.png and b/textures/041.png differ diff --git a/textures/042.png b/textures/042.png index f47e259..6c189a5 100644 Binary files a/textures/042.png and b/textures/042.png differ diff --git a/textures/043.png b/textures/043.png index f4b2160..202f2f4 100644 Binary files a/textures/043.png and b/textures/043.png differ diff --git a/textures/044.png b/textures/044.png index db732e3..c4fb951 100644 Binary files a/textures/044.png and b/textures/044.png differ diff --git a/textures/045.png b/textures/045.png index 1eda2e2..2577363 100644 Binary files a/textures/045.png and b/textures/045.png differ diff --git a/textures/046.png b/textures/046.png index a1dd5f3..d3320f5 100644 Binary files a/textures/046.png and b/textures/046.png differ diff --git a/textures/047.png b/textures/047.png index 575703d..b1a7df8 100644 Binary files a/textures/047.png and b/textures/047.png differ diff --git a/textures/048.png b/textures/048.png index 787b9eb..05dfc11 100644 Binary files a/textures/048.png and b/textures/048.png differ diff --git a/textures/049.png b/textures/049.png index 775c419..5f410ac 100644 Binary files a/textures/049.png and b/textures/049.png differ diff --git a/textures/050.png b/textures/050.png index a343a7c..766f709 100644 Binary files a/textures/050.png and b/textures/050.png differ diff --git a/textures/051.png b/textures/051.png index 2b5e54e..dc771f4 100644 Binary files a/textures/051.png and b/textures/051.png differ diff --git a/textures/052.png b/textures/052.png index 9d40617..f4e2832 100644 Binary files a/textures/052.png and b/textures/052.png differ diff --git a/textures/053.png b/textures/053.png index 7059e87..2994d71 100644 Binary files a/textures/053.png and b/textures/053.png differ diff --git a/textures/054.png b/textures/054.png index c2ac54d..2057298 100644 Binary files a/textures/054.png and b/textures/054.png differ diff --git a/textures/055.png b/textures/055.png index f4052c5..5b2ea47 100644 Binary files a/textures/055.png and b/textures/055.png differ diff --git a/textures/056.png b/textures/056.png index efd2f5f..07cf2bb 100644 Binary files a/textures/056.png and b/textures/056.png differ diff --git a/textures/057.png b/textures/057.png index 120d8bf..f76ed66 100644 Binary files a/textures/057.png and b/textures/057.png differ diff --git a/textures/058.png b/textures/058.png index a237d6d..c09ff63 100644 Binary files a/textures/058.png and b/textures/058.png differ diff --git a/textures/059.png b/textures/059.png index d5de0ef..d7836ea 100644 Binary files a/textures/059.png and b/textures/059.png differ diff --git a/textures/060.png b/textures/060.png index 19cf020..463bef1 100644 Binary files a/textures/060.png and b/textures/060.png differ diff --git a/textures/061.png b/textures/061.png index d673f35..dd4110a 100644 Binary files a/textures/061.png and b/textures/061.png differ diff --git a/textures/062.png b/textures/062.png index e3c57fa..d8a5681 100644 Binary files a/textures/062.png and b/textures/062.png differ diff --git a/textures/063.png b/textures/063.png index 5c3803c..eb38f40 100644 Binary files a/textures/063.png and b/textures/063.png differ diff --git a/textures/064.png b/textures/064.png index 0f8e5a0..fa030a4 100644 Binary files a/textures/064.png and b/textures/064.png differ diff --git a/textures/065.png b/textures/065.png index 16e3671..19e1c79 100644 Binary files a/textures/065.png and b/textures/065.png differ diff --git a/textures/066.png b/textures/066.png index 543458b..90565ea 100644 Binary files a/textures/066.png and b/textures/066.png differ diff --git a/textures/067.png b/textures/067.png index dfb4bd9..d43d262 100644 Binary files a/textures/067.png and b/textures/067.png differ diff --git a/textures/068.png b/textures/068.png index 2541770..1cc7f3d 100644 Binary files a/textures/068.png and b/textures/068.png differ diff --git a/textures/069.png b/textures/069.png index b51293b..8e69a98 100644 Binary files a/textures/069.png and b/textures/069.png differ diff --git a/textures/070.png b/textures/070.png index 4ab4124..1897158 100644 Binary files a/textures/070.png and b/textures/070.png differ diff --git a/textures/071.png b/textures/071.png index adb87d5..db24a8d 100644 Binary files a/textures/071.png and b/textures/071.png differ diff --git a/textures/072.png b/textures/072.png index cd775ae..14da18e 100644 Binary files a/textures/072.png and b/textures/072.png differ diff --git a/textures/073.png b/textures/073.png index 980bbaf..e3891af 100644 Binary files a/textures/073.png and b/textures/073.png differ diff --git a/textures/074.png b/textures/074.png index ba2fe11..605693a 100644 Binary files a/textures/074.png and b/textures/074.png differ diff --git a/textures/075.png b/textures/075.png index 3ba848e..71b5e49 100644 Binary files a/textures/075.png and b/textures/075.png differ diff --git a/textures/076.png b/textures/076.png index 2cc684c..160b9c0 100644 Binary files a/textures/076.png and b/textures/076.png differ diff --git a/textures/077.png b/textures/077.png index 0c35c1e..3e7e3b3 100644 Binary files a/textures/077.png and b/textures/077.png differ diff --git a/textures/078.png b/textures/078.png index 2c0f036..ded4bd2 100644 Binary files a/textures/078.png and b/textures/078.png differ diff --git a/textures/079.png b/textures/079.png index 17d7c00..b8d2cce 100644 Binary files a/textures/079.png and b/textures/079.png differ diff --git a/textures/080.png b/textures/080.png index e9bfe0e..e89747a 100644 Binary files a/textures/080.png and b/textures/080.png differ diff --git a/textures/081.png b/textures/081.png index 93b5f57..11d8a98 100644 Binary files a/textures/081.png and b/textures/081.png differ diff --git a/textures/082.png b/textures/082.png index a6216eb..384e948 100644 Binary files a/textures/082.png and b/textures/082.png differ diff --git a/textures/083.png b/textures/083.png index 6c1d3c8..f332a74 100644 Binary files a/textures/083.png and b/textures/083.png differ diff --git a/textures/084.png b/textures/084.png index 823ea77..5a755d8 100644 Binary files a/textures/084.png and b/textures/084.png differ diff --git a/textures/085.png b/textures/085.png index 345c789..7879cb8 100644 Binary files a/textures/085.png and b/textures/085.png differ diff --git a/textures/086.png b/textures/086.png index cd70246..6fefbd3 100644 Binary files a/textures/086.png and b/textures/086.png differ diff --git a/textures/087.png b/textures/087.png index 2c79a40..b2170bf 100644 Binary files a/textures/087.png and b/textures/087.png differ diff --git a/textures/088.png b/textures/088.png index 16046eb..543b606 100644 Binary files a/textures/088.png and b/textures/088.png differ diff --git a/textures/089.png b/textures/089.png index cec54db..34d8b5d 100644 Binary files a/textures/089.png and b/textures/089.png differ diff --git a/textures/090.png b/textures/090.png index df0cddb..132769c 100644 Binary files a/textures/090.png and b/textures/090.png differ diff --git a/textures/091.png b/textures/091.png index a1c9e2a..9456925 100644 Binary files a/textures/091.png and b/textures/091.png differ diff --git a/textures/092.png b/textures/092.png index 5ae68dd..01b4fea 100644 Binary files a/textures/092.png and b/textures/092.png differ diff --git a/textures/093.png b/textures/093.png index 3a14443..6a62a60 100644 Binary files a/textures/093.png and b/textures/093.png differ diff --git a/textures/094.png b/textures/094.png index 61afe83..4c7dc97 100644 Binary files a/textures/094.png and b/textures/094.png differ diff --git a/textures/095.png b/textures/095.png index 64ecae6..cfd2cd0 100644 Binary files a/textures/095.png and b/textures/095.png differ diff --git a/textures/096.png b/textures/096.png index 9880561..26f188f 100644 Binary files a/textures/096.png and b/textures/096.png differ diff --git a/textures/097.png b/textures/097.png index b57b209..e21c1d7 100644 Binary files a/textures/097.png and b/textures/097.png differ diff --git a/textures/098.png b/textures/098.png index 619c708..48c94f0 100644 Binary files a/textures/098.png and b/textures/098.png differ diff --git a/textures/099.png b/textures/099.png index 1bcadc8..82355f5 100644 Binary files a/textures/099.png and b/textures/099.png differ diff --git a/textures/100.png b/textures/100.png index 14252e0..fd88174 100644 Binary files a/textures/100.png and b/textures/100.png differ diff --git a/textures/101.png b/textures/101.png index c9d8dab..604bb6c 100644 Binary files a/textures/101.png and b/textures/101.png differ diff --git a/textures/102.png b/textures/102.png index 2018bbf..38db98f 100644 Binary files a/textures/102.png and b/textures/102.png differ diff --git a/textures/103.png b/textures/103.png index 5752eef..ca5242b 100644 Binary files a/textures/103.png and b/textures/103.png differ diff --git a/textures/104.png b/textures/104.png index fa8f601..6ce1388 100644 Binary files a/textures/104.png and b/textures/104.png differ diff --git a/textures/105.png b/textures/105.png index f554f4b..cf7cc67 100644 Binary files a/textures/105.png and b/textures/105.png differ diff --git a/textures/106.png b/textures/106.png index ad94959..b14ec88 100644 Binary files a/textures/106.png and b/textures/106.png differ diff --git a/textures/107.png b/textures/107.png index 72fd839..38aeceb 100644 Binary files a/textures/107.png and b/textures/107.png differ diff --git a/textures/108.png b/textures/108.png index d3d9d41..5678229 100644 Binary files a/textures/108.png and b/textures/108.png differ diff --git a/textures/109.png b/textures/109.png index 32ef808..00d0869 100644 Binary files a/textures/109.png and b/textures/109.png differ diff --git a/textures/110.png b/textures/110.png index d594ce4..7214cb6 100644 Binary files a/textures/110.png and b/textures/110.png differ diff --git a/textures/111.png b/textures/111.png index b3e350a..b67c879 100644 Binary files a/textures/111.png and b/textures/111.png differ diff --git a/textures/112.png b/textures/112.png index 45de829..a511d7f 100644 Binary files a/textures/112.png and b/textures/112.png differ diff --git a/textures/113.png b/textures/113.png index 1398fa0..069c9e9 100644 Binary files a/textures/113.png and b/textures/113.png differ diff --git a/textures/114.png b/textures/114.png index 4b5b9c4..5038d97 100644 Binary files a/textures/114.png and b/textures/114.png differ diff --git a/textures/115.png b/textures/115.png index e631567..70ce3b5 100644 Binary files a/textures/115.png and b/textures/115.png differ diff --git a/textures/116.png b/textures/116.png index 867d57b..73557c9 100644 Binary files a/textures/116.png and b/textures/116.png differ diff --git a/textures/117.png b/textures/117.png index 94ffc6a..65727e4 100644 Binary files a/textures/117.png and b/textures/117.png differ diff --git a/textures/118.png b/textures/118.png index a87d7d0..c0e0971 100644 Binary files a/textures/118.png and b/textures/118.png differ diff --git a/textures/119.png b/textures/119.png index bbe662b..fb50b51 100644 Binary files a/textures/119.png and b/textures/119.png differ diff --git a/textures/120.png b/textures/120.png index fe42537..f06435d 100644 Binary files a/textures/120.png and b/textures/120.png differ diff --git a/textures/121.png b/textures/121.png index 3bc63c6..552f662 100644 Binary files a/textures/121.png and b/textures/121.png differ diff --git a/textures/122.png b/textures/122.png index 83696c2..ca469f5 100644 Binary files a/textures/122.png and b/textures/122.png differ diff --git a/textures/123.png b/textures/123.png index d4160e9..1b0e62a 100644 Binary files a/textures/123.png and b/textures/123.png differ diff --git a/textures/124.png b/textures/124.png index c21c6d5..0f40e4f 100644 Binary files a/textures/124.png and b/textures/124.png differ diff --git a/textures/125.png b/textures/125.png index ab3e8c2..7fa2585 100644 Binary files a/textures/125.png and b/textures/125.png differ diff --git a/textures/126.png b/textures/126.png index 523e071..ce04815 100644 Binary files a/textures/126.png and b/textures/126.png differ diff --git a/textures/127.png b/textures/127.png index fda05ee..40820a1 100644 Binary files a/textures/127.png and b/textures/127.png differ diff --git a/textures/128.png b/textures/128.png index f0fd572..546d6e9 100644 Binary files a/textures/128.png and b/textures/128.png differ diff --git a/textures/129.png b/textures/129.png index aa58d66..bffb31e 100644 Binary files a/textures/129.png and b/textures/129.png differ diff --git a/textures/130.png b/textures/130.png index 99d4ea5..771f656 100644 Binary files a/textures/130.png and b/textures/130.png differ diff --git a/textures/131.png b/textures/131.png index 81d1e83..713cbeb 100644 Binary files a/textures/131.png and b/textures/131.png differ diff --git a/textures/132.png b/textures/132.png index 9bb3a35..89b30bc 100644 Binary files a/textures/132.png and b/textures/132.png differ diff --git a/textures/133.png b/textures/133.png index 3030f7a..69d8821 100644 Binary files a/textures/133.png and b/textures/133.png differ diff --git a/textures/134.png b/textures/134.png index 176c2d4..749e2c3 100644 Binary files a/textures/134.png and b/textures/134.png differ diff --git a/textures/135.png b/textures/135.png index c726f0d..e09a8d4 100644 Binary files a/textures/135.png and b/textures/135.png differ diff --git a/textures/136.png b/textures/136.png index 59f79bb..6e3a090 100644 Binary files a/textures/136.png and b/textures/136.png differ diff --git a/textures/137.png b/textures/137.png index 94679cf..080e76e 100644 Binary files a/textures/137.png and b/textures/137.png differ diff --git a/textures/138.png b/textures/138.png index c222bc4..50cd54f 100644 Binary files a/textures/138.png and b/textures/138.png differ diff --git a/textures/139.png b/textures/139.png index c3d45cf..0b2f6ba 100644 Binary files a/textures/139.png and b/textures/139.png differ diff --git a/textures/140.png b/textures/140.png index c45070f..d20c4e6 100644 Binary files a/textures/140.png and b/textures/140.png differ diff --git a/textures/141.png b/textures/141.png index 7a81388..9c16d30 100644 Binary files a/textures/141.png and b/textures/141.png differ diff --git a/textures/142.png b/textures/142.png index e6c4f13..e9a6158 100644 Binary files a/textures/142.png and b/textures/142.png differ diff --git a/textures/143.png b/textures/143.png index c948e1c..537ef22 100644 Binary files a/textures/143.png and b/textures/143.png differ diff --git a/textures/144.png b/textures/144.png index 7064486..6b37b03 100644 Binary files a/textures/144.png and b/textures/144.png differ diff --git a/textures/145.png b/textures/145.png index 0d4fe37..f4f2e5e 100644 Binary files a/textures/145.png and b/textures/145.png differ diff --git a/textures/146.png b/textures/146.png index ac76a29..d8d3641 100644 Binary files a/textures/146.png and b/textures/146.png differ diff --git a/textures/147.png b/textures/147.png index b189019..559ee31 100644 Binary files a/textures/147.png and b/textures/147.png differ diff --git a/textures/148.png b/textures/148.png index 952a07c..d831e2a 100644 Binary files a/textures/148.png and b/textures/148.png differ diff --git a/textures/149.png b/textures/149.png index d1df1db..9020332 100644 Binary files a/textures/149.png and b/textures/149.png differ diff --git a/textures/150.png b/textures/150.png index 3a718fc..faed548 100644 Binary files a/textures/150.png and b/textures/150.png differ diff --git a/textures/151.png b/textures/151.png index 68d59ad..e7dc75d 100644 Binary files a/textures/151.png and b/textures/151.png differ diff --git a/textures/152.png b/textures/152.png index 3edf79a..03a60ba 100644 Binary files a/textures/152.png and b/textures/152.png differ diff --git a/textures/153.png b/textures/153.png index 2392efc..452538f 100644 Binary files a/textures/153.png and b/textures/153.png differ diff --git a/textures/154.png b/textures/154.png index 97e7dc3..f476d50 100644 Binary files a/textures/154.png and b/textures/154.png differ diff --git a/textures/155.png b/textures/155.png index 315761e..41bcbf8 100644 Binary files a/textures/155.png and b/textures/155.png differ diff --git a/textures/156.png b/textures/156.png index a215e57..d728d46 100644 Binary files a/textures/156.png and b/textures/156.png differ diff --git a/textures/157.png b/textures/157.png index 2d87000..4ca180e 100644 Binary files a/textures/157.png and b/textures/157.png differ diff --git a/textures/158.png b/textures/158.png index bf586eb..388afd4 100644 Binary files a/textures/158.png and b/textures/158.png differ diff --git a/textures/159.png b/textures/159.png index 057a2b5..a8fd738 100644 Binary files a/textures/159.png and b/textures/159.png differ diff --git a/textures/160.png b/textures/160.png index cac1128..6fd2e5d 100644 Binary files a/textures/160.png and b/textures/160.png differ diff --git a/textures/161.png b/textures/161.png index e78fd51..5652e0d 100644 Binary files a/textures/161.png and b/textures/161.png differ diff --git a/textures/162.png b/textures/162.png index 22ab351..4275e7e 100644 Binary files a/textures/162.png and b/textures/162.png differ diff --git a/textures/163.png b/textures/163.png index d613df1..6ac873e 100644 Binary files a/textures/163.png and b/textures/163.png differ diff --git a/textures/164.png b/textures/164.png index 42e61f7..9ec5483 100644 Binary files a/textures/164.png and b/textures/164.png differ diff --git a/textures/165.png b/textures/165.png index 77b91aa..ff46e79 100644 Binary files a/textures/165.png and b/textures/165.png differ diff --git a/textures/166.png b/textures/166.png index c9ae920..ae146ed 100644 Binary files a/textures/166.png and b/textures/166.png differ diff --git a/textures/167.png b/textures/167.png index b51325b..b42d8b2 100644 Binary files a/textures/167.png and b/textures/167.png differ diff --git a/textures/168.png b/textures/168.png index d540e8d..838f1fd 100644 Binary files a/textures/168.png and b/textures/168.png differ diff --git a/textures/169.png b/textures/169.png index 87b131e..53b01af 100644 Binary files a/textures/169.png and b/textures/169.png differ diff --git a/textures/170.png b/textures/170.png index 079bfa6..a3e5328 100644 Binary files a/textures/170.png and b/textures/170.png differ diff --git a/textures/171.png b/textures/171.png index 7364839..c5ba9ab 100644 Binary files a/textures/171.png and b/textures/171.png differ diff --git a/textures/172.png b/textures/172.png index 5e92f4a..d867ea1 100644 Binary files a/textures/172.png and b/textures/172.png differ diff --git a/textures/173.png b/textures/173.png index 6c638b9..6d19056 100644 Binary files a/textures/173.png and b/textures/173.png differ diff --git a/textures/174.png b/textures/174.png index c60fee1..7f225b4 100644 Binary files a/textures/174.png and b/textures/174.png differ diff --git a/textures/175.png b/textures/175.png index 20b4017..cffd6f4 100644 Binary files a/textures/175.png and b/textures/175.png differ diff --git a/textures/176.png b/textures/176.png index 6284767..45c51d0 100644 Binary files a/textures/176.png and b/textures/176.png differ diff --git a/textures/177.png b/textures/177.png index ade87ac..7f6726f 100644 Binary files a/textures/177.png and b/textures/177.png differ diff --git a/textures/178.png b/textures/178.png index 195d719..e7b1a56 100644 Binary files a/textures/178.png and b/textures/178.png differ diff --git a/textures/179.png b/textures/179.png index 44f809c..6055ebe 100644 Binary files a/textures/179.png and b/textures/179.png differ diff --git a/textures/180.png b/textures/180.png index 46798d1..c97a1d0 100644 Binary files a/textures/180.png and b/textures/180.png differ diff --git a/textures/181.png b/textures/181.png index ca5a3d9..ba18cfb 100644 Binary files a/textures/181.png and b/textures/181.png differ diff --git a/textures/182.png b/textures/182.png index 20bffbd..8602b28 100644 Binary files a/textures/182.png and b/textures/182.png differ diff --git a/textures/183.png b/textures/183.png index a49939b..d0ddb81 100644 Binary files a/textures/183.png and b/textures/183.png differ diff --git a/textures/184.png b/textures/184.png index cdfe098..3a75810 100644 Binary files a/textures/184.png and b/textures/184.png differ diff --git a/textures/185.png b/textures/185.png index 58d72b5..ab0ddd9 100644 Binary files a/textures/185.png and b/textures/185.png differ diff --git a/textures/186.png b/textures/186.png index eab73eb..f249e10 100644 Binary files a/textures/186.png and b/textures/186.png differ diff --git a/textures/187.png b/textures/187.png index edd8abf..f240b77 100644 Binary files a/textures/187.png and b/textures/187.png differ diff --git a/textures/188.png b/textures/188.png index 67fa5d5..db524ef 100644 Binary files a/textures/188.png and b/textures/188.png differ diff --git a/textures/189.png b/textures/189.png index 80d1278..0e2a6a1 100644 Binary files a/textures/189.png and b/textures/189.png differ diff --git a/textures/190.png b/textures/190.png index 844a3dc..0f62ad4 100644 Binary files a/textures/190.png and b/textures/190.png differ diff --git a/textures/191.png b/textures/191.png index ff95b25..d9cd0d8 100644 Binary files a/textures/191.png and b/textures/191.png differ diff --git a/textures/192.png b/textures/192.png index eb26413..479e45d 100644 Binary files a/textures/192.png and b/textures/192.png differ diff --git a/textures/193.png b/textures/193.png index dffcf97..b1d7874 100644 Binary files a/textures/193.png and b/textures/193.png differ diff --git a/textures/194.png b/textures/194.png index 76efb94..10d4e88 100644 Binary files a/textures/194.png and b/textures/194.png differ diff --git a/textures/195.png b/textures/195.png index f3ef658..eb7d168 100644 Binary files a/textures/195.png and b/textures/195.png differ diff --git a/textures/196.png b/textures/196.png index c53860e..1f9b363 100644 Binary files a/textures/196.png and b/textures/196.png differ diff --git a/textures/197.png b/textures/197.png index c7806ba..63e28ac 100644 Binary files a/textures/197.png and b/textures/197.png differ diff --git a/textures/198.png b/textures/198.png index 40d16e7..2ec6225 100644 Binary files a/textures/198.png and b/textures/198.png differ diff --git a/textures/199.png b/textures/199.png index de5d257..c6e2c33 100644 Binary files a/textures/199.png and b/textures/199.png differ diff --git a/textures/200.png b/textures/200.png index 3bd81bb..87daf1e 100644 Binary files a/textures/200.png and b/textures/200.png differ diff --git a/textures/201.png b/textures/201.png index 8dd67a6..6e0b967 100644 Binary files a/textures/201.png and b/textures/201.png differ diff --git a/textures/202.png b/textures/202.png index cb36863..297cb35 100644 Binary files a/textures/202.png and b/textures/202.png differ diff --git a/textures/203.png b/textures/203.png index 09cfee4..61a0742 100644 Binary files a/textures/203.png and b/textures/203.png differ diff --git a/textures/204.png b/textures/204.png index 36c5693..61f975a 100644 Binary files a/textures/204.png and b/textures/204.png differ diff --git a/textures/205.png b/textures/205.png index 2823919..6aa7de4 100644 Binary files a/textures/205.png and b/textures/205.png differ diff --git a/textures/206.png b/textures/206.png index 8cdcd81..17c0bc5 100644 Binary files a/textures/206.png and b/textures/206.png differ diff --git a/textures/207.png b/textures/207.png index 6c2ff33..714ebdf 100644 Binary files a/textures/207.png and b/textures/207.png differ diff --git a/textures/208.png b/textures/208.png index 9e0928a..dcdbc81 100644 Binary files a/textures/208.png and b/textures/208.png differ diff --git a/textures/209.png b/textures/209.png index 4b55a38..51c534a 100644 Binary files a/textures/209.png and b/textures/209.png differ diff --git a/textures/210.png b/textures/210.png index 6e26c80..88fe76b 100644 Binary files a/textures/210.png and b/textures/210.png differ diff --git a/textures/211.png b/textures/211.png index 5770e14..ebbf4cc 100644 Binary files a/textures/211.png and b/textures/211.png differ diff --git a/textures/212.png b/textures/212.png index 5ea37a3..b86bd67 100644 Binary files a/textures/212.png and b/textures/212.png differ diff --git a/textures/213.png b/textures/213.png index d2cefc9..6960eb1 100644 Binary files a/textures/213.png and b/textures/213.png differ diff --git a/textures/214.png b/textures/214.png index fb274a8..49a44f0 100644 Binary files a/textures/214.png and b/textures/214.png differ diff --git a/textures/215.png b/textures/215.png index dbaaf17..244b6b6 100644 Binary files a/textures/215.png and b/textures/215.png differ diff --git a/textures/216.png b/textures/216.png index f907f59..8c7a663 100644 Binary files a/textures/216.png and b/textures/216.png differ diff --git a/textures/217.png b/textures/217.png index 0453152..6da9f3b 100644 Binary files a/textures/217.png and b/textures/217.png differ diff --git a/textures/218.png b/textures/218.png index ae67bef..9c7b7b5 100644 Binary files a/textures/218.png and b/textures/218.png differ diff --git a/textures/219.png b/textures/219.png index 71506ad..4b4df4f 100644 Binary files a/textures/219.png and b/textures/219.png differ diff --git a/textures/220.png b/textures/220.png index db6eb8f..9970e92 100644 Binary files a/textures/220.png and b/textures/220.png differ diff --git a/textures/221.png b/textures/221.png index 1eb3c87..6657a0e 100644 Binary files a/textures/221.png and b/textures/221.png differ diff --git a/textures/222.png b/textures/222.png index 69364bb..601540c 100644 Binary files a/textures/222.png and b/textures/222.png differ diff --git a/textures/223.png b/textures/223.png index e260734..5367d8d 100644 Binary files a/textures/223.png and b/textures/223.png differ diff --git a/textures/224.png b/textures/224.png index ef52734..58aa493 100644 Binary files a/textures/224.png and b/textures/224.png differ diff --git a/textures/225.png b/textures/225.png index 6ac843a..7d8796c 100644 Binary files a/textures/225.png and b/textures/225.png differ diff --git a/textures/226.png b/textures/226.png index 6d1f764..d43744a 100644 Binary files a/textures/226.png and b/textures/226.png differ diff --git a/textures/227.png b/textures/227.png index 13e9832..06cfb4a 100644 Binary files a/textures/227.png and b/textures/227.png differ diff --git a/textures/228.png b/textures/228.png index 7ab3143..70af135 100644 Binary files a/textures/228.png and b/textures/228.png differ diff --git a/textures/229.png b/textures/229.png index b577929..2330c9e 100644 Binary files a/textures/229.png and b/textures/229.png differ diff --git a/textures/230.png b/textures/230.png index 7ee9120..94ed430 100644 Binary files a/textures/230.png and b/textures/230.png differ diff --git a/textures/231.png b/textures/231.png index 0483284..407e138 100644 Binary files a/textures/231.png and b/textures/231.png differ diff --git a/textures/232.png b/textures/232.png index da75be6..70bdf42 100644 Binary files a/textures/232.png and b/textures/232.png differ diff --git a/textures/233.png b/textures/233.png index 98675c9..4f755b1 100644 Binary files a/textures/233.png and b/textures/233.png differ diff --git a/textures/234.png b/textures/234.png index ca46b04..ca6025f 100644 Binary files a/textures/234.png and b/textures/234.png differ diff --git a/textures/235.png b/textures/235.png index ca46b04..4b4df4f 100644 Binary files a/textures/235.png and b/textures/235.png differ diff --git a/textures/236.png b/textures/236.png index ca46b04..4b4df4f 100644 Binary files a/textures/236.png and b/textures/236.png differ diff --git a/textures/237.png b/textures/237.png index ca46b04..4b4df4f 100644 Binary files a/textures/237.png and b/textures/237.png differ diff --git a/textures/238.png b/textures/238.png index ca46b04..4b4df4f 100644 Binary files a/textures/238.png and b/textures/238.png differ diff --git a/textures/239.png b/textures/239.png index ca46b04..4b4df4f 100644 Binary files a/textures/239.png and b/textures/239.png differ diff --git a/textures/240.png b/textures/240.png index c87aef5..32730c0 100644 Binary files a/textures/240.png and b/textures/240.png differ diff --git a/textures/241.png b/textures/241.png index 90f4552..51bcea3 100644 Binary files a/textures/241.png and b/textures/241.png differ diff --git a/textures/242.png b/textures/242.png index fa251ae..a04accd 100644 Binary files a/textures/242.png and b/textures/242.png differ diff --git a/textures/243.png b/textures/243.png index 886fbfa..534e454 100644 Binary files a/textures/243.png and b/textures/243.png differ diff --git a/textures/244.png b/textures/244.png index ca46b04..3621b5c 100644 Binary files a/textures/244.png and b/textures/244.png differ diff --git a/textures/245.png b/textures/245.png index ca46b04..4b4df4f 100644 Binary files a/textures/245.png and b/textures/245.png differ diff --git a/textures/246.png b/textures/246.png index 92a1c85..c8380f5 100644 Binary files a/textures/246.png and b/textures/246.png differ diff --git a/textures/247.png b/textures/247.png index 9834a29..ab3839e 100644 Binary files a/textures/247.png and b/textures/247.png differ diff --git a/textures/248.png b/textures/248.png index 50a4f0f..17a3d9f 100644 Binary files a/textures/248.png and b/textures/248.png differ diff --git a/textures/249.png b/textures/249.png index 4728085..c4cc005 100644 Binary files a/textures/249.png and b/textures/249.png differ diff --git a/textures/250.png b/textures/250.png index 7dfa780..8ca1890 100644 Binary files a/textures/250.png and b/textures/250.png differ diff --git a/textures/251.png b/textures/251.png index 9b68564..802b5c7 100644 Binary files a/textures/251.png and b/textures/251.png differ diff --git a/textures/252.png b/textures/252.png index 243005a..1c79fad 100644 Binary files a/textures/252.png and b/textures/252.png differ diff --git a/textures/253.png b/textures/253.png index d08f9cf..5584d7f 100644 Binary files a/textures/253.png and b/textures/253.png differ diff --git a/textures/254.png b/textures/254.png index 0d97cbf..138ed23 100644 Binary files a/textures/254.png and b/textures/254.png differ diff --git a/textures/255.png b/textures/255.png index ca46b04..4b4df4f 100644 Binary files a/textures/255.png and b/textures/255.png differ diff --git a/textures/arrow.png b/textures/arrow.png index 5bb1116..c8116e8 100644 Binary files a/textures/arrow.png and b/textures/arrow.png differ diff --git a/textures/cpu.png b/textures/cpu.png index d178edb..6e99d78 100644 Binary files a/textures/cpu.png and b/textures/cpu.png differ diff --git a/textures/face-back.png b/textures/face-back.png new file mode 100644 index 0000000..0a3db4d Binary files /dev/null and b/textures/face-back.png differ diff --git a/textures/face.png b/textures/face.png index 7ec5976..b991d22 100644 Binary files a/textures/face.png and b/textures/face.png differ diff --git a/textures/face3.png b/textures/face3.png new file mode 100644 index 0000000..e408585 Binary files /dev/null and b/textures/face3.png differ diff --git a/textures/left-hand.png b/textures/left-hand.png new file mode 100644 index 0000000..00c068f Binary files /dev/null and b/textures/left-hand.png differ diff --git a/textures/legs.png b/textures/legs.png new file mode 100644 index 0000000..86191a3 Binary files /dev/null and b/textures/legs.png differ diff --git a/textures/right-hand.png b/textures/right-hand.png new file mode 100644 index 0000000..2f6c70c Binary files /dev/null and b/textures/right-hand.png differ diff --git a/textures/robot_side.png b/textures/robot_side.png new file mode 100644 index 0000000..b24c72f Binary files /dev/null and b/textures/robot_side.png differ diff --git a/textures/topface.png b/textures/topface.png new file mode 100644 index 0000000..b5da74a Binary files /dev/null and b/textures/topface.png differ