958 lines
29 KiB
Lua
958 lines
29 KiB
Lua
-- Turtle mod for Minetest
|
|
-- License: LGPL
|
|
|
|
local FUEL_EFFICIENCY = 3 -- How many moves can the turtle do with a second fuel
|
|
local TURTLES_FORCE_LOAD = true -- Useless for now, has to wait until force_load is merged
|
|
|
|
|
|
|
|
--TODO: Change serialization so that it supports functions
|
|
local safe_serialize = function(value)
|
|
return minetest.serialize(value)
|
|
end
|
|
local safe_deserialize = minetest.deserialize
|
|
|
|
local turtle_invs
|
|
local turtle_updates
|
|
|
|
local serialize_inv = function(l)
|
|
local l2={}
|
|
for _,i in pairs(l or {}) do
|
|
l2[_]=i:to_table()
|
|
end
|
|
return l2
|
|
end
|
|
|
|
local deserialize_inv = function(l)
|
|
local l2={}
|
|
for _,i in pairs(l or {}) do
|
|
l2[_]=ItemStack(i)
|
|
end
|
|
return l2
|
|
end
|
|
|
|
|
|
|
|
local wpath = minetest.get_worldpath()
|
|
local function read_file(fn)
|
|
local f = io.open(fn, "r")
|
|
if f==nil then return {} end
|
|
local t = f:read("*all")
|
|
f:close()
|
|
if t=="" or t==nil then return {} end
|
|
return minetest.deserialize(t)
|
|
end
|
|
|
|
local function write_file(fn, tbl)
|
|
local f = io.open(fn, "w")
|
|
f:write(minetest.serialize(tbl))
|
|
f:close()
|
|
end
|
|
|
|
local get_turtle_info
|
|
|
|
turtle_infos = read_file(wpath.."/turtle_infos")
|
|
turtle_updates = read_file(wpath.."/turtle_updates")
|
|
|
|
turtle_updates_to_add={}
|
|
|
|
local tupdate
|
|
|
|
minetest.register_globalstep(function(dtime)
|
|
for _, timer in ipairs(turtle_updates_to_add) do
|
|
table.insert(turtle_updates, timer)
|
|
end
|
|
turtle_updates_to_add = {}
|
|
for index, timer in ipairs(turtle_updates) do
|
|
local info = get_turtle_info(timer.update.turtle)
|
|
if info["turtle"]~=nil then --turtle is loaded
|
|
timer.time = timer.time - dtime
|
|
if timer.time <= 0 then
|
|
tupdate(timer.update)
|
|
table.remove(turtle_updates,index)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
local function turtle_add_update(time, update)
|
|
table.insert(turtle_updates_to_add, {time=time, update=update})
|
|
end
|
|
|
|
minetest.register_on_shutdown(function()
|
|
for turtle,i in pairs(turtle_infos) do
|
|
i["turtle"]=nil
|
|
i["inventory"]=serialize_inv(turtle_invs:get_list(turtle))
|
|
end
|
|
write_file(wpath.."/turtle_infos",turtle_infos)
|
|
for _, timer in ipairs(turtle_updates_to_add) do
|
|
table.insert(turtle_updates, timer)
|
|
end
|
|
write_file(wpath.."/turtle_updates",turtle_updates)
|
|
end)
|
|
|
|
get_turtle_info = function(turtle)
|
|
if turtle_infos[turtle]==nil then turtle_infos[turtle]={} end
|
|
return turtle_infos[turtle]
|
|
end
|
|
|
|
local function get_turtle_id()
|
|
i=0
|
|
while true do
|
|
if turtle_infos["turtle"..tostring(i)]==nil then return "turtle"..tostring(i) end
|
|
i=i+1
|
|
end
|
|
end
|
|
|
|
local function round_pos(p)
|
|
return {x=math.floor(p.x+0.5),
|
|
y=math.floor(p.y+0.5),
|
|
z=math.floor(p.z+0.5)}
|
|
end
|
|
|
|
local update_formspec = function(turtle, code, errmsg, filename, player, exit)
|
|
local info = get_turtle_info(turtle)
|
|
info["code"]=code or ""
|
|
if minetest.formspec_escape then
|
|
code = minetest.formspec_escape(code or "")
|
|
errmsg = minetest.formspec_escape(errmsg or "")
|
|
else
|
|
code = string.gsub(code or "", "%[", "(") -- would otherwise
|
|
code = string.gsub(code, "%]", ")") -- corrupt formspec
|
|
errmsg = string.gsub(errmsg or "", "%[", "(") -- would otherwise
|
|
errmsg = string.gsub(errmsg, "%]", ")") -- corrupt formspec
|
|
end
|
|
info["filename"] = filename
|
|
info["formspec"]= "size[9,10]"..
|
|
"textarea[0.3,0;4.7,5;code;;"..code.."]"..
|
|
"list[detached:turtle:invs;"..turtle..";4.8,0;4,4;]"..
|
|
"image_button[0,4.6;2.5,1;turtle_execute.png;program;]"..
|
|
"image_button_exit[8.72,-0.25;0.425,0.4;turtle_close.png;exit;]"..
|
|
"label[4.6,4;"..errmsg.."]"..
|
|
"list[current_player;main;0.5,6;8,4;]"..
|
|
"field[3,5;4,1;filename;Filename:;"..filename.."]"..
|
|
"button[7,4.65;1,1;open;Open]"..
|
|
"button[8,4.65;1,1;save;Save]"
|
|
info["heat"]=0
|
|
if exit==nil then
|
|
minetest.show_formspec(player:get_player_name(), turtle, info["formspec"])
|
|
end
|
|
end
|
|
|
|
--------------------
|
|
-- Overheat stuff --
|
|
--------------------
|
|
|
|
local heat = function (turtle) -- warm up
|
|
local info = get_turtle_info(turtle)
|
|
local h = info["heat"]
|
|
if h ~= nil then
|
|
info["heat"]=h+1
|
|
else
|
|
info["heat"]=1
|
|
end
|
|
end
|
|
|
|
local cool = function (turtle) -- cool down after a while
|
|
local info = get_turtle_info(turtle)
|
|
local h = info["heat"]
|
|
if h ~= nil then
|
|
info["heat"]=h-1
|
|
end
|
|
end
|
|
|
|
local overheat = function (turtle) -- determine if too hot
|
|
local info = get_turtle_info(turtle)
|
|
local h = info["heat"]
|
|
return h==nil or h>400
|
|
end
|
|
|
|
-------------------
|
|
-- Parsing stuff --
|
|
-------------------
|
|
|
|
local code_prohibited = function(code)
|
|
-- Clean code
|
|
local prohibited = {"while", "for", "repeat", "until", "goto"}--, "function"}
|
|
for _, p in ipairs(prohibited) do
|
|
if string.find(code, "%A"..p.."%A") then
|
|
return "Prohibited command: "..p
|
|
end
|
|
end
|
|
end
|
|
|
|
local safe_print = function(param)
|
|
print(dump(param))
|
|
end
|
|
|
|
local interrupt = function(params)
|
|
turtle_update(params.turtle, {type="interrupt", iid = params.iid})
|
|
end
|
|
|
|
local getinterrupt = function(turtle)
|
|
local interrupt = function (time, iid) -- iid = interrupt id
|
|
if type(time) ~= "number" then return end
|
|
local iid = iid or math.random()
|
|
local info = get_turtle_info(turtle)
|
|
--local interrupts = safe_deserialize(info["interrupts"]) or {}
|
|
local interrupts = info["interrupts"] or {}
|
|
local found = false
|
|
local search = safe_serialize(iid)
|
|
for _, i in ipairs(interrupts) do
|
|
if safe_serialize(i) == search then
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
if not found then
|
|
table.insert(interrupts, iid)
|
|
--info["interrupts"]= safe_serialize(interrupts)
|
|
info["interrupts"]=interrupts
|
|
end
|
|
turtle_add_update(time, {turtle=turtle, type="interrupt", iid = iid})
|
|
end
|
|
return interrupt
|
|
end
|
|
|
|
local function getv(dir)
|
|
if dir==0 then return {x=0,y=0,z=1}
|
|
elseif dir==1 then return {x=1,y=0,z=0}
|
|
elseif dir==2 then return {x=0,y=0,z=-1}
|
|
elseif dir==3 then return {x=-1,y=0,z=0} end
|
|
end
|
|
|
|
local function v3add(v1,v2)
|
|
return {x=v1.x+v2.x,y=v1.y+v2.y,z=v1.z+v2.z}
|
|
end
|
|
|
|
local function turtle_can_go(nname)
|
|
return nname=="air" or minetest.registered_nodes[nname].liquidtype~="none"
|
|
end
|
|
|
|
local function stack_set_count(stack, count)
|
|
stack = stack:to_table()
|
|
if stack==nil then return nil end
|
|
stack.count=count
|
|
return ItemStack(stack)
|
|
end
|
|
|
|
--------------------------
|
|
-- /\ |--\ | --
|
|
-- /--\ |__/ | --
|
|
-- / \ | | --
|
|
--------------------------
|
|
|
|
tupdate = function(update)
|
|
local turtle = update.turtle
|
|
local t = update.type
|
|
local info = get_turtle_info(turtle)
|
|
if t=="failmove" then
|
|
turtle_update(turtle,{type="failmove",iid=update.iid})
|
|
elseif t=="endmove" then
|
|
info["moveint"]=nil
|
|
info["spos"]=info["npos"]
|
|
info["npos"]=nil
|
|
local tobject = info["turtle"]
|
|
tobject.object:setvelocity({x=0,y=0,z=0})
|
|
tobject.object:setpos(info["spos"])
|
|
turtle_update(turtle,{type="endmove",iid=update.iid})
|
|
elseif t=="endturn" then
|
|
info["moveint"]=nil
|
|
info["dir"]=info["ndir"]
|
|
info["ndir"]=nil
|
|
info["rotate"]=nil
|
|
local tobject = info["turtle"]
|
|
tobject.object:setyaw(info["dir"]*math.pi/2)
|
|
turtle_update(turtle,{type="endmove",iid=update.iid})
|
|
elseif t=="interrupt" then
|
|
interrupt(update)
|
|
elseif t=="cool" then
|
|
cool(turtle)
|
|
end
|
|
end
|
|
|
|
local get_turtle_funcs = function(turtle)
|
|
return {
|
|
forward = function(iid)
|
|
if iid==nil then iid="nil" end
|
|
local info = get_turtle_info(turtle)
|
|
local tobject = info["turtle"]
|
|
if info["fuel"]==0 then
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
return
|
|
end
|
|
if info["moveint"]==nil then
|
|
local spos = info["spos"]
|
|
local dir = info["dir"]
|
|
info["npos"] = v3add(spos, getv(dir))
|
|
if not turtle_can_go(minetest.env:get_node(info["npos"]).name) then
|
|
info["npos"]=nil
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
return
|
|
end
|
|
info["fuel"]=info["fuel"]-1
|
|
info["moveint"]=iid
|
|
tobject.object:setvelocity(getv(dir))
|
|
turtle_add_update(1,{turtle=turtle, type="endmove", iid=iid})
|
|
else
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
end
|
|
end,
|
|
back = function(iid)
|
|
if iid==nil then iid="nil" end
|
|
local info = get_turtle_info(turtle)
|
|
local tobject = info["turtle"]
|
|
if info["fuel"]==0 then
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
return
|
|
end
|
|
if info["moveint"]==nil then
|
|
local spos = info["spos"]
|
|
local dir = (info["dir"]+2)%4
|
|
info["npos"] = v3add(spos, getv(dir))
|
|
if not turtle_can_go(minetest.env:get_node(info["npos"]).name) then
|
|
info["npos"]=nil
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
return
|
|
end
|
|
info["fuel"]=info["fuel"]-1
|
|
info["moveint"]=iid
|
|
tobject.object:setvelocity(getv(dir))
|
|
turtle_add_update(1,{turtle=turtle, type="endmove", iid=iid})
|
|
else
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
end
|
|
end,
|
|
up = function(iid)
|
|
if iid==nil then iid="nil" end
|
|
local info = get_turtle_info(turtle)
|
|
local tobject = info["turtle"]
|
|
if info["fuel"]==0 then
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
return
|
|
end
|
|
if info["moveint"]==nil then
|
|
local spos = info["spos"]
|
|
info["npos"] = v3add(spos, {x=0,y=1,z=0})
|
|
if not turtle_can_go(minetest.env:get_node(info["npos"]).name) then
|
|
info["npos"]=nil
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
return
|
|
end
|
|
info["fuel"]=info["fuel"]-1
|
|
info["moveint"]=iid
|
|
tobject.object:setvelocity({x=0,y=1,z=0})
|
|
turtle_add_update(1,{turtle=turtle, type="endmove", iid=iid})
|
|
else
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
end
|
|
end,
|
|
down = function(iid)
|
|
if iid==nil then iid="nil" end
|
|
local info = get_turtle_info(turtle)
|
|
local tobject = info["turtle"]
|
|
if info["fuel"]==0 then
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
return
|
|
end
|
|
if info["moveint"]==nil then
|
|
local spos = info["spos"]
|
|
info["npos"] = v3add(spos, {x=0,y=-1,z=0})
|
|
if not turtle_can_go(minetest.env:get_node(info["npos"]).name) then
|
|
info["npos"]=nil
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
return
|
|
end
|
|
info["fuel"]=info["fuel"]-1
|
|
info["moveint"]=iid
|
|
tobject.object:setvelocity({x=0,y=-1,z=0})
|
|
turtle_add_update(1,{turtle=turtle, type="endmove", iid=iid})
|
|
else
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
end
|
|
end,
|
|
turnleft = function(iid)
|
|
if iid==nil then iid="nil" end
|
|
local info = get_turtle_info(turtle)
|
|
local tobject = info["turtle"]
|
|
if info["moveint"]==nil then
|
|
local dir = info["dir"]
|
|
info["ndir"]=(dir+3)%4
|
|
info["moveint"]=iid
|
|
info["rotate"]=math.pi/2
|
|
turtle_add_update(1,{turtle=turtle, type="endturn", iid=iid})
|
|
else
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
end
|
|
end,
|
|
turnright = function(iid)
|
|
if iid==nil then iid="nil" end
|
|
local info = get_turtle_info(turtle)
|
|
local tobject = info["turtle"]
|
|
if info["moveint"]==nil then
|
|
local dir = info["dir"]
|
|
info["ndir"]=(dir+1)%4
|
|
info["moveint"]=iid
|
|
info["rotate"]=-math.pi/2
|
|
turtle_add_update(1,{turtle=turtle, type="endturn", iid=iid})
|
|
else
|
|
turtle_add_update(0,{turtle=turtle, type="failmove", iid=iid})
|
|
end
|
|
end,
|
|
detect = function()
|
|
local info = get_turtle_info(turtle)
|
|
local pos = v3add(info["spos"],getv(info["dir"]))
|
|
return minetest.env:get_node(pos).name
|
|
end,
|
|
detectup = function()
|
|
local info = get_turtle_info(turtle)
|
|
local pos = v3add(info["spos"],{x=0,y=1,z=0})
|
|
return minetest.env:get_node(pos).name
|
|
end,
|
|
detectdown = function()
|
|
local info = get_turtle_info(turtle)
|
|
local pos = v3add(info["spos"],{x=0,y=-1,z=0})
|
|
return minetest.env:get_node(pos).name
|
|
end,
|
|
dig = function()
|
|
local info = get_turtle_info(turtle)
|
|
local dpos = v3add(info["spos"],getv(info["dir"]))
|
|
local dnode = minetest.env:get_node(dpos)
|
|
if turtle_can_go(dnode.name) or dnode.name=="ignore" then return false end
|
|
local drops = minetest.get_node_drops(dnode.name, "default:pick_mese")
|
|
local _, dropped_item
|
|
for _, dropped_item in ipairs(drops) do
|
|
local leftover = turtle_invs:add_item(turtle,dropped_item)
|
|
minetest.env:add_item(info["spos"],leftover)
|
|
end
|
|
minetest.env:remove_node(dpos)
|
|
return true
|
|
end,
|
|
digup = function()
|
|
local info = get_turtle_info(turtle)
|
|
local dpos = v3add(info["spos"],{x=0,y=1,z=0})
|
|
local dnode = minetest.env:get_node(dpos)
|
|
if turtle_can_go(dnode.name) or dnode.name=="ignore" then return false end
|
|
local drops = minetest.get_node_drops(dnode.name, "default:pick_mese")
|
|
local _, dropped_item
|
|
for _, dropped_item in ipairs(drops) do
|
|
local leftover = turtle_invs:add_item(turtle,dropped_item)
|
|
minetest.env:add_item(info["spos"],leftover)
|
|
end
|
|
minetest.env:remove_node(dpos)
|
|
return true
|
|
end,
|
|
digdown = function()
|
|
local info = get_turtle_info(turtle)
|
|
local dpos = v3add(info["spos"],{x=0,y=-1,z=0})
|
|
local dnode = minetest.env:get_node(dpos)
|
|
if turtle_can_go(dnode.name) or dnode.name=="ignore" then return false end
|
|
local drops = minetest.get_node_drops(dnode.name, "default:pick_mese")
|
|
local _, dropped_item
|
|
for _, dropped_item in ipairs(drops) do
|
|
local leftover = turtle_invs:add_item(turtle,dropped_item)
|
|
minetest.env:add_item(info["spos"],leftover)
|
|
end
|
|
minetest.env:remove_node(dpos)
|
|
return true
|
|
end,
|
|
place = function(slot)
|
|
local info = get_turtle_info(turtle)
|
|
local ppos = v3add(info["spos"],getv(info["dir"]))
|
|
local dnode = minetest.env:get_node(ppos)
|
|
if (not turtle_can_go(dnode.name)) or dnode.name=="ignore" then return false end
|
|
local stack = turtle_invs:get_stack(turtle,slot)
|
|
if stack:is_empty() or minetest.registered_nodes[stack:get_name()]==nil then return false end
|
|
minetest.env:set_node(ppos, {name=stack:get_name()})
|
|
stack:take_item()
|
|
turtle_invs:set_stack(turtle, slot, stack)
|
|
return true
|
|
end,
|
|
placeup = function(slot)
|
|
local info = get_turtle_info(turtle)
|
|
local ppos = v3add(info["spos"],{x=0,y=1,z=0})
|
|
local dnode = minetest.env:get_node(ppos)
|
|
if (not turtle_can_go(dnode.name)) or dnode.name=="ignore" then return false end
|
|
local stack = turtle_invs:get_stack(turtle,slot)
|
|
if stack:is_empty() or minetest.registered_nodes[stack:get_name()]==nil then return false end
|
|
minetest.env:set_node(ppos, {name=stack:get_name()})
|
|
stack:take_item()
|
|
turtle_invs:set_stack(turtle, slot, stack)
|
|
return true
|
|
end,
|
|
placedown = function(slot)
|
|
local info = get_turtle_info(turtle)
|
|
local ppos = v3add(info["spos"],{x=0,y=-1,z=0})
|
|
local dnode = minetest.env:get_node(ppos)
|
|
if (not turtle_can_go(dnode.name)) or dnode.name=="ignore" then return false end
|
|
local stack = turtle_invs:get_stack(turtle,slot)
|
|
if stack:is_empty() or minetest.registered_nodes[stack:get_name()]==nil then return false end
|
|
minetest.env:set_node(ppos, {name=stack:get_name()})
|
|
stack:take_item()
|
|
turtle_invs:set_stack(turtle, slot, stack)
|
|
return true
|
|
end,
|
|
getstack = function(slot)
|
|
local s = turtle_invs:get_stack(turtle,slot):to_table()
|
|
if s==nil then return {name="", count=0} end
|
|
return s
|
|
end,
|
|
moveto = function(slot1,slot2, nmax)
|
|
local stack1 = turtle_invs:get_stack(turtle,slot1)
|
|
local stack2 = turtle_invs:get_stack(turtle,slot2)
|
|
local move
|
|
if nmax==0 then nmax = stack1:get_count() end
|
|
if stack2:is_empty() then
|
|
move = math.min(stack1:get_count(), nmax)
|
|
local taken = stack1:take_item(move)
|
|
stack2:add_item(taken)
|
|
else
|
|
if stack1:get_name()~=stack2:get_name() then return 0 end
|
|
move = math.min(stack1:get_count(), stack2:get_free_space(), nmax)
|
|
local taken = stack1:take_item(move)
|
|
stack2:add_item(taken)
|
|
end
|
|
turtle_invs:set_stack(turtle,slot1,stack1)
|
|
turtle_invs:set_stack(turtle,slot2,stack2)
|
|
return move
|
|
end,
|
|
refuel = function(slot, nmax)
|
|
local info = get_turtle_info(turtle)
|
|
local stack = turtle_invs:get_stack(turtle, slot)
|
|
local fuel, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = {stack}})
|
|
if fuel.time<=0 then return false end
|
|
if nmax==nil then nmax=stack:get_count() end
|
|
local count = math.min(stack:get_count(), nmax)
|
|
local fs = stack:to_table()
|
|
fs["count"]=1
|
|
local fstack = ItemStack(fs)
|
|
local fuel, afterfuel
|
|
fuel, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = {fstack}})
|
|
info["fuel"]=info["fuel"]+FUEL_EFFICIENCY*count*fuel.time
|
|
stack:take_item(count)
|
|
if afterfuel~=nil then
|
|
afterfuel = afterfuel.items[1]
|
|
end
|
|
if afterfuel~=nil then
|
|
afterfuel = stack_set_count(afterfuel, afterfuel:get_count()*count)
|
|
end
|
|
if afterfuel~=nil then
|
|
local leftover = stack:add_item(ItemStack(afterfuel))
|
|
turtle_invs:set_stack(turtle, slot, stack)
|
|
local leftover2 = turtle_invs:add_item(turtle, leftover)
|
|
minetest.env:add_item(info["spos"],leftover2)
|
|
else
|
|
turtle_invs:set_stack(turtle, slot, stack)
|
|
end
|
|
end,
|
|
get_fuel_time = function()
|
|
local info = get_turtle_info(turtle)
|
|
return info["fuel"]
|
|
end,
|
|
craft = function(nmax)
|
|
local info = get_turtle_info(turtle)
|
|
local invl = turtle_invs:get_list(turtle)
|
|
local recipe = {}
|
|
local craftmax=nmax
|
|
for i=1,16 do
|
|
recipe[i]=ItemStack({name=invl[i]:get_name(),count=1})
|
|
if invl[i]:get_count()>0 then
|
|
craftmax=math.min(craftmax, invl[i]:get_count())
|
|
end
|
|
end
|
|
local result,new=minetest.get_craft_result({method="normal",width=4,items=recipe})
|
|
if result.item:is_empty() then return 0 end
|
|
result=result.item
|
|
for i=1,16 do
|
|
invl[i]:take_item(craftmax)
|
|
turtle_invs:set_stack(turtle, i, invl[i])
|
|
end
|
|
result = stack_set_count(result, result:get_count()*craftmax)
|
|
local leftover = turtle_invs:add_item(turtle,result)
|
|
minetest.env:add_item(info["spos"],leftover)
|
|
for i=1,16 do
|
|
local s=stack_set_count(new.items[i], new.items[i]:get_count()*craftmax)
|
|
if s~=nil then
|
|
local leftover = turtle_invs:add_item(turtle,s)
|
|
minetest.env:add_item(info["spos"],leftover)
|
|
end
|
|
end
|
|
end,
|
|
drop = function(slot)
|
|
local info = get_turtle_info(turtle)
|
|
local stack = turtle_invs:get_stack(turtle, slot)
|
|
turtle_invs:set_stack(turtle, slot, ItemStack(""))
|
|
local spos = info["spos"]
|
|
local item = tube_item({x=spos.x,y=spos.y,z=spos.z},stack)
|
|
item:get_luaentity().start_pos = {x=spos.x,y=spos.y,z=spos.z}
|
|
item:setvelocity(getv(info["dir"]))
|
|
end,
|
|
dropup = function(slot)
|
|
local info = get_turtle_info(turtle)
|
|
local stack = turtle_invs:get_stack(turtle, slot)
|
|
turtle_invs:set_stack(turtle, slot, ItemStack(""))
|
|
local spos = info["spos"]
|
|
local item = tube_item({x=spos.x,y=spos.y,z=spos.z},stack)
|
|
item:get_luaentity().start_pos = {x=spos.x,y=spos.y,z=spos.z}
|
|
item:setvelocity({x=0,y=1,z=0})
|
|
end,
|
|
dropdown = function(slot)
|
|
local info = get_turtle_info(turtle)
|
|
local stack = turtle_invs:get_stack(turtle, slot)
|
|
turtle_invs:set_stack(turtle, slot, ItemStack(""))
|
|
local spos = info["spos"]
|
|
local item = tube_item({x=spos.x,y=spos.y,z=spos.z},stack)
|
|
item:get_luaentity().start_pos = {x=spos.x,y=spos.y,z=spos.z}
|
|
item:setvelocity({x=0,y=-1,z=0})
|
|
end,
|
|
suck = function()
|
|
local info = get_turtle_info(turtle)
|
|
local frompos=v3add(info["spos"],getv(info["dir"]))
|
|
local fromnode=minetest.env:get_node(frompos)
|
|
local frominv
|
|
if not (minetest.registered_nodes[fromnode.name].tube and
|
|
minetest.registered_nodes[fromnode.name].tube.input_inventory) then
|
|
for _,object in ipairs(minetest.env:get_objects_inside_radius(frompos, 1)) do
|
|
if not object:is_player() and object:get_luaentity() and object:get_luaentity().name == "__builtin:item" then
|
|
if object:get_luaentity().itemstring ~= "" then
|
|
local leftover = turtle_invs:add_item(turtle,ItemStack(object:get_luaentity().itemstring))
|
|
minetest.env:add_item(info["spos"],leftover)
|
|
object:get_luaentity().itemstring = ""
|
|
object:remove()
|
|
return
|
|
end
|
|
end
|
|
end
|
|
return
|
|
end
|
|
local frommeta=minetest.env:get_meta(frompos)
|
|
local frominvname=minetest.registered_nodes[fromnode.name].tube.input_inventory
|
|
local frominv=frommeta:get_inventory()
|
|
for spos,stack in ipairs(frominv:get_list(frominvname)) do
|
|
if stack:get_name()~="" then
|
|
local leftover = turtle_invs:add_item(turtle,stack)
|
|
frominv:set_stack(frominvname, spos ,leftover)
|
|
return
|
|
end
|
|
end
|
|
end,
|
|
suckup = function()
|
|
local info = get_turtle_info(turtle)
|
|
local frompos=v3add(info["spos"],{x=0,y=1,z=0})
|
|
local fromnode=minetest.env:get_node(frompos)
|
|
local frominv
|
|
if not (minetest.registered_nodes[fromnode.name].tube and
|
|
minetest.registered_nodes[fromnode.name].tube.input_inventory) then
|
|
for _,object in ipairs(minetest.env:get_objects_inside_radius(frompos, 1)) do
|
|
if not object:is_player() and object:get_luaentity() and object:get_luaentity().name == "__builtin:item" then
|
|
if object:get_luaentity().itemstring ~= "" then
|
|
local leftover = turtle_invs:add_item(turtle,ItemStack(object:get_luaentity().itemstring))
|
|
minetest.env:add_item(info["spos"],leftover)
|
|
object:get_luaentity().itemstring = ""
|
|
object:remove()
|
|
return
|
|
end
|
|
end
|
|
end
|
|
return
|
|
end
|
|
local frommeta=minetest.env:get_meta(frompos)
|
|
local frominvname=minetest.registered_nodes[fromnode.name].tube.input_inventory
|
|
local frominv=frommeta:get_inventory()
|
|
for spos,stack in ipairs(frominv:get_list(frominvname)) do
|
|
if stack:get_name()~="" then
|
|
local leftover = turtle_invs:add_item(turtle,stack)
|
|
frominv:set_stack(frominvname, spos, leftover)
|
|
return
|
|
end
|
|
end
|
|
end,
|
|
suckdown = function()
|
|
local info = get_turtle_info(turtle)
|
|
local frompos=v3add(info["spos"],{x=0,y=-1,z=0})
|
|
local fromnode=minetest.env:get_node(frompos)
|
|
local frominv
|
|
if not (minetest.registered_nodes[fromnode.name].tube and
|
|
minetest.registered_nodes[fromnode.name].tube.input_inventory) then
|
|
for _,object in ipairs(minetest.env:get_objects_inside_radius(frompos, 1)) do
|
|
if not object:is_player() and object:get_luaentity() and object:get_luaentity().name == "__builtin:item" then
|
|
if object:get_luaentity().itemstring ~= "" then
|
|
local leftover = turtle_invs:add_item(turtle,ItemStack(object:get_luaentity().itemstring))
|
|
minetest.env:add_item(info["spos"],leftover)
|
|
object:get_luaentity().itemstring = ""
|
|
object:remove()
|
|
return
|
|
end
|
|
end
|
|
end
|
|
return
|
|
end
|
|
local frommeta=minetest.env:get_meta(frompos)
|
|
local frominvname=minetest.registered_nodes[fromnode.name].tube.input_inventory
|
|
local frominv=frommeta:get_inventory()
|
|
for spos,stack in ipairs(frominv:get_list(frominvname)) do
|
|
if stack:get_name()~="" then
|
|
local leftover = turtle_invs:add_item(turtle,stack)
|
|
frominv:set_stack(frominvname, spos, leftover)
|
|
return
|
|
end
|
|
end
|
|
end,
|
|
}
|
|
end
|
|
|
|
|
|
|
|
local create_environment = function(turtle, mem, event)
|
|
-- Gather variables for the environment
|
|
return {
|
|
print = safe_print,
|
|
interrupt = getinterrupt(turtle),
|
|
turtle = get_turtle_funcs(turtle),
|
|
mem = mem,
|
|
tostring = tostring,
|
|
tonumber = tonumber,
|
|
string = {
|
|
byte = string.byte,
|
|
char = string.char,
|
|
find = string.find,
|
|
format = string.format,
|
|
gmatch = string.gmatch,
|
|
gsub = string.gsub,
|
|
len = string.len,
|
|
lower = string.lower,
|
|
match = string.match,
|
|
rep = string.rep,
|
|
reverse = string.reverse,
|
|
sub = string.sub,
|
|
},
|
|
math = {
|
|
abs = math.abs,
|
|
acos = math.acos,
|
|
asin = math.asin,
|
|
atan = math.atan,
|
|
atan2 = math.atan2,
|
|
ceil = math.ceil,
|
|
cos = math.cos,
|
|
cosh = math.cosh,
|
|
deg = math.deg,
|
|
exp = math.exp,
|
|
floor = math.floor,
|
|
fmod = math.fmod,
|
|
frexp = math.frexp,
|
|
huge = math.huge,
|
|
ldexp = math.ldexp,
|
|
log = math.log,
|
|
log10 = math.log10,
|
|
max = math.max,
|
|
min = math.min,
|
|
modf = math.modf,
|
|
pi = math.pi,
|
|
pow = math.pow,
|
|
rad = math.rad,
|
|
random = math.random,
|
|
sin = math.sin,
|
|
sinh = math.sinh,
|
|
sqrt = math.sqrt,
|
|
tan = math.tan,
|
|
tanh = math.tanh,
|
|
},
|
|
table = {
|
|
insert = table.insert,
|
|
maxn = table.maxn,
|
|
remove = table.remove,
|
|
sort = table.sort
|
|
},
|
|
event = event,
|
|
}
|
|
end
|
|
|
|
local create_sandbox = function (code, env)
|
|
-- Create Sandbox
|
|
if code:byte(1) == 27 then
|
|
return _, "You Hacker You! Don't use binary code!"
|
|
end
|
|
f, msg = loadstring(code)
|
|
if not f then return _, msg end
|
|
setfenv(f, env)
|
|
return f
|
|
end
|
|
|
|
local do_overheat = function (turtle)
|
|
-- Overheat protection
|
|
heat(turtle)
|
|
turtle_add_update(0.5,{turtle=turtle, type="cool"})
|
|
if overheat(turtle) then
|
|
--TODO
|
|
return true
|
|
end
|
|
end
|
|
|
|
local load_memory = function(turtle)
|
|
local info = get_turtle_info(turtle)
|
|
return info["memory"] or {}
|
|
end
|
|
|
|
local save_memory = function(turtle, mem)
|
|
local info = get_turtle_info(turtle)
|
|
info["memory"] = mem
|
|
end
|
|
|
|
local interrupt_allow = function (turtle, event)
|
|
if event.type ~= "interrupt" then return true end
|
|
local info = get_turtle_info(turtle)
|
|
local interrupts = info["interrupts"] or {}
|
|
local search = safe_serialize(event.iid)
|
|
for _, i in ipairs(interrupts) do
|
|
if safe_serialize(i) == search then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
----------------------
|
|
-- Parsing function --
|
|
----------------------
|
|
|
|
turtle_update = function (turtle, event)
|
|
local info = get_turtle_info(turtle)
|
|
if not interrupt_allow(turtle, event) then return end
|
|
if do_overheat(turtle) then return end
|
|
|
|
-- load code & mem from memory
|
|
local mem = load_memory(turtle)
|
|
local code = info["code"]
|
|
|
|
-- make sure code is ok and create environment
|
|
local prohibited = code_prohibited(code)
|
|
if prohibited then return prohibited end
|
|
local env = create_environment(turtle, mem, event)
|
|
|
|
-- create the sandbox and execute code
|
|
local chunk, msg = create_sandbox (code, env)
|
|
if not chunk then return msg end
|
|
local success, msg = pcall(f)
|
|
if not success then
|
|
print(msg)
|
|
update_formspec(turtle, code, msg, info["filename"], nil, "")
|
|
end
|
|
|
|
save_memory(turtle, mem)
|
|
end
|
|
|
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|
if formname:sub(1,6)~="turtle" then return end
|
|
update_formspec(formname, fields.code, "", fields.filename, player, fields.exit)
|
|
if fields.program~=nil or fields.exit~=nil then
|
|
local err = turtle_update(formname, {type="program"})
|
|
if err then print(err) end
|
|
update_formspec(formname, fields.code, err, fields.filename, player, fields.exit)
|
|
end
|
|
if fields.save then
|
|
if fields.filename:sub(1,1)=="." then return end -- Not allowed to save because could change the user's files (including the mod's files, dangerous)
|
|
local fn = minetest.get_modpath("turtle").."/progs/"..fields.filename..".lua"
|
|
local f = io.open(fn, "w")
|
|
f:write(fields.code)
|
|
f:close()
|
|
end
|
|
if fields.open then
|
|
local fn = minetest.get_modpath("turtle").."/progs/"..fields.filename..".lua"
|
|
local f = io.open(fn, "r")
|
|
local code
|
|
if f==nil then
|
|
code=""
|
|
else
|
|
code = f:read("*all")
|
|
f:close()
|
|
end
|
|
if code==nil then code="" end
|
|
update_formspec(formname, code, "", fields.filename, player)
|
|
end
|
|
end)
|
|
|
|
minetest.register_craftitem("turtle:turtle",{
|
|
description="Turtle",
|
|
image = "turtle_turtle_inv.png",
|
|
on_place = function(itemstack, placer, pointed_thing)
|
|
if pointed_thing.type~="node" then return end
|
|
local obj = minetest.env:add_entity(pointed_thing.above, "turtle:turtle")
|
|
itemstack:take_item()
|
|
return itemstack
|
|
end
|
|
})
|
|
|
|
minetest.register_craft( {
|
|
output = "turtle:turtle",
|
|
recipe = {
|
|
{ "default:diamond", "default:pick_mese", "default:diamond" },
|
|
{ "default:diamond", "default:mese", "default:diamond" },
|
|
{ "default:mese", "default:diamond", "default:mese" },
|
|
},
|
|
})
|
|
|
|
turtle_invs = minetest.create_detached_inventory("turtle:invs")
|
|
for turtle,i in pairs(turtle_infos) do
|
|
turtle_invs:set_size(turtle,16)
|
|
for l,stack in pairs(deserialize_inv(i["inventory"])) do
|
|
turtle_invs:set_stack(turtle, l, stack)
|
|
end
|
|
end
|
|
|
|
minetest.register_entity("turtle:turtle", {
|
|
physical = true,
|
|
force_load = TURTLES_FORCE_LOAD,
|
|
collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
|
|
visual = "mesh",
|
|
mesh="turtle.x",
|
|
textures = {"default_wood.png","default_wood.png"},
|
|
visual_size = {x=1, y=1},
|
|
on_activate = function(self, staticdata)
|
|
local info
|
|
if staticdata==nil or staticdata=="" then
|
|
self.n=get_turtle_id()
|
|
info=get_turtle_info(self.n)
|
|
turtle_invs:set_size(self.n,16)
|
|
info["turtle"]=self
|
|
info["spos"]=round_pos(self.object:getpos())
|
|
info["dir"]=0
|
|
info["fuel"]=0
|
|
info["formspec"]= "size[9,10]"..
|
|
"textarea[0.3,0;4.7,5;code;;]"..
|
|
"list[detached:turtle:invs;"..self.n..";4.8,0;4,4;]"..
|
|
"image_button[0,4.6;2.5,1;turtle_execute.png;program;]"..
|
|
"image_button_exit[8.72,-0.25;0.425,0.4;turtle_close.png;exit;]"..
|
|
"label[4.6,4;]"..
|
|
"list[current_player;main;0.5,6;8,4;]"..
|
|
"field[3,5;4,1;filename;Filename:;]"..
|
|
"button[7,4.65;1,1;open;Open]"..
|
|
"button[8,4.65;1,1;save;Save]"
|
|
else
|
|
self.n=staticdata
|
|
info=get_turtle_info(self.n)
|
|
info["turtle"]=self
|
|
end
|
|
end,
|
|
on_step = function(self, dtime)
|
|
local info=get_turtle_info(self.n)
|
|
if info["rotate"] then
|
|
self.object:setyaw(self.object:getyaw()+info["rotate"]*dtime)
|
|
end
|
|
end,
|
|
on_rightclick = function(self, clicker)
|
|
minetest.show_formspec(clicker:get_player_name(), self.n, get_turtle_info(self.n)["formspec"])
|
|
end,
|
|
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
|
self.object:remove()
|
|
minetest.env:add_item(turtle_infos[self.n]["spos"],"turtle:turtle")
|
|
for i=1,16 do
|
|
turtle_invs:set_stack(self.n, i, ItemStack(""))
|
|
end
|
|
turtle_infos[self.n] = nil
|
|
end,
|
|
get_staticdata = function(self)
|
|
return self.n
|
|
end,
|
|
})
|