Get all already implemented working with hsl
additional futures: support for mts files more intelligent npc
This commit is contained in:
parent
cb34bad5e3
commit
5aeb2388b0
34
README.md
34
README.md
@ -6,16 +6,26 @@ A minetest mod contains a chest with building definitions. The chest can spawn a
|
||||
|
||||
## Features
|
||||
|
||||
- WorldEdit file target
|
||||
- A Chest that allows you to choose a building and manage the building options
|
||||
- The most WorldEdit files are supported. just put the file to the buildings directory
|
||||
- Building target from file
|
||||
- A Chest that allow you to choose a building and manage the building options
|
||||
- The most ".we", ".wem" or ".mts" files are supported. just put the file to the buildings directory
|
||||
- Flatting and cleanup the building place, remove all cruft-nodes from building inside
|
||||
- Cleanup unknown nodes
|
||||
- Ground level detection trough dirt_with_* nodes
|
||||
- Optimized for realy big buildings. Try the AMMOnym_coloseum.we as a showcase
|
||||
|
||||
- Creative build
|
||||
- Instant build allow you to get the building instantly
|
||||
- Building target configured
|
||||
- Some simple tasks implemented:
|
||||
- Fill with air
|
||||
- Fill with stone
|
||||
- Build a box
|
||||
- Build a plate
|
||||
|
||||
- Survival mode
|
||||
- TODO
|
||||
|
||||
- Creative mode
|
||||
- Instant build allow you to get the result instantly
|
||||
- Creative build by NPC without providing needed nodes
|
||||
|
||||
- Builder-NPC's
|
||||
@ -32,14 +42,21 @@ A minetest mod contains a chest with building definitions. The chest can spawn a
|
||||
- a way to change the needed nodes (like in building defined default:wood but I like to use something from moretrees mod)
|
||||
- the mapping should be able to map the unknown nodes
|
||||
|
||||
- Support mts files
|
||||
|
||||
## Vision / Ideas / maybe
|
||||
- other chests that generates a plan. The generated plan can be the daily work of a lumberjack as example
|
||||
- Architect-NPC instead of the chest to coordinate the build
|
||||
- Interface (API) to allow other mods create own "Plan coordinator"'s
|
||||
|
||||
## Credits
|
||||
- cornernote - made the towntest mod that was used as template for townchest
|
||||
- PilzAdam - made the initial NPC entity movement code (towntest)
|
||||
|
||||
# Code
|
||||
- cornernote - towntest mod was used as template for townchest
|
||||
- rubenwardy - take my smartfs enhancements upstream ready
|
||||
npcf_ng builder ousted the original towntest builder
|
||||
- Sokomine - handle_schematics is the base of hsl (=handle_schematics library)
|
||||
|
||||
# Buildings
|
||||
- VanessaE - contributed buildings (towntest)
|
||||
- kddekadenz - contributed buildings (towntest)
|
||||
- ACDC - contributed buildings (towntest)
|
||||
@ -47,6 +64,7 @@ A minetest mod contains a chest with building definitions. The chest can spawn a
|
||||
- irksomeduck - contributed buildings (towntest)
|
||||
- AMMOnym_coloseum.we https://forum.minetest.net/viewtopic.php?p=121294#p121294
|
||||
- PEAK_BremerHaus.we https://forum.minetest.net/viewtopic.php?p=207103#p207103
|
||||
|
||||
- All contributions welcome!
|
||||
|
||||
|
||||
|
234
chest.lua
234
chest.lua
@ -1,8 +1,7 @@
|
||||
local dprint = townchest.dprint --debug
|
||||
local dprint = townchest.dprint_off --debug
|
||||
local smartfs = townchest.smartfs
|
||||
|
||||
local preparing_plan_chunk = 10000
|
||||
|
||||
local ASYNC_WAIT=0.2 -- schould be > 0 to restrict performance consumption
|
||||
--------------------------------------
|
||||
-- class attributes and methods
|
||||
--------------------------------------
|
||||
@ -64,7 +63,7 @@ townchest.chest.new = function()
|
||||
if formname == "file_open" then
|
||||
infotext = "please select a building"
|
||||
elseif formname == "build_status" then
|
||||
infotext = "Nodes in plan: "..self.plan.building_size
|
||||
infotext = "Nodes in plan: "..self.plan.data.nodecount
|
||||
else
|
||||
infotext = self.infotext or ""
|
||||
end
|
||||
@ -94,8 +93,8 @@ 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)
|
||||
local we = {}
|
||||
|
||||
if taskname then
|
||||
self.info.taskname = taskname
|
||||
@ -103,8 +102,17 @@ townchest.chest.new = function()
|
||||
|
||||
if self.info.taskname == "file" then
|
||||
-- check if file could be read
|
||||
we = townchest.files.readfile(self.info.filename)
|
||||
if not we or #we == 0 then
|
||||
if not self.info.filename then
|
||||
-- something wrong, back to file selection
|
||||
minetest.after(0, self.set_form, self, "file_open")
|
||||
self.current_stage = "select"
|
||||
self:persist_info()
|
||||
return
|
||||
end
|
||||
|
||||
self.plan.data = townchest.files.readfile(self.info.filename)
|
||||
|
||||
if not self.plan.data then
|
||||
self.infotext = "No building found in ".. self.info.filename
|
||||
self:set_form("status")
|
||||
self.current_stage = "select"
|
||||
@ -115,58 +123,64 @@ 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 = {}
|
||||
|
||||
if self.info.genblock.variant == 1 then
|
||||
-- Fill with air
|
||||
for x = 0, self.info.genblock.x-1 do
|
||||
for y = 0, self.info.genblock.y-1 do
|
||||
for z = 0, self.info.genblock.z-1 do
|
||||
table.insert(we, {x=x,y=y,z=z, name = "air"})
|
||||
end
|
||||
end
|
||||
end
|
||||
-- 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 = 0, self.info.genblock.x-1 do
|
||||
for y = 0, self.info.genblock.y-1 do
|
||||
for z = 0, self.info.genblock.z-1 do
|
||||
table.insert(we, {x=x,y=y,z=z, name = "default:cobble"})
|
||||
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})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif self.info.genblock.variant == 3 then
|
||||
-- Build a box
|
||||
for x = 0, self.info.genblock.x-1 do
|
||||
for y = 0, self.info.genblock.y-1 do
|
||||
for z = 0, self.info.genblock.z-1 do
|
||||
if x == 0 or x == self.info.genblock.x-1 or
|
||||
y == 0 or y == self.info.genblock.y-1 or
|
||||
z == 0 or z == self.info.genblock.z-1 then
|
||||
table.insert(we, {x=x,y=y,z=z, name = "default:cobble"})
|
||||
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})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- build ground level under chest
|
||||
self.plan.relative.ground_y = 1
|
||||
self.plan.data.ground_y = 1
|
||||
|
||||
-- Build a plate
|
||||
elseif self.info.genblock.variant == 4 then
|
||||
local y = 0
|
||||
for x = 0, self.info.genblock.x-1 do
|
||||
for z = 0, self.info.genblock.z-1 do
|
||||
table.insert(we, {x=x,y=y,z=z, name = "default:cobble"})
|
||||
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})
|
||||
end
|
||||
end
|
||||
-- build ground level under chest
|
||||
self.plan.relative.ground_y = 1
|
||||
self.plan.data.ground_y = 1
|
||||
end
|
||||
end
|
||||
|
||||
self.rawdata = we
|
||||
|
||||
self:run_async(self.prepare_building_plan_chain)
|
||||
-- TODO: go to customizing screen
|
||||
self.infotext = "Build preparation"
|
||||
self:set_form("status")
|
||||
self:run_async(self.prepare_building_plan)
|
||||
end
|
||||
|
||||
|
||||
@ -187,94 +201,122 @@ townchest.chest.new = function()
|
||||
end
|
||||
|
||||
self:persist_info()
|
||||
minetest.after(0.2, async_call, self.pos)
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Async task: create building plan from rawdata
|
||||
--------------------------------------
|
||||
function self.prepare_building_plan_chain(self)
|
||||
local chunksize, lastchunk
|
||||
-- go trough all file entries
|
||||
if #self.rawdata > preparing_plan_chunk then
|
||||
chunksize = preparing_plan_chunk
|
||||
lastchunk = true
|
||||
else
|
||||
chunksize = #self.rawdata
|
||||
end
|
||||
|
||||
for i=#self.rawdata, #self.rawdata-chunksize+1, -1 do
|
||||
-- map to the internal node format
|
||||
local wenode = self.rawdata[i]
|
||||
if wenode and wenode.x and wenode.y and wenode.z and wenode.name then
|
||||
self.plan:adjust_flatting_requrement(wenode)
|
||||
local node = townchest.nodes.new(self.rawdata[i]):map() --mapped
|
||||
if node and node.x and node.y and node.z then
|
||||
self.plan:add_node(node)
|
||||
end
|
||||
end
|
||||
self.rawdata[i] = nil
|
||||
end
|
||||
|
||||
if lastchunk then
|
||||
dprint("next processing chunk")
|
||||
self.infotext = "Preparing, nodes left: "..#self.rawdata
|
||||
self:set_form("status")
|
||||
return true --repeat async call
|
||||
else
|
||||
dprint("reading of building done. Save them to the chest metadata")
|
||||
self.infotext = "Reading done, preparing"
|
||||
self:set_form("status")
|
||||
self:run_async(self.prepare_building_plan_chain_postprocess)
|
||||
return false
|
||||
end
|
||||
minetest.after(ASYNC_WAIT, async_call, self.pos)
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Async task: Post-processing of plan preparation
|
||||
--------------------------------------
|
||||
function self.prepare_building_plan_chain_postprocess(self)
|
||||
self.plan:prepare()
|
||||
function self.prepare_building_plan(self)
|
||||
self.plan:flood_with_air()
|
||||
|
||||
-- self.plan:do_mapping() -- on demand called
|
||||
self.current_stage = "ready"
|
||||
self:set_form("build_status")
|
||||
self:persist_info()
|
||||
self:run_async(self.instant_build_chain) --just trigger, there is a check if active
|
||||
self:run_async(self.instant_build_chunk) --just trigger, there is a check if active
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Async Task: Do a instant build step
|
||||
--------------------------------------
|
||||
function self.instant_build_chain(self)
|
||||
function self.instant_build_chunk(self)
|
||||
if not self.info.instantbuild == true then --instantbuild disabled
|
||||
return
|
||||
end
|
||||
dprint("Instant build is running")
|
||||
dprint("--- Instant build is running")
|
||||
|
||||
local startingnode = self.plan:get_nodes(1)
|
||||
-- go trough all file entries
|
||||
if startingnode[1] then -- the one node given
|
||||
dprint("start building chunk for", minetest.pos_to_string(startingnode[1]))
|
||||
minetest.forceload_block(self.plan:get_world_pos(startingnode[1]))
|
||||
for idx, node in ipairs(self.plan:get_nodes_from_chunk(startingnode[1])) do
|
||||
local wpos = self.plan:get_world_pos(node)
|
||||
if wpos.x ~= self.pos.x or wpos.y ~= self.pos.y or wpos.z ~= self.pos.z then --skip chest pos
|
||||
--- Place node
|
||||
local startingpos = self.plan:get_random_node_pos()
|
||||
if not startingpos 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
|
||||
local wpos = self.plan:get_world_pos(nodeplan)
|
||||
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(nodeplan, wpos)
|
||||
if node then
|
||||
minetest.env:add_node(wpos, node)
|
||||
if node.meta then
|
||||
minetest.env:get_meta(wpos):from_table(node.meta)
|
||||
end
|
||||
end
|
||||
self.plan:set_node_processed(node)
|
||||
end
|
||||
minetest.forceload_free_block(self.plan:get_world_pos(startingnode[1]))
|
||||
self.plan:remove_node(nodeplan)
|
||||
end
|
||||
|
||||
-- chunk done handle next chunk call
|
||||
dprint("instant nodes left:", self.plan.data.nodecount)
|
||||
self:update_info("build_status")
|
||||
if self.plan.building_size > 0 then
|
||||
if self.plan.data.nodecount > 0 then
|
||||
--start next plan chain
|
||||
return true
|
||||
else
|
||||
-- finished. disable processing
|
||||
self.instantbuild = false
|
||||
self.info.instantbuild = false
|
||||
return false
|
||||
end
|
||||
end
|
||||
@ -310,8 +352,10 @@ townchest.chest.new = function()
|
||||
dprint("restoral not necessary, current stage is", self.current_stage)
|
||||
return
|
||||
end
|
||||
|
||||
if chestinfo.taskname then -- file selected but no plan. Restore the plan
|
||||
self.current_stage = "restore"
|
||||
self:persist_info()
|
||||
self:set_rawdata(chestinfo.taskname)
|
||||
else
|
||||
self:set_form("file_open")
|
||||
|
36
files.lua
36
files.lua
@ -1,15 +1,13 @@
|
||||
local dprint = townchest.dprint_off --debug
|
||||
|
||||
-- get worldedit parser load_schematic from worldedit mod
|
||||
dofile(townchest.modpath.."/".."worldedit-serialization.lua")
|
||||
|
||||
files = {}
|
||||
|
||||
-----------------------------------------------
|
||||
-- get files
|
||||
-- no input parameters
|
||||
-- returns a table containing buildings
|
||||
-----------------------------------------------
|
||||
local __get = function()
|
||||
function files.get()
|
||||
local files = {}
|
||||
if os.getenv('HOME')~=nil then
|
||||
dprint("use GNU tools to get files")
|
||||
@ -37,20 +35,24 @@ end
|
||||
-- filename - the building file to load
|
||||
-- return - WE-Shema, containing the pos and nodes to build
|
||||
-----------------------------------------------
|
||||
local __readfile = function(filename)
|
||||
local filepath = townchest.modpath.."/buildings/"..filename
|
||||
local file, err = io.open(filepath, "rb")
|
||||
if err ~= nil then
|
||||
dprint("[townchest] error: could not open file \"" .. filepath .. "\"")
|
||||
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
|
||||
-- load the building starting from the lowest y
|
||||
local building_plan = townchest.we_load_schematic(file:read("*a"))
|
||||
return building_plan
|
||||
|
||||
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
|
||||
|
||||
|
||||
townchest.files = {
|
||||
get = __get,
|
||||
readfile = __readfile
|
||||
}
|
||||
------------------------------------------
|
||||
-- return the files methods to the caller
|
||||
return files
|
||||
|
4
init.lua
4
init.lua
@ -23,11 +23,11 @@ local smartfs = townchest.smartfs
|
||||
dofile(townchest.modpath.."/chest.lua")
|
||||
|
||||
-- Reading building files (WorldEdit)
|
||||
dofile(townchest.modpath.."/files.lua")
|
||||
townchest.files = dofile(townchest.modpath.."/files.lua")
|
||||
townchest.hsl = dofile(townchest.modpath.."/libs/hsl/init.lua")
|
||||
|
||||
-- Nodes mapping
|
||||
dofile(townchest.modpath.."/nodes.lua")
|
||||
dofile(townchest.modpath.."/mapping.lua")
|
||||
|
||||
-- building plan
|
||||
dofile(townchest.modpath.."/plan.lua")
|
||||
|
@ -1,217 +0,0 @@
|
||||
local handle_schematics = {}
|
||||
|
||||
-- This code is used to read Minecraft schematic files.
|
||||
--
|
||||
-- The .schematic file format is described here:
|
||||
-- http://minecraft.gamepedia.com/Schematic_file_format?cookieSetup=true
|
||||
-- It is based on the NBT format, which is described here:
|
||||
-- http://minecraft.gamepedia.com/NBT_Format?cookieSetup=true
|
||||
|
||||
|
||||
-- position in the decompressed string data_stream
|
||||
local curr_index = 1;
|
||||
|
||||
-- helper arry so that the values do not have to be calculated anew each time
|
||||
local pot256 = { 1 };
|
||||
for i=1,8 do
|
||||
pot256[ #pot256+1 ] = pot256[ #pot256 ] * 256;
|
||||
end
|
||||
|
||||
-- read length bytes from data_stream and turn it into an integer value
|
||||
local read_signed = function( data_stream, length)
|
||||
local res = 0;
|
||||
for i=length,1,-1 do
|
||||
res = res + (string.byte( data_stream, curr_index )* pot256[ i ]);
|
||||
-- move one further
|
||||
curr_index = curr_index+1;
|
||||
end
|
||||
return res;
|
||||
end
|
||||
|
||||
-- this table will collect the few tags we're actually intrested in
|
||||
local mc_schematic_data = {};
|
||||
|
||||
-- this will be a recursive function
|
||||
local read_tag;
|
||||
|
||||
-- needs to be defined now because it will contain a recursive function
|
||||
local read_one_tag;
|
||||
|
||||
-- read payload of one tag (= a data element in a NBT data structure)
|
||||
read_one_tag = function( data_stream, tag, title_tag )
|
||||
if( tag<= 0 or not(data_stream)) then
|
||||
return;
|
||||
elseif( tag==1 ) then -- TAG_BYTE: 1 byte
|
||||
return read_signed( data_stream, 1 );
|
||||
elseif( tag==2 ) then -- TAG_SHORT: 2 bytes
|
||||
return read_signed( data_stream, 2 );
|
||||
elseif( tag==3 ) then -- TAG_INT: 4 bytes
|
||||
return read_signed( data_stream, 4 );
|
||||
elseif( tag==4 ) then -- TAG_LONG: 8 bytes
|
||||
return read_signed( data_stream, 8 );
|
||||
elseif( tag==5 ) then -- TAG_FLOAT: 4 bytes
|
||||
return read_signed( data_stream, 4 ); -- the float values are unused here
|
||||
elseif( tag==6 ) then -- TAG_DOUBLE: 8 bytes
|
||||
return read_signed( data_stream, 8 ); -- the float values are unused here
|
||||
elseif( tag==7 ) then -- TAG_Byte_Array
|
||||
local size = read_signed( data_stream, 4 ); -- TAG_INT
|
||||
local res = {};
|
||||
for i=1,size do
|
||||
-- a Byte_Array does not contain any sub-tags
|
||||
res[i] = read_one_tag( data_stream, 1, nil ); -- read TAG_BYTE
|
||||
end
|
||||
return res;
|
||||
|
||||
elseif( tag==8 ) then -- TAG_String
|
||||
local size = read_signed( data_stream, 2);
|
||||
local res = string.sub( data_stream, curr_index, curr_index+size-1 );
|
||||
-- move on in the data stream
|
||||
curr_index = curr_index + size;
|
||||
return res;
|
||||
|
||||
elseif( tag==9 ) then -- TAG_List
|
||||
-- these exact values are not particulary intresting
|
||||
local tagtyp = read_signed( data_stream, 1 ); -- TAG_BYTE
|
||||
local size = read_signed( data_stream, 4 ); -- TAG_INT
|
||||
local res = {};
|
||||
for i=1,size do
|
||||
-- we need to pass title_tag on to the "child"
|
||||
res[i] = read_one_tag( data_stream, tagtyp, title_tag );
|
||||
end
|
||||
return res;
|
||||
|
||||
elseif( tag==10 ) then -- TAG_Compound
|
||||
return read_tag( data_stream, title_tag );
|
||||
|
||||
elseif( tag==11 ) then -- TAG_Int_Array
|
||||
local size = read_signed( data_stream, 4 ); -- TAG_INT
|
||||
local res = {};
|
||||
for i=1,size do
|
||||
-- a Int_Array does not contain any sub-tags
|
||||
res[i] = read_one_tag( data_stream, 3, nil ); -- TAG_INT
|
||||
end
|
||||
return res;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- read tag type, tag name and call read_one_tag to get the payload;
|
||||
read_tag = function( data_stream, title_tag )
|
||||
local schematic_data = {};
|
||||
while( data_stream ) do
|
||||
local tag = string.byte( data_stream, curr_index);
|
||||
-- move on in the data stream
|
||||
curr_index = curr_index + 1;
|
||||
if( not( tag ) or tag <= 0 ) then
|
||||
return;
|
||||
end
|
||||
local tag_name_length = string.byte( data_stream, curr_index ) * 256 + string.byte(data_stream, curr_index + 1);
|
||||
-- move 2 further
|
||||
curr_index = curr_index + 2;
|
||||
local tag_name = string.sub( data_stream, curr_index, curr_index+tag_name_length-1 );
|
||||
-- move on...
|
||||
curr_index = curr_index + tag_name_length;
|
||||
--print('[analyze_mc_schematic_file] Found: Tag '..tostring( tag )..' <'..tostring( tag_name )..'>');
|
||||
local res = read_one_tag( data_stream, tag, tag_name );
|
||||
-- Entities and TileEntities are ignored
|
||||
if( title_tag == 'Schematic'
|
||||
and( tag_name == 'Width'
|
||||
or tag_name == 'Height'
|
||||
or tag_name == 'Length'
|
||||
or tag_name == 'Materials' -- "Classic" or "Alpha" (=Survival)
|
||||
or tag_name == 'Blocks'
|
||||
or tag_name == 'Data'
|
||||
)) then
|
||||
mc_schematic_data[ tag_name ] = res;
|
||||
end
|
||||
end
|
||||
return;
|
||||
end
|
||||
|
||||
|
||||
handle_schematics.analyze_mc_schematic_file = function( path )
|
||||
-- these files are usually compressed; there is no point to start if the
|
||||
-- decompress function is missing
|
||||
if( minetest.decompress == nil) then
|
||||
return nil;
|
||||
end
|
||||
|
||||
local file, err = save_restore.file_access(path..'.schematic', "rb")
|
||||
if (file == nil) then
|
||||
-- print('[analyze_mc_schematic_file] ERROR: NO such file: '..tostring( path..'.schematic'));
|
||||
return nil
|
||||
end
|
||||
|
||||
local compressed_data = file:read( "*all" );
|
||||
--local data_string = minetest.decompress(compressed_data, "deflate" );
|
||||
local data_string = compressed_data; -- TODO
|
||||
print('FILE SIZE: '..tostring( string.len( data_string ))); -- TODO
|
||||
file.close(file)
|
||||
|
||||
|
||||
-- we use this (to this file) global variable to store gathered information;
|
||||
-- doing so inside the recursive functions proved problematic
|
||||
mc_schematic_data = {};
|
||||
-- this index will iterate through the schematic data
|
||||
curr_index = 1;
|
||||
-- actually analyze the data
|
||||
read_tag( data_string, nil );
|
||||
|
||||
if( not( mc_schematic_data.Width )
|
||||
or not( mc_schematic_data.Height )
|
||||
or not( mc_schematic_data.Length )
|
||||
or not( mc_schematic_data.Blocks )
|
||||
or not( mc_schematic_data.Data )) then
|
||||
print('[analyze_mc_schematic_file] ERROR: Failed to analyze '..tostring( path..'.schematic'));
|
||||
return nil;
|
||||
end
|
||||
|
||||
local translation_function = handle_schematics.findMC2MTConversion;
|
||||
if( minetest.get_modpath('mccompat')) then
|
||||
translation_function = mccompat.findMC2MTConversion;
|
||||
end
|
||||
|
||||
local max_msg = 40; -- just for error handling
|
||||
local size = {x=mc_schematic_data.Width, y=mc_schematic_data.Height, z=mc_schematic_data.Length};
|
||||
local scm = {};
|
||||
local nodenames = {};
|
||||
local nodenames_id = {};
|
||||
for y=1,size.y do
|
||||
scm[y] = {};
|
||||
for x=1,size.x do
|
||||
scm[y][x] = {};
|
||||
for z =1,size.z do
|
||||
local new_node = translation_function(
|
||||
-- (Y×length + Z)×width + X.
|
||||
mc_schematic_data.Blocks[ ((y-1)*size.z + (z-1) )*size.x + (size.x-x) +1],
|
||||
mc_schematic_data.Data[ ((y-1)*size.z + (z-1) )*size.x + (size.x-x) +1] );
|
||||
-- some MC nodes store the information about a node in TWO block and data fields (doors, large flowers, ...)
|
||||
if( new_node[3] and new_node[3]~=0 ) then
|
||||
new_node = translation_function(
|
||||
-- (Y×length + Z)×width + X.
|
||||
mc_schematic_data.Blocks[ ((y-1)*size.z + (z-1) )*size.x + (size.x-x) +1],
|
||||
mc_schematic_data.Data[ ((y-1)*size.z + (z-1) )*size.x + (size.x-x) +1],
|
||||
mc_schematic_data.Blocks[ ((y-1+new_node[3])*size.z + (z-1) )*size.x + (size.x-x) +1],
|
||||
mc_schematic_data.Data[ ((y-1+new_node[3])*size.z + (z-1) )*size.x + (size.x-x) +1] );
|
||||
end
|
||||
if( not( nodenames_id[ new_node[1]] )) then
|
||||
nodenames_id[ new_node[1] ] = #nodenames + 1;
|
||||
nodenames[ nodenames_id[ new_node[1] ]] = new_node[1];
|
||||
end
|
||||
-- print a few warning messages in case something goes wrong - but do not exaggerate
|
||||
if( not( new_node[2] and max_msg>0)) then
|
||||
-- print('[handle_schematics:schematic] MISSING param2: '..minetest.serialize( new_node ));
|
||||
new_node[2]=0;
|
||||
max_msg=max_msg-1;
|
||||
end
|
||||
-- save some space by not saving air
|
||||
if( new_node[1] ~= 'air' ) then
|
||||
scm[y][x][z] = { nodenames_id[ new_node[1]], new_node[2]};
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return { size = { x=size.x, y=size.y, z=size.z}, nodenames = nodenames, on_constr = {}, after_place_node = {}, rotated=90, burried=0, scm_data_cache = scm, metadata = {}};
|
||||
end
|
||||
|
||||
return handle_schematics
|
@ -37,20 +37,10 @@ local handle_schematics = {}
|
||||
-- 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( path )
|
||||
handle_schematics.analyze_mts_file = function(file)
|
||||
local size = { x = 0, y = 0, z = 0, version = 0 }
|
||||
local version = 0;
|
||||
|
||||
local file, err = save_restore.file_access(path..'.mts', "rb")
|
||||
if (file == nil) then
|
||||
return nil
|
||||
end
|
||||
--print('[handle_schematics] Analyzing .mts file '..tostring( path..'.mts' ));
|
||||
--if( not( string.byte )) then
|
||||
-- print( '[handle_schematics] Error: string.byte undefined.');
|
||||
-- return nil;
|
||||
--end
|
||||
|
||||
-- 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))
|
||||
@ -72,48 +62,28 @@ handle_schematics.analyze_mts_file = function( path )
|
||||
-- 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 );
|
||||
file:read( size.y )
|
||||
end
|
||||
|
||||
|
||||
-- this list is not yet used for anything
|
||||
local nodenames = {};
|
||||
-- this list is needed for calling on_construct after place_schematic
|
||||
local on_constr = {};
|
||||
-- nodes that require after_place_node to be called
|
||||
local after_place_node = {};
|
||||
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 );
|
||||
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 );
|
||||
local name_length = read_s16( file )
|
||||
-- the text of the next name
|
||||
local name_text = file:read( name_length );
|
||||
|
||||
table.insert( nodenames, name_text );
|
||||
-- in order to get this information, the node has to be defined and loaded
|
||||
if( minetest.registered_nodes[ name_text ] and minetest.registered_nodes[ name_text ].on_construct) then
|
||||
table.insert( on_constr, name_text );
|
||||
end
|
||||
-- some nodes need after_place_node to be called for initialization
|
||||
if( minetest.registered_nodes[ name_text ] and minetest.registered_nodes[ name_text ].after_place_node) then
|
||||
table.insert( after_place_node, name_text );
|
||||
end
|
||||
end
|
||||
|
||||
local rotated = 0;
|
||||
local burried = 0;
|
||||
local parts = path:split('_');
|
||||
if( parts and #parts > 2 ) then
|
||||
if( parts[#parts]=="0" or parts[#parts]=="90" or parts[#parts]=="180" or parts[#parts]=="270" ) then
|
||||
rotated = tonumber( parts[#parts] );
|
||||
burried = tonumber( parts[ #parts-1 ] );
|
||||
if( not( burried ) or burried>20 or burried<0) then
|
||||
burried = 0;
|
||||
end
|
||||
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
|
||||
|
||||
@ -121,141 +91,80 @@ handle_schematics.analyze_mts_file = function( path )
|
||||
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
|
||||
-- return { size = { x=size.x, y=size.y, z=size.z}, nodenames = nodenames, on_constr = on_constr, after_place_node = after_place_node, rotated=rotated, burried=burried, scm_data_cache = nil };
|
||||
end
|
||||
|
||||
local compressed_data = file:read( "*all" );
|
||||
local data_string = minetest.decompress(compressed_data, "deflate" );
|
||||
file.close(file)
|
||||
|
||||
local ids = {};
|
||||
local needs_on_constr = {};
|
||||
local is_air = 0;
|
||||
-- translate nodenames to ids
|
||||
for i,v in ipairs( nodenames ) do
|
||||
ids[ i ] = minetest.get_content_id( v );
|
||||
needs_on_constr[ i ] = false;
|
||||
if( minetest.registered_nodes[ v ] and minetest.registered_nodes[ v ].on_construct ) then
|
||||
needs_on_constr[ i ] = true;
|
||||
end
|
||||
if( v == 'air' ) then
|
||||
is_air = i;
|
||||
end
|
||||
end
|
||||
|
||||
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 = 0
|
||||
local groundnode_count = 0
|
||||
|
||||
for z = 1, size.z do
|
||||
for y = 1, size.y do
|
||||
for x = 1, size.x do
|
||||
if( not( scm[y] )) then
|
||||
scm[y] = {};
|
||||
end
|
||||
if( not( scm[y][x] )) then
|
||||
scm[y][x] = {};
|
||||
end
|
||||
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;
|
||||
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
|
||||
|
||||
if( id ~= is_air ) then
|
||||
scm[y][x][z] = {id, p2};
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- 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
|
||||
|
||||
return { size = { x=size.x, y=size.y, z=size.z}, nodenames = nodenames, on_constr = on_constr, after_place_node = after_place_node, rotated=rotated, burried=burried, scm_data_cache = scm };
|
||||
end
|
||||
|
||||
|
||||
|
||||
handle_schematics.store_mts_file = function( path, data )
|
||||
|
||||
data.nodenames[ #data.nodenames+1 ] = 'air';
|
||||
|
||||
local file, err = save_restore.file_access(path..'.mts', "wb")
|
||||
if (file == nil) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local write_s16 = function( fi, a )
|
||||
fi:write( string.char( math.floor( a/256) ));
|
||||
fi:write( string.char( a%256 ));
|
||||
end
|
||||
|
||||
data.size.version = 3; -- we only support version 3 of the .mts file format
|
||||
|
||||
file:write( "MTSM" );
|
||||
write_s16( file, data.size.version );
|
||||
write_s16( file, data.size.x );
|
||||
write_s16( file, data.size.y );
|
||||
write_s16( file, data.size.z );
|
||||
|
||||
|
||||
-- set the slice probability for each y value that was introduced in version 3
|
||||
if( data.size.version >= 3 ) then
|
||||
-- the probability is not very intresting for buildings so we just skip it
|
||||
for i=1,data.size.y do
|
||||
file:write( string.char(255) );
|
||||
-- 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
|
||||
|
||||
-- set how many diffrent nodenames (node_name_count) are present in the file
|
||||
write_s16( file, #data.nodenames );
|
||||
|
||||
for i = 1, #data.nodenames do
|
||||
-- the length of the next name
|
||||
write_s16( file, string.len( data.nodenames[ i ] ));
|
||||
file:write( data.nodenames[ i ] );
|
||||
end
|
||||
|
||||
-- this string will later be compressed
|
||||
local node_data = "";
|
||||
|
||||
-- actual node data
|
||||
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 and type( a ) == 'table') then
|
||||
node_data = node_data..string.char( math.floor( a[1]/256) )..string.char( a[1]%256-1);
|
||||
else
|
||||
node_data = node_data..string.char( 0 )..string.char( #data.nodenames-1 );
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- probability of occurance
|
||||
for z = 1, data.size.z do
|
||||
for y = 1, data.size.y do
|
||||
for x = 1, data.size.x do
|
||||
node_data = node_data..string.char( 255 );
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- param2
|
||||
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 and type( a) == 'table' ) then
|
||||
node_data = node_data..string.char( a[2] );
|
||||
else
|
||||
node_data = node_data..string.char( 0 );
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local compressed_data = minetest.compress( node_data, "deflate" );
|
||||
file:write( compressed_data );
|
||||
file.close(file);
|
||||
print('SAVING '..path..'.mts (converted from .we).');
|
||||
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,54 +1,73 @@
|
||||
local handle_schematics = {}
|
||||
|
||||
handle_schematics.analyze_we_file = function(scm, we_origin)
|
||||
local c_ignore = minetest.get_content_id("ignore")
|
||||
-- receive parameter modpath
|
||||
local modpath = ...
|
||||
|
||||
-- this table will contain the nodes read
|
||||
local nodes = {}
|
||||
-- deserialize worldedit savefiles
|
||||
local worldedit_file = dofile(modpath.."worldedit_file.lua")
|
||||
|
||||
-- check if it is a worldedit file
|
||||
-- (no idea why reading that is done in such a complicated way; a simple deserialize and iteration over all nodes ought to do as well)
|
||||
local f, err = save_restore.file_access( scm..".we", "r")
|
||||
if not f then
|
||||
f, err = save_restore.file_access( scm..".wem", "r")
|
||||
if not f then
|
||||
-- error("Could not open schematic '" .. scm .. ".we': " .. err)
|
||||
return nil;
|
||||
|
||||
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 = 0
|
||||
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
|
||||
end
|
||||
|
||||
local value = f:read("*a")
|
||||
f:close()
|
||||
-- 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
|
||||
|
||||
local nodes = worldedit_file.load_schematic(value, we_origin)
|
||||
-- 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
|
||||
|
||||
-- create a list of nodenames
|
||||
local nodenames = {};
|
||||
local nodenames_id = {};
|
||||
for i,ent in ipairs( nodes ) do
|
||||
if( ent and ent.name and not( nodenames_id[ ent.name ])) then
|
||||
nodenames_id[ ent.name ] = #nodenames + 1;
|
||||
nodenames[ nodenames_id[ ent.name ] ] = ent.name;
|
||||
end
|
||||
end
|
||||
|
||||
scm = {}
|
||||
local maxx, maxy, maxz = -1, -1, -1
|
||||
local all_meta = {};
|
||||
for i = 1, #nodes do
|
||||
local ent = nodes[i]
|
||||
ent.x = ent.x + 1
|
||||
ent.y = ent.y + 1
|
||||
ent.z = ent.z + 1
|
||||
if ent.x > maxx then
|
||||
maxx = ent.x
|
||||
end
|
||||
if ent.y > maxy then
|
||||
maxy = ent.y
|
||||
end
|
||||
if ent.z > maxz then
|
||||
maxz = ent.z
|
||||
end
|
||||
-- build to scm data tree
|
||||
if scm[ent.y] == nil then
|
||||
scm[ent.y] = {}
|
||||
end
|
||||
@ -58,47 +77,36 @@ handle_schematics.analyze_we_file = function(scm, we_origin)
|
||||
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;
|
||||
local has_meta = false
|
||||
for _,v in pairs( ent.meta.fields ) do
|
||||
has_meta = true;
|
||||
has_meta = true
|
||||
break
|
||||
end
|
||||
for _,v in pairs(ent.meta.inventory) do
|
||||
has_meta = true;
|
||||
has_meta = true
|
||||
break
|
||||
end
|
||||
if( has_meta == true ) then
|
||||
all_meta[ #all_meta+1 ] = {
|
||||
x=ent.x,
|
||||
y=ent.y,
|
||||
z=ent.z,
|
||||
fields = ent.meta.fields,
|
||||
inventory = ent.meta.inventory};
|
||||
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}
|
||||
|
||||
scm[ent.y][ent.x][ent.z] = { nodenames_id[ ent.name ], ent.param2 };
|
||||
|
||||
nodecount = nodecount + 1
|
||||
end
|
||||
|
||||
for y = 1, maxy do
|
||||
if scm[y] == nil then
|
||||
scm[y] = {}
|
||||
end
|
||||
for x = 1, maxx do
|
||||
if scm[y][x] == nil then
|
||||
scm[y][x] = {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local size = {};
|
||||
size.y = math.max(maxy,0);
|
||||
size.x = math.max(maxx,0);
|
||||
size.z = math.max(maxz,0);
|
||||
|
||||
return { size = { x=size.x, y=size.y, z=size.z}, nodenames = nodenames, on_constr = {}, after_place_node = {}, rotated=0, burried=0, scm_data_cache = scm, metadata = all_meta };
|
||||
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
|
||||
|
@ -3,25 +3,17 @@
|
||||
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/'
|
||||
|
||||
-- deserialize worldedit savefiles
|
||||
hsl.worldedit_file = dofile(modpath.."worldedit_file.lua")
|
||||
|
||||
-- reads and analyzes .mts files (minetest schematics)
|
||||
hsl.analyze_mts = dofile(modpath.."/analyze_mts_file.lua")
|
||||
|
||||
-- reads and analyzes worldedit files
|
||||
hsl.analyze_we = dofile(modpath.."/analyze_we_file.lua")
|
||||
|
||||
-- reads and analyzes Minecraft schematic files
|
||||
hsl.translate_mc = dofile(modpath.."/translate_nodenames_for_mc_schematic.lua")
|
||||
hsl.analyze_mc = dofile(modpath.."/analyze_mc_schematic_file.lua")
|
||||
hsl.analyze_we = assert(loadfile(modpath.."/analyze_we_file.lua"))(modpath)
|
||||
|
||||
-- handles rotation and mirroring
|
||||
hsl.rotate = dofile(modpath.."/rotate.lua")
|
||||
@ -33,5 +25,4 @@ hsl.misc = dofile(modpath.."/handle_schematics_misc.lua")
|
||||
hsl.save_restore = dofile(modpath.."/save_restore.lua");
|
||||
hsl.meta = dofile(modpath.."/handle_schematics_meta.lua");
|
||||
|
||||
|
||||
return hsl
|
||||
|
@ -1,405 +0,0 @@
|
||||
local handle_schematics = {}
|
||||
|
||||
-- based on:
|
||||
-- # Minecraft to Minetest WE schematic MCEdit filter
|
||||
-- # by sfan5
|
||||
|
||||
-- #blockdata -1 means ignore
|
||||
local P2_IGNORE = -1;
|
||||
-- #blockdata -2 means copy without change
|
||||
local P2_COPY = -2;
|
||||
-- #blockdata -3 means copy and convert the mc facedir value to mt facedir
|
||||
local P2_CONVERT= -3;
|
||||
-- #blockdata -4 is for stairs to support upside down ones
|
||||
local P2_STAIR = -4;
|
||||
-- #blockdata selects one of the listed subtypes
|
||||
local P2_SELECT = -5;
|
||||
|
||||
-- #Reference MC: http://media-mcw.cursecdn.com/8/8c/DataValuesBeta.png
|
||||
-- #Reference MT:
|
||||
-- # https://github.com/minetest/common/blob/master/mods/default/init.lua
|
||||
-- # https://github.com/minetest/common/blob/master/mods/wool/init.lua
|
||||
-- # https://github.com/minetest/common/blob/master/mods/stairs/init.lua
|
||||
local conversionTable = {
|
||||
-- #blockid blockdata minetest-nodename
|
||||
|
||||
-- [0] = {P2_IGNORE, "air"},
|
||||
[1] = {P2_IGNORE, "default:stone"},
|
||||
-- 0: stone; 1: granite; 2: polished granite;
|
||||
-- 3: diorite; 4: polished diorite; 5: andesite;
|
||||
-- 6: polished andesite
|
||||
[2] = {P2_IGNORE, "default:dirt_with_grass"},
|
||||
[3] = {P2_IGNORE, "default:dirt"},
|
||||
-- 0: dirt; 1: coarse dirt; 2: podzol
|
||||
[4] = {P2_IGNORE, "default:cobble"},
|
||||
[5] = {P2_SELECT, { [0]="default:wood",
|
||||
[1]="moretrees:spruce_planks",
|
||||
[2]="moretrees:birch_planks",
|
||||
[3]="default:junglewood",
|
||||
[4]="moretrees:acacia_planks",
|
||||
[5]="moretrees:oak_planks"}},
|
||||
[6] = {P2_SELECT, { [0]="default:wood",
|
||||
[1]="moretrees:spruce_sapling",
|
||||
[2]="moretrees:birch_sapling",
|
||||
[3]="default:junglesapling",
|
||||
[4]="moretrees:acacia_sapling",
|
||||
[5]="moretrees:oak_sapling"}},
|
||||
[7] = {P2_IGNORE, "minecraft:bedrock"}, --# FIXME Bedrock
|
||||
[8] = {P2_IGNORE, "default:water_flowing"},
|
||||
[9] = {P2_IGNORE, "default:water_source"},
|
||||
[10] = {P2_IGNORE, "default:lava_flowing"},
|
||||
[11] = {P2_IGNORE, "default:lava_source"},
|
||||
[12] = {P2_SELECT, { [0]="default:sand",
|
||||
[1]="default:desert_sand"}},
|
||||
[13] = {P2_IGNORE, "default:gravel"},
|
||||
[14] = {P2_IGNORE, "default:stone_with_gold"},
|
||||
[15] = {P2_IGNORE, "default:stone_with_iron"},
|
||||
[16] = {P2_IGNORE, "default:stone_with_coal"},
|
||||
-- TODO: the trees have facedir
|
||||
[17] = {P2_SELECT, { [0]="default:tree",
|
||||
[1]="moretrees:spruce_trunk",
|
||||
[2]="moretrees:birch_trunk",
|
||||
[3]="default:jungletree",
|
||||
[4]="moretrees:acacia_trunk",
|
||||
[5]="moretrees:oak_trunk"}},
|
||||
[18] = {P2_SELECT, { [0]="default:leaves",
|
||||
[1]="moretrees:spruce_leaves",
|
||||
[2]="moretrees:birch_leaves",
|
||||
[3]="default:jungleleaves",
|
||||
[4]="default:leaves",
|
||||
[5]="moretrees:spruce_leaves",
|
||||
[6]="moretrees:birch_leaves",
|
||||
[7]="default:jungleleaves",
|
||||
[8]="default:leaves",
|
||||
[9]="moretrees:spruce_leaves",
|
||||
[10]="moretrees:birch_leaves",
|
||||
[11]="default:jungleleaves",
|
||||
[12]="default:leaves",
|
||||
[13]="moretrees:spruce_leaves",
|
||||
[14]="moretrees:birch_leaves",
|
||||
[15]="default:jungleleaves"}},
|
||||
[19] = {P2_CONVERT,"minecraft:sponge"},
|
||||
[20] = {P2_IGNORE, "default:glass"},
|
||||
[21] = {P2_IGNORE, "default:stone_with_copper"}, -- Lapis Lazuli Ore
|
||||
[22] = {P2_IGNORE, "default:copperblock"}, -- Lapis Lazuli Block
|
||||
[23] = {P2_CONVERT,"minecraft:dispenser"},
|
||||
[24] = {P2_SELECT, { [0]="default:sandstone",
|
||||
[1]="default:sandstonebrick",
|
||||
[2]="default:sandstone"}},
|
||||
[25] = {P2_CONVERT,"minecraft:nodeblock"},
|
||||
[26] = {P2_CONVERT,"beds:bed"}, -- TODO: might require special handling?
|
||||
[27] = {P2_CONVERT,"minecraft:golden_rail"},
|
||||
[28] = {P2_CONVERT,"minecraft:detector_rail"},
|
||||
-- 29: sticky piston
|
||||
[30] = {P2_CONVERT,"minecraft:web"},
|
||||
[31] = {P2_SELECT, { [0]="default:dry_shrub",
|
||||
[1]="default:grass_4",
|
||||
[2]="ferns:fern_02"}},
|
||||
[32] = {P2_IGNORE, "default:dry_shrub"},
|
||||
-- 34: piston head
|
||||
[35] = {P2_SELECT, { [0]="wool:white",
|
||||
[1]="wool:orange",
|
||||
[2]="wool:magenta",
|
||||
[3]="wool:light_blue",
|
||||
[4]="wool:yellow",
|
||||
[5]="wool:green",
|
||||
[6]="wool:pink",
|
||||
[7]="wool:dark_grey",
|
||||
[8]="wool:grey",
|
||||
[9]="wool:cyan",
|
||||
[10]="wool:violet",
|
||||
[11]="wool:blue",
|
||||
[12]="wool:brown",
|
||||
[13]="wool:dark_green",
|
||||
[14]="wool:red",
|
||||
[15]="wool:black"}},
|
||||
-- 36: piston extension
|
||||
[37] = {P2_IGNORE, "flowers:dandelion_yellow"},
|
||||
[38] = {P2_SELECT, { [0]="flowers:rose",
|
||||
[1]="flowers:geranium",
|
||||
[2]="flowers:viola",
|
||||
[3]="flowers:dandelion_white",
|
||||
[4]="tulips:red",
|
||||
[5]="flowers:tulip",
|
||||
[6]="tulips:white",
|
||||
[7]="tulips:pink",
|
||||
[8]="tulips:black"}},
|
||||
[41] = {P2_IGNORE, "default:goldblock"},
|
||||
[42] = {P2_IGNORE, "default:steelblock"},
|
||||
-- double stone slabs...full blocks?
|
||||
[43] = {P2_SELECT, { [0]="default:stone",
|
||||
[1]="default:sandstonebrick",
|
||||
[2]="default:wood",
|
||||
[3]="default:cobble",
|
||||
[4]="default:brick",
|
||||
[5]="default:stonebrick",
|
||||
[6]="nether:brick",
|
||||
[7]="quartz:quartz",
|
||||
[8]="moreblocks:split_stone_tile",
|
||||
[9]="default:sandstone"}},
|
||||
[44] = {P2_SELECT, { [0]="stairs:slab_stone",
|
||||
[1]="stairs:slab_sandstone",
|
||||
[2]="stairs:slab_wood",
|
||||
[3]="stairs:slab_cobble",
|
||||
[4]="stairs:slab_brick",
|
||||
[5]="stairs:slab_stonebrick",
|
||||
[6]="stairs:slab_nether",
|
||||
[7]="stairs:slab_quartz",
|
||||
[8]="stairs:slab_stoneupside_down",
|
||||
[9]="stairs:slab_sandstoneupside_down",
|
||||
[10]="stairs:slab_woodupside_down",
|
||||
[11]="stairs:slab_cobbleupside_down",
|
||||
[12]="stairs:slab_brickupside_down",
|
||||
[13]="stairs:slab_stonebrickupside_down",
|
||||
[14]="stairs:slab_netzerupside_down",
|
||||
[15]="stairs:slab_quartzupside_down"}},
|
||||
[45] = {P2_IGNORE, "default:brick"},
|
||||
[46] = {P2_CONVERT,"tnt:tnt"},
|
||||
[47] = {P2_IGNORE, "default:bookshelf"},
|
||||
[48] = {P2_IGNORE, "default:mossycobble"},
|
||||
[49] = {P2_IGNORE, "default:obsidian"},
|
||||
[50] = {P2_CONVERT,"default:torch"},
|
||||
[51] = {P2_IGNORE, "fire:basic_flame"},
|
||||
[52] = {P2_CONVERT,"minecraft:mob_spawner"},
|
||||
[53] = {P2_STAIR, "stairs:stair_wood"},
|
||||
[54] = {P2_IGNORE, "default:chest"},
|
||||
[56] = {P2_IGNORE, "default:stone_with_diamond"},
|
||||
[57] = {P2_IGNORE, "default:diamondblock"},
|
||||
[58] = {P2_CONVERT,"minecraft:crafting_table"},
|
||||
[59] = {P2_IGNORE, "farming:wheat_8"},
|
||||
[60] = {P2_IGNORE, "farming:soil_wet"},
|
||||
[61] = {P2_IGNORE, "default:furnace"},
|
||||
[62] = {P2_IGNORE, "default:furnace_active"},
|
||||
[63] = {P2_IGNORE, "default:sign_wall"},
|
||||
[64] = {P2_IGNORE, "doors:door_wood_t_1"},
|
||||
[65] = {P2_IGNORE, "default:ladder"},
|
||||
[66] = {P2_IGNORE, "default:rail"},
|
||||
[67] = {P2_STAIR, "stairs:stair_cobble"},
|
||||
[68] = {P2_CONVERT,"default:sign_wall"},
|
||||
[71] = {P2_IGNORE, "doors:door_steel_t_1"},
|
||||
[78] = {P2_IGNORE, "default:snow"},
|
||||
[79] = {P2_IGNORE, "default:ice"},
|
||||
[80] = {P2_IGNORE, "default:snowblock"},
|
||||
[81] = {P2_IGNORE, "default:cactus"},
|
||||
[82] = {P2_IGNORE, "default:clay"},
|
||||
[83] = {P2_IGNORE, "default:papyrus"},
|
||||
[84] = {P2_CONVERT,"minecraft:jukebox"},
|
||||
[85] = {P2_IGNORE, "default:fence_wood"},
|
||||
[86] = {P2_CONVERT,"farming:pumpkin"},
|
||||
[91] = {P2_CONVERT,"farming:pumpkin_face_light"},
|
||||
[92] = {P2_CONVERT,"minecraft:cake"},
|
||||
[95] = {P2_IGNORE, "minecraft:stained_glass"}, -- TODO
|
||||
[96] = {P2_CONVERT,"doors:trapdoor"},
|
||||
[97] = {P2_IGNORE, "minecraft:monster_egg"},
|
||||
[98] = {P2_IGNORE, "default:stonebrick"},
|
||||
[108]= {P2_STAIR, "stairs:stair_brick"},
|
||||
[109]= {P2_CONVERT,"stairs:stair_stonebrick"},
|
||||
-- TODO: double ... wood slab...
|
||||
[125]= {P2_SELECT, { [0]="default:wood",
|
||||
[1]="moretrees:spruce_planks",
|
||||
[2]="moretrees:birch_planks",
|
||||
[3]="default:junglewood",
|
||||
[4]="moretrees:acacia_planks",
|
||||
[5]="moretrees:oak_planks"}},
|
||||
[125]= {P2_IGNORE, "default:wood"},
|
||||
[126]= {P2_SELECT, { [0]="stairs:slab_wood",
|
||||
[1]="stairs:slab_spruce_planks",
|
||||
[2]="stairs:slab_birch_planks",
|
||||
[3]="stairs:slab_junglewood",
|
||||
[4]="stairs:slab_acacia_planks",
|
||||
[5]="stairs:slab_oak_planks",
|
||||
[8]="stairs:slab_woodupside_down",
|
||||
[9]="stairs:slab_spruce_planksupside_down",
|
||||
[10]="stairs:slab_birch_planksupside_down",
|
||||
[11]="stairs:slab_junglewoodupside_down",
|
||||
[12]="stairs:slab_acacia_planksupside_down",
|
||||
[13]="stairs:slab_oak_planksupside_down"}},
|
||||
[126]= {P2_IGNORE, "stairs:slab_wood"},
|
||||
[128]= {P2_STAIR, "stairs:stair_sandstone"},
|
||||
[129]= {P2_IGNORE, "default:stone_with_mese"},
|
||||
[133]= {P2_IGNORE, "default:mese"},
|
||||
[134]= {P2_STAIR, "stairs:stair_wood"},
|
||||
[135]= {P2_STAIR, "stairs:stair_wood"},
|
||||
[136]= {P2_STAIR, "stairs:stair_junglewood"},
|
||||
|
||||
-- #Mesecons section
|
||||
-- # Reference: https://github.com/Jeija/minetest-mod-mesecons/blob/master/mesecons_alias/init.lua
|
||||
[25] = {P2_IGNORE, "mesecons_noteblock:noteblock"},
|
||||
[29] = {P2_CONVERT,"mesecons_pistons:piston_sticky_off"},
|
||||
[33] = {P2_CONVERT,"mesecons_pistons:piston_normal_off"},
|
||||
[55] = {P2_IGNORE, "mesecons:wire_00000000_off"},
|
||||
[69] = {P2_CONVERT,"mesecons_walllever:wall_lever_off"},
|
||||
[70] = {P2_IGNORE, "mesecons_pressureplates:pressure_plate_stone_off"},
|
||||
[72] = {P2_IGNORE, "mesecons_pressureplates:pressure_plate_wood_off"},
|
||||
[73] = {P2_IGNORE, "default:stone_with_mese"},
|
||||
[74] = {P2_IGNORE, "default:stone_with_mese"},
|
||||
[75] = {P2_CONVERT,"mesecons_torch:torch_off"},
|
||||
[76] = {P2_CONVERT,"mesecons_torch:torch_on"},
|
||||
[77] = {P2_CONVERT,"mesecons_button:button_off"},
|
||||
[93] = {P2_CONVERT,"mesecons_delayer:delayer_off_1"},
|
||||
[94] = {P2_CONVERT,"mesecons_delayer:delayer_on_1"},
|
||||
-- see mod https://github.com/doyousketch2/stained_glass
|
||||
[95] = {P2_SELECT, { [0]="default:glass", -- TODO
|
||||
[1]="stained_glass:orange__",
|
||||
[2]="stained_glass:magenta__",
|
||||
[3]="stained_glass:skyblue__",
|
||||
[4]="stained_glass:yellow__",
|
||||
[5]="stained_glass:lime__",
|
||||
[6]="stained_glass:redviolet__",
|
||||
[7]="stained_glass:dark_grey__", -- TODO
|
||||
[8]="stained_glass:grey__", -- TODO
|
||||
[9]="stained_glass:cyan__",
|
||||
[10]="stained_glass:violet__",
|
||||
[11]="stained_glass:blue__",
|
||||
[12]="stained_glass:orange_dark_",
|
||||
[13]="stained_glass:green__",
|
||||
[14]="stained_glass:red__",
|
||||
[15]="stained_glass:black__"}}, -- TODO
|
||||
[101]= {P2_CONVERT,"xpanes:bar"},
|
||||
[102]= {P2_CONVERT,"xpanes:pane"},
|
||||
[103]= {P2_IGNORE, "farming:melon"},
|
||||
[104]= {P2_IGNORE, "minecraft:pumpkin_stem"},
|
||||
[105]= {P2_IGNORE, "minecraft:melon_stem"},
|
||||
[106]= {P2_CONVERT,"vines:vine"},
|
||||
[107]= {P2_CONVERT,"minecraft:fence_gate"},
|
||||
[108]= {P2_STAIR, "stairs:stair_brick"},
|
||||
[109]= {P2_STAIR, "stairs:stair_stonebrick"},
|
||||
[110]= {P2_CONVERT,"minecraft:mycelium"},
|
||||
[111]= {P2_CONVERT,"flowers:waterlily"},
|
||||
[112]= {P2_CONVERT,"minecraft:nether_brick"},
|
||||
[113]= {P2_CONVERT,"minecraft:nether_brick_fence"},
|
||||
[114]= {P2_CONVERT,"minecraft:nether_brick_stairs"},
|
||||
[115]= {P2_CONVERT,"minecraft:nether_wart"},
|
||||
[116]= {P2_CONVERT,"minecraft:enchanting_table"},
|
||||
[117]= {P2_CONVERT,"minecraft:brewing_stand"},
|
||||
[118]= {P2_CONVERT,"minecraft:cauldron"},
|
||||
[119]= {P2_CONVERT,"minecraft:end_portal"},
|
||||
[120]= {P2_CONVERT,"minecraft:end_portal_frame"},
|
||||
[121]= {P2_CONVERT,"minecraft:end_stone"},
|
||||
[122]= {P2_CONVERT,"minecraft:dragon_egg"},
|
||||
[123]= {P2_IGNORE, "mesecons_lightstone_red_off"},
|
||||
[124]= {P2_IGNORE, "mesecons_lightstone_red_on"},
|
||||
[125]= {P2_CONVERT,"minecraft:double_wooden_slab"},
|
||||
[126]= {P2_CONVERT,"stairs:slab_wood"},
|
||||
[127]= {P2_CONVERT,"farming_plus:cocoa"},
|
||||
[137]= {P2_IGNORE, "mesecons_commandblock:commandblock_off"},
|
||||
[151]= {P2_IGNORE, "mesecons_solarpanel:solar_panel_off"},
|
||||
[152]= {P2_IGNORE, "default:mese"},
|
||||
-- see mod https://github.com/tenplus1/bakedclay
|
||||
[159] = {P2_SELECT, { [0]="bakedclay:white",
|
||||
[1]="bakedclay:orange",
|
||||
[2]="bakedclay:magenta",
|
||||
[3]="bakedclay:light_blue", -- TODO
|
||||
[4]="bakedclay:yellow",
|
||||
[5]="bakedclay:green",
|
||||
[6]="bakedclay:pink",
|
||||
[7]="bakedclay:dark_grey",
|
||||
[8]="bakedclay:grey",
|
||||
[9]="bakedclay:cyan",
|
||||
[10]="bakedclay:violet",
|
||||
[11]="bakedclay:blue",
|
||||
[12]="bakedclay:brown",
|
||||
[13]="bakedclay:dark_green",
|
||||
[14]="bakedclay:red",
|
||||
[15]="bakedclay:black"}},
|
||||
-- see mod mccarpet https://forum.minetest.net/viewtopic.php?t=7419
|
||||
[171] = {P2_SELECT, { [0]="mccarpet:white",
|
||||
[1]="mccarpet:orange",
|
||||
[2]="mccarpet:magenta",
|
||||
[3]="mccarpet:light_blue", -- TODO
|
||||
[4]="mccarpet:yellow",
|
||||
[5]="mccarpet:green",
|
||||
[6]="mccarpet:pink",
|
||||
[7]="mccarpet:dark_grey",
|
||||
[8]="mccarpet:grey",
|
||||
[9]="mccarpet:cyan",
|
||||
[10]="mccarpet:violet",
|
||||
[11]="mccarpet:blue",
|
||||
[12]="mccarpet:brown",
|
||||
[13]="mccarpet:dark_green",
|
||||
[14]="mccarpet:red",
|
||||
[15]="mccarpet:black"}},
|
||||
[181] = {P2_SELECT, { [0]="default:desert_stonebrick",
|
||||
[1]="default:desertstone"}},
|
||||
|
||||
-- #Nether section
|
||||
-- # Reference: https://github.com/PilzAdam/nether/blob/master/init.lua
|
||||
[43] = {P2_IGNORE, "nether:brick"},
|
||||
[87] = {P2_IGNORE, "nether:rack"},
|
||||
[88] = {P2_IGNORE, "nether:sand"},
|
||||
[89] = {P2_IGNORE, "nether:glowstone"},
|
||||
[90] = {P2_CONVERT,"nether:portal"},
|
||||
|
||||
-- #Riesenpilz Section
|
||||
-- # Reference: https://github.com/HybridDog/riesenpilz/blob/master/init.lua
|
||||
[39] = {P2_IGNORE, "riesenpilz:brown"},
|
||||
[40] = {P2_IGNORE, "riesenpilz:red"},
|
||||
[99] = {P2_CONVERT,"riesenpilz:head_brown"},
|
||||
[100]= {P2_CONVERT,"riesenpilz:head_brown"},
|
||||
}
|
||||
|
||||
|
||||
local mc2mtFacedir = function(blockdata)
|
||||
-- #Minetest
|
||||
-- # x+ = 2
|
||||
-- # x- = 3
|
||||
-- # z+ = 1
|
||||
-- # z- = 0
|
||||
-- #Minecraft
|
||||
-- # x+ = 3
|
||||
-- # x- = 1
|
||||
-- # z+ = 0
|
||||
-- # z- = 2
|
||||
local tbl = {
|
||||
[3]= 2,
|
||||
[1]= 1,
|
||||
[0]= 3,
|
||||
[2]= 0,
|
||||
}
|
||||
if( tbl[ blockdata ] ) then
|
||||
return tbl[ blockdata ];
|
||||
-- this happens with i.e. wallmounted torches...
|
||||
else
|
||||
return blockdata;
|
||||
end
|
||||
end
|
||||
|
||||
local mc2mtstairs = function( name, blockdata)
|
||||
if blockdata >= 4 then
|
||||
return {name.. "upside_down", mc2mtFacedir(blockdata - 4)}
|
||||
else
|
||||
return {name, mc2mtFacedir(blockdata)}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- returns {translated_node_name, translated_param2}
|
||||
handle_schematics.findMC2MTConversion = function(blockid, blockdata)
|
||||
if (blockid == 0 ) then
|
||||
return {"air",0};
|
||||
-- fallback
|
||||
elseif( not( conversionTable[ blockid ])) then
|
||||
return { "minecraft:"..tostring( blockid )..'_'..tostring( blockdata ), 0};
|
||||
end
|
||||
local conv = conversionTable[ blockid ];
|
||||
if( conv[1] == P2_IGNORE ) then
|
||||
return { conv[2], 0};
|
||||
elseif( conv[1] == P2_COPY ) then
|
||||
return { conv[2], blockdata};
|
||||
elseif( conv[1] == P2_CONVERT) then
|
||||
return { conv[2], mc2mtFacedir(blockdata)};
|
||||
elseif( conv[1] == P2_STAIR ) then
|
||||
return mc2mtstairs(conv[2], blockdata);
|
||||
elseif( conv[1] == P2_SELECT
|
||||
and conv[2][ blockdata ] ) then
|
||||
return { conv[2][ blockdata ], 0};
|
||||
elseif( conv[1] == P2_SELECT
|
||||
and not(conv[2][ blockdata ] )) then
|
||||
return { conv[2][0], 0};
|
||||
else
|
||||
return { conv[2], 0 };
|
||||
end
|
||||
return {air, 0};
|
||||
end
|
||||
|
||||
return handle_schematics
|
@ -8,7 +8,8 @@
|
||||
-- * 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 = {}
|
||||
|
||||
local worldedit_file = {} -- add the namespace
|
||||
|
||||
--- Schematic serialization and deserialiation.
|
||||
-- @module worldedit.serialization
|
||||
@ -90,18 +91,15 @@ function worldedit_file.load_schematic(value, we_origin)
|
||||
entry[2] = nil
|
||||
end
|
||||
end
|
||||
elseif version == 3 or version=="3" then -- List format
|
||||
if( not( we_origin ) or #we_origin <3) then
|
||||
we_origin = { 0, 0, 0 };
|
||||
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 = we_origin[1] + tonumber(x),
|
||||
y = we_origin[2] + tonumber(y),
|
||||
z = we_origin[3] + tonumber(z),
|
||||
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,
|
||||
|
248
mapping.lua
Normal file
248
mapping.lua
Normal file
@ -0,0 +1,248 @@
|
||||
--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" }
|
||||
|
||||
|
||||
-----------------------------------------------
|
||||
-- copy table of mapping entry
|
||||
-----------------------------------------------
|
||||
function mapping.merge_map_entry(entry1, entry2)
|
||||
if entry2 then
|
||||
return {name = entry1.name or entry2.name, --not a typo: used to merge fallback to mapped data. The mapped data is preferred
|
||||
node_def = entry1.node_def or entry2.node_def,
|
||||
content_id = entry1.content_id or entry2.content_id,
|
||||
param2 = entry2.param2 or entry1.param2,
|
||||
meta = entry2.meta or entry1.meta,
|
||||
custom_function = entry2.custom_function or entry1.custom_function,
|
||||
cost_item = entry2.cost_item or entry1.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
|
||||
dprint("map", name, "to", mr.name, mr.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)
|
||||
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
|
271
nodes.lua
271
nodes.lua
@ -1,271 +0,0 @@
|
||||
local dprint = townchest.dprint_off --debug
|
||||
|
||||
local _c_free_item = "default:cloud"
|
||||
|
||||
-- Fallback nodes replacement of unknown nodes
|
||||
-- Maybe it is beter to use aliases for unknown notes. But anyway
|
||||
-- TODO: should be editable in game trough a nice gui, to customize the building before build
|
||||
local __map_unknown = function(self)
|
||||
|
||||
local map = townchest.nodes.unknown_nodes_data[self.name]
|
||||
if not map or map.name == self.name then -- no fallback mapping. don't use the node
|
||||
dprint("mapping failed:", self.name, dump(map))
|
||||
print("unknown node in building", self.name)
|
||||
return nil
|
||||
end
|
||||
|
||||
dprint("mapped", self.name, "to", self.name)
|
||||
local mappednode = townchest.nodes.new(self)
|
||||
mappednode.name = map.name -- must be there!
|
||||
|
||||
if map.meta then
|
||||
if not mappednode.meta then
|
||||
mappednode.meta = {}
|
||||
end
|
||||
for k, v in pairs(map.meta) do
|
||||
mappednode.meta[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
if map.param1 then
|
||||
if type(map.param1) == "function" then
|
||||
dprint("map param1 by function")
|
||||
mappednode.param1 = map.param1(node)
|
||||
else
|
||||
mappednode.param1 = map.param1
|
||||
dprint("map param1 by value")
|
||||
end
|
||||
end
|
||||
|
||||
if map.param2 then
|
||||
if type(map.param2) == "function" then
|
||||
dprint("map param2 by function")
|
||||
mappednode.param2 = map.param2(map)
|
||||
else
|
||||
dprint("map param2 by value")
|
||||
mappednode.param2 = map.param2
|
||||
end
|
||||
end
|
||||
|
||||
return mappednode
|
||||
end
|
||||
|
||||
|
||||
-- Nodes replacement to customizie buildings
|
||||
-- TODO: should be editable in game trough a nice gui, to customize the building before build
|
||||
local __customize = function(self)
|
||||
local map = townchest.nodes.customize_data[self.name]
|
||||
if not map then -- no mapping. return unchanged
|
||||
return self
|
||||
end
|
||||
-- dprint("map", self.name, "to", map.name, map.matname)
|
||||
local mappednode = townchest.nodes.new(self)
|
||||
if map.name then
|
||||
mappednode.name = map.name
|
||||
end
|
||||
if map.matname then
|
||||
mappednode.matname = map.matname
|
||||
end
|
||||
|
||||
if map.meta then
|
||||
if not mappednode.meta then
|
||||
mappednode.meta = {}
|
||||
end
|
||||
for k, v in pairs(map.meta) do
|
||||
mappednode.meta[k] = v
|
||||
end
|
||||
end
|
||||
return mappednode
|
||||
end
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------
|
||||
-- towntest_chest.mapping.mapnode Take filters and actions on nodes before building. Currently the payment item determination and check for registred node only
|
||||
-- node - Node (from file) to check if buildable and payable
|
||||
-- return - node with enhanced informations
|
||||
-----------------------------------------------
|
||||
local __map = function(self)
|
||||
|
||||
local node_chk = minetest.registered_nodes[self.name]
|
||||
|
||||
if not node_chk then
|
||||
local fallbacknode = self:map_unknown()
|
||||
if fallbacknode then
|
||||
return fallbacknode:map()
|
||||
end
|
||||
else
|
||||
-- known node Map them?
|
||||
local customizednode = self:customize()
|
||||
|
||||
if customizednode.name == "" then --disabled by mapping
|
||||
return nil
|
||||
end
|
||||
|
||||
if not customizednode.matname then --no matname override customizied.
|
||||
|
||||
--Check for price or if it is free
|
||||
local recipe = minetest.get_craft_recipe(node_chk.name)
|
||||
if (node_chk.groups.not_in_creative_inventory and --not in creative
|
||||
not (node_chk.groups.not_in_creative_inventory == 0) and
|
||||
(not recipe or not recipe.items)) --and not craftable
|
||||
or
|
||||
(not node_chk.description or node_chk.description == "") then -- no description
|
||||
if node_chk.drop and node_chk.drop ~= "" then
|
||||
-- use possible drop as payment
|
||||
if type(node_chk.drop) == "table" then -- drop table
|
||||
customizednode.matname = node_chk.drop[1] -- use the first one
|
||||
else
|
||||
customizednode.matname = node_chk.drop
|
||||
end
|
||||
else --something not supported, but known
|
||||
customizednode.matname = _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
|
||||
customizednode.matname = customizednode.name
|
||||
end
|
||||
end
|
||||
return customizednode
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------
|
||||
-- is_equal_meta - compare meta information of 2 nodes
|
||||
-- name - Node name to check and map
|
||||
-- return - item name used as payment
|
||||
-----------------------------------------------
|
||||
local __is_equal_meta = function(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 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
|
||||
|
||||
-- door compatibility. Seems the old doors was facedir and now the wallmounted values should be used
|
||||
local __param2_wallmounted_to_facedir = function(self)
|
||||
if self.param2 == 0 then -- +y?
|
||||
return 0
|
||||
elseif self.param2 == 1 then -- -y?
|
||||
return 1
|
||||
elseif self.param2 == 2 then --unsure
|
||||
return 3
|
||||
elseif self.param2 == 3 then --unsure
|
||||
return 1
|
||||
elseif self.param2 == 4 then --unsure
|
||||
return 2
|
||||
elseif self.param2 == 5 then --unsure
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local __id = function(this)
|
||||
return minetest.pos_to_string(this)
|
||||
end
|
||||
|
||||
|
||||
|
||||
local 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"}}},param2 = __param2_wallmounted_to_facedir} --closed
|
||||
u["doors:door_wood_b_o"] = {name = "doors:door_wood_b", {["meta"] = {["fields"] = {["state"] = "1"}}},param2 = __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"}}},param2 = __param2_wallmounted_to_facedir} --closed
|
||||
u["doors:door_glass_b_o"] = {name = "doors:door_glass_b", {["meta"] = {["fields"] = {["state"] = "1"}}},param2 = __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"}}},param2 = __param2_wallmounted_to_facedir} --closed
|
||||
u["doors:door_steel_b_o"] = {name = "doors:door_steel_b", {["meta"] = {["fields"] = {["state"] = "1"}}},param2 = __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 = {}
|
||||
-- "name" and "matname" are optional.
|
||||
-- if name is missed it will not be changed
|
||||
-- if matname 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", matname = "default:wood" }
|
||||
|
||||
c["beds:bed_top"] = { matname = _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"] = { matname = "bucket:bucket_lava" }
|
||||
c["default:river_water_source"] = { matname = "bucket:bucket_river_water" }
|
||||
c["default:water_source"] = { matname = "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"] = { matname = "default:dirt" }
|
||||
c["default:dirt_with_grass"] = { matname = "default:dirt" }
|
||||
c["default:dirt_with_snow"] = { matname = "default:dirt" }
|
||||
|
||||
|
||||
townchest.nodes = {
|
||||
-- We need a free item that always available to get visible working on them
|
||||
c_free_item = _c_free_item,
|
||||
unknown_nodes_data = u,
|
||||
customize_data = c,
|
||||
is_equal_meta = __is_equal_meta
|
||||
}
|
||||
|
||||
townchest.nodes.new = function(nodelike)
|
||||
local this = {}
|
||||
if nodelike then
|
||||
this = nodelike --!by reference It will remain the same node, but just
|
||||
end
|
||||
this.id = __id
|
||||
this.map_unknown = __map_unknown
|
||||
this.customize = __customize
|
||||
this.map = __map
|
||||
this.param2_wallmounted_to_facedir = __param2_wallmounted_to_facedir
|
||||
return this
|
||||
end
|
195
npcf-worker.lua
195
npcf-worker.lua
@ -1,4 +1,5 @@
|
||||
local dprint = townchest.dprint --debug
|
||||
local dprint = townchest.dprint_off --debug
|
||||
local dprint = townchest.dprint
|
||||
|
||||
local MAX_SPEED = 5
|
||||
local BUILD_DISTANCE = 3
|
||||
@ -59,50 +60,55 @@ local select_chest = function(self)
|
||||
end
|
||||
|
||||
|
||||
local get_if_buildable = function(self, realpos)
|
||||
local get_if_buildable = function(self, realpos, node_prep)
|
||||
local pos = self.chest.plan:get_plan_pos(realpos)
|
||||
-- dprint("in plan", minetest.pos_to_string(pos))
|
||||
local node = self.chest.plan.building_full[minetest.pos_to_string(pos)]
|
||||
if not node then
|
||||
return nil
|
||||
local node
|
||||
if node_prep then
|
||||
node = node_prep
|
||||
else
|
||||
node = self.chest.plan:prepare_node_for_build(pos, realpos)
|
||||
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:set_node_processed(node)
|
||||
|
||||
if not node then
|
||||
-- remove something crufty
|
||||
self.chest.plan:remove_node(pos)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- check if already build (skip the most air)
|
||||
local success = minetest.forceload_block(realpos) --keep the target node loaded
|
||||
if not success then
|
||||
dprint("error forceloading:", minetest.pos_to_string(realpos))
|
||||
-- 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)
|
||||
minetest.forceload_free_block(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("check ignored")
|
||||
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.nodes.c_free_item -- adjust params for free
|
||||
-- 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:set_node_processed(node)
|
||||
self.chest.plan:remove_node(pos)
|
||||
return nil
|
||||
elseif townchest.nodes.is_equal_meta(minetest.get_meta(realpos):to_table(), node.meta) then
|
||||
elseif townchest.mapping.is_equal_meta(minetest.get_meta(realpos):to_table(), node.meta) then
|
||||
--metadata adjustment
|
||||
self.chest.plan:set_node_processed(node)
|
||||
self.chest.plan:remove_node(pos)
|
||||
return nil
|
||||
elseif node.matname == townchest.nodes.c_free_item then
|
||||
elseif node.matname == townchest.mapping.c_free_item then
|
||||
-- TODO: check if nearly nodes are already built
|
||||
return node
|
||||
else
|
||||
@ -141,14 +147,14 @@ local function prefer_target(npc, t1, t2)
|
||||
|
||||
-- prefer the last target node
|
||||
if npc.targetnode then
|
||||
if t1.x == npc.targetnode.pos.x and
|
||||
t1.y == npc.targetnode.pos.y and
|
||||
t1.z == npc.targetnode.pos.z 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.x == npc.targetnode.pos.x and
|
||||
t2.y == npc.targetnode.pos.y and
|
||||
t2.z == npc.targetnode.pos.z then
|
||||
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
|
||||
@ -161,9 +167,17 @@ local function prefer_target(npc, t1, t2)
|
||||
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 + 1
|
||||
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
|
||||
@ -171,7 +185,7 @@ local function prefer_target(npc, t1, t2)
|
||||
|
||||
-- prefer lower node if not air
|
||||
if t2.name ~= "air" then
|
||||
t2_c.y = t2_c.y + 1
|
||||
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
|
||||
@ -179,12 +193,12 @@ local function prefer_target(npc, t1, t2)
|
||||
|
||||
-- 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) < 2 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) < 2 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
|
||||
@ -203,74 +217,97 @@ 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=math.floor(npcpos.x)-5, math.floor(npcpos.x)+5 do
|
||||
for y=math.floor(npcpos.y)-5, math.floor(npcpos.y)+5 do
|
||||
for z=math.floor(npcpos.z)-5, math.floor(npcpos.z)+5 do
|
||||
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 = plan:get_world_pos(node)
|
||||
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
|
||||
-- get the old target to compare
|
||||
if self.targetnode and self.targetnode.pos then
|
||||
selectednode = get_if_buildable(self, self.targetnode.pos)
|
||||
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")
|
||||
for idx, nodeplan in ipairs(plan:get_nodes_from_chunk(plan:get_plan_pos(npcpos))) do
|
||||
local node = get_if_buildable(self, plan:get_world_pos(nodeplan))
|
||||
|
||||
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 wpos = plan:get_world_pos(nodeplan)
|
||||
local node = get_if_buildable(self, wpos, nodeplan.node)
|
||||
if node then
|
||||
node.pos = plan:get_world_pos(node)
|
||||
node.pos = 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
|
||||
--get anything - with forceloading, so the NPC can go away
|
||||
dprint("get node with random jump")
|
||||
local jump = plan.building_size
|
||||
if jump > 1000 then
|
||||
jump = 1000
|
||||
end
|
||||
if jump > 1 then
|
||||
jump = math.floor(math.random(jump))
|
||||
else
|
||||
jump = 0
|
||||
end
|
||||
|
||||
local startingnode = plan:get_nodes(1,jump)
|
||||
if startingnode[1] then -- the one node given
|
||||
dprint("---check chunk", minetest.pos_to_string(startingnode[1]))
|
||||
for idx, nodeplan in ipairs(plan:get_nodes_from_chunk(startingnode[1])) do
|
||||
local node_wp = plan:get_world_pos(nodeplan)
|
||||
local node = get_if_buildable(self, node_wp)
|
||||
if node then
|
||||
node.pos = node_wp
|
||||
selectednode = prefer_target(self, selectednode, node)
|
||||
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(plan:get_plan_pos(random_pos))
|
||||
dprint("Chunk loaeded: nodes:", #chunk_nodes)
|
||||
|
||||
for idx, nodeplan in ipairs(chunk_nodes) do
|
||||
local node = get_if_buildable(self, plan:get_world_pos(nodeplan), nodeplan.node)
|
||||
if node then
|
||||
node.pos = plan:get_world_pos(node)
|
||||
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 startningnode")
|
||||
dprint("something wrong with random_pos")
|
||||
end
|
||||
end
|
||||
|
||||
if selectednode then
|
||||
selectednode.pos = plan:get_world_pos(selectednode)
|
||||
assert(selectednode.pos, "BUG: a position should exists")
|
||||
return selectednode
|
||||
else
|
||||
dprint("no next node found", plan.building_size)
|
||||
if plan.building_size == 0 then
|
||||
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()
|
||||
@ -287,7 +324,7 @@ npcf:register_npc("townchest:npcf_builder" ,{
|
||||
self.timer = 0
|
||||
select_chest(self)
|
||||
self.target_prev = self.targetnode
|
||||
if self.chest and self.chest.plan and self.chest.plan.building_size > 0 then
|
||||
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
|
||||
@ -328,7 +365,8 @@ npcf:register_npc("townchest:npcf_builder" ,{
|
||||
yaw = npcf:get_face_direction(pos, self.targetnode.pos)
|
||||
-- target reached build
|
||||
if target_distance <= BUILD_DISTANCE and self.dest_type == "build" then
|
||||
-- do the build
|
||||
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
|
||||
@ -343,7 +381,7 @@ npcf:register_npc("townchest:npcf_builder" ,{
|
||||
if self.targetnode.meta then
|
||||
minetest.env:get_meta(self.targetnode.pos):from_table(self.targetnode.meta)
|
||||
end
|
||||
self.chest.plan:set_node_processed(self.targetnode)
|
||||
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}
|
||||
@ -373,8 +411,10 @@ npcf:register_npc("townchest:npcf_builder" ,{
|
||||
self.dest_type = "home_reached"
|
||||
self.targetnode = nil
|
||||
else
|
||||
--target not reached
|
||||
--target not reached -- route
|
||||
state = NPCF_ANIM_WALK
|
||||
self.path = minetest.find_path(pos, self.targetnode.pos, 10, 1, 5, "A*")
|
||||
|
||||
-- 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
|
||||
@ -388,14 +428,19 @@ npcf:register_npc("townchest:npcf_builder" ,{
|
||||
end
|
||||
|
||||
-- teleport in direction in case of stucking
|
||||
if (last_distance - 0.01) <= target_distance and self.laststep == "walk" and
|
||||
(self.target_prev == self.targetnode) then
|
||||
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
|
||||
@ -408,6 +453,10 @@ npcf:register_npc("townchest:npcf_builder" ,{
|
||||
else
|
||||
dprint("no target")
|
||||
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)
|
||||
|
396
plan.lua
396
plan.lua
@ -1,176 +1,238 @@
|
||||
local dprint = townchest.dprint_off --debug
|
||||
|
||||
local __add_node = function(this,node)
|
||||
-- add to the full list
|
||||
|
||||
this.building_full[node:id()] = node --collect references for direct access
|
||||
this.building_size = this.building_size + 1
|
||||
end
|
||||
|
||||
|
||||
local __adjust_flatting_requrement = function(this,node)
|
||||
-- create relative sizing information
|
||||
if not this.relative.min_x or this.relative.min_x > node.x then
|
||||
this.relative.min_x = node.x
|
||||
end
|
||||
if not this.relative.max_x or this.relative.max_x < node.x then
|
||||
this.relative.max_x = node.x
|
||||
end
|
||||
if not this.relative.min_y or this.relative.min_y > node.y then
|
||||
this.relative.min_y = node.y
|
||||
end
|
||||
if not this.relative.max_y or this.relative.max_y < node.y then
|
||||
this.relative.max_y = node.y
|
||||
end
|
||||
if not this.relative.min_z or this.relative.min_z > node.z then
|
||||
this.relative.min_z = node.z
|
||||
end
|
||||
if not this.relative.max_z or this.relative.max_z < node.z then
|
||||
this.relative.max_z = node.z
|
||||
end
|
||||
|
||||
-- create ground level information
|
||||
if string.sub(node.name, 1, 18) == "default:dirt_with_" or
|
||||
node.name == "farming:soil_wet" then
|
||||
if not this.relative.groundnode_count then
|
||||
this.relative.groundnode_count = 1
|
||||
this.relative.ground_y = node.y + 1
|
||||
else
|
||||
this.relative.groundnode_count = this.relative.groundnode_count + 1
|
||||
this.relative.ground_y = this.relative.ground_y + (node.y + 1 - this.relative.ground_y) / this.relative.groundnode_count
|
||||
dprint("ground calc:", node.name, this.relative.groundnode_count, node.y, this.relative.ground_y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local __prepare = function(this)
|
||||
-- round ground level to full block
|
||||
this.relative.ground_y = math.floor(this.relative.ground_y)
|
||||
local additional = 0
|
||||
local add_max = 5
|
||||
|
||||
dprint("create flatting plan")
|
||||
for y = this.relative.min_y, this.relative.max_y + 5 do -- with additional 5 on top
|
||||
--calculate additional grounding
|
||||
if y >= this.relative.ground_y then --only over ground
|
||||
local high = y-this.relative.ground_y
|
||||
additional = high+1
|
||||
if additional > add_max then --set to max
|
||||
additional = add_max
|
||||
end
|
||||
dprint("additional flat", y, additional)
|
||||
end
|
||||
for x = this.relative.min_x - additional, this.relative.max_x + additional do
|
||||
for z = this.relative.min_z - additional, this.relative.max_z + additional do
|
||||
if not this.building_full[minetest.pos_to_string({x=x, y=y, z=z})] then -- not in plan - flat them
|
||||
local node = townchest.nodes.new({ x=x, y=y, z=z, name="air", matname = townchest.nodes.c_free_item })
|
||||
this:add_node(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
dprint("flatting plan done")
|
||||
end
|
||||
|
||||
|
||||
local __get_world_pos = function(this,pos)
|
||||
local fpos = { x=pos.x+this.chest.pos.x,
|
||||
y=pos.y+this.chest.pos.y - this.relative.ground_y,
|
||||
z=pos.z+this.chest.pos.z
|
||||
}
|
||||
-- dprint("world_pos y:"..pos.y.."+"..this.chest.pos.y.."-"..this.relative.ground_y)
|
||||
return fpos
|
||||
end
|
||||
-- revert get_world_pos
|
||||
local __get_plan_pos = function(this,pos)
|
||||
local fpos = { x=pos.x-this.chest.pos.x,
|
||||
y=pos.y-this.chest.pos.y + this.relative.ground_y,
|
||||
z=pos.z-this.chest.pos.z
|
||||
}
|
||||
-- dprint("plan_pos y:"..pos.y.."-"..this.chest.pos.y.."+"..this.relative.ground_y)
|
||||
return fpos
|
||||
end
|
||||
|
||||
|
||||
local __get_nodes = function(this, count, skip)
|
||||
local ret = {}
|
||||
local counter = 0
|
||||
if not skip then
|
||||
skip = 0
|
||||
end
|
||||
|
||||
for key, node in pairs(this.building_full) do
|
||||
counter = counter + 1
|
||||
if counter > skip then
|
||||
table.insert(ret, node)
|
||||
end
|
||||
if counter >= count + skip then
|
||||
break
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
-- to be able working with forceload chunks
|
||||
local __get_nodes_from_chunk = function(this, node)
|
||||
|
||||
-- calculate the begin of the chunk
|
||||
--local BLOCKSIZE = core.MAP_BLOCKSIZE
|
||||
local BLOCKSIZE = 16
|
||||
|
||||
local wpos = this:get_world_pos(node)
|
||||
wpos.x = (math.floor(wpos.x/BLOCKSIZE))*BLOCKSIZE
|
||||
wpos.y = (math.floor(wpos.y/BLOCKSIZE))*BLOCKSIZE
|
||||
wpos.z = (math.floor(wpos.z/BLOCKSIZE))*BLOCKSIZE
|
||||
|
||||
dprint("nodes for chunk (wpos)", wpos.x, wpos.y, wpos.z)
|
||||
local vpos = this:get_plan_pos(wpos)
|
||||
dprint("nodes for chunk (vpos)", vpos.x, vpos.y, vpos.z)
|
||||
|
||||
local ret = {}
|
||||
for x = vpos.x, vpos.x + BLOCKSIZE do
|
||||
for y = vpos.y, vpos.y + BLOCKSIZE do
|
||||
for z = vpos.z, vpos.z + BLOCKSIZE do
|
||||
--local node = this.building_full[node:id()]
|
||||
local node = this.building_full[minetest.pos_to_string({x=x,y=y,z=z})]
|
||||
if node then
|
||||
table.insert(ret, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
local __set_node_processed = function(this, node)
|
||||
this.building_full[node:id()] = nil
|
||||
this.building_size = this.building_size - 1
|
||||
end
|
||||
|
||||
|
||||
--local dprint = townchest.dprint--debug
|
||||
|
||||
townchest.plan = {}
|
||||
|
||||
townchest.plan.new = function( chest )
|
||||
local this = {}
|
||||
this.relative = {} --relative infos
|
||||
this.relative.ground_y = 0
|
||||
local self = {}
|
||||
self.chest = chest
|
||||
|
||||
this.chest = chest
|
||||
-- helper: get scm entry for position
|
||||
function self.get_scm_node(self, pos)
|
||||
assert(pos.x, "pos without xyz")
|
||||
if not self.data.scm_data_cache[pos.y] then
|
||||
return nil
|
||||
end
|
||||
if not self.data.scm_data_cache[pos.y][pos.x] then
|
||||
return nil
|
||||
end
|
||||
if not self.data.scm_data_cache[pos.y][pos.x][pos.z] then
|
||||
return nil
|
||||
end
|
||||
return self.data.scm_data_cache[pos.y][pos.x][pos.z]
|
||||
end
|
||||
|
||||
-- full plan - key-indexed - equvalent to the we-file
|
||||
this.building_full = {}
|
||||
this.building_size = 0
|
||||
-- "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
|
||||
|
||||
this.add_node = __add_node
|
||||
this.adjust_flatting_requrement = __adjust_flatting_requrement
|
||||
this.prepare = __prepare
|
||||
this.get_world_pos = __get_world_pos
|
||||
this.get_plan_pos = __get_plan_pos
|
||||
this.get_nodes = __get_nodes
|
||||
this.set_node_processed = __set_node_processed
|
||||
this.get_nodes_from_chunk = __get_nodes_from_chunk
|
||||
return this
|
||||
|
||||
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 not self:get_scm_node(airnode) 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 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)
|
||||
wpos.x = (math.floor(wpos.x/BLOCKSIZE))*BLOCKSIZE
|
||||
wpos.y = (math.floor(wpos.y/BLOCKSIZE))*BLOCKSIZE
|
||||
wpos.z = (math.floor(wpos.z/BLOCKSIZE))*BLOCKSIZE
|
||||
|
||||
dprint("nodes for chunk (wpos)", wpos.x, wpos.y, wpos.z)
|
||||
local vpos = self:get_plan_pos(wpos)
|
||||
dprint("nodes for chunk (vpos)", vpos.x, vpos.y, vpos.z)
|
||||
|
||||
local ret = {}
|
||||
for y = vpos.y, vpos.y + BLOCKSIZE do
|
||||
if self.data.scm_data_cache[y] then
|
||||
for x = vpos.x, vpos.x + BLOCKSIZE do
|
||||
if self.data.scm_data_cache[y][x] then
|
||||
for z = vpos.z, vpos.z + BLOCKSIZE do
|
||||
if self.data.scm_data_cache[y][x][z] then
|
||||
table.insert(ret, {x=x,y=y,z=z, node=self:prepare_node_for_build({x=x,y=y,z=z}, wpos)})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function self.remove_node(self, pos)
|
||||
-- cleanup raw data
|
||||
if self.data.scm_data_cache[pos.y] then
|
||||
if self.data.scm_data_cache[pos.y][pos.x]then
|
||||
if self.data.scm_data_cache[pos.y][pos.x][pos.z] 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] then
|
||||
if self.data.prepared_cache[pos.y][pos.x]then
|
||||
if self.data.prepared_cache[pos.y][pos.x][pos.z] 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
|
||||
|
||||
|
||||
-- prepare node for build
|
||||
function self.prepare_node_for_build(self, pos, wpos)
|
||||
-- first run, generate mapping data
|
||||
if not self.data.mappednodes then
|
||||
townchest.mapping.do_mapping(self.data)
|
||||
end
|
||||
|
||||
-- get from cache
|
||||
if self.data.prepared_cache and
|
||||
self.data.prepared_cache[pos.y] and
|
||||
self.data.prepared_cache[pos.y][pos.x] and
|
||||
self.data.prepared_cache[pos.y][pos.x][pos.z] 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 not scm_node then
|
||||
return nil
|
||||
end
|
||||
|
||||
--get mapping data
|
||||
local map = self.data.mappednodes[scm_node.name_id]
|
||||
if not map then
|
||||
return nil
|
||||
end
|
||||
|
||||
local node = townchest.mapping.merge_map_entry(map, scm_node)
|
||||
|
||||
if node.custom_function 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
|
||||
|
@ -155,7 +155,6 @@ local _build_status = function(state)
|
||||
print("BUG: no plan in build_status dialog!")
|
||||
return false -- no update
|
||||
end
|
||||
local relative = chest.plan.relative
|
||||
|
||||
-- create screen
|
||||
state:size(10,5)
|
||||
@ -180,7 +179,7 @@ local _build_status = function(state)
|
||||
end
|
||||
set_dynamic_values()
|
||||
chest:persist_info()
|
||||
chest:run_async(chest.instant_build_chain)
|
||||
chest:run_async(chest.instant_build_chunk)
|
||||
end)
|
||||
|
||||
-- NPC build button
|
||||
@ -212,12 +211,14 @@ local _build_status = function(state)
|
||||
elseif chest.info.taskname == "generate" then
|
||||
l1:setText("Simple task: "..chest.info.genblock.variant_name)
|
||||
end
|
||||
l2:setText("Size: "..(relative.max_x-relative.min_x).." x "..(relative.max_z-relative.min_z))
|
||||
l3:setText("Building high: "..(relative.max_y-relative.min_y).." Ground high: "..(relative.ground_y-relative.min_y))
|
||||
local size = vector.add(vector.subtract(chest.plan.data.max_pos, chest.plan.data.min_pos),1)
|
||||
|
||||
l2:setText("Size: "..size.x.." x "..size.z)
|
||||
l3:setText("Building high: "..size.y.." Ground high: "..(chest.plan.data.ground_y-chest.plan.data.min_pos.y))
|
||||
|
||||
--update data on demand without rebuild the state
|
||||
set_dynamic_values = function()
|
||||
l4:setText("Nodes to do: "..chest.plan.building_size)
|
||||
l4:setText("Nodes to do: "..chest.plan.data.nodecount)
|
||||
|
||||
if chest.info.npc_build == true then
|
||||
npc_tg:setId(2)
|
||||
|
@ -1,122 +0,0 @@
|
||||
--- Schematic serialization and deserialiation.
|
||||
-- @module worldedit.serialization
|
||||
|
||||
--worldedit.LATEST_SERIALIZATION_VERSION = 5
|
||||
--local LATEST_SERIALIZATION_HEADER = worldedit.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 townchest.we_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 townchest.we_load_schematic(value)
|
||||
local version, header, content = townchest.we_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
|
Loading…
x
Reference in New Issue
Block a user