Port to schemlib / and schemlib_builder_npcf
In last test the builder lost some nodes :-(
This commit is contained in:
parent
721d03301f
commit
07fc95fe30
@ -1,6 +1,7 @@
|
||||
# TownChest
|
||||
|
||||
A minetest mod contains a chest with building definitions. The chest can spawn a NPC that does build the building for you.
|
||||
The mod uses the schemlib framework to handle the buildings
|
||||
|
||||
![Screenshot](https://raw.github.com/bell07/minetest-townchest/master/screenshot.png)
|
||||
|
||||
|
148
chest.lua
148
chest.lua
@ -1,4 +1,6 @@
|
||||
--local dprint = townchest.dprint
|
||||
local dprint = townchest.dprint_off --debug
|
||||
|
||||
local smartfs = townchest.smartfs
|
||||
|
||||
local ASYNC_WAIT=0.2 -- schould be > 0 to restrict performance consumption
|
||||
@ -93,8 +95,9 @@ townchest.chest.new = function()
|
||||
-- Create the task that should be managed by chest
|
||||
--------------------------------------
|
||||
function self.set_rawdata(self, taskname)
|
||||
|
||||
self.plan = townchest.plan.new(self)
|
||||
self.plan = schemlib.plan.new(minetest.pos_to_string(self.pos), self.pos)
|
||||
self.plan.chest = self
|
||||
self.plan.on_status = townchest.npc.plan_update_hook
|
||||
|
||||
if taskname then
|
||||
self.info.taskname = taskname
|
||||
@ -109,10 +112,9 @@ townchest.chest.new = function()
|
||||
self:persist_info()
|
||||
return
|
||||
end
|
||||
self.plan:read_from_schem_file(townchest.modpath.."/buildings/"..self.info.filename)
|
||||
|
||||
self.plan.data = townchest.files.readfile(self.info.filename)
|
||||
|
||||
if not self.plan.data then
|
||||
if self.plan.data.nodecount == 0 then
|
||||
self.infotext = "No building found in ".. self.info.filename
|
||||
self:set_form("status")
|
||||
self.current_stage = "select"
|
||||
@ -123,37 +125,33 @@ townchest.chest.new = function()
|
||||
end
|
||||
|
||||
elseif self.info.taskname == "generate" then
|
||||
self.plan.data = {}
|
||||
self.plan.data.min_pos = { x=1, y=1, z=1 }
|
||||
self.plan.data.max_pos = { x=self.info.genblock.x, y=self.info.genblock.y, z=self.info.genblock.z}
|
||||
self.plan.data.nodecount = 0
|
||||
self.plan.data.ground_y = 0
|
||||
self.plan.data.nodenames = {}
|
||||
self.plan.data.scm_data_cache = {}
|
||||
|
||||
-- set directly instead of counting each step
|
||||
self.plan.data.min_pos = { x=1, y=1, z=1 }
|
||||
self.plan.data.max_pos = { x=self.info.genblock.x, y=self.info.genblock.y, z=self.info.genblock.z}
|
||||
self.plan.data.ground_y = 0
|
||||
local filler_node = {name = "default:cobble"}
|
||||
if self.info.genblock.variant == 1 then
|
||||
-- nothing special, just let fill them with air
|
||||
|
||||
elseif self.info.genblock.variant == 2 then
|
||||
table.insert(self.plan.data.nodenames, "default:cobble") -- index 1
|
||||
-- Fill with stone
|
||||
for x = 1, self.info.genblock.x do
|
||||
for y = 1, self.info.genblock.y do
|
||||
for z = 1, self.info.genblock.z do
|
||||
self.plan:add_node({x=x,y=y,z=z, name_id = 1})
|
||||
self.plan:add_node({x=x,y=y,z=z}, schemlib.node.new(filler_node))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif self.info.genblock.variant == 3 then
|
||||
-- Build a box
|
||||
table.insert(self.plan.data.nodenames, "default:cobble") -- index 1
|
||||
for x = 1, self.info.genblock.x do
|
||||
for y = 1, self.info.genblock.y do
|
||||
for z = 1, self.info.genblock.z do
|
||||
if x == 1 or x == self.info.genblock.x or
|
||||
y == 1 or y == self.info.genblock.y or
|
||||
z == 1 or z == self.info.genblock.z then
|
||||
self.plan:add_node({x=x,y=y,z=z, name_id = 1})
|
||||
self.plan:add_node({x=x,y=y,z=z}, schemlib.node.new(filler_node))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -164,12 +162,11 @@ townchest.chest.new = function()
|
||||
|
||||
-- Build a plate
|
||||
elseif self.info.genblock.variant == 4 then
|
||||
table.insert(self.plan.data.nodenames, "default:cobble") -- index 1
|
||||
local y = self.plan.data.min_pos.y
|
||||
self.plan.data.max_pos.y = self.plan.data.min_pos.y
|
||||
for x = 1, self.info.genblock.x do
|
||||
for z = 1, self.info.genblock.z do
|
||||
self.plan:add_node({x=x,y=y,z=z, name_id = 1})
|
||||
self.plan:add_node({x=x,y=y,z=z}, schemlib.node.new(filler_node))
|
||||
end
|
||||
end
|
||||
-- build ground level under chest
|
||||
@ -199,7 +196,6 @@ townchest.chest.new = function()
|
||||
chest:run_async(func)
|
||||
end
|
||||
end
|
||||
|
||||
self:persist_info()
|
||||
minetest.after(ASYNC_WAIT, async_call, self.pos)
|
||||
end
|
||||
@ -208,127 +204,55 @@ townchest.chest.new = function()
|
||||
-- Async task: Post-processing of plan preparation
|
||||
--------------------------------------
|
||||
function self.prepare_building_plan(self)
|
||||
self.plan:flood_with_air()
|
||||
|
||||
-- self.plan:do_mapping() -- on demand called
|
||||
self.plan:apply_flood_with_air(3, 0, 5) --(add_max, add_min, add_top)
|
||||
self.plan:del_node(self.plan:get_plan_pos(self.pos)) -- Do not override the chest node
|
||||
self.current_stage = "ready"
|
||||
self:set_form("build_status")
|
||||
self:run_async(self.instant_build_chunk) --just trigger, there is a check if active
|
||||
if self.info.npc_build == true then
|
||||
self.plan:set_status("build")
|
||||
townchest.npc.enable_build(self.plan)
|
||||
end
|
||||
if self.info.instantbuild == true then
|
||||
self.plan:set_status("build")
|
||||
self:run_async(self.instant_build_chunk)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Async Task: Do a instant build step
|
||||
--------------------------------------
|
||||
function self.instant_build_chunk(self)
|
||||
dprint("chunk processing called", self.info.instantbuild)
|
||||
if not self.info.instantbuild == true then --instantbuild disabled
|
||||
return
|
||||
end
|
||||
dprint("--- Instant build is running")
|
||||
|
||||
local startingpos = self.plan:get_random_node_pos()
|
||||
if not startingpos then
|
||||
local random_pos = self.plan:get_random_plan_pos()
|
||||
if not random_pos then
|
||||
self.info.instantbuild = false
|
||||
return false
|
||||
end
|
||||
|
||||
local chunk_pos = self.plan:get_world_pos(startingpos)
|
||||
dprint("---build chunk", minetest.pos_to_string(startingpos))
|
||||
|
||||
-- TODO: in customizing switchable implementation
|
||||
--[[ --- implementation with VoxelArea - bad gameplay responsivity :( - back to per-node update
|
||||
-- work on VoxelArea
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local minp, maxp = vm:read_from_map(chunk_pos, chunk_pos)
|
||||
local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
|
||||
local data = vm:get_data()
|
||||
local param2_data = vm:get_param2_data()
|
||||
local light_fix = {}
|
||||
local meta_fix = {}
|
||||
-- for idx in a:iterp(vector.add(minp, 8), vector.subtract(maxp, 8)) do -- do not touch for beter light update
|
||||
for idx, origdata in pairs(data) do -- do not touch for beter light update
|
||||
local wpos = a:position(idx)
|
||||
local pos = self.plan:get_plan_pos(wpos)
|
||||
if wpos.x ~= self.pos.x or wpos.y ~= self.pos.y or wpos.z ~= self.pos.z then --skip chest pos
|
||||
local node = self.plan:prepare_node_for_build(pos, wpos)
|
||||
if node and node.content_id then
|
||||
-- write to voxel
|
||||
data[idx] = node.content_id
|
||||
param2_data[idx] = node.param2
|
||||
|
||||
-- mark for light update
|
||||
assert(node.node_def, dump(node))
|
||||
if node.node_def.light_source and node.node_def.light_source > 0 then
|
||||
table.insert(light_fix, {pos = wpos, node = node})
|
||||
end
|
||||
if node.meta then
|
||||
table.insert(meta_fix, {pos = wpos, node = node})
|
||||
end
|
||||
self.plan:remove_node(node)
|
||||
--TODO: metadata
|
||||
end
|
||||
end
|
||||
self.plan:remove_node(pos) --if exists
|
||||
end
|
||||
|
||||
-- store the changed map data
|
||||
vm:set_data(data)
|
||||
vm:set_param2_data(param2_data)
|
||||
vm:calc_lighting()
|
||||
vm:update_liquids()
|
||||
vm:write_to_map()
|
||||
vm:update_map()
|
||||
|
||||
-- fix the lights
|
||||
dprint("fix lights", #light_fix)
|
||||
for _, fix in ipairs(light_fix) do
|
||||
minetest.env:add_node(fix.pos, fix.node)
|
||||
end
|
||||
|
||||
dprint("process meta", #meta_fix)
|
||||
for _, fix in ipairs(meta_fix) do
|
||||
minetest.env:get_meta(fix.pos):from_table(fix.node.meta)
|
||||
end
|
||||
]]
|
||||
|
||||
-- implementation using usual "add_node"
|
||||
local chunk_nodes = self.plan:get_nodes_for_chunk(self.plan:get_plan_pos(chunk_pos))
|
||||
dprint("Instant build of chunk: nodes:", #chunk_nodes)
|
||||
for idx, nodeplan in ipairs(chunk_nodes) do
|
||||
if nodeplan.wpos.x ~= self.pos.x or nodeplan.wpos.y ~= self.pos.y or nodeplan.wpos.z ~= self.pos.z then --skip chest pos
|
||||
if nodeplan.node then
|
||||
minetest.env:add_node(nodeplan.wpos, nodeplan.node)
|
||||
if nodeplan.node.meta then
|
||||
minetest.env:get_meta(nodeplan.wpos):from_table(nodeplan.node.meta)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.plan:remove_node(nodeplan.pos)
|
||||
end
|
||||
dprint("---build chunk", minetest.pos_to_string(random_pos))
|
||||
|
||||
-- self.plan:do_add_chunk(random_pos)
|
||||
self.plan:do_add_chunk_voxel(random_pos)
|
||||
-- chunk done handle next chunk call
|
||||
dprint("instant nodes left:", self.plan.data.nodecount)
|
||||
self:update_info("build_status")
|
||||
if self.plan.data.nodecount > 0 then
|
||||
if self.plan:get_status() == "build" then
|
||||
self:update_info("build_status")
|
||||
--start next plan chain
|
||||
return true
|
||||
else
|
||||
-- finished. disable processing
|
||||
self.info.npc_build = false
|
||||
self.info.instantbuild = false
|
||||
self:update_info("build_status")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Check if the chest is ready to build something
|
||||
--------------------------------------
|
||||
function self.npc_build_allowed(self)
|
||||
if self.current_stage ~= "ready" then
|
||||
return false
|
||||
else
|
||||
return self.info.npc_build
|
||||
end
|
||||
end
|
||||
|
||||
function self.update_statistics(self)
|
||||
if self.current_stage == "ready" then --update building status in case of ready (or build in process after ready)
|
||||
self:update_info("build_status")
|
||||
|
@ -1,2 +1,2 @@
|
||||
default
|
||||
npcf
|
||||
schemlib_builder_npcf?
|
||||
|
49
files.lua
49
files.lua
@ -1,49 +0,0 @@
|
||||
local dprint = townchest.dprint_off --debug
|
||||
|
||||
files = {}
|
||||
|
||||
-----------------------------------------------
|
||||
-- get files
|
||||
-- no input parameters
|
||||
-- returns a table containing buildings
|
||||
-----------------------------------------------
|
||||
function files.get()
|
||||
local files = minetest.get_dir_list(townchest.modpath..'/buildings/', false) or {}
|
||||
local i, t = 0, {}
|
||||
for _,filename in ipairs(files) do
|
||||
if filename ~= "." and filename ~= ".." then
|
||||
i = i + 1
|
||||
t[i] = filename
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(t,function(a,b) return a<b end)
|
||||
return t
|
||||
end
|
||||
|
||||
-----------------------------------------------
|
||||
-- read file
|
||||
-- filename - the building file to load
|
||||
-- return - WE-Shema, containing the pos and nodes to build
|
||||
-----------------------------------------------
|
||||
function files.readfile(filename)
|
||||
local file = townchest.hsl.save_restore.file_access(townchest.modpath.."/buildings/"..filename, "r")
|
||||
if not file then
|
||||
dprint("[townchest] error: could not open file \"" .. filename .. "\"")
|
||||
return
|
||||
end
|
||||
|
||||
local building_info
|
||||
|
||||
-- different file types
|
||||
if string.find( filename, '.mts', -4 ) then
|
||||
return townchest.hsl.analyze_mts.analyze_mts_file(file)
|
||||
end
|
||||
if string.find( filename, '.we', -3 ) or string.find( filename, '.wem', -4 ) then
|
||||
return townchest.hsl.analyze_we.analyze_we_file(file)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------
|
||||
-- return the files methods to the caller
|
||||
return files
|
36
init.lua
36
init.lua
@ -4,38 +4,36 @@ townchest.modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
|
||||
|
||||
-- debug. Used for debug messages. In production the function should be empty
|
||||
local dprint = function(...)
|
||||
-- debug print. Comment out the next line if you don't need debug out
|
||||
print(unpack(arg))
|
||||
end
|
||||
local dprint_off = function(...)
|
||||
end
|
||||
local dprint = print
|
||||
local dprint_off = function()end
|
||||
townchest.dprint = dprint
|
||||
townchest.dprint_off = dprint_off
|
||||
|
||||
-- UI tools/ formspec
|
||||
townchest.smartfs = dofile(townchest.modpath.."/smartfs.lua")
|
||||
local smartfs = dofile(townchest.modpath.."/smartfs.lua")
|
||||
townchest.smartfs = smartfs
|
||||
dofile(townchest.modpath.."/smartfs-forms.lua")
|
||||
|
||||
local smartfs = townchest.smartfs
|
||||
|
||||
-- The Chest
|
||||
dofile(townchest.modpath.."/chest.lua")
|
||||
|
||||
-- Reading building files (WorldEdit)
|
||||
townchest.files = dofile(townchest.modpath.."/files.lua")
|
||||
townchest.hsl = dofile(townchest.modpath.."/libs/hsl/init.lua")
|
||||
|
||||
-- Nodes mapping
|
||||
dofile(townchest.modpath.."/mapping.lua")
|
||||
|
||||
-- building plan
|
||||
dofile(townchest.modpath.."/plan.lua")
|
||||
|
||||
-- NPC's
|
||||
dofile(townchest.modpath.."/npcf-worker.lua")
|
||||
|
||||
|
||||
-- Read the townchest building files
|
||||
function townchest.files_get()
|
||||
local files = minetest.get_dir_list(townchest.modpath..'/buildings/', false) or {}
|
||||
local i, t = 0, {}
|
||||
for _,filename in ipairs(files) do
|
||||
table.insert(t, filename)
|
||||
end
|
||||
table.sort(t,function(a,b) return a<b end)
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------
|
||||
-- __cheststep - triggered building step
|
||||
-----------------------------------------------
|
||||
|
@ -1,170 +0,0 @@
|
||||
local handle_schematics = {}
|
||||
|
||||
--[[ taken from src/mg_schematic.cpp:
|
||||
Minetest Schematic File Format
|
||||
|
||||
All values are stored in big-endian byte order.
|
||||
[u32] signature: 'MTSM'
|
||||
[u16] version: 3
|
||||
[u16] size X
|
||||
[u16] size Y
|
||||
[u16] size Z
|
||||
For each Y:
|
||||
[u8] slice probability value
|
||||
[Name-ID table] Name ID Mapping Table
|
||||
[u16] name-id count
|
||||
For each name-id mapping:
|
||||
[u16] name length
|
||||
[u8[] ] name
|
||||
ZLib deflated {
|
||||
For each node in schematic: (for z, y, x)
|
||||
[u16] content
|
||||
For each node in schematic:
|
||||
[u8] probability of occurance (param1)
|
||||
For each node in schematic:
|
||||
[u8] param2
|
||||
}
|
||||
|
||||
Version changes:
|
||||
1 - Initial version
|
||||
2 - Fixed messy never/always place; 0 probability is now never, 0xFF is always
|
||||
3 - Added y-slice probabilities; this allows for variable height structures
|
||||
--]]
|
||||
|
||||
--handle_schematics = {}
|
||||
|
||||
-- taken from https://github.com/MirceaKitsune/minetest_mods_structures/blob/master/structures_io.lua (Taokis Sructures I/O mod)
|
||||
-- gets the size of a structure file
|
||||
-- nodenames: contains all the node names that are used in the schematic
|
||||
-- on_constr: lists all the node names for which on_construct has to be called after placement of the schematic
|
||||
handle_schematics.analyze_mts_file = function(file)
|
||||
local size = { x = 0, y = 0, z = 0, version = 0 }
|
||||
local version = 0;
|
||||
|
||||
-- thanks to sfan5 for this advanced code that reads the size from schematic files
|
||||
local read_s16 = function(fi)
|
||||
return string.byte(fi:read(1)) * 256 + string.byte(fi:read(1))
|
||||
end
|
||||
|
||||
local function get_schematic_size(f)
|
||||
-- make sure those are the first 4 characters, otherwise this might be a corrupt file
|
||||
if f:read(4) ~= "MTSM" then
|
||||
return nil
|
||||
end
|
||||
-- advance 2 more characters
|
||||
local version = read_s16(f); --f:read(2)
|
||||
-- the next characters here are our size, read them
|
||||
return read_s16(f), read_s16(f), read_s16(f), version
|
||||
end
|
||||
|
||||
size.x, size.y, size.z, size.version = get_schematic_size(file)
|
||||
|
||||
-- read the slice probability for each y value that was introduced in version 3
|
||||
if( size.version >= 3 ) then
|
||||
-- the probability is not very intresting for buildings so we just skip it
|
||||
file:read( size.y )
|
||||
end
|
||||
|
||||
-- this list is not yet used for anything
|
||||
local nodenames = {}
|
||||
local ground_id = {}
|
||||
local is_air = 0
|
||||
|
||||
-- after that: read_s16 (2 bytes) to find out how many diffrent nodenames (node_name_count) are present in the file
|
||||
local node_name_count = read_s16( file )
|
||||
|
||||
for i = 1, node_name_count do
|
||||
-- the length of the next name
|
||||
local name_length = read_s16( file )
|
||||
-- the text of the next name
|
||||
local name_text = file:read( name_length )
|
||||
nodenames[i] = name_text
|
||||
if string.sub(name_text, 1, 18) == "default:dirt_with_" or
|
||||
name_text == "farming:soil_wet" then
|
||||
ground_id[i] = true
|
||||
elseif( name_text == 'air' ) then
|
||||
is_air = i;
|
||||
end
|
||||
end
|
||||
|
||||
-- decompression was recently added; if it is not yet present, we need to use normal place_schematic
|
||||
if( minetest.decompress == nil) then
|
||||
file.close(file);
|
||||
return nil; -- normal place_schematic is no longer supported as minetest.decompress is now part of the release version of minetest
|
||||
end
|
||||
|
||||
local compressed_data = file:read( "*all" );
|
||||
local data_string = minetest.decompress(compressed_data, "deflate" );
|
||||
file.close(file)
|
||||
|
||||
local p2offset = (size.x*size.y*size.z)*3;
|
||||
local i = 1;
|
||||
|
||||
local scm = {};
|
||||
local min_pos = {}
|
||||
local max_pos = {}
|
||||
local nodecount = 0
|
||||
local ground_y = -1 --if nothing defined, it is under the building
|
||||
local groundnode_count = 0
|
||||
|
||||
for z = 1, size.z do
|
||||
for y = 1, size.y do
|
||||
for x = 1, size.x do
|
||||
local id = string.byte( data_string, i ) * 256 + string.byte( data_string, i+1 );
|
||||
i = i + 2;
|
||||
local p2 = string.byte( data_string, p2offset + math.floor(i/2));
|
||||
id = id+1;
|
||||
if( id ~= is_air ) then
|
||||
-- use node
|
||||
if( not( scm[y] )) then
|
||||
scm[y] = {};
|
||||
end
|
||||
if( not( scm[y][x] )) then
|
||||
scm[y][x] = {};
|
||||
end
|
||||
scm[y][x][z] = {name_id = id, param2 = p2};
|
||||
nodecount = nodecount + 1
|
||||
|
||||
-- adjust position information
|
||||
if not max_pos.x or x > max_pos.x then
|
||||
max_pos.x = x
|
||||
end
|
||||
if not max_pos.y or y > max_pos.y then
|
||||
max_pos.y = y
|
||||
end
|
||||
if not max_pos.z or z > max_pos.z then
|
||||
max_pos.z = z
|
||||
end
|
||||
if not min_pos.x or x < min_pos.x then
|
||||
min_pos.x = x
|
||||
end
|
||||
if not min_pos.y or y < min_pos.y then
|
||||
min_pos.y = y
|
||||
end
|
||||
if not min_pos.z or z < min_pos.z then
|
||||
min_pos.z = z
|
||||
end
|
||||
|
||||
-- calculate ground_y value
|
||||
if ground_id[id] then
|
||||
groundnode_count = groundnode_count + 1
|
||||
if groundnode_count == 1 then
|
||||
ground_y = y
|
||||
else
|
||||
ground_y = ground_y + (y - ground_y) / groundnode_count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return { min_pos = min_pos, -- minimal {x,y,z} vector
|
||||
max_pos = max_pos, -- maximal {x,y,z} vector
|
||||
nodenames = nodenames, -- nodenames[1] = "default:sample"
|
||||
scm_data_cache = scm, -- scm[y][x][z] = { name_id, ent.param2 }
|
||||
nodecount = nodecount, -- integer, count
|
||||
ground_y = ground_y } -- average ground high
|
||||
end
|
||||
|
||||
return handle_schematics
|
@ -1,112 +0,0 @@
|
||||
local handle_schematics = {}
|
||||
|
||||
-- receive parameter modpath
|
||||
local modpath = ...
|
||||
|
||||
-- deserialize worldedit savefiles
|
||||
local worldedit_file = dofile(modpath.."worldedit_file.lua")
|
||||
|
||||
|
||||
handle_schematics.analyze_we_file = function(file)
|
||||
-- returning parameters
|
||||
local nodenames = {}
|
||||
local scm = {}
|
||||
local all_meta = {}
|
||||
local min_pos = {}
|
||||
local max_pos = {}
|
||||
local ground_y = -1 --if nothing defined, it is under the building
|
||||
local nodecount = 0
|
||||
|
||||
-- helper
|
||||
local nodes = worldedit_file.load_schematic(file:read("*a"))
|
||||
local nodenames_id = {}
|
||||
local ground_id = {}
|
||||
local groundnode_count = 0
|
||||
|
||||
-- analyze the file
|
||||
for i, ent in ipairs( nodes ) do
|
||||
-- get nodename_id and analyze ground elements
|
||||
local name_id = nodenames_id[ent.name]
|
||||
if not name_id then
|
||||
name_id = #nodenames + 1
|
||||
nodenames_id[ent.name] = name_id
|
||||
nodenames[name_id] = ent.name
|
||||
if string.sub(ent.name, 1, 18) == "default:dirt_with_" or
|
||||
ent.name == "farming:soil_wet" then
|
||||
ground_id[name_id] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- calculate ground_y value
|
||||
if ground_id[name_id] then
|
||||
groundnode_count = groundnode_count + 1
|
||||
if groundnode_count == 1 then
|
||||
ground_y = ent.y
|
||||
else
|
||||
ground_y = ground_y + (ent.y - ground_y) / groundnode_count
|
||||
end
|
||||
end
|
||||
|
||||
-- adjust position information
|
||||
if not max_pos.x or ent.x > max_pos.x then
|
||||
max_pos.x = ent.x
|
||||
end
|
||||
if not max_pos.y or ent.y > max_pos.y then
|
||||
max_pos.y = ent.y
|
||||
end
|
||||
if not max_pos.z or ent.z > max_pos.z then
|
||||
max_pos.z = ent.z
|
||||
end
|
||||
if not min_pos.x or ent.x < min_pos.x then
|
||||
min_pos.x = ent.x
|
||||
end
|
||||
if not min_pos.y or ent.y < min_pos.y then
|
||||
min_pos.y = ent.y
|
||||
end
|
||||
if not min_pos.z or ent.z < min_pos.z then
|
||||
min_pos.z = ent.z
|
||||
end
|
||||
|
||||
-- build to scm data tree
|
||||
if scm[ent.y] == nil then
|
||||
scm[ent.y] = {}
|
||||
end
|
||||
if scm[ent.y][ent.x] == nil then
|
||||
scm[ent.y][ent.x] = {}
|
||||
end
|
||||
if ent.param2 == nil then
|
||||
ent.param2 = 0
|
||||
end
|
||||
|
||||
-- metadata is only of intrest if it is not empty
|
||||
if( ent.meta and (ent.meta.fields or ent.meta.inventory)) then
|
||||
local has_meta = false
|
||||
for _,v in pairs( ent.meta.fields ) do
|
||||
has_meta = true
|
||||
break
|
||||
end
|
||||
for _,v in pairs(ent.meta.inventory) do
|
||||
has_meta = true
|
||||
break
|
||||
end
|
||||
if has_meta ~= true then
|
||||
ent.meta = nil
|
||||
end
|
||||
else
|
||||
ent.meta = nil
|
||||
end
|
||||
|
||||
scm[ent.y][ent.x][ent.z] = {name_id = name_id, param2 = ent.param2, meta = ent.meta}
|
||||
|
||||
nodecount = nodecount + 1
|
||||
end
|
||||
|
||||
return { min_pos = min_pos, -- minimal {x,y,z} vector
|
||||
max_pos = max_pos, -- maximal {x,y,z} vector
|
||||
nodenames = nodenames, -- nodenames[1] = "default:sample"
|
||||
scm_data_cache = scm, -- scm[y][x][z] = { name_id=, param2=, meta= }
|
||||
nodecount = nodecount, -- integer, count
|
||||
ground_y = ground_y } -- average ground high
|
||||
end
|
||||
|
||||
return handle_schematics
|
@ -1,164 +0,0 @@
|
||||
local handle_schematics = {}
|
||||
|
||||
handle_schematics.sort_pos_get_size = function( p1, p2 )
|
||||
local res = {x=p1.x, y=p1.y, z=p1.z,
|
||||
sizex = math.abs( p1.x - p2.x )+1,
|
||||
sizey = math.abs( p1.y - p2.y )+1,
|
||||
sizez = math.abs( p1.z - p2.z )+1};
|
||||
if( p2.x < p1.x ) then
|
||||
res.x = p2.x;
|
||||
end
|
||||
if( p2.y < p1.y ) then
|
||||
res.y = p2.y;
|
||||
end
|
||||
if( p2.z < p1.z ) then
|
||||
res.z = p2.z;
|
||||
end
|
||||
return res;
|
||||
end
|
||||
|
||||
|
||||
|
||||
local handle_schematics_get_meta_table = function( pos, all_meta, start_pos )
|
||||
local m = minetest.get_meta( pos ):to_table();
|
||||
local empty_meta = true;
|
||||
|
||||
-- the inventory part contains functions and cannot be fed to minetest.serialize directly
|
||||
local invlist = {};
|
||||
local count_inv = 0;
|
||||
local inv_is_empty = true;
|
||||
for name, list in pairs( m.inventory ) do
|
||||
invlist[ name ] = {};
|
||||
count_inv = count_inv + 1;
|
||||
for i, stack in ipairs(list) do
|
||||
if( not( stack:is_empty())) then
|
||||
invlist[ name ][ i ] = stack:to_string();
|
||||
empty_meta = false;
|
||||
inv_is_empty = false;
|
||||
end
|
||||
end
|
||||
end
|
||||
-- the fields part at least is unproblematic
|
||||
local count_fields = 0;
|
||||
if( empty_meta and m.fields ) then
|
||||
for k,v in pairs( m.fields ) do
|
||||
empty_meta = false;
|
||||
count_fields = count_fields + 1;
|
||||
end
|
||||
end
|
||||
|
||||
-- ignore default:sign_wall without text on it
|
||||
if( count_inv==0
|
||||
and count_fields<=3 and m.fields.formspec and m.fields.infotext
|
||||
and m.fields.formspec == "field[text;;${text}]"
|
||||
and m.fields.infotext == "\"\"") then
|
||||
-- also consider signs empty if their text has been set once and deleted afterwards
|
||||
if( not( m.fields.text ) or m.fields.text == "" ) then
|
||||
print('SKIPPING empty sign AT '..minetest.pos_to_string( pos)..' while saving metadata.');
|
||||
empty_meta = true;
|
||||
end
|
||||
|
||||
elseif( count_inv > 0 and inv_is_empty
|
||||
and count_fields>0 and m.fields.formspec ) then
|
||||
|
||||
local n = minetest.get_node( pos );
|
||||
if( n and n.name
|
||||
and (n.name=='default:chest' or n.name=='default:chest_locked' or n.name=='default:bookshelf'
|
||||
or n.name=='default:furnace' or n.name=='default:furnace_active'
|
||||
or n.name=='cottages:shelf' or n.name=='cottages:anvil' or n.name=='cottages:threshing_floor' )) then
|
||||
print('SKIPPING empty '..tostring(n.name)..' AT '..minetest.pos_to_string( pos )..' while saving metadata.');
|
||||
empty_meta = true;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- only save if there is something to be saved
|
||||
if( not( empty_meta )) then
|
||||
-- positions are stored as relative positions
|
||||
all_meta[ #all_meta+1 ] = {
|
||||
x=pos.x-start_pos.x,
|
||||
y=pos.y-start_pos.y,
|
||||
z=pos.z-start_pos.z,
|
||||
fields = m.fields,
|
||||
inventory = invlist};
|
||||
end
|
||||
end
|
||||
|
||||
-- reads metadata values from start_pos to end_pos and stores them in a file
|
||||
handle_schematics.save_meta = function( start_pos, end_pos, filename )
|
||||
local all_meta = {};
|
||||
local p = handle_schematics.sort_pos_get_size( start_pos, end_pos );
|
||||
|
||||
if( minetest.find_nodes_with_meta ) then
|
||||
for _,pos in ipairs( minetest.find_nodes_with_meta( start_pos, end_pos )) do
|
||||
handle_schematics_get_meta_table( pos, all_meta, p );
|
||||
end
|
||||
else
|
||||
for x=p.x, p.x+p.sizex do
|
||||
for y=p.y, p.y+p.sizey do
|
||||
for z=p.z, p.z+p.sizez do
|
||||
handle_schematics_get_meta_table( {x=x-p.x, y=y-p.y, z=z-p.z}, all_meta, p );
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if( #all_meta > 0 ) then
|
||||
save_restore.save_data( 'schems/'..filename..'.meta', all_meta );
|
||||
end
|
||||
end
|
||||
|
||||
-- all metadata values will be deleted when this function is called,
|
||||
-- making the area ready for new voxelmanip/schematic data
|
||||
handle_schematics.clear_meta = function( start_pos, end_pos )
|
||||
local empty_meta = { inventory = {}, fields = {} };
|
||||
|
||||
if( minetest.find_nodes_with_meta ) then
|
||||
for _,pos in ipairs( minetest.find_nodes_with_meta( start_pos, end_pos )) do
|
||||
local meta = minetest.get_meta( pos );
|
||||
meta:from_table( empty_meta );
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- restore metadata from file
|
||||
-- TODO: use relative instead of absolute positions (already done for .we files)
|
||||
-- TODO: handle mirror
|
||||
handle_schematics.restore_meta = function( filename, all_meta, start_pos, end_pos, rotate, mirror )
|
||||
|
||||
if( not( all_meta ) and filename ) then
|
||||
all_meta = save_restore.restore_data( filename..'.meta' );
|
||||
end
|
||||
for _,pos in ipairs( all_meta ) do
|
||||
local p = {};
|
||||
if( rotate == 0 ) then
|
||||
p = {x=start_pos.x+pos.x-1, y=start_pos.y+pos.y-1, z=start_pos.z+pos.z-1};
|
||||
elseif( rotate == 1 ) then
|
||||
p = {x=start_pos.x+pos.z-1, y=start_pos.y+pos.y-1, z=end_pos.z -pos.x+1};
|
||||
elseif( rotate == 2 ) then
|
||||
p = {x=end_pos.x -pos.x+1, y=start_pos.y+pos.y-1, z=end_pos.z -pos.z+1};
|
||||
elseif( rotate == 3 ) then
|
||||
p = {x=end_pos.x -pos.z+1, y=start_pos.y+pos.y-1, z=start_pos.z+pos.x-1};
|
||||
end
|
||||
local meta = minetest.get_meta( p );
|
||||
meta:from_table( {inventory = pos.inventory, fields = pos.fields });
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- return true on success; will overwrite existing files
|
||||
handle_schematics.create_schematic_with_meta = function( p1, p2, base_filename )
|
||||
|
||||
-- create directory for the schematics (same path as WorldEdit uses)
|
||||
save_restore.create_schems_directory();
|
||||
local complete_filename = minetest.get_worldpath()..'/schems/'..base_filename..'.mts';
|
||||
-- actually create the schematic
|
||||
minetest.create_schematic( p1, p2, nil, complete_filename, nil);
|
||||
-- save metadata; the file will only be created if there is any metadata that is to be saved
|
||||
handle_schematics.save_meta( p1, p2, base_filename );
|
||||
|
||||
return save_restore.file_exists( complete_filename );
|
||||
end
|
||||
|
||||
return handle_schematics
|
@ -1,109 +0,0 @@
|
||||
local handle_schematics = {}
|
||||
|
||||
-- helper function; sorts by the second element of the table
|
||||
local function handle_schematics_comp(a,b)
|
||||
if (a[2] > b[2]) then
|
||||
return true;
|
||||
end
|
||||
end
|
||||
|
||||
-- create a statistic about how frequent each node name occoured
|
||||
handle_schematics.count_nodes = function( data )
|
||||
local statistic = {};
|
||||
-- make sure all node names are counted (air may sometimes be included without occouring)
|
||||
for id=1, #data.nodenames do
|
||||
statistic[ id ] = { id, 0};
|
||||
end
|
||||
|
||||
for z = 1, data.size.z do
|
||||
for y = 1, data.size.y do
|
||||
for x = 1, data.size.x do
|
||||
|
||||
local a = data.scm_data_cache[y][x][z];
|
||||
if( a ) then
|
||||
local id = 0;
|
||||
if( type( a )=='table' ) then
|
||||
id = a[1];
|
||||
else
|
||||
id = a;
|
||||
end
|
||||
if( statistic[ id ] and statistic[ id ][ 2 ] ) then
|
||||
statistic[ id ] = { id, statistic[ id ][ 2 ]+1 };
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort( statistic, handle_schematics_comp );
|
||||
return statistic;
|
||||
end
|
||||
|
||||
|
||||
-- this function makes sure that the building will always extend to the right and in front of the build chest
|
||||
handle_schematics.translate_param2_to_rotation = function( param2, mirror, start_pos, orig_max, rotated, burried, orients, yoff )
|
||||
|
||||
-- mg_villages stores available rotations of buildings in orients={0,1,2,3] format
|
||||
if( orients and #orients and orients[1]~=0) then
|
||||
-- reset rotated - else we'd apply it twice
|
||||
rotated = 0;
|
||||
if( orients[1]==1 ) then
|
||||
rotated = rotated + 90;
|
||||
elseif( orients[1]==2 ) then
|
||||
rotated = rotated + 180;
|
||||
elseif( orients[1]==3 ) then
|
||||
rotated = rotated + 270;
|
||||
end
|
||||
if( rotated >= 360 ) then
|
||||
rotated = rotated % 360;
|
||||
end
|
||||
end
|
||||
|
||||
local max = {x=orig_max.x, y=orig_max.y, z=orig_max.z};
|
||||
-- if the schematic has been saved in a rotated way, swapping x and z may be necessary
|
||||
if( rotated==90 or rotated==270) then
|
||||
max.x = orig_max.z;
|
||||
max.z = orig_max.x;
|
||||
end
|
||||
|
||||
-- the building may have a cellar or something alike
|
||||
if( burried and burried ~= 0 and yoff == nil ) then
|
||||
start_pos.y = start_pos.y - burried;
|
||||
end
|
||||
|
||||
-- make sure the building always extends forward and to the right of the player
|
||||
local rotate = 0;
|
||||
if( param2 == 0 ) then rotate = 270; if( mirror==1 ) then start_pos.x = start_pos.x - max.x + max.z; end -- z gets larger
|
||||
elseif( param2 == 1 ) then rotate = 0; start_pos.z = start_pos.z - max.z; -- x gets larger
|
||||
elseif( param2 == 2 ) then rotate = 90; start_pos.z = start_pos.z - max.x;
|
||||
if( mirror==0 ) then start_pos.x = start_pos.x - max.z; -- z gets smaller
|
||||
else start_pos.x = start_pos.x - max.x; end
|
||||
elseif( param2 == 3 ) then rotate = 180; start_pos.x = start_pos.x - max.x; -- x gets smaller
|
||||
end
|
||||
|
||||
if( param2 == 1 or param2 == 0) then
|
||||
start_pos.z = start_pos.z + 1;
|
||||
elseif( param2 == 1 or param2 == 2 ) then
|
||||
start_pos.x = start_pos.x + 1;
|
||||
end
|
||||
if( param2 == 1 ) then
|
||||
start_pos.x = start_pos.x + 1;
|
||||
end
|
||||
|
||||
rotate = rotate + rotated;
|
||||
-- make sure the rotation does not reach or exceed 360 degree
|
||||
if( rotate >= 360 ) then
|
||||
rotate = rotate - 360;
|
||||
end
|
||||
-- rotate dimensions when needed
|
||||
if( param2==0 or param2==2) then
|
||||
local tmp = max.x;
|
||||
max.x = max.z;
|
||||
max.z = tmp;
|
||||
end
|
||||
|
||||
return { rotate=rotate, start_pos = {x=start_pos.x, y=start_pos.y, z=start_pos.z},
|
||||
end_pos = {x=(start_pos.x+max.x-1), y=(start_pos.y+max.y-1), z=(start_pos.z+max.z-1) },
|
||||
max = {x=max.x, y=max.y, z=max.z}};
|
||||
end
|
||||
|
||||
return handle_schematics
|
@ -1,28 +0,0 @@
|
||||
--[[ handle_schematics library
|
||||
extracted from https://github.com/Sokomine/handle_schematics
|
||||
see https://github.com/Sokomine/handle_schematics/issues/7
|
||||
]]
|
||||
|
||||
local hsl = {}
|
||||
|
||||
-- temporary path assignment till the hsl is own mod
|
||||
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
modpath = modpath..'/libs/hsl/'
|
||||
|
||||
-- reads and analyzes .mts files (minetest schematics)
|
||||
hsl.analyze_mts = dofile(modpath.."/analyze_mts_file.lua")
|
||||
|
||||
-- reads and analyzes worldedit files
|
||||
hsl.analyze_we = assert(loadfile(modpath.."/analyze_we_file.lua"))(modpath)
|
||||
|
||||
-- handles rotation and mirroring
|
||||
hsl.rotate = dofile(modpath.."/rotate.lua")
|
||||
|
||||
-- count nodes, take param2 into account for rotation etc.
|
||||
hsl.misc = dofile(modpath.."/handle_schematics_misc.lua")
|
||||
|
||||
-- store and restore metadata
|
||||
hsl.save_restore = dofile(modpath.."/save_restore.lua");
|
||||
hsl.meta = dofile(modpath.."/handle_schematics_meta.lua");
|
||||
|
||||
return hsl
|
@ -1,118 +0,0 @@
|
||||
local handle_schematics = {}
|
||||
|
||||
local rotate_facedir = function(facedir)
|
||||
return ({1, 2, 3, 0,
|
||||
13, 14, 15, 12,
|
||||
17, 18, 19, 16,
|
||||
9, 10, 11, 8,
|
||||
5, 6, 7, 4,
|
||||
21, 22, 23, 20})[facedir+1]
|
||||
end
|
||||
|
||||
|
||||
-- accessd through handle_schematics.mirror_facedir[ (rotation%2)+1 ][ facedir+1 ]
|
||||
handle_schematics.mirror_facedir =
|
||||
{{ 2, 1, 0, 3, -- 0, 1, 2, 3
|
||||
8, 9, 10, 11, -- 4, 5, 6, 7
|
||||
4, 5, 6, 7, -- 8, 9,10,11
|
||||
12, 13, 14, 15, --12,13,14,15
|
||||
16, 17, 18, 19, --16,17,18,19
|
||||
22, 21, 20, 23 --20,21,22,23
|
||||
},
|
||||
{ 0, 3, 2, 1, -- 0, 1, 2, 3
|
||||
4, 7, 6, 5, -- 4, 5, 6, 7
|
||||
8, 9, 10, 11, -- 8, 9,10,11
|
||||
16, 17, 18, 19, --12,13,14,15
|
||||
12, 15, 14, 13, --16,17,18,19
|
||||
20, 23, 22, 21 --20,21,22,23
|
||||
}};
|
||||
|
||||
local rotate_wallmounted = function(wallmounted)
|
||||
return ({0, 1, 5, 4, 2, 3})[wallmounted+1]
|
||||
end
|
||||
|
||||
handle_schematics.get_param2_rotated = function( paramtype2, p2 )
|
||||
local p2r = {};
|
||||
p2r[ 1 ] = p2;
|
||||
if( paramtype2 == 'wallmounted' ) then
|
||||
for i = 2,4 do
|
||||
p2r[ i ] = rotate_wallmounted( p2r[ i-1 ]);
|
||||
end
|
||||
elseif( paramtype2 == 'facedir' ) then
|
||||
for i = 2,4 do
|
||||
p2r[ i ] = rotate_facedir( p2r[ i-1 ]);
|
||||
end
|
||||
p2r[5]=1; -- indicate that it is wallmounted
|
||||
else
|
||||
return { p2, p2, p2, p2 };
|
||||
end
|
||||
return p2r;
|
||||
end
|
||||
|
||||
|
||||
handle_schematics.mirrored_node = {};
|
||||
|
||||
handle_schematics.add_mirrored_node_type = function( name, mirrored_name )
|
||||
handle_schematics.mirrored_node[ name ] = mirrored_name;
|
||||
local id = minetest.get_content_id( name );
|
||||
local id_mi = minetest.get_content_id( mirrored_name );
|
||||
local c_ignore = minetest.get_content_id( 'ignore' );
|
||||
if( id and id_mi and id ~= c_ignore and id_mi ~= c_ignore ) then
|
||||
handle_schematics.mirrored_node[ id ] = id_mi;
|
||||
end
|
||||
end
|
||||
|
||||
local door_materials = {'wood','steel','glass','obsidian_glass'};
|
||||
for _,material in ipairs( door_materials ) do
|
||||
handle_schematics.add_mirrored_node_type( 'doors:door_'..material..'_b_1', 'doors:door_'..material..'_b_2' );
|
||||
handle_schematics.add_mirrored_node_type( 'doors:door_'..material..'_t_1', 'doors:door_'..material..'_t_2' );
|
||||
handle_schematics.add_mirrored_node_type( 'doors:door_'..material..'_b_2', 'doors:door_'..material..'_b_1' );
|
||||
handle_schematics.add_mirrored_node_type( 'doors:door_'..material..'_t_2', 'doors:door_'..material..'_t_1' );
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
handle_schematics.rotation_table = {};
|
||||
handle_schematics.rotation_table[ 'facedir' ] = {};
|
||||
handle_schematics.rotation_table[ 'wallmounted' ] = {};
|
||||
|
||||
|
||||
for paramtype2,v in pairs( handle_schematics.rotation_table ) do
|
||||
for param2 = 0,23 do
|
||||
|
||||
if( param2 < 6 or paramtype2 == 'facedir' ) then
|
||||
local param2list = handle_schematics.get_param2_rotated( paramtype2, param2);
|
||||
|
||||
handle_schematics.rotation_table[ paramtype2 ][ param2+1 ] = {};
|
||||
|
||||
for rotation = 0,3 do
|
||||
local np2 = param2list[ rotation + 1];
|
||||
local mirror_x = np2;
|
||||
local mirror_z = np2;
|
||||
|
||||
-- mirror_x
|
||||
if( #param2list==5) then
|
||||
mirror_x = handle_schematics.mirror_facedir[ (( rotation +1)%2)+1 ][ np2+1 ];
|
||||
elseif( #param2list<5
|
||||
and (( rotation%2==1 and (np2==4 or np2==5))
|
||||
or ( rotation%2==0 and (np2==2 or np2==3)))) then
|
||||
mirror_x = param2list[ ( rotation + 2)%4 +1];
|
||||
end
|
||||
|
||||
-- mirror_z
|
||||
if( #param2list==5) then
|
||||
mirror_z = handle_schematics.mirror_facedir[ (rotation %2)+1 ][ np2+1 ];
|
||||
elseif( #param2list<5
|
||||
and (( rotation%2==0 and (np2==4 or np2==5))
|
||||
or ( rotation%2==1 and (np2==2 or np2==3)))) then
|
||||
mirror_z = param2list[ ( rotation + 2)%4 +1];
|
||||
end
|
||||
|
||||
handle_schematics.rotation_table[ paramtype2 ][ param2+1 ][ rotation+1 ] = { np2, mirror_x, mirror_z };
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return handle_schematics
|
@ -1,89 +0,0 @@
|
||||
|
||||
-- reserve the namespace
|
||||
local save_restore = {}
|
||||
|
||||
-- TODO: if this gets more versatile, add sanity checks for filename
|
||||
-- TODO: apart from allowing filenames, schems/<filename> also needs to be allowed
|
||||
|
||||
-- TODO: save and restore ought to be library functions and not implemented in each individual mod!
|
||||
save_restore.save_data = function( filename, data )
|
||||
|
||||
local path = minetest.get_worldpath()..'/'..filename;
|
||||
|
||||
local file = io.open( path, 'w' );
|
||||
if( file ) then
|
||||
file:write( minetest.serialize( data ));
|
||||
file:close();
|
||||
else
|
||||
print("[save_restore] Error: Savefile '"..tostring( path ).."' could not be written.");
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
save_restore.restore_data = function( filename )
|
||||
local file = io.open( filename, 'r' );
|
||||
if( file ) then
|
||||
local data = file:read("*all");
|
||||
file:close();
|
||||
return minetest.deserialize( data );
|
||||
else
|
||||
print("[save_restore] Error: Savefile '"..tostring( filename ).."' not found.");
|
||||
return {}; -- return empty table
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
save_restore.file_exists = function( filename )
|
||||
|
||||
local file = save_restore.file_access( filename, 'r' );
|
||||
if( file ) then
|
||||
file:close();
|
||||
return true;
|
||||
end
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
save_restore.create_schems_directory = function()
|
||||
local directory = minetest.get_worldpath()..'/schems';
|
||||
if( not( save_restore.file_exists( directory ))) then
|
||||
if( minetest.mkdir ) then
|
||||
minetest.mkdir( directory );
|
||||
else
|
||||
os.execute("mkdir \""..directory.. "\"");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- we only need the function io.open in a version that can read schematic files from diffrent places,
|
||||
-- even if a secure environment is enforced; this does require an exception for the mod
|
||||
local ie_io_open = io.open;
|
||||
if( minetest.request_insecure_environment ) then
|
||||
local ie, req_ie = _G, minetest.request_insecure_environment
|
||||
if req_ie then ie = req_ie() end
|
||||
if ie then
|
||||
ie_io_open = ie.io.open;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- only a certain type of files can be read and written
|
||||
save_restore.file_access = function( path, params )
|
||||
if( (params=='r' or params=='rb')
|
||||
and ( string.find( path, '.mts', -4 )
|
||||
or string.find( path, '.schematic', -11 )
|
||||
or string.find( path, '.we', -3 )
|
||||
or string.find( path, '.wem', -4 ) )) then
|
||||
return ie_io_open( path, params );
|
||||
elseif( (params=='w' or params=='wb')
|
||||
and ( string.find( path, '.mts', -4 )
|
||||
or string.find( path, '.schematic', -11 )
|
||||
or string.find( path, '.we', -3 )
|
||||
or string.find( path, '.wem', -4 ) )) then
|
||||
return ie_io_open( path, params );
|
||||
end
|
||||
end
|
||||
|
||||
return save_restore
|
@ -1,137 +0,0 @@
|
||||
------------------------------------------------------------------------------------------
|
||||
-- This is the file
|
||||
-- https://github.com/Uberi/Minetest-WorldEdit/blob/master/worldedit/serialization.lua
|
||||
-- Changes:
|
||||
-- * worldedit namespace renamed to worldeit_file
|
||||
-- * eliminiated functions that are not needed
|
||||
-- * made function load_schematic non-local
|
||||
-- * originx, originy and originz are now passed as parameters to worldedit_file.load_schematic;
|
||||
-- they are required for an old file format
|
||||
------------------------------------------------------------------------------------------
|
||||
|
||||
local worldedit_file = {} -- add the namespace
|
||||
|
||||
--- Schematic serialization and deserialiation.
|
||||
-- @module worldedit.serialization
|
||||
|
||||
worldedit_file.LATEST_SERIALIZATION_VERSION = 5
|
||||
local LATEST_SERIALIZATION_HEADER = worldedit_file.LATEST_SERIALIZATION_VERSION .. ":"
|
||||
|
||||
|
||||
--[[
|
||||
Serialization version history:
|
||||
1: Original format. Serialized Lua table with a weird linked format...
|
||||
2: Position and node seperated into sub-tables in fields `1` and `2`.
|
||||
3: List of nodes, one per line, with fields seperated by spaces.
|
||||
Format: <X> <Y> <Z> <Name> <Param1> <Param2>
|
||||
4: Serialized Lua table containing a list of nodes with `x`, `y`, `z`,
|
||||
`name`, `param1`, `param2`, and `meta` fields.
|
||||
5: Added header and made `param1`, `param2`, and `meta` fields optional.
|
||||
Header format: <Version>,<ExtraHeaderField1>,...:<Content>
|
||||
--]]
|
||||
|
||||
|
||||
--- Reads the header of serialized data.
|
||||
-- @param value Serialized WorldEdit data.
|
||||
-- @return The version as a positive natural number, or 0 for unknown versions.
|
||||
-- @return Extra header fields as a list of strings, or nil if not supported.
|
||||
-- @return Content (data after header).
|
||||
function worldedit_file.read_header(value)
|
||||
if value:find("^[0-9]+[%-:]") then
|
||||
local header_end = value:find(":", 1, true)
|
||||
local header = value:sub(1, header_end - 1):split(",")
|
||||
local version = tonumber(header[1])
|
||||
table.remove(header, 1)
|
||||
local content = value:sub(header_end + 1)
|
||||
return version, header, content
|
||||
end
|
||||
-- Old versions that didn't include a header with a version number
|
||||
if value:find("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)") and not value:find("%{") then -- List format
|
||||
return 3, nil, value
|
||||
elseif value:find("^[^\"']+%{%d+%}") then
|
||||
if value:find("%[\"meta\"%]") then -- Meta flat table format
|
||||
return 2, nil, value
|
||||
end
|
||||
return 1, nil, value -- Flat table format
|
||||
elseif value:find("%{") then -- Raw nested table format
|
||||
return 4, nil, value
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Loads the schematic in `value` into a node list in the latest format.
|
||||
-- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile)
|
||||
-- by ChillCode, available under the MIT license.
|
||||
-- @return A node list in the latest format, or nil on failure.
|
||||
function worldedit_file.load_schematic(value, we_origin)
|
||||
local version, header, content = worldedit_file.read_header(value)
|
||||
local nodes = {}
|
||||
if version == 1 or version == 2 then -- Original flat table format
|
||||
local tables = minetest.deserialize(content)
|
||||
if not tables then return nil end
|
||||
|
||||
-- Transform the node table into an array of nodes
|
||||
for i = 1, #tables do
|
||||
for j, v in pairs(tables[i]) do
|
||||
if type(v) == "table" then
|
||||
tables[i][j] = tables[v[1]]
|
||||
end
|
||||
end
|
||||
end
|
||||
nodes = tables[1]
|
||||
|
||||
if version == 1 then --original flat table format
|
||||
for i, entry in ipairs(nodes) do
|
||||
local pos = entry[1]
|
||||
entry.x, entry.y, entry.z = pos.x, pos.y, pos.z
|
||||
entry[1] = nil
|
||||
local node = entry[2]
|
||||
entry.name, entry.param1, entry.param2 = node.name, node.param1, node.param2
|
||||
entry[2] = nil
|
||||
end
|
||||
end
|
||||
elseif version == 3 then -- List format
|
||||
for x, y, z, name, param1, param2 in content:gmatch(
|
||||
"([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+" ..
|
||||
"([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do
|
||||
param1, param2 = tonumber(param1), tonumber(param2)
|
||||
table.insert(nodes, {
|
||||
x = tonumber(x),
|
||||
y = tonumber(y),
|
||||
z = tonumber(z),
|
||||
name = name,
|
||||
param1 = param1 ~= 0 and param1 or nil,
|
||||
param2 = param2 ~= 0 and param2 or nil,
|
||||
})
|
||||
end
|
||||
elseif version == 4 or version == 5 then -- Nested table format
|
||||
if not jit then
|
||||
-- This is broken for larger tables in the current version of LuaJIT
|
||||
nodes = minetest.deserialize(content)
|
||||
else
|
||||
-- XXX: This is a filthy hack that works surprisingly well - in LuaJIT, `minetest.deserialize` will fail due to the register limit
|
||||
nodes = {}
|
||||
content = content:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data
|
||||
local escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)
|
||||
local startpos, startpos1, endpos = 1, 1
|
||||
while true do -- go through each individual node entry (except the last)
|
||||
startpos, endpos = escaped:find("},%s*{", startpos)
|
||||
if not startpos then
|
||||
break
|
||||
end
|
||||
local current = content:sub(startpos1, startpos)
|
||||
local entry = minetest.deserialize("return " .. current)
|
||||
table.insert(nodes, entry)
|
||||
startpos, startpos1 = endpos, endpos
|
||||
end
|
||||
local entry = minetest.deserialize("return " .. content:sub(startpos1)) -- process the last entry
|
||||
table.insert(nodes, entry)
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
return worldedit_file
|
250
mapping.lua
250
mapping.lua
@ -1,250 +0,0 @@
|
||||
local dprint = townchest.dprint_off --debug
|
||||
--local dprint = townchest.dprint
|
||||
|
||||
|
||||
local mapping = {}
|
||||
townchest.mapping = mapping
|
||||
|
||||
-- visual for cost_item free for payment
|
||||
mapping.c_free_item = "default:cloud"
|
||||
|
||||
|
||||
-----------------------------------------------
|
||||
-- door compatibility. Seems the old doors was facedir and now the wallmounted values should be used
|
||||
-----------------------------------------------
|
||||
local function __param2_wallmounted_to_facedir(nodeinfo, pos, wpos)
|
||||
if nodeinfo.param2 == 0 then -- +y?
|
||||
nodeinfo.param2 = 0
|
||||
elseif nodeinfo.param2 == 1 then -- -y?
|
||||
nodeinfo.param2 = 1
|
||||
elseif nodeinfo.param2 == 2 then --unsure
|
||||
nodeinfo.param2 = 3
|
||||
elseif nodeinfo.param2 == 3 then --unsure
|
||||
nodeinfo.param2 = 1
|
||||
elseif nodeinfo.param2 == 4 then --unsure
|
||||
nodeinfo.param2 = 2
|
||||
elseif nodeinfo.param2 == 5 then --unsure
|
||||
nodeinfo.param2 = 0
|
||||
end
|
||||
end
|
||||
|
||||
local u = {}
|
||||
local unknown_nodes_data = u
|
||||
-- Fallback nodes replacement of unknown nodes
|
||||
-- Maybe it is beter to use aliases for unknown notes. But anyway
|
||||
u["xpanes:pane_glass_10"] = { name = "xpanes:pane_10" }
|
||||
u["xpanes:pane_glass_5"] = { name = "xpanes:pane_5" }
|
||||
u["beds:bed_top_blue"] = { name = "beds:bed_top" }
|
||||
u["beds:bed_bottom_blue"] = { name = "beds:bed_bottom" }
|
||||
|
||||
u["homedecor:table_lamp_max"] = { name = "homedecor:table_lamp_white_max" }
|
||||
u["homedecor:refrigerator"] = { name = "homedecor:refrigerator_steel" }
|
||||
|
||||
u["ethereal:green_dirt"] = { name = "default:dirt_with_grass" }
|
||||
|
||||
u["doors:door_wood_b_c"] = {name = "doors:door_wood_b", {["meta"] = {["fields"] = {["state"] = "0"}}}, custom_function = __param2_wallmounted_to_facedir } --closed
|
||||
u["doors:door_wood_b_o"] = {name = "doors:door_wood_b", {["meta"] = {["fields"] = {["state"] = "1"}}}, custom_function = __param2_wallmounted_to_facedir } --open
|
||||
u["doors:door_wood_b_1"] = {name = "doors:door_wood_b", {["meta"] = {["fields"] = {["state"] = "0"}}}} --closed
|
||||
u["doors:door_wood_b_2"] = {name = "doors:door_wood_b", {["meta"] = {["fields"] = {["state"] = "3"}}}} --closed / reversed ??
|
||||
u["doors:door_wood_a_c"] = {name = "doors:hidden" }
|
||||
u["doors:door_wood_a_o"] = {name = "doors:hidden" }
|
||||
u["doors:door_wood_t_1"] = {name = "doors:hidden" }
|
||||
u["doors:door_wood_t_2"] = {name = "doors:hidden" }
|
||||
|
||||
u["doors:door_glass_b_c"] = {name = "doors:door_glass_b", {["meta"] = {["fields"] = {["state"] = "0"}}}, custom_function = __param2_wallmounted_to_facedir } --closed
|
||||
u["doors:door_glass_b_o"] = {name = "doors:door_glass_b", {["meta"] = {["fields"] = {["state"] = "1"}}}, custom_function = __param2_wallmounted_to_facedir } --open
|
||||
u["doors:door_glass_b_1"] = {name = "doors:door_glass_b", {["meta"] = {["fields"] = {["state"] = "0"}}}} --closed
|
||||
u["doors:door_glass_b_2"] = {name = "doors:door_glass_b", {["meta"] = {["fields"] = {["state"] = "3"}}}} --closed / reversed ??
|
||||
u["doors:door_glass_a_c"] = {name = "doors:hidden" }
|
||||
u["doors:door_glass_a_o"] = {name = "doors:hidden" }
|
||||
u["doors:door_glass_t_1"] = {name = "doors:hidden" }
|
||||
u["doors:door_glass_t_2"] = {name = "doors:hidden" }
|
||||
|
||||
u["doors:door_steel_b_c"] = {name = "doors:door_steel_b", {["meta"] = {["fields"] = {["state"] = "0"}}}, custom_function = __param2_wallmounted_to_facedir } --closed
|
||||
u["doors:door_steel_b_o"] = {name = "doors:door_steel_b", {["meta"] = {["fields"] = {["state"] = "1"}}}, custom_function = __param2_wallmounted_to_facedir } --open
|
||||
u["doors:door_steel_b_1"] = {name = "doors:door_steel_b", {["meta"] = {["fields"] = {["state"] = "0"}}}} --closed
|
||||
u["doors:door_steel_b_2"] = {name = "doors:door_steel_b", {["meta"] = {["fields"] = {["state"] = "3"}}}} --closed / reversed ??
|
||||
u["doors:door_steel_a_c"] = {name = "doors:hidden" }
|
||||
u["doors:door_steel_a_o"] = {name = "doors:hidden" }
|
||||
u["doors:door_steel_t_1"] = {name = "doors:hidden" }
|
||||
u["doors:door_steel_t_2"] = {name = "doors:hidden" }
|
||||
|
||||
|
||||
local c = {}
|
||||
local default_replacements = c
|
||||
-- "name" and "cost_item" are optional.
|
||||
-- if name is missed it will not be changed
|
||||
-- if cost_item is missed it will be determinated as usual (from changed name)
|
||||
-- a crazy sample is: instead of cobble place goldblock, use wood as payment
|
||||
-- c["default:cobble"] = { name = "default:goldblock", cost_item = "default:wood" }
|
||||
|
||||
c["beds:bed_top"] = { cost_item = mapping.c_free_item } -- the bottom of the bed is payed, so buld the top for free
|
||||
|
||||
-- it is hard to get a source in survival, so we use buckets. Note, the bucket is lost after usage by NPC
|
||||
c["default:lava_source"] = { cost_item = "bucket:bucket_lava" }
|
||||
c["default:river_water_source"] = { cost_item = "bucket:bucket_river_water" }
|
||||
c["default:water_source"] = { cost_item = "bucket:bucket_water" }
|
||||
|
||||
-- does not sense to set flowing water because it flow away without the source (and will be generated trough source)
|
||||
c["default:water_flowing"] = { name = "" }
|
||||
c["default:lava_flowing"] = { name = "" }
|
||||
c["default:river_water_flowing"] = { name = "" }
|
||||
|
||||
-- pay different dirt types by the sane dirt
|
||||
c["default:dirt_with_dry_grass"] = { cost_item = "default:dirt" }
|
||||
c["default:dirt_with_grass"] = { cost_item = "default:dirt" }
|
||||
c["default:dirt_with_snow"] = { cost_item = "default:dirt" }
|
||||
|
||||
c["xpanes:pane_5"] = { name = "xpanes:pane_flat", param2 = 0 } --unsure
|
||||
c["xpanes:pane_10"] = { name = "xpanes:pane_flat", param2 = 1 } --unsure
|
||||
|
||||
-----------------------------------------------
|
||||
-- copy table of mapping entry
|
||||
-----------------------------------------------
|
||||
function mapping.merge_map_entry(entry1, entry2)
|
||||
if entry2 then
|
||||
return {name = entry1.name or entry2.name,
|
||||
node_def = entry1.node_def or entry2.node_def,
|
||||
content_id = entry1.content_id or entry2.content_id,
|
||||
param2 = entry1.param2 or entry2.param2,
|
||||
meta = entry1.meta or entry2.meta,
|
||||
custom_function = entry1.custom_function or entry2.custom_function,
|
||||
cost_item = entry1.cost_item or entry2.cost_item,
|
||||
}
|
||||
else
|
||||
return {name = entry1.name,
|
||||
content_id = entry1.content_id,
|
||||
node_def = entry1.node_def,
|
||||
param2 = entry1.param2,
|
||||
meta = entry1.meta,
|
||||
custom_function = entry1.custom_function,
|
||||
cost_item = entry1.cost_item}
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------
|
||||
-- is_equal_meta - compare meta information of 2 nodes
|
||||
-- name - Node name to check and map
|
||||
-- return - item name used as payment
|
||||
-----------------------------------------------
|
||||
function mapping.is_equal_meta(a,b)
|
||||
local typa = type(a)
|
||||
local typb = type(b)
|
||||
if typa ~= typb then
|
||||
return false
|
||||
end
|
||||
|
||||
if typa == "table" then
|
||||
if #a ~= #b then
|
||||
return false
|
||||
else
|
||||
for i,v in ipairs(a) do
|
||||
if not mapping.is_equal_meta(a[i],b[i]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
else
|
||||
if a == b then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------
|
||||
-- Fallback nodes replacement of unknown nodes
|
||||
-----------------------------------------------
|
||||
function mapping.map_unknown(name)
|
||||
local map = unknown_nodes_data[name]
|
||||
if not map or map.name == name then -- no fallback mapping. don't use the node
|
||||
dprint("mapping failed:", name, dump(map))
|
||||
print("unknown nodes in building", name)
|
||||
return nil
|
||||
end
|
||||
|
||||
dprint("mapped", name, "to", map.name)
|
||||
return mapping.merge_map_entry(map)
|
||||
end
|
||||
|
||||
-----------------------------------------------
|
||||
-- Take filters and actions on nodes before building
|
||||
-----------------------------------------------
|
||||
function mapping.map_name(name)
|
||||
-- get mapped registred node name for further mappings
|
||||
local node_chk = minetest.registered_nodes[name]
|
||||
|
||||
--do fallback mapping if not registred node
|
||||
if not node_chk then
|
||||
local fallback = mapping.map_unknown(name)
|
||||
if fallback then
|
||||
dprint("map fallback:", dump(fallback))
|
||||
local fbmapped = mapping.map_name(fallback.name)
|
||||
if fbmapped then
|
||||
return mapping.merge_map_entry(fbmapped, fallback) --merge fallback values into the mapped node
|
||||
end
|
||||
end
|
||||
dprint("unmapped node", name)
|
||||
return
|
||||
end
|
||||
|
||||
-- get default replacement
|
||||
local map = default_replacements[name]
|
||||
local mr -- mapped return table
|
||||
if not map then
|
||||
mr = {}
|
||||
mr.name = name
|
||||
mr.node_def = node_chk
|
||||
else
|
||||
mr = mapping.merge_map_entry(map)
|
||||
if mr.name == nil then
|
||||
mr.name = name
|
||||
end
|
||||
end
|
||||
|
||||
--disabled by mapping
|
||||
if mr.name == "" then
|
||||
return nil
|
||||
end
|
||||
|
||||
mr.node_def = minetest.registered_nodes[mr.name]
|
||||
|
||||
-- determine cost_item
|
||||
if not mr.cost_item then
|
||||
|
||||
--Check for price or if it is free
|
||||
local recipe = minetest.get_craft_recipe(mr.name)
|
||||
if (mr.node_def.groups.not_in_creative_inventory and --not in creative
|
||||
not (mr.node_def.groups.not_in_creative_inventory == 0) and
|
||||
(not recipe or not recipe.items)) --and not craftable
|
||||
|
||||
or (not mr.node_def.description or mr.node_def.description == "") then -- no description
|
||||
if mr.node_def.drop and mr.node_def.drop ~= "" then
|
||||
-- use possible drop as payment
|
||||
if type(mr.node_def.drop) == "table" then -- drop table
|
||||
mr.cost_item = mr.node_def.drop[1] -- use the first one
|
||||
else
|
||||
mr.cost_item = mr.node_def.drop
|
||||
end
|
||||
else --something not supported, but known
|
||||
mr.cost_item = mapping.c_free_item -- 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
|
||||
mr.cost_item = mr.name
|
||||
end
|
||||
end
|
||||
|
||||
mr.content_id = minetest.get_content_id(mr.name)
|
||||
dprint("map", name, "to", mr.name, mr.param2, mr.cost_item)
|
||||
return mr
|
||||
end
|
||||
|
||||
-----------------------------------------------
|
||||
-- create a "mappednodes" using the data from analyze_* files
|
||||
-----------------------------------------------
|
||||
function mapping.do_mapping(data)
|
||||
data.mappednodes = {}
|
||||
for node_id, name in ipairs(data.nodenames) do
|
||||
data.mappednodes[node_id] = mapping.map_name(name)
|
||||
end
|
||||
end
|
488
npcf-worker.lua
488
npcf-worker.lua
@ -5,469 +5,43 @@ local MAX_SPEED = 5
|
||||
local BUILD_DISTANCE = 3
|
||||
local HOME_RANGE = 10
|
||||
|
||||
|
||||
townchest.npc = {
|
||||
spawn_nearly = function(pos, owner)
|
||||
local npcid = tostring(math.random(10000))
|
||||
npcf.index[npcid] = owner --owner
|
||||
local ref = {
|
||||
id = npcid,
|
||||
pos = {x=(pos.x+math.random(0,4)-4),y=(pos.y + 0.5),z=(pos.z+math.random(0,4)-4)},
|
||||
yaw = math.random(math.pi),
|
||||
name = "townchest:npcf_builder",
|
||||
owner = owner,
|
||||
}
|
||||
local npc = npcf:add_npc(ref)
|
||||
npcf:save(ref.id)
|
||||
if npc then
|
||||
npc:update()
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
local function get_speed(distance)
|
||||
local speed = distance * 0.5
|
||||
if speed > MAX_SPEED then
|
||||
speed = MAX_SPEED
|
||||
end
|
||||
return speed
|
||||
end
|
||||
|
||||
|
||||
local select_chest = function(self)
|
||||
-- do nothing if the chest not ready
|
||||
if not self.metadata.chestpos
|
||||
or not townchest.chest.list[minetest.pos_to_string(self.metadata.chestpos)] --chest position not valid
|
||||
or not self.chest
|
||||
or not self.chest:npc_build_allowed() then --chest buid not ready
|
||||
|
||||
local npcpos = self.object:getpos()
|
||||
local selectedchest = nil
|
||||
for key, chest in pairs(townchest.chest.list) do
|
||||
if (not selectedchest or vector.distance(npcpos, chest.pos) < vector.distance(npcpos, selectedchest.pos)) and chest:npc_build_allowed() then
|
||||
selectedchest = chest
|
||||
end
|
||||
end
|
||||
if selectedchest then
|
||||
self.metadata.chestpos = selectedchest.pos
|
||||
self.chest = selectedchest
|
||||
dprint("Now I will build for chest",self.chest)
|
||||
|
||||
-- the chest is the new home of npc
|
||||
if vector.distance(self.origin.pos, selectedchest.pos) > HOME_RANGE then
|
||||
self.origin.pos = selectedchest.pos
|
||||
self.origin.yaw = npcf:get_face_direction(npcpos, selectedchest.pos)
|
||||
end
|
||||
|
||||
else --stay if no chest assigned
|
||||
self.metadata.chestpos = nil
|
||||
self.chest = nil
|
||||
self.chestpos = nil
|
||||
end
|
||||
townchest.npc = {}
|
||||
function townchest.npc.spawn_nearly(pos, owner)
|
||||
local npcid = tostring(math.random(10000))
|
||||
npcf.index[npcid] = owner --owner
|
||||
local ref = {
|
||||
id = npcid,
|
||||
pos = {x=(pos.x+math.random(0,4)-4),y=(pos.y + 0.5),z=(pos.z+math.random(0,4)-4)},
|
||||
yaw = math.random(math.pi),
|
||||
name = "schemlib_builder_npcf:builder",
|
||||
owner = owner,
|
||||
}
|
||||
local npc = npcf:add_npc(ref)
|
||||
npcf:save(ref.id)
|
||||
if npc then
|
||||
npc:update()
|
||||
end
|
||||
end
|
||||
|
||||
function townchest.npc.enable_build(plan)
|
||||
schemlib_builder_npcf.plan_manager:add(minetest.pos_to_string(plan.anchor_pos), plan)
|
||||
end
|
||||
|
||||
local get_if_buildable = function(self, realpos, node_prep)
|
||||
local pos = self.chest.plan:get_plan_pos(realpos)
|
||||
local node
|
||||
if node_prep then
|
||||
node = node_prep
|
||||
else
|
||||
node = self.chest.plan:prepare_node_for_build(pos, realpos)
|
||||
end
|
||||
function townchest.npc.disable_build(plan)
|
||||
schemlib_builder_npcf.plan_manager:set_finished(minetest.pos_to_string(plan.anchor_pos))
|
||||
end
|
||||
|
||||
if not node then
|
||||
-- remove something crufty
|
||||
self.chest.plan:remove_node(pos)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- skip the chest position
|
||||
if realpos.x == self.chest.pos.x and realpos.y == self.chest.pos.y and realpos.z == self.chest.pos.z then --skip chest pos
|
||||
self.chest.plan:remove_node(pos)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- get info about placed node to compare
|
||||
local orig_node = minetest.get_node(realpos)
|
||||
if orig_node.name == "ignore" then
|
||||
minetest.get_voxel_manip():read_from_map(realpos, realpos)
|
||||
orig_node = minetest.get_node(realpos)
|
||||
end
|
||||
|
||||
if not orig_node or orig_node.name == "ignore" then --not loaded chunk. can be forced by forceload_block before check if buildable
|
||||
dprint("ignore node at", minetest.pos_to_string(realpos))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- check if already built
|
||||
if orig_node.name == node.name or orig_node.name == minetest.registered_nodes[node.name].name then
|
||||
-- right node is at the place. there are no costs to touch them. Check if a touch needed
|
||||
if (node.param2 ~= orig_node.param2 and not (node.param2 == nil and orig_node.param2 == 0)) then
|
||||
--param2 adjustment
|
||||
-- node.matname = townchest.mapping.c_free_item -- adjust params for free
|
||||
return node
|
||||
elseif not node.meta then
|
||||
--same item without metadata. nothing to do
|
||||
self.chest.plan:remove_node(pos)
|
||||
return nil
|
||||
elseif townchest.mapping.is_equal_meta(minetest.get_meta(realpos):to_table(), node.meta) then
|
||||
--metadata adjustment
|
||||
self.chest.plan:remove_node(pos)
|
||||
return nil
|
||||
elseif node.matname == townchest.mapping.c_free_item then
|
||||
-- TODO: check if nearly nodes are already built
|
||||
return node
|
||||
else
|
||||
return node
|
||||
-- hook to trigger chest update each node placement
|
||||
function townchest.npc.plan_update_hook(plan, status)
|
||||
if plan.chest then
|
||||
plan.chest:update_info("build_status")
|
||||
if status == "finished" then
|
||||
dprint("----Finished event called in npc hook----")
|
||||
plan.chest.info.npc_build = false
|
||||
plan.chest.info.instantbuild = false
|
||||
townchest.npc.disable_build(plan)
|
||||
end
|
||||
else
|
||||
-- no right node at place
|
||||
return node
|
||||
plan.chest:update_statistics()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function prefer_target(npc, t1, t2)
|
||||
if not t1 then
|
||||
return t2
|
||||
end
|
||||
|
||||
local npcpos = npc.object:getpos()
|
||||
-- npc is 1.5 blocks over the work
|
||||
npcpos.y = npcpos.y - 1.5
|
||||
|
||||
-- variables for preference manipulation
|
||||
local t1_c = {x=t1.pos.x, y=t1.pos.y, z=t1.pos.z}
|
||||
local t2_c = {x=t2.pos.x, y=t2.pos.y, z=t2.pos.z}
|
||||
local prefer = 0
|
||||
|
||||
--prefer same items in building order
|
||||
if npc.lastnode then
|
||||
if npc.lastnode.name == t1.name then
|
||||
prefer = prefer + 2.5
|
||||
end
|
||||
if npc.lastnode.name == t2.name then
|
||||
prefer = prefer - 2.5
|
||||
end
|
||||
end
|
||||
|
||||
-- prefer the last target node
|
||||
if npc.targetnode then
|
||||
if t1.pos.x == npc.targetnode.pos.x and
|
||||
t1.pos.y == npc.targetnode.pos.y and
|
||||
t1.pos.z == npc.targetnode.pos.z then
|
||||
prefer = prefer + BUILD_DISTANCE
|
||||
end
|
||||
if t2.pos.x == npc.targetnode.pos.x and
|
||||
t2.pos.y == npc.targetnode.pos.y and
|
||||
t2.pos.z == npc.targetnode.pos.z then
|
||||
prefer = prefer - BUILD_DISTANCE
|
||||
end
|
||||
end
|
||||
|
||||
-- prefer air in general
|
||||
if t1.name == "air" then
|
||||
prefer = prefer + 2
|
||||
end
|
||||
if t2.name == "air" then
|
||||
prefer = prefer - 2
|
||||
end
|
||||
|
||||
-- prefer reachable in general
|
||||
if vector.distance(npcpos, t1.pos) <= BUILD_DISTANCE then
|
||||
prefer = prefer + 2
|
||||
end
|
||||
if vector.distance(npcpos, t2.pos) <= BUILD_DISTANCE then
|
||||
prefer = prefer - 2
|
||||
end
|
||||
|
||||
-- prefer lower node if not air
|
||||
if t1.name ~= "air" then
|
||||
t1_c.y = t1_c.y + 2
|
||||
elseif math.abs(npcpos.y - t1.pos.y) <= BUILD_DISTANCE then
|
||||
-- prefer higher node if air in reachable distance
|
||||
t1_c.y = t1_c.y - 4
|
||||
end
|
||||
|
||||
-- prefer lower node if not air
|
||||
if t2.name ~= "air" then
|
||||
t2_c.y = t2_c.y + 2
|
||||
elseif math.abs(npcpos.y - t1.pos.y) <= BUILD_DISTANCE then
|
||||
-- prefer higher node if air in reachable distance
|
||||
t2_c.y = t2_c.y - 4
|
||||
end
|
||||
|
||||
-- avoid build directly under or in the npc
|
||||
if math.abs(npcpos.x - t1.pos.x) < 0.5 and
|
||||
math.abs(npcpos.y - t1.pos.y) < 3 and
|
||||
math.abs(npcpos.z - t1.pos.z) < 0.5 then
|
||||
prefer = prefer-1.5
|
||||
end
|
||||
if math.abs(npcpos.x - t2.pos.x) < 0.5 and
|
||||
math.abs(npcpos.y - t1.pos.y) < 3 and
|
||||
math.abs(npcpos.z - t2.pos.z) < 0.5 then
|
||||
prefer = prefer+1.5
|
||||
end
|
||||
|
||||
-- compare
|
||||
if vector.distance(npcpos, t1_c) - prefer > vector.distance(npcpos, t2_c) then
|
||||
return t2
|
||||
else
|
||||
return t1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
local get_target = function(self)
|
||||
local npcpos = self.object:getpos()
|
||||
local plan = self.chest.plan
|
||||
npcpos.y = npcpos.y - 1.5 -- npc is 1.5 blocks over the work
|
||||
|
||||
local npcpos_round = vector.round(npcpos)
|
||||
local selectednode
|
||||
|
||||
-- first try: look for nearly buildable nodes
|
||||
dprint("search for nearly node")
|
||||
for x=npcpos_round.x-5, npcpos_round.x+5 do
|
||||
for y=npcpos_round.y-5, npcpos_round.y+5 do
|
||||
for z=npcpos_round.z-5, npcpos_round.z+5 do
|
||||
local node = get_if_buildable(self,{x=x,y=y,z=z})
|
||||
if node then
|
||||
node.pos = {x=x,y=y,z=z}
|
||||
selectednode = prefer_target(self, selectednode, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if selectednode then
|
||||
dprint("nearly found: NPC: "..minetest.pos_to_string(npcpos).." Block "..minetest.pos_to_string(selectednode.pos))
|
||||
end
|
||||
|
||||
if not selectednode then
|
||||
dprint("nearly nothing found")
|
||||
-- get the old target to compare
|
||||
if self.targetnode and self.targetnode.pos and
|
||||
(self.targetnode.node_id or self.targetnode.name) then
|
||||
selectednode = get_if_buildable(self, self.targetnode.pos, self.targetnode)
|
||||
end
|
||||
end
|
||||
|
||||
-- second try. Check the current chunk
|
||||
dprint("search for node in current chunk")
|
||||
|
||||
local chunk_nodes = plan:get_nodes_for_chunk(plan:get_plan_pos(npcpos_round))
|
||||
dprint("Chunk loaeded: nodes:", #chunk_nodes)
|
||||
|
||||
for idx, nodeplan in ipairs(chunk_nodes) do
|
||||
local node = get_if_buildable(self, nodeplan.wpos, nodeplan.node)
|
||||
if node then
|
||||
node.pos = nodeplan.wpos
|
||||
selectednode = prefer_target(self, selectednode, node)
|
||||
end
|
||||
end
|
||||
|
||||
if selectednode then
|
||||
dprint("found in current chunk: NPC: "..minetest.pos_to_string(npcpos).." Block "..minetest.pos_to_string(selectednode.pos))
|
||||
end
|
||||
|
||||
if not selectednode then
|
||||
dprint("get random node")
|
||||
|
||||
local random_pos = plan:get_random_node_pos()
|
||||
if random_pos then
|
||||
dprint("---check chunk", minetest.pos_to_string(random_pos))
|
||||
local wpos = plan:get_world_pos(random_pos)
|
||||
local node = get_if_buildable(self, wpos)
|
||||
if node then
|
||||
node.pos = wpos
|
||||
selectednode = prefer_target(self, selectednode, node)
|
||||
end
|
||||
|
||||
if selectednode then
|
||||
dprint("random node: Block "..minetest.pos_to_string(random_pos))
|
||||
else
|
||||
dprint("random node not buildable, check the whole chunk", minetest.pos_to_string(random_pos))
|
||||
local chunk_nodes = plan:get_nodes_for_chunk(random_pos)
|
||||
dprint("Chunk loaeded: nodes:", #chunk_nodes)
|
||||
|
||||
for idx, nodeplan in ipairs(chunk_nodes) do
|
||||
local node = get_if_buildable(self, nodeplan.wpos, nodeplan.node)
|
||||
if node then
|
||||
node.pos = nodeplan.wpos
|
||||
selectednode = prefer_target(self, selectednode, node)
|
||||
end
|
||||
end
|
||||
if selectednode then
|
||||
dprint("found in current chunk: Block "..minetest.pos_to_string(selectednode.pos))
|
||||
end
|
||||
end
|
||||
else
|
||||
dprint("something wrong with random_pos")
|
||||
end
|
||||
end
|
||||
|
||||
if selectednode then
|
||||
assert(selectednode.pos, "BUG: a position should exists")
|
||||
return selectednode
|
||||
else
|
||||
dprint("no next node found", plan.data.nodecount)
|
||||
if plan.data.nodecount == 0 then
|
||||
self.chest.info.npc_build = false
|
||||
end
|
||||
self.chest:update_statistics()
|
||||
end
|
||||
end
|
||||
|
||||
npcf:register_npc("townchest:npcf_builder" ,{
|
||||
description = "Townchest Builder NPC",
|
||||
textures = {"npcf_builder_skin.png"},
|
||||
stepheight = 1.1,
|
||||
inventory_image = "npcf_builder_inv.png",
|
||||
on_step = function(self)
|
||||
if self.timer > 1 then
|
||||
self.timer = 0
|
||||
select_chest(self)
|
||||
self.target_prev = self.targetnode
|
||||
if self.chest and self.chest.plan and self.chest.plan.data.nodecount > 0 then
|
||||
self.targetnode = get_target(self)
|
||||
self.dest_type = "build"
|
||||
else
|
||||
if self.dest_type ~= "home_reached" then
|
||||
self.targetnode = self.origin
|
||||
self.dest_type = "home"
|
||||
end
|
||||
end
|
||||
|
||||
-- simple check if target reached
|
||||
elseif self.targetnode then
|
||||
local pos = self.object:getpos()
|
||||
local target_distance = vector.distance(pos, self.targetnode.pos)
|
||||
if target_distance < 1 then
|
||||
local yaw = self.object:getyaw()
|
||||
local speed = 0
|
||||
self.object:setvelocity(npcf:get_walk_velocity(speed, self.object:getvelocity().y, yaw))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local pos = self.object:getpos()
|
||||
local yaw = self.object:getyaw()
|
||||
local state = NPCF_ANIM_STAND
|
||||
local speed = 0
|
||||
local acceleration = {x=0, y=-10, z=0}
|
||||
if self.targetnode then
|
||||
local target_distance = vector.distance(pos, self.targetnode.pos)
|
||||
local last_distance = 0
|
||||
if self.var.last_pos then
|
||||
last_distance = vector.distance(self.var.last_pos, self.targetnode.pos)
|
||||
end
|
||||
|
||||
yaw = npcf:get_face_direction(pos, self.targetnode.pos)
|
||||
-- target reached build
|
||||
if target_distance <= BUILD_DISTANCE and self.dest_type == "build" then
|
||||
dprint("target reached - build", self.targetnode.name, minetest.pos_to_string(self.targetnode.pos))
|
||||
-- do the build ---TODO: move outsite of this function
|
||||
local soundspec
|
||||
if minetest.registered_items[self.targetnode.name].sounds then
|
||||
soundspec = minetest.registered_items[self.targetnode.name].sounds.place
|
||||
elseif self.targetnode.name == "air" then --TODO: should be determinated on old node, if the material handling is implemented
|
||||
soundspec = default.node_sound_leaves_defaults({place = {name = "default_place_node", gain = 0.25}})
|
||||
end
|
||||
if soundspec then
|
||||
soundspec.pos = pos
|
||||
minetest.sound_play(soundspec.name, soundspec)
|
||||
end
|
||||
minetest.env:add_node(self.targetnode.pos, self.targetnode)
|
||||
if self.targetnode.meta then
|
||||
minetest.env:get_meta(self.targetnode.pos):from_table(self.targetnode.meta)
|
||||
end
|
||||
self.chest.plan:remove_node(self.targetnode)
|
||||
self.chest:update_statistics()
|
||||
|
||||
local cur_pos = {x=pos.x, y=pos.y - 0.5, z=pos.z}
|
||||
local cur_node = minetest.registered_items[minetest.get_node(cur_pos).name]
|
||||
if cur_node.walkable then
|
||||
pos = {x=pos.x, y=pos.y + 1.5, z=pos.z}
|
||||
self.object:setpos(pos)
|
||||
end
|
||||
|
||||
if target_distance > 2 then
|
||||
speed = 1
|
||||
state = NPCF_ANIM_WALK_MINE
|
||||
else
|
||||
speed = 0
|
||||
state = NPCF_ANIM_MINE
|
||||
end
|
||||
|
||||
self.timer = 0
|
||||
self.lastnode = self.targetnode
|
||||
self.laststep = "build"
|
||||
self.targetnode = nil
|
||||
self.path = nil
|
||||
-- home reached
|
||||
elseif target_distance < HOME_RANGE and self.dest_type == "home" then
|
||||
-- self.object:setpos(self.origin.pos)
|
||||
yaw = self.origin.yaw
|
||||
speed = 0
|
||||
self.dest_type = "home_reached"
|
||||
self.targetnode = nil
|
||||
self.path = nil
|
||||
else
|
||||
--target not reached -- route
|
||||
state = NPCF_ANIM_WALK
|
||||
-- Big jump / teleport upsite
|
||||
if (self.targetnode.pos.y -(pos.y-1.5)) > BUILD_DISTANCE and
|
||||
math.abs(self.targetnode.pos.x - pos.x) <= 0.5 and
|
||||
math.abs(self.targetnode.pos.z - pos.z) <= 0.5 then
|
||||
acceleration = {x=0, y=0, z=0}
|
||||
pos = {x=pos.x, y=self.targetnode.pos.y + 1.5, z=pos.z}
|
||||
self.object:setpos(pos)
|
||||
target_distance = 0 -- to skip the next part and set speed to 0
|
||||
state = NPCF_ANIM_STAND
|
||||
self.path = nil
|
||||
dprint("Big jump to"..minetest.pos_to_string(pos))
|
||||
end
|
||||
|
||||
if self.timer == 0 or not self.path then
|
||||
self.path = minetest.find_path(pos, self.targetnode.pos, 10, 1, 5, "A*")
|
||||
end
|
||||
|
||||
-- teleport in direction in case of stucking
|
||||
dprint("check for stuck:", last_distance, target_distance, self.laststep)
|
||||
if not self.path and --no stuck if path known
|
||||
(last_distance - 0.01) <= target_distance and -- stucking
|
||||
self.laststep == "walk" and -- second step stuck
|
||||
self.target_prev and
|
||||
( minetest.pos_to_string(self.target_prev.pos) == minetest.pos_to_string(self.targetnode.pos)) then -- destination unchanged
|
||||
local target_direcion = vector.direction(pos, self.targetnode.pos)
|
||||
pos = vector.add(pos, vector.multiply(target_direcion, 2))
|
||||
if pos.y < self.targetnode.pos.y then
|
||||
pos = {x=pos.x, y=self.targetnode.pos.y + 1.5, z=pos.z}
|
||||
end
|
||||
self.object:setpos(pos)
|
||||
self.laststep = "teleport"
|
||||
acceleration = {x=0, y=0, z=0}
|
||||
target_distance = 0 -- to skip the next part and set speed to 0
|
||||
state = NPCF_ANIM_STAND
|
||||
self.path = nil
|
||||
dprint("Teleport to"..minetest.pos_to_string(pos))
|
||||
end
|
||||
self.var.last_pos = pos
|
||||
speed = get_speed(target_distance)
|
||||
self.laststep = "walk"
|
||||
end
|
||||
end
|
||||
|
||||
if self.path then
|
||||
yaw = npcf:get_face_direction(pos, self.path[1])
|
||||
end
|
||||
self.object:setacceleration(acceleration)
|
||||
self.object:setvelocity(npcf:get_walk_velocity(speed, self.object:getvelocity().y, yaw))
|
||||
self.object:setyaw(yaw)
|
||||
npcf:set_animation(self, state)
|
||||
end,
|
||||
})
|
||||
|
||||
|
246
plan.lua
246
plan.lua
@ -1,246 +0,0 @@
|
||||
local dprint = townchest.dprint_off --debug
|
||||
--local dprint = townchest.dprint--debug
|
||||
|
||||
townchest.plan = {}
|
||||
|
||||
townchest.plan.new = function( chest )
|
||||
local self = {}
|
||||
self.chest = chest
|
||||
|
||||
-- helper: get scm entry for position
|
||||
function self.get_scm_node(self, pos)
|
||||
assert(pos.x, "pos without xyz")
|
||||
if self.data.scm_data_cache[pos.y] == nil then
|
||||
return nil
|
||||
end
|
||||
if self.data.scm_data_cache[pos.y][pos.x] == nil then
|
||||
return nil
|
||||
end
|
||||
if self.data.scm_data_cache[pos.y][pos.x][pos.z] == nil then
|
||||
return nil
|
||||
end
|
||||
return self.data.scm_data_cache[pos.y][pos.x][pos.z]
|
||||
end
|
||||
|
||||
-- "node" = {x=,y=,z=,name_id=,param2=}
|
||||
function self.add_node(self, node)
|
||||
-- insert new
|
||||
if self.data.scm_data_cache[node.y] == nil then
|
||||
self.data.scm_data_cache[node.y] = {}
|
||||
end
|
||||
if self.data.scm_data_cache[node.y][node.x] == nil then
|
||||
self.data.scm_data_cache[node.y][node.x] = {}
|
||||
end
|
||||
self.data.nodecount = self.data.nodecount + 1
|
||||
self.data.scm_data_cache[node.y][node.x][node.z] = node
|
||||
end
|
||||
|
||||
function self.remove_node(self, pos)
|
||||
-- cleanup raw data
|
||||
if self.data.scm_data_cache[pos.y] ~= nil then
|
||||
if self.data.scm_data_cache[pos.y][pos.x] ~= nil then
|
||||
if self.data.scm_data_cache[pos.y][pos.x][pos.z] ~= nil then
|
||||
self.data.nodecount = self.data.nodecount - 1
|
||||
self.data.scm_data_cache[pos.y][pos.x][pos.z] = nil
|
||||
end
|
||||
if next(self.data.scm_data_cache[pos.y][pos.x]) == nil then
|
||||
self.data.scm_data_cache[pos.y][pos.x] = nil
|
||||
end
|
||||
end
|
||||
if next(self.data.scm_data_cache[pos.y]) == nil then
|
||||
self.data.scm_data_cache[pos.y] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- remove cached mapping data
|
||||
if self.data.prepared_cache and self.data.prepared_cache[pos.y] ~= nil then
|
||||
if self.data.prepared_cache[pos.y][pos.x]then
|
||||
if self.data.prepared_cache[pos.y][pos.x][pos.z] ~= nil then
|
||||
self.data.prepared_cache[pos.y][pos.x][pos.z] = nil
|
||||
end
|
||||
if next(self.data.prepared_cache[pos.y][pos.x]) == nil then
|
||||
self.data.prepared_cache[pos.y][pos.x] = nil
|
||||
end
|
||||
end
|
||||
if next(self.data.prepared_cache[pos.y]) == nil then
|
||||
self.data.prepared_cache[pos.y] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function self.flood_with_air(self)
|
||||
self.data.ground_y = math.floor(self.data.ground_y)
|
||||
local add_max = 5
|
||||
local additional = 0
|
||||
|
||||
-- define nodename-ID for air
|
||||
local air_id = #self.data.nodenames + 1
|
||||
self.data.nodenames[ air_id ] = "air"
|
||||
|
||||
dprint("create flatting plan")
|
||||
for y = self.data.min_pos.y, self.data.max_pos.y + 5 do -- with additional 5 on top
|
||||
--calculate additional grounding
|
||||
if y > self.data.ground_y then --only over ground
|
||||
local high = y-self.data.ground_y
|
||||
additional = high + 1
|
||||
if additional > add_max then --set to max
|
||||
additional = add_max
|
||||
end
|
||||
end
|
||||
|
||||
dprint("flat level:", y)
|
||||
|
||||
for x = self.data.min_pos.x - additional, self.data.max_pos.x + additional do
|
||||
for z = self.data.min_pos.z - additional, self.data.max_pos.z + additional do
|
||||
local airnode = {x=x, y=y, z=z, name_id=air_id}
|
||||
if self:get_scm_node(airnode) == nil then
|
||||
self:add_node(airnode)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
dprint("flatting plan done")
|
||||
end
|
||||
|
||||
|
||||
function self.get_world_pos(self,pos)
|
||||
return { x=pos.x+self.chest.pos.x,
|
||||
y=pos.y+self.chest.pos.y - self.data.ground_y - 1,
|
||||
z=pos.z+self.chest.pos.z
|
||||
}
|
||||
end
|
||||
|
||||
-- revert get_world_pos
|
||||
function self.get_plan_pos(self,pos)
|
||||
return { x=pos.x-self.chest.pos.x,
|
||||
y=pos.y-self.chest.pos.y + self.data.ground_y + 1,
|
||||
z=pos.z-self.chest.pos.z
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
-- get nodes for selection which one should be build
|
||||
-- skip parameter is randomized
|
||||
function self.get_random_node_pos(self)
|
||||
dprint("get something from list")
|
||||
|
||||
-- get random existing y
|
||||
local keyset = {}
|
||||
for k in pairs(self.data.scm_data_cache) do table.insert(keyset, k) end
|
||||
if #keyset == 0 then --finished
|
||||
return nil
|
||||
end
|
||||
local y = keyset[math.random(#keyset)]
|
||||
|
||||
-- get random existing x
|
||||
keyset = {}
|
||||
for k in pairs(self.data.scm_data_cache[y]) do table.insert(keyset, k) end
|
||||
local x = keyset[math.random(#keyset)]
|
||||
|
||||
-- get random existing z
|
||||
keyset = {}
|
||||
for k in pairs(self.data.scm_data_cache[y][x]) do table.insert(keyset, k) end
|
||||
local z = keyset[math.random(#keyset)]
|
||||
|
||||
if z ~= nil then
|
||||
return {x=x,y=y,z=z}
|
||||
end
|
||||
end
|
||||
|
||||
-- to be able working with forceload chunks
|
||||
function self.get_nodes_for_chunk(self, node)
|
||||
|
||||
-- calculate the begin of the chunk
|
||||
--local BLOCKSIZE = core.MAP_BLOCKSIZE
|
||||
local BLOCKSIZE = 16
|
||||
local wpos = self:get_world_pos(node)
|
||||
local minp = {}
|
||||
minp.x = (math.floor(wpos.x/BLOCKSIZE))*BLOCKSIZE
|
||||
minp.y = (math.floor(wpos.y/BLOCKSIZE))*BLOCKSIZE
|
||||
minp.z = (math.floor(wpos.z/BLOCKSIZE))*BLOCKSIZE
|
||||
local maxp = vector.add(minp, 16)
|
||||
|
||||
dprint("nodes for chunk (real-pos)", minetest.pos_to_string(minp), minetest.pos_to_string(maxp))
|
||||
|
||||
local minv = self:get_plan_pos(minp)
|
||||
local maxv = self:get_plan_pos(maxp)
|
||||
dprint("nodes for chunk (plan-pos)", minetest.pos_to_string(minv), minetest.pos_to_string(maxv))
|
||||
|
||||
local ret = {}
|
||||
for y = minv.y, maxv.y do
|
||||
if self.data.scm_data_cache[y] ~= nil then
|
||||
for x = minv.x, maxv.x do
|
||||
if self.data.scm_data_cache[y][x] ~= nil then
|
||||
for z = minv.z, maxv.z do
|
||||
if self.data.scm_data_cache[y][x][z] ~= nil then
|
||||
local pos = {x=x,y=y,z=z}
|
||||
local wpos = self:get_world_pos(pos)
|
||||
table.insert(ret, {pos = pos, wpos = wpos, node=self:prepare_node_for_build(pos, wpos)})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
dprint("nodes in chunk to build", #ret)
|
||||
return ret
|
||||
end
|
||||
|
||||
-- prepare node for build
|
||||
function self.prepare_node_for_build(self, pos, wpos)
|
||||
-- first run, generate mapping data
|
||||
if self.data.mappednodes == nil then
|
||||
townchest.mapping.do_mapping(self.data)
|
||||
end
|
||||
|
||||
-- get from cache
|
||||
if self.data.prepared_cache ~= nil and
|
||||
self.data.prepared_cache[pos.y] ~= nil and
|
||||
self.data.prepared_cache[pos.y][pos.x] ~= nil and
|
||||
self.data.prepared_cache[pos.y][pos.x][pos.z] ~= nil then
|
||||
return self.data.prepared_cache[pos.y][pos.x][pos.z]
|
||||
end
|
||||
|
||||
-- get scm data
|
||||
local scm_node = self:get_scm_node(pos)
|
||||
if scm_node == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
--get mapping data
|
||||
local map = self.data.mappednodes[scm_node.name_id]
|
||||
if map == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local node = townchest.mapping.merge_map_entry(map, scm_node)
|
||||
|
||||
if node.custom_function ~= nil then
|
||||
node.custom_function(node, pos, wpos)
|
||||
end
|
||||
|
||||
-- maybe node name is changed in custom function. Update the content_id in this case
|
||||
node.content_id = minetest.get_content_id(node.name)
|
||||
node.node_def = minetest.registered_nodes[node.name]
|
||||
|
||||
-- store the mapped node info in cache
|
||||
if self.data.prepared_cache == nil then
|
||||
self.data.prepared_cache = {}
|
||||
end
|
||||
if self.data.prepared_cache[pos.y] == nil then
|
||||
self.data.prepared_cache[pos.y] = {}
|
||||
end
|
||||
if self.data.prepared_cache[pos.y][pos.x] == nil then
|
||||
self.data.prepared_cache[pos.y][pos.x] = {}
|
||||
end
|
||||
self.data.prepared_cache[pos.y][pos.x][pos.z] = node
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
--------------------
|
||||
--------------------
|
||||
return self -- the plan object
|
||||
end
|
@ -24,10 +24,10 @@ local _file_open_dialog = function(state)
|
||||
for name, def in pairs(self._tabs) do
|
||||
if name == tabname then
|
||||
def.button:setBackground("default_gold_block.png")
|
||||
def.view:setIsHidden(false)
|
||||
def.view:setVisible(true)
|
||||
else
|
||||
def.button:setBackground(nil)
|
||||
def.view:setIsHidden(true)
|
||||
def.view:setVisible(false)
|
||||
end
|
||||
end
|
||||
self.active_name = tabname
|
||||
@ -49,12 +49,12 @@ local _file_open_dialog = function(state)
|
||||
tab1.button:onClick(function(self)
|
||||
tab_controller:set_active("tab1")
|
||||
end)
|
||||
tab1.view = state:view(0,1,"tab1_view")
|
||||
tab1.viewstate = tab1.view:getViewState()
|
||||
tab1.view = state:container(0,1,"tab1_view")
|
||||
tab1.viewstate = tab1.view:getContainerState()
|
||||
-- file selection tab view state
|
||||
tab1.viewstate:label(0,0,"header","Please select a building")
|
||||
local listbox = tab1.viewstate:listbox(0,0.5,6,5.5,"fileslist")
|
||||
for idx, file in ipairs(townchest.files.get()) do
|
||||
for idx, file in ipairs(townchest.files_get()) do
|
||||
listbox:addItem(file)
|
||||
end
|
||||
tab_controller:tab_add("tab1", tab1)
|
||||
@ -68,8 +68,8 @@ local _file_open_dialog = function(state)
|
||||
tab2.button:onClick(function(self)
|
||||
tab_controller:set_active("tab2")
|
||||
end)
|
||||
tab2.view = state:view(0,1,"tab2_view")
|
||||
tab2.viewstate = tab2.view:getViewState()
|
||||
tab2.view = state:container(0,1,"tab2_view")
|
||||
tab2.viewstate = tab2.view:getContainerState()
|
||||
-- Tasks tab view state
|
||||
tab2.viewstate:label(0,0.2,"header","Build simple form")
|
||||
local variant = tab2.viewstate:dropdown(3.5,0.2,4,0.5,"variant", 1)
|
||||
@ -174,21 +174,25 @@ local _build_status = function(state)
|
||||
inst_tg:onToggle(function(self, state, player)
|
||||
if self:getId() == 2 then
|
||||
chest.info.instantbuild = true
|
||||
chest.plan:set_status("build")
|
||||
chest:run_async(chest.instant_build_chunk)
|
||||
else
|
||||
chest.info.instantbuild = false
|
||||
end
|
||||
set_dynamic_values()
|
||||
chest:persist_info()
|
||||
chest:run_async(chest.instant_build_chunk)
|
||||
end)
|
||||
|
||||
-- NPC build button
|
||||
local npc_tg = state:toggle(5,3,3,0.5,"npc_tg",{ "Start NPC build", "Stop NPC build"})
|
||||
npc_tg:onToggle(function(self, state, player)
|
||||
if self:getId() == 2 then
|
||||
chest.info.npc_build = true --is used by NPC
|
||||
chest.info.npc_build = true
|
||||
chest.plan:set_status("build")
|
||||
townchest.npc.enable_build(chest.plan)
|
||||
else
|
||||
chest.info.npc_build = false
|
||||
townchest.npc.disable_build(chest.plan)
|
||||
end
|
||||
set_dynamic_values()
|
||||
chest:persist_info()
|
||||
@ -233,9 +237,9 @@ local _build_status = function(state)
|
||||
end
|
||||
|
||||
if chest.info.npc_build == true or chest.info.instantbuild == true then
|
||||
reload_bt:setIsHidden(true)
|
||||
reload_bt:setVisible(false)
|
||||
else
|
||||
reload_bt:setIsHidden(false)
|
||||
reload_bt:setVisible(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
997
smartfs.lua
997
smartfs.lua
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user