Port to schemlib / and schemlib_builder_npcf

In last test the builder lost some nodes :-(
master
Alexander Weber 2017-07-15 21:31:59 +02:00
parent 721d03301f
commit 07fc95fe30
18 changed files with 674 additions and 2496 deletions

View File

@ -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
View File

@ -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")

View File

@ -1,2 +1,2 @@
default
npcf
schemlib_builder_npcf?

View File

@ -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

View File

@ -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
-----------------------------------------------

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff