diff --git a/mods/basic_robot/commands.lua b/mods/basic_robot/commands.lua index 4787da0..8b80c8f 100644 --- a/mods/basic_robot/commands.lua +++ b/mods/basic_robot/commands.lua @@ -49,7 +49,7 @@ 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 @@ -277,35 +277,35 @@ basic_robot.no_teleport_table = { } ---basic_robot.commands.pickup = function(r,name) +basic_robot.commands.pickup = function(r,name) - --if r>8 then return false end + if r>8 then return false end - --local pos = basic_robot.data[name].obj:getpos(); - --local spos = basic_robot.data[name].spawnpos; -- position of spawner block - --local meta = minetest.get_meta(spos); - --local inv = minetest.get_meta(spos):get_inventory(); - --local picklist = {}; + local pos = basic_robot.data[name].obj:getpos(); + local spos = basic_robot.data[name].spawnpos; -- position of spawner block + local meta = minetest.get_meta(spos); + local inv = minetest.get_meta(spos):get_inventory(); + local picklist = {}; - --for _,obj in pairs(minetest.get_objects_inside_radius({x=pos.x,y=pos.y,z=pos.z}, r)) do - --local lua_entity = obj:get_luaentity() - --if not obj:is_player() and lua_entity and lua_entity.itemstring then - --local detected_obj = lua_entity.itemstring or "" - --if not basic_robot.no_teleport_table[detected_obj] then -- object on no teleport list + for _,obj in pairs(minetest.get_objects_inside_radius({x=pos.x,y=pos.y,z=pos.z}, r)) do + local lua_entity = obj:get_luaentity() + if not obj:is_player() and lua_entity and lua_entity.itemstring then + local detected_obj = lua_entity.itemstring or "" + if not basic_robot.no_teleport_table[detected_obj] then -- object on no teleport list -- put item in chest - --local stack = ItemStack(lua_entity.itemstring) - --picklist[#picklist+1]=detected_obj; - --if inv:room_for_item("main", stack) then - --inv:add_item("main", stack); - --obj:setpos({x=0,y=0,z=0}) -- no dupe - --end - --obj:remove(); - --end - --end - --end - --if not picklist[1] then return nil end - --return picklist ---end + local stack = ItemStack(lua_entity.itemstring) + picklist[#picklist+1]=detected_obj; + if inv:room_for_item("main", stack) then + inv:add_item("main", stack); + obj:setpos({x=0,y=0,z=0}) -- no dupe + end + obj:remove(); + end + end + end + if not picklist[1] then return nil end + return picklist +end basic_robot.commands.read_node = function(name,dir) @@ -571,7 +571,7 @@ local register_robot_button = function(R,G,B,type) on_punch = function(pos, node, player) local name = player:get_player_name(); if name==nil then return end local round = math.floor; - local r = 32; local ry = 2*r; -- note: this is skyblock adjusted + local r = basic_robot.radius; local ry = 2*r; -- note: this is skyblock adjusted local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; -- just on top of basic_protect:protector! local meta = minetest.get_meta(ppos); local name = meta:get_string("name"); @@ -595,7 +595,7 @@ minetest.register_node("basic_robot:button"..number, on_punch = function(pos, node, player) local name = player:get_player_name(); if name==nil then return end local round = math.floor; - local r = 32; local ry = 2*r; + local r = basic_robot.radius; local ry = 2*r; local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; local meta = minetest.get_meta(ppos); local name = meta:get_string("name"); @@ -618,7 +618,7 @@ minetest.register_node("basic_robot:button_"..number, on_punch = function(pos, node, player) local name = player:get_player_name(); if name==nil then return end local round = math.floor; - local r = 32; local ry = 2*r; + local r = basic_robot.radius; local ry = 2*r; local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; local meta = minetest.get_meta(ppos); local name = meta:get_string("name"); @@ -640,7 +640,7 @@ minetest.register_node("basic_robot:button_"..number, on_punch = function(pos, node, player) local name = player:get_player_name(); if name==nil then return end local round = math.floor; - local r = 32; local ry = 2*r; + local r = basic_robot.radius; local ry = 2*r; local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; local meta = minetest.get_meta(ppos); local name = meta:get_string("name"); @@ -675,6 +675,9 @@ register_robot_button_custom(281,"puzzle_NOT") register_robot_button_custom(282,"puzzle_delayer") register_robot_button_custom(283,"puzzle_platform") +register_robot_button_custom(284,"puzzle_giver") +register_robot_button_custom(285,"puzzle_checker") + -- interactive button for robot: place robot on top of protector to intercept events @@ -1184,7 +1187,7 @@ basic_robot.commands.crypto = {encrypt = encrypt, decrypt = decrypt, scramble = local is_same_block = function(pos1,pos2) local round = math.floor; - local r = 32; local ry = 2*r; -- note: this is skyblock adjusted + local r = basic_robot.radius; local ry = 2*r; -- note: this is skyblock adjusted local ppos1 = {round(pos1.x/r+0.5)*r,round(pos1.y/ry+0.5)*ry,round(pos1.z/r+0.5)*r}; local ppos2 = {round(pos2.x/r+0.5)*r,round(pos2.y/ry+0.5)*ry,round(pos2.z/r+0.5)*r}; return ppos1[1]==ppos2[1] and ppos1[2]==ppos2[2] and ppos1[3] == ppos2[3] @@ -1351,3 +1354,116 @@ 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/mods/basic_robot/init.lua b/mods/basic_robot/init.lua index b3933df..5483929 100644 --- a/mods/basic_robot/init.lua +++ b/mods/basic_robot/init.lua @@ -4,11 +4,12 @@ basic_robot = {}; ---- SETTINGS ------ basic_robot.call_limit = 48; -- how many execution calls per script run allowed -basic_robot.entry_count = 2 -- how many robot ordinary player can have +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.bad_inventory_blocks = { -- disallow taking from these nodes inventories +basic_robot.bad_inventory_blocks = { -- disallow taking from these nodes inventories to prevent player abuses ["craft_guide:sign_wall"] = true, } basic_robot.maxoperations = 2; -- how many operations (dig, generate energy,..) available per run, 0 = unlimited @@ -17,15 +18,17 @@ basic_robot.dig_require_energy = true; -- does robot require energy to dig? basic_robot.http_api = minetest.request_http_api(); -basic_robot.version = "2017/10 /07a"; +basic_robot.version = "2018/02/06a"; -basic_robot.data = {}; -- stores all robot data +basic_robot.data = {}; -- stores all robot related data --[[ -[name] = { sandbox= .., bytecode = ..., ram = ..., obj = robot object,spawnpos=...} +[name] = { sandbox= .., bytecode = ..., ram = ..., obj = robot object, spawnpos= ..., authlevel = ...} robot object = object of entity, used to manipulate movements and more --]] -basic_robot.ids = {}; -- stores maxid for all players ---[name] = {id = .., maxid = .. }, current id, how many robot ids player can use +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").."/commands.lua") @@ -38,6 +41,8 @@ local check_code, preprocess_code,is_inside_string; function getSandboxEnv (name) + local authlevel = basic_robot.data[name].authlevel or 0; + local commands = basic_robot.commands; local directions = {left = 1, right = 2, forward = 3, backward = 4, up = 5, down = 6, left_down = 7, right_down = 8, forward_down = 9, backward_down = 10, @@ -114,17 +119,6 @@ function getSandboxEnv (name) return sender,mail end, - read_form = function() - local fields = basic_robot.data[name].read_form; - local sender = basic_robot.data[name].form_sender; - basic_robot.data[name].read_form = nil; - basic_robot.data[name].form_sender = nil; - return sender,fields - end, - - show_form = function(playername, form) - commands.show_form(name, playername, form) - end, send_mail = function(target,mail) if not basic_robot.data[target] then return false end @@ -157,7 +151,7 @@ function getSandboxEnv (name) end end, - fire = function(speed, pitch,gravity, is_entity) -- experimental: fires an projectile + 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(); @@ -171,7 +165,7 @@ function getSandboxEnv (name) expirationtime = 10, velocity = {x=speed*math.cos(yaw)*math.cos(pitch), y=speed*math.sin(pitch),z=speed*math.sin(yaw)*math.cos(pitch)}, size = 5, - texture = "default_apple.png", + texture = texture or "default_apple.png", acceleration = {x=0,y=-gravity,z=0}, collisiondetection = true, collision_removal = true, @@ -346,7 +340,7 @@ function getSandboxEnv (name) end, run = function(script) - if basic_robot.data[name].isadmin ~= 1 then + if basic_robot.data[name].authlevel < 3 then local err = check_code(script); script = preprocess_code(script); if err then @@ -485,10 +479,23 @@ function getSandboxEnv (name) env.write_text[dir] = function(text) return commands.write_text(name, dir_id,text) end end - -- set up sandbox for puzzle + if authlevel>=1 then -- robot privs + env.self.read_form = function() + local fields = basic_robot.data[name].read_form; + local sender = basic_robot.data[name].form_sender; + basic_robot.data[name].read_form = nil; + basic_robot.data[name].form_sender = nil; + return sender,fields + end + + env.self.show_form = function(playername, form) + commands.show_form(name, playername, form) + end + end - local ispuzzle = basic_robot.data[name].ispuzzle; -- need puzzle privs - if ispuzzle == 1 then + -- set up sandbox for puzzle + + if authlevel>=2 then -- puzzle privs basic_robot.data[name].puzzle = {}; local data = basic_robot.data[name]; local pdata = data.puzzle; @@ -516,13 +523,11 @@ function getSandboxEnv (name) pdata = pdata, ItemStack = ItemStack, } + end - --special sandbox for admin - local isadmin=basic_robot.data[name].isadmin - - if isadmin~=1 then + if authlevel<3 then -- is admin? env._G = env; else env.minetest = minetest; @@ -675,7 +680,7 @@ end local function setCode( name, script ) -- to run script: 1. initSandbox 2. setCode 3. runSandbox local err; - if basic_robot.data[name].isadmin~=1 then + if basic_robot.data[name].authlevel<3 then -- not admin err = check_code(script); script = preprocess_code(script); end @@ -806,9 +811,7 @@ local function init_robot(obj) basic_robot.data[name].quiet_mode = false; -- can chat globally -- check if admin robot - if self.isadmin then basic_robot.data[name].isadmin = 1 end - -- can we do puzzles? - if self.ispuzzle then basic_robot.data[name].ispuzzle = 1 end + basic_robot.data[name].authlevel = self.authlevel or 0 --robot appearance,armor... obj:set_properties({infotext = "robot " .. name}); @@ -833,7 +836,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","face.png","face-back.png","left-hand.png","right-hand.png"}, visual_size={x=1,y=1}, running = 0, -- does it run code or is it idle? @@ -856,6 +859,8 @@ minetest.register_entity("basic_robot:robot",{ end self.owner = data.owner; + self.authlevel = data.authlevel; + self.spawnpos = {x=data.spawnpos.x,y=data.spawnpos.y,z=data.spawnpos.z}; init_robot(self.object); self.running = 1; @@ -985,7 +990,13 @@ local spawn_robot = function(pos,node,ttl) basic_robot.data[name] = {}; data = basic_robot.data[name]; meta:set_string("infotext",minetest.get_gametime().. " code changed ") data.owner = owner; - if meta:get_int("admin") == 1 then data.isadmin = 1 end + data.authlevel = meta:get_int("authlevel") + + 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_player(owner,"#ROBOT: " .. name .. " is using fake auth level. dig and place again.") + return + end if not data.obj then --create virtual robot that reports position and other properties @@ -1007,8 +1018,8 @@ local spawn_robot = function(pos,node,ttl) if not data.bytecode then local script = meta:get_string("code"); - - if data.isadmin~=1 then + + if data.authlevel<3 then -- not admin err = check_code(script); script = preprocess_code(script); end @@ -1059,8 +1070,14 @@ local spawn_robot = function(pos,node,ttl) luaent.name = name; luaent.code = meta:get_string("code"); luaent.spawnpos = {x=pos.x,y=pos.y-1,z=pos.z}; - if meta:get_int("admin") == 1 then luaent.isadmin = 1 end - if meta:get_int("puzzle") == 1 then luaent.ispuzzle = 1 end + luaent.authlevel = meta:get_int("authlevel") + + 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_player(owner,"#ROBOT: " .. name .. " is using fake auth level. dig and place again.") + obj:remove(); + return + end local data = basic_robot.data[name]; if data == nil then @@ -1253,6 +1270,9 @@ local on_receive_robot_form = function(pos, formname, fields, sender) 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 local privs = minetest.get_player_privs(name); -- only admin can edit admin robot code @@ -1346,7 +1366,7 @@ local on_receive_robot_form = function(pos, formname, fields, sender) " 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) fires a projectile from 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".. @@ -1520,6 +1540,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,"#ROBOT: text too long") return + end item:set_metadata(fields.code); player:set_wielded_item(item); if fields.id then @@ -1661,6 +1684,9 @@ 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 + 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 @@ -1720,10 +1746,24 @@ minetest.register_node("basic_robot:spawner", { alpha = 150, after_place_node = function(pos, placer) local meta = minetest.env:get_meta(pos) - meta:set_string("owner", placer:get_player_name()); + local owner = placer:get_player_name(); + meta:set_string("owner", owner); + local privs = minetest.get_player_privs(placer:get_player_name()); - if privs.privs then meta:set_int("admin",1) end - if privs.puzzle then meta:set_int("puzzle",1) end + local authlevel = 0; + if privs.privs then -- set auth level depending on privs + authlevel = 3 + elseif privs.puzzle then + authlevel = 2 + elseif privs.robot then + authlevel = 1 + else + authlevel = 0 + end + + meta:set_int("authlevel",authlevel) + local sec_hash = minetest.get_password_hash("",authlevel .. owner .. basic_robot.password) -- 'digitally sign' authlevel using password + meta:set_string("sec_hash", sec_hash); meta:set_string("code",""); meta:set_int("id",1); -- initial robot id @@ -1827,9 +1867,9 @@ minetest.register_craftitem("basic_robot:control", { local owner = user:get_player_name(); local script = itemstack:get_metadata(); - if script == "@" then -- remote control as a tool - notify robot in current block of pointed position + if script == "@" then -- remote control as a tool - notify robot in current block of pointed position, using keyboard event type 0 local round = math.floor; - local r = 32; local ry = 2*r; -- note: this is skyblock adjusted + local r = basic_robot.radius; local ry = 2*r; -- note: this is skyblock adjusted local pos = pointed_thing.under if not pos then return end local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; -- just on top of basic_protect:protector! @@ -1850,7 +1890,7 @@ minetest.register_craftitem("basic_robot:control", { if data and data.sandbox then else - minetest.chat_send_player(name, "#remote control: your robot must be running"); + minetest.chat_send_player(owner, "#remote control: your robot must be running"); return end @@ -1859,7 +1899,7 @@ minetest.register_craftitem("basic_robot:control", { if t1-t0<1 then return end data.remoteuse = t1; - if data.isadmin == 1 then + if data.authlevel >= 3 then local privs = minetest.get_player_privs(owner); -- only admin can run admin robot if not privs.privs then return @@ -1872,7 +1912,7 @@ minetest.register_craftitem("basic_robot:control", { return end - if not data.isadmin then + if data.authlevel<3 then if check_code(script)~=nil then return end end @@ -1967,3 +2007,5 @@ 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") + +print('[MOD]'.. " basic_robot " .. basic_robot.version .. " loaded.") \ No newline at end of file diff --git a/mods/basic_robot/scripts/sokoban_game.lua b/mods/basic_robot/scripts/sokoban_game.lua index 99d0b1c..3d3e8af 100644 --- a/mods/basic_robot/scripts/sokoban_game.lua +++ b/mods/basic_robot/scripts/sokoban_game.lua @@ -1,7 +1,7 @@ -- SOKOBAN GAME, by rnd, robots port if not sokoban then sokoban = {}; - local players = find_player(4); + local players = find_player(5); if not players then error("sokoban: no player near") end name = players[1]; @@ -37,7 +37,7 @@ 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") + 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 diff --git a/mods/basic_robot/textures/arrow.png b/mods/basic_robot/textures/arrow.png index 5bb1116..c8116e8 100644 Binary files a/mods/basic_robot/textures/arrow.png and b/mods/basic_robot/textures/arrow.png differ diff --git a/mods/basic_robot/textures/cpu.png b/mods/basic_robot/textures/cpu.png index d178edb..6e99d78 100644 Binary files a/mods/basic_robot/textures/cpu.png and b/mods/basic_robot/textures/cpu.png differ diff --git a/mods/basic_robot/textures/face.png b/mods/basic_robot/textures/face.png index 7ec5976..b991d22 100644 Binary files a/mods/basic_robot/textures/face.png and b/mods/basic_robot/textures/face.png differ diff --git a/mods/basic_robot/textures/puzzle_checker.png b/mods/basic_robot/textures/puzzle_checker.png new file mode 100644 index 0000000..25a6d41 Binary files /dev/null and b/mods/basic_robot/textures/puzzle_checker.png differ diff --git a/mods/basic_robot/textures/puzzle_giver.png b/mods/basic_robot/textures/puzzle_giver.png new file mode 100644 index 0000000..c28d896 Binary files /dev/null and b/mods/basic_robot/textures/puzzle_giver.png differ