changes (22.09.2016):

- preparation for node overrides
- adjustment on route logic (processing in chunks) for really big buildings (>100.000 nodes)
This commit is contained in:
Alexander Weber 2016-09-22 17:39:10 +02:00 committed by Elinvention
parent 4b4ee70781
commit 3124c5ae8f

View File

@ -13,7 +13,7 @@ CHEST
--if the value is to big, it can happen the builder stucks and just stay (beter hardware required in RL) --if the value is to big, it can happen the builder stucks and just stay (beter hardware required in RL)
--if to low, it can happen the searching next near node is poor and the builder acts overwhelmed, fail to see some nearly gaps. The order seems to be randomized --if to low, it can happen the searching next near node is poor and the builder acts overwhelmed, fail to see some nearly gaps. The order seems to be randomized
--the right value is depend on building size. If the building (or the not builded rest) can full imaginated (less blocks in building then c_npc_imagination) there is the full search potencial active --the right value is depend on building size. If the building (or the not builded rest) can full imaginated (less blocks in building then c_npc_imagination) there is the full search potencial active
local c_npc_imagination = 600 local c_npc_imagination = 500
-- expose api -- expose api
@ -65,46 +65,41 @@ towntest_chest.load = function(filename)
end end
-- mapname Take filters and actions on nodes before building. Currently the payment item determination and check for registred node only -- towntest_chest.mapnodes Take filters and actions on nodes before building. Currently the payment item determination and check for registred node only
-- name - Node name to check and map -- node - Node (from file) to check if buildable and payable
-- return - item name used as payment -- return - node with enhanced informations
local function mapname(name) towntest_chest.mapnodes = function(node)
-- no name given - something wrong -- no name given - something wrong
if not name or name == "" then if not node or node.name == "" then
return nil return nil
end end
-- item table (can be happen in drops). Search for the first valid one local node_chk = minetest.registered_items[node.name]
if type(name) == "table" then -- drop table. Search for "any payment"
for _, namex in pairs(name) do
local validname = mapname(namex)
if validname then
return validname
else
--nothing valid found
return nil
end
end
end
local node = minetest.registered_items[name] if not node_chk then
dprint("unknown node in building: "..node.name)
if not node then
dprint("unknown node in building: "..name)
return nil return nil
else else
-- known node. Check for price or if it is free -- known node. Check for price or if it is free
if (node.groups.not_in_creative_inventory and not (node.groups.not_in_creative_inventory == 0)) or if (node_chk.groups.not_in_creative_inventory and not (node_chk.groups.not_in_creative_inventory == 0)) or
(not node.description or node.description == "") then (not node_chk.description or node_chk.description == "") then
if node.drop then if node_chk.drop then
return mapname(node.drop) --use the drop as payment -- use possible drop as payment
else --something not supported, but known if type(node_chk.drop) == "table" then -- drop table
return "free" -- will be build for free. they are something like doors:hidden or second part of coffee lrfurn:coffeetable_back node.matname = node_chk.drop[1] -- use the first one
end
else else
return node.name node.matname = node_chk.drop
end
else --something not supported, but known
node.matname = "free" -- will be build for free. they are something like doors:hidden or second part of coffee lrfurn:coffeetable_back
end
else -- build for payment the 1:1
node.matname = node.name
end end
end end
return node
end end
@ -157,7 +152,7 @@ local function skip_already_placed(building_plan, chestpos)
table.insert(building_out, def) table.insert(building_out, def)
dprint("rebuild to correct metadata",def.name) dprint("rebuild to correct metadata",def.name)
end end
elseif mapname(node_placed.name) == mapname(def.name) then elseif towntest_chest.mapnodes(node_placed).matname == towntest_chest.mapnodes(def).matname then
def.matname = "free" --same price. Check/set for free def.matname = "free" --same price. Check/set for free
table.insert(building_out, def) table.insert(building_out, def)
dprint("rebuild for free because of the same drop",def.name) dprint("rebuild for free because of the same drop",def.name)
@ -177,12 +172,10 @@ towntest_chest.do_prepare_building = function(building_in)
for idx,def in pairs(building_in) do for idx,def in pairs(building_in) do
if (def.x and def.y and def.z) and -- more robust. Values should be existing if (def.x and def.y and def.z) and -- more robust. Values should be existing
(tonumber(def.x)~=0 or tonumber(def.y)~=0 or tonumber(def.z)~=0) then (tonumber(def.x)~=0 or tonumber(def.y)~=0 or tonumber(def.z)~=0) then
if not def.matname then local mapped_def = towntest_chest.mapnodes(def)
def.matname = mapname(def.name) if mapped_def and mapped_def.matname then -- found
end
if def.matname then -- found
-- the node will be built -- the node will be built
table.insert(building_out, {x=def.x,y=def.y,z=def.z,name=def.name,param1=def.param1,param2=def.param2,meta=def.meta,matname=def.matname}) table.insert(building_out, mapped_def)
end end
end end
end end
@ -194,9 +187,15 @@ end
-- inv - inventory object of the chest -- inv - inventory object of the chest
-- building - table containing pos and nodes to build -- building - table containing pos and nodes to build
towntest_chest.update_needed = function(inv,building) towntest_chest.update_needed = function(inv,building)
dprint("update_needed - started")
for i=1,inv:get_size("needed") do for i=1,inv:get_size("needed") do
inv:set_stack("needed", i, nil) inv:set_stack("needed", i, nil)
end end
if building == nil then
return
end
local materials = {} local materials = {}
-- sort by y to prefer lower nodes in building order. At the same level prefer nodes nearly the chest -- sort by y to prefer lower nodes in building order. At the same level prefer nodes nearly the chest
@ -205,7 +204,7 @@ towntest_chest.update_needed = function(inv,building)
return ((a.y<b.y) or (a.y==b.y and a.x+a.z<b.x+b.z)) end return ((a.y<b.y) or (a.y==b.y and a.x+a.z<b.x+b.z)) end
end end
) )
dprint("update_needed - sort")
for i,v in ipairs(building) do for i,v in ipairs(building) do
if v.matname ~= "free" then --free materials will be built for free if v.matname ~= "free" then --free materials will be built for free
if not materials[v.matname] then if not materials[v.matname] then
@ -214,8 +213,12 @@ towntest_chest.update_needed = function(inv,building)
materials[v.matname].count = materials[v.matname].count+1 materials[v.matname].count = materials[v.matname].count+1
end end
end end
if i > (c_npc_imagination * 20) then --don't calculate all needs if it is really big value
break
end
end end
dprint("update_needed - index")
-- order the needed by building plan order -- order the needed by building plan order
local keys = {} local keys = {}
for key in pairs(materials) do for key in pairs(materials) do
@ -227,6 +230,7 @@ towntest_chest.update_needed = function(inv,building)
for _, key in ipairs(keys) do for _, key in ipairs(keys) do
inv:add_item("needed",materials[key].matname.." "..materials[key].count) inv:add_item("needed",materials[key].matname.." "..materials[key].count)
end end
dprint("update_needed - finished")
end end
-- set_status - activate or deactivate building -- set_status - activate or deactivate building
@ -244,15 +248,20 @@ towntest_chest.set_status = function(meta,status)
meta:set_string("infotext", "Building Chest (active)") meta:set_string("infotext", "Building Chest (active)")
meta:set_int("building_status",1) meta:set_int("building_status",1)
end end
dprint("status changed", meta:get_int("building_status"))
end end
-- build - build a node of the structure -- build - build a node of the structure
-- chestpos - the position of the chest containing the instructions -- chestpos - the position of the chest containing the instructions
towntest_chest.build = function(chestpos) towntest_chest.build = function(chestpos)
-- load the building_plan -- load the building_plan
local meta = minetest.env:get_meta(chestpos) local meta = minetest.env:get_meta(chestpos)
if meta:get_int("building_status")~=1 then return end if meta:get_int("building_status")~=1 then return end
dprint("build step started")
local building_plan = minetest.deserialize((meta:get_string("building_plan"))) local building_plan = minetest.deserialize((meta:get_string("building_plan")))
-- create the npc if needed -- create the npc if needed
local inv = meta:get_inventory() local inv = meta:get_inventory()
local k = chestpos.x..","..chestpos.y..","..chestpos.z local k = chestpos.x..","..chestpos.y..","..chestpos.z
@ -265,32 +274,32 @@ towntest_chest.build = function(chestpos)
inv:set_stack("builder", i, nil) inv:set_stack("builder", i, nil)
end end
end end
if building_plan then
towntest_chest.update_needed(meta:get_inventory(),building_plan)
end
end end
local npc = towntest_chest.npc[k] local npc = towntest_chest.npc[k]
local npclua = npc:get_luaentity() local npclua = npc:get_luaentity()
-- no building plan if npclua and npclua.target == "reached" then
if not building_plan then dprint("build step cancelled because the npc is working")
-- move the npc to the chest return --no thinking during working
dprint("no building plan")
npclua:moveto({x=chestpos.x,y=chestpos.y+1.5,z=chestpos.z},2,2)
towntest_chest.set_status(meta,0)
return
end end
-- search for next buildable node from builder inventory
local npcpos = npc:getpos() local npcpos = npc:getpos()
if not npcpos then --fallback if not npcpos then --fallback
npcpos = chestpos npcpos = chestpos
end end
local nextnode = {} local nextnode = {}
-- building plan
if building_plan then
dprint("current building plan chunksize:", #building_plan)
local laterprocnode = {} local laterprocnode = {}
local buildable_counter = 0 local buildable_counter = 0
dprint("building plan size", #building_plan) local really_stuck = false
-- search for next buildable node from builder inventory
dprint("start searching for the next node", #building_plan)
for i,v in ipairs(building_plan) do for i,v in ipairs(building_plan) do
-- is payed or for free and the item is at the end of building plan (all next items already built, to avoid all free items are placed at the first) -- is payed or for free and the item is at the end of building plan (all next items already built, to avoid all free items are placed at the first)
if inv:contains_item("builder", v.matname) or v.matname == "free" then if inv:contains_item("builder", v.matname) or v.matname == "free" then
@ -314,11 +323,10 @@ towntest_chest.build = function(chestpos)
end end
end end
else else
-- not buildable at the time. Move to the end of plan till next grabbing from chest -- not buildable anymore (material used up). remove from current building chunk
-- for better chance to find buildable in the next run (c_npc_imagination pack)
table.remove(building_plan,i) table.remove(building_plan,i)
table.insert(building_plan,v)
end end
--respect "c_npc_imagination" --respect "c_npc_imagination"
if i > c_npc_imagination and nextnode.v then if i > c_npc_imagination and nextnode.v then
dprint("stuck at:", buildable_counter) dprint("stuck at:", buildable_counter)
@ -333,8 +341,17 @@ towntest_chest.build = function(chestpos)
laterprocnode.v = nil laterprocnode.v = nil
end end
break break
elseif i > (c_npc_imagination * 5 ) then
dprint("really stuck! try again at the next building step")
really_stuck = true
break
end
end
if really_stuck == true then
-- save current state and search again at the next step
meta:set_string("building_plan", minetest.serialize(building_plan))
return
end end
end end
-- next buildable node found -- next buildable node found
@ -352,7 +369,9 @@ towntest_chest.build = function(chestpos)
-- move the npc to the build area -- move the npc to the build area
npclua:moveto({x=nextnode.pos.x, y=nextnode.pos.y+1.5, z=nextnode.pos.z}, 2, 2, 0, function(self,after_param) npclua:moveto({x=nextnode.pos.x, y=nextnode.pos.y+1.5, z=nextnode.pos.z}, 2, 2, 0, function(self,after_param)
-- take from the inv -- take from the inv
if after_param.v.matname then
after_param.inv:remove_item("builder", after_param.v.matname.." 1") after_param.inv:remove_item("builder", after_param.v.matname.." 1")
end
-- add the node to the world -- add the node to the world
minetest.env:add_node(after_param.pos, {name=after_param.v.name,param1=after_param.v.param1,param2=after_param.v.param2}) minetest.env:add_node(after_param.pos, {name=after_param.v.name,param1=after_param.v.param1,param2=after_param.v.param2})
if after_param.v.meta then if after_param.v.meta then
@ -368,6 +387,17 @@ towntest_chest.build = function(chestpos)
end end
end end
meta:set_string("building_plan", minetest.serialize(building_plan)) meta:set_string("building_plan", minetest.serialize(building_plan))
-- update the chest building plan
building_plan = minetest.deserialize(meta:get_string("full_plan"))
for i,v in ipairs(building_plan) do
if v.x == after_param.v.x and v.y == after_param.v.y and v.z == after_param.v.z then
table.remove(building_plan,i)
break
end
end
meta:set_string("full_plan", minetest.serialize(building_plan))
end, {pos=nextnode.pos, v=nextnode.v, inv=inv, meta=nextnode.meta}) end, {pos=nextnode.pos, v=nextnode.v, inv=inv, meta=nextnode.meta})
else else
if npclua.target then if npclua.target then
@ -380,19 +410,12 @@ towntest_chest.build = function(chestpos)
else else
dprint("<<--- get new items and re-sort building plan --->>>") dprint("<<--- get new items and re-sort building plan --->>>")
-- sort by y to prefer lower nodes in building order. At the same level prefer nodes nearly the chest
table.sort(building_plan,function(a,b)
if a and b then
return ((a.y<b.y) or (a.y==b.y and a.x+a.z<b.x+b.z)) end
end
)
-- update the needed and sort -- update the needed and sort
towntest_chest.update_needed(meta:get_inventory(),building_plan) local full_plan = minetest.deserialize(meta:get_string("full_plan"))
towntest_chest.update_needed(meta:get_inventory(),full_plan)
-- try to get items from chest into builder inventory
meta:set_string("building_plan", minetest.serialize(building_plan)) --save the used order
local items_needed = true local items_needed = true
for i,v in ipairs(building_plan) do for i,v in ipairs(full_plan) do
-- check if the chest has the node -- check if the chest has the node
if inv:contains_item("main", v.matname) then if inv:contains_item("main", v.matname) then
items_needed = false items_needed = false
@ -402,7 +425,8 @@ towntest_chest.build = function(chestpos)
npclua:moveto({x=chestpos.x, y=chestpos.y+1.5, z=chestpos.z}, 2, 2, 0, function(self, params) npclua:moveto({x=chestpos.x, y=chestpos.y+1.5, z=chestpos.z}, 2, 2, 0, function(self, params)
-- check for food -- check for food
local inv = params.inv local inv = params.inv
local building_plan = params.building_plan local full_plan = params.full_plan
local next_plan = {}
if not inv:is_empty("main") then if not inv:is_empty("main") then
for i=1,inv:get_size("main") do for i=1,inv:get_size("main") do
-- check if this is a food, if so take it -- check if this is a food, if so take it
@ -421,25 +445,42 @@ towntest_chest.build = function(chestpos)
end end
end end
end end
for i,v in ipairs(full_plan) do
-- take from the inv -- take from the inv
for i,v in ipairs(building_plan) do
if inv:contains_item("main",v.matname.." 1") and inv:room_for_item("builder",v.matname.." 1") then if inv:contains_item("main",v.matname.." 1") and inv:room_for_item("builder",v.matname.." 1") then
inv:add_item("builder",inv:remove_item("main",v.matname.." 1")) inv:add_item("builder",inv:remove_item("main",v.matname.." 1"))
inv:remove_item("needed", v.matname.." 1") inv:remove_item("needed", v.matname.." 1")
end end
-- create next chunk to be processed. only buildable items
if inv:contains_item("builder",v.matname.." 1") then
table.insert(next_plan,v)
end end
end, {inv=inv,building_plan=building_plan})
if i > c_npc_imagination * 20 then --limit the building plan chunk size
break
end end
end end
meta:set_string("building_plan", minetest.serialize(next_plan)) --save the used order
dprint("next building plan chunk", #next_plan)
end, {inv=inv,full_plan=full_plan})
break --there is a update loop in moveto-function
end end
end
if i > c_npc_imagination * 5 then --limit the building plan chunk size
break
end
end
-- stop building and tell the player what we need -- stop building and tell the player what we need
if npclua and items_needed then if npclua and items_needed then
npclua:moveto({x=chestpos.x,y=chestpos.y+1.5,z=chestpos.z},2) npclua:moveto({x=chestpos.x,y=chestpos.y+1.5,z=chestpos.z},2)
towntest_chest.set_status(meta,0) towntest_chest.set_status(meta,0)
towntest_chest.update_needed(meta:get_inventory(),building_plan) towntest_chest.update_needed(meta:get_inventory(),minetest.deserialize(meta:get_string("full_plan")))
end end
end end
end end
-- formspec - get the chest formspec -- formspec - get the chest formspec
@ -520,15 +561,18 @@ towntest_chest.on_receive_fields = function(pos, formname, fields, sender)
dprint("nodes loaded from file:", #we) dprint("nodes loaded from file:", #we)
end end
local filtered = towntest_chest.do_prepare_building(we) local filtered = towntest_chest.do_prepare_building(we)
if filtered then
dprint("nodes filtered:", #filtered)
end
local building_plan = skip_already_placed(filtered,pos) local building_plan = skip_already_placed(filtered,pos)
if building_plan then if building_plan then
dprint("nodes in building plan:", #building_plan) dprint("nodes in building plan:", #building_plan)
end end
meta:set_string("building_plan", minetest.serialize(building_plan)) meta:set_string("full_plan", minetest.serialize(building_plan))
meta:set_string("formspec", towntest_chest.formspec(pos,"chest")) meta:set_string("formspec", towntest_chest.formspec(pos,"chest"))
towntest_chest.set_status(meta,1)
towntest_chest.update_needed(meta:get_inventory(),building_plan) towntest_chest.update_needed(meta:get_inventory(),building_plan)
towntest_chest.set_status(meta,1)
elseif fields.nav then elseif fields.nav then
meta:set_string("formspec", towntest_chest.formspec(pos, fields.nav)) meta:set_string("formspec", towntest_chest.formspec(pos, fields.nav))
end end
@ -544,6 +588,7 @@ towntest_chest.on_construct = function(pos)
meta:get_inventory():set_size("lumberjack", 2*2) meta:get_inventory():set_size("lumberjack", 2*2)
meta:set_string("formspec", towntest_chest.formspec(pos, "")) meta:set_string("formspec", towntest_chest.formspec(pos, ""))
meta:set_string("building_plan", "") -- delete previous building plan on this node meta:set_string("building_plan", "") -- delete previous building plan on this node
meta:set_string("full_plan", "") -- delete previous building plan on this node
towntest_chest.set_status(meta, 0) --inactive till a building was selected towntest_chest.set_status(meta, 0) --inactive till a building was selected
dprint("chest initialization done") dprint("chest initialization done")
end end