Big update around persistance
parent
284b2eba05
commit
c43bc12e40
102
README.md
102
README.md
|
@ -17,10 +17,19 @@ License: LGPLv2
|
|||
## data types (and usual names):
|
||||
- node_obj - object representing a node in plan
|
||||
- plan_obj - object representing a whole plan
|
||||
- plan_pos - a relative position vector used in plan. Note: the relative 0,0,0 in plan will be the placed to the defined world_pos position
|
||||
- world_pos - a absolute ("real") position vector in the world
|
||||
- plan_meta_obj - Simplified plan object that does not contain nodes data
|
||||
- plan_pos - a relative position vector used in plan. Note: the relative
|
||||
- world_pos - a absolute ("real") position vector in the world
|
||||
- anchor_pos - World position assigned to plan for plan_pos<=>world_pos calculations
|
||||
|
||||
## Plan object
|
||||
The plan is the building draft that can be readed from file or be generated.
|
||||
The plan manages the nodes and some attributes how the target building should be placed.
|
||||
The plan does have two stages, the first is the preparation stage the plan does not have
|
||||
an anchor to the "real world". In this stage no world interactions are possible.
|
||||
The second stage is after the plan get the anchor attribute set.
|
||||
In this stage the world interaction methods like place node are possible.
|
||||
|
||||
### class-methods
|
||||
- plan_obj = schemlib.plan.new([plan_id][,anchor_pos]) - Constructor - create a new plan object
|
||||
|
||||
|
@ -39,75 +48,118 @@ License: LGPLv2
|
|||
- plan_obj:del_node(plan_pos) - delete a node from plan
|
||||
- plan_obj:get_random_plan_pos() - get a random existing plan_pos from plan
|
||||
- plan_obj:read_from_schem_file(file) - read from WorldEdit or mts file
|
||||
- plan_obj:apply_flood_with_air
|
||||
(add_max, add_min, add_top) - Fill a building with air
|
||||
- plan_obj:propose_anchor(world_pos, bool, add_xz)
|
||||
- propose anchor pos nearly given world_pos to be placed.
|
||||
if bool is given true a check will be done to prevent overbuilding of existing structures
|
||||
additional space to check for all sites can be given by add_xz (default 3)
|
||||
- returns "false, world_pos" in case of error. The world_pos is the issued not buildable position in this case
|
||||
- plan_obj:apply_flood_with_air
|
||||
(add_max, add_min, add_top) - Fill a building with air
|
||||
- plan_obj:get_status() - get the plan status. Returns values are "new", "build" and "finished"
|
||||
- plan_obj:set_status(status) - set the plan status. Created plan is new, allowed new stati are "build" and "finished"
|
||||
|
||||
#### Methods interferring with the real world (anchor_pos exists or needs to be given optional)
|
||||
- plan_obj:get_world_pos(plan_pos[,anchor_pos]) - get a world position for a plan position
|
||||
- plan_obj:get_plan_pos(world_pos[,anchor_pos]) - get a plan position for a world position
|
||||
- plan_obj:get_world_minp([anchor_pos]) - get lowest world position
|
||||
- plan_obj:get_world_maxp([anchor_pos]) - get highest world position
|
||||
- plan_obj:contains(world_pos[,anchor_pos]) - check if the given world position is in the plan
|
||||
- plan_obj:get_plan_pos(world_pos[,anchor_pos]) - get a plan position for a world position
|
||||
- plan_obj:check_overlap(pos1, pos2[,add_distance][,anchor_pos]) - check if the plan overlap the area in pos1/pos2
|
||||
- plan_obj:get_chunk_nodes(plan_pos[,anchor_pos]) - get a list of all nodes from chunk of a pos
|
||||
- plan_obj:do_add_chunk_place(plan_pos) - Place all nodes for chunk in real world using add_node()
|
||||
- plan_obj:load_region(min_world_pos[, max_world_pos]) - Load a Voxel-Manip for faster lookups to the real world
|
||||
- plan_obj:do_add_chunk_voxel(plan_pos) - Place all nodes for chunk in real world using voxelmanip after emerge area
|
||||
- plan_obj:do_add_chunk_mapgen() - Place all nodes for current chunk in on_mapgen. Used internally for registered chunks in plan.mapgen_process
|
||||
- plan_obj:do_add_all_voxel_async() - Place all plan nodes in multiple async calls of do_add_chunk_voxel()
|
||||
- plan_obj:do_add_all_mapgen_async() - Register all nodes for mapgen processing
|
||||
- plan_obj:load_region(min_world_pos[, max_world_pos]) - Load a Voxel-Manip for faster lookups to the real world
|
||||
|
||||
### Hooks
|
||||
- plan_obj:on_status() - if defined, is called from get_plan_status() to get custom updates
|
||||
#### Processing related
|
||||
- plan_obj:get_status() - get the plan status. Returns values are "new", "build", "finished" or custom value like "pause"
|
||||
- plan_obj:set_status(status) - set the plan status. Created plan is new, allowed new stati are "build" and "finished"
|
||||
- status = "new" - Plan is in design mode
|
||||
- status = "build" - do_add_all_voxel_async is running
|
||||
- status = "finished" - Processing is done
|
||||
|
||||
### Attributes
|
||||
- plan_obj.plan_id - a id of the plan
|
||||
- plan_obj.status - plan status
|
||||
- plan_obj.anchor_pos - position vector in world
|
||||
- plan_obj.data.nodeinfos - a list of node information for name_id with counter (list={pos_hash,...}, count=1})
|
||||
- plan_obj.data.ground_y - explicit ground adjustment for anchor_pos
|
||||
- plan_obj.data.nodecount - count of the nodes in plan
|
||||
- plan_obj.data.groundnode_count - count of nodes found for ground_y determination (internal)
|
||||
- plan_obj.data.status - plan status
|
||||
- plan_obj.data.min_pos - minimal {x,y,z} vector
|
||||
- plan_obj.data.max_pos - maximal {x,y,z} vector
|
||||
- plan_obj.facedir - Plan rotation - x+ axis supported only (values 0-3)
|
||||
- plan_obj.mirrored - (bool) Mirrored build - mirror to z-axis. Note: if you need x-axis mirror - just rotate the building by 2 in addition
|
||||
- plan_obj.data.groundnode_count - count of nodes found for ground_y determination (internal)
|
||||
- plan_obj.data.nodecount - count of the nodes in plan
|
||||
- plan_obj.data.nodeinfos - a list of node information for name_id with counter (list={pos_hash,...}, count=1})
|
||||
- plan_obj.data.ground_y - explicit ground adjustment for anchor_pos
|
||||
- plan_obj.data.facedir - Plan rotation - x+ axis supported only (values 0-3)
|
||||
- plan_obj.data.mirrored - (bool) Mirrored build - mirror to z-axis. Note: if you need x-axis mirror - just rotate the building by 2 in addition
|
||||
- plan_obj.data.anchor_pos - position vector in world
|
||||
- plan_obj.data.npc_build - <schemlib_npc>: if true, the building is allowed to be build by schemlib_npc
|
||||
|
||||
## Node object
|
||||
The node object represents one node on plan. This object does manage node rotations,
|
||||
mapping to costs item and compatibility mapping for older worldedit files.
|
||||
A node is usually assigned to a plan, the most methods require the plan assignment.
|
||||
|
||||
### class-methods
|
||||
- node_obj = schemlib.plan.new(data) - Constructor - create a new node object with given data
|
||||
- node_obj = schemlib.plan.new([plan_id], [anchor_pos]) - Constructor - create a new node object. plan_id and anchor_pos is optional
|
||||
|
||||
### object-methods
|
||||
- node_obj:get_world_pos() - nodes assigned to plan only
|
||||
- node_obj:rotate_facedir(facedir) - rotate the node - is internally used for plan rotation in get_mapped() - supported 0-3 (x+ axis) only
|
||||
- node_obj:get_mapped() - get mapped data for this node as it should be placed - returns a table {name=, param2=, meta=, content_id=, node_def=, final_nod_name=}
|
||||
- name, param2, meta - data used to place node
|
||||
- content_id, node_def - game references, VoxelMap ID and registered_nodes definition
|
||||
- final_node_name - if set, the node is not deleted from plan by place(). Contains the node name to be placed at the end. used for replacing by air before build the node
|
||||
- world_node_name - contains the node name currently placed to the world
|
||||
- param2_plan_rotation - param2 value before rotation
|
||||
- node_obj:place() - place node to world using "add_node" and remove them from plan
|
||||
- node_obj:remove_from_plan() - remove this node from plan
|
||||
- node_obj:get_under() - returns the node under this one if exists in plan
|
||||
- node_obj:get_above() - returns the node above this one if exists in plan
|
||||
- node_obj:get_attached_to() - returns the position the node is attached to
|
||||
- node_obj:rotate_facedir(facedir) - rotate the node - is internally used for plan rotation in get_mapped() - supported 0-3 (x+ axis) only
|
||||
- node_obj:place() - place node to world using "add_node" and remove them from plan
|
||||
- node_obj:remove_from_plan() - remove this node from plan
|
||||
|
||||
### object-attributes
|
||||
- node_obj.name - original node name without mapping
|
||||
- node_obj.data - table with original param2 / meta / prob
|
||||
|
||||
assigned in plan:add_node(plan_pos, node_obj) method
|
||||
- node_obj.plan - assigned plan
|
||||
- node_obj.nodeinfo - assigned nodeinfo in plan
|
||||
|
||||
Node mapping functions
|
||||
- schemlib.mapping.is_equal_meta(data,data) - Recursivelly compare data. Primary developed to check node metadata for changes
|
||||
- schemlib.mapping.map_unknown(item_name) - Internally used to map unknown nodes
|
||||
- schemlib.mapping.map(name, plan) - Get mapping informations without any callback calls
|
||||
|
||||
|
||||
## Plan Manager object
|
||||
The plan manager does manage the persistance for WIP plans. Also the manager allow to check overlaps to other buildings.
|
||||
Usually the manager does not store the full plan but the parameters including file how to load or
|
||||
generate the plan again. The work can be resumed by starting new because
|
||||
already placed nodes / chunks are passed
|
||||
The plan manager is a singleton that means you have only one instance in game. Therefore implemented as functions
|
||||
|
||||
### Functions
|
||||
- schemlib.plan_manager.get_plan_meta(plan_id) - Get Meta-Plan-Object
|
||||
- schemlib.plan_manager.get_plan(plan_id) - Get Plan Object from persistance manager
|
||||
- schemlib.plan_manager.set_plan(plan_obj) - Add plan to persistance manager. Note, the definitely plan_obj.plan_id must be set.
|
||||
- schemlib.plan_manager.delete_plan(plan_id) - Remove plan from persistance manager
|
||||
|
||||
### plan_meta_class
|
||||
Simplified plan class that works on metadata only, without nodes operations. Next methods are available:
|
||||
adjust_building_info, get_world_pos, get_plan_pos, get_world_minp, get_world_maxp, contains, check_overlap
|
||||
|
||||
### Plan manager settings
|
||||
Note: all modified plans will be saved on shutdown independing on this parameters
|
||||
|
||||
#### schemlib.save_interval (Plan manager save interval) int 10 0
|
||||
Save interval in seconds for plan changes in manager. 0 disables the interval saving.
|
||||
|
||||
#### schemlib.save_maxnodes (Maximum building size for autosave) int 10000 0
|
||||
Maximum building size in nodes that are handled in interval save to avoid performance issue. 0 enables saving for all sizes.
|
||||
Note: all (including big) modified plans will be saved on shutdown
|
||||
|
||||
|
||||
## Builder NPC AI object
|
||||
The builder NPC AI provides the logic how to build a building by NPC.
|
||||
Basically an NPC get the next nearly buildable node from plan, and the method how to place,
|
||||
but the navigation needs to be done in NPC Framework
|
||||
|
||||
### class-methods
|
||||
- npc_ai_obj = schemlib.npc_ai.new(plan_obj, build_distance) - Constructor - create a new NPC AI handler for this plan. Build distance is the lenght of npc
|
||||
|
||||
|
@ -117,7 +169,7 @@ License: LGPLv2
|
|||
|
||||
next methods internally used in plan_target_get
|
||||
- npc_ai_obj:get_if_buildable(node_obj) - Check the node_obj if it can be built in the world. Compares if similar node already at the place
|
||||
- npc_ai_obj:get_node_rating(node, npcpos) - internally used - rate a node for importance to build at the next
|
||||
- npc_ai_obj:get_node_rating(node, npcpos) - internally used - rate a node for importance to build at the next step
|
||||
- npc_ai_obj:prefer_target(npcpos, nodeslist) - Does rating of all nodes in nodeslist and returns the highest rated node
|
||||
|
||||
### object-attributes
|
||||
|
|
4
init.lua
4
init.lua
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
schemlib = {}
|
||||
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
schemlib.modpath = modpath
|
||||
|
@ -7,8 +5,8 @@ schemlib.modpath = modpath
|
|||
schemlib.mapping = dofile(modpath.."/mapping.lua")
|
||||
schemlib.node = dofile(modpath.."/node.lua")
|
||||
schemlib.worldedit_file = dofile(modpath.."/worldedit_file.lua")
|
||||
schemlib.save_restore = dofile(modpath.."/save_restore.lua")
|
||||
schemlib.plan = dofile(modpath.."/plan.lua")
|
||||
schemlib.plan_manager = dofile(modpath.."/plan_manager.lua")
|
||||
schemlib.npc_ai = dofile(modpath.."/npc_ai.lua")
|
||||
|
||||
-- log that we started
|
||||
|
|
|
@ -136,7 +136,7 @@ local default_replacements = {
|
|||
-----------------------------------------------
|
||||
-- Handle doors mirroring (_a vs _b)
|
||||
-----------------------------------------------
|
||||
function __mirror_doors(mr)
|
||||
local function __mirror_doors(mr)
|
||||
if not mr.node_def.door then
|
||||
return
|
||||
end
|
||||
|
@ -242,7 +242,7 @@ function mapping.map(name, plan)
|
|||
local node_def = minetest.registered_nodes[mr.name]
|
||||
mr.node_def = node_def
|
||||
|
||||
if plan and plan.mirrored then
|
||||
if plan and plan.data.mirrored then
|
||||
__mirror_doors(mr)
|
||||
end
|
||||
|
||||
|
@ -310,6 +310,7 @@ minetest.after(0, function()
|
|||
mapping._airlike_contend_ids[minetest.get_content_id(name)] = name
|
||||
end
|
||||
end
|
||||
mapping._protected_content_ids[minetest.get_content_id("default:ice")] = nil --allow ice removal
|
||||
|
||||
mapping._over_surface_content_ids[minetest.get_content_id("air")] = "air"
|
||||
mapping._over_surface_content_ids[minetest.get_content_id("default:snow")] = "default:snow"
|
||||
|
|
38
node.lua
38
node.lua
|
@ -4,15 +4,14 @@ local mapping = schemlib.mapping
|
|||
-- Node class
|
||||
--------------------------------------
|
||||
local node_class = {}
|
||||
node_class.__index = node_class
|
||||
local node_class_mt = {__index = node_class}
|
||||
local node = {}
|
||||
node.node_class = node_class
|
||||
-------------------------------------
|
||||
-- Create new node
|
||||
--------------------------------------
|
||||
function node.new(data)
|
||||
local self = setmetatable({}, node_class)
|
||||
self.__index = node_class
|
||||
local self = setmetatable({}, node_class_mt)
|
||||
|
||||
self.name = data.name
|
||||
assert(self.name, "No name given for node object")
|
||||
|
@ -53,7 +52,7 @@ function node_class:rotate_facedir(facedir)
|
|||
if mapped.node_def.paramtype2 == "wallmounted" then
|
||||
local param2_dir = mapped.param2 % 8
|
||||
local param2_color = mapped.param2 - param2_dir
|
||||
if self.plan.mirrored then
|
||||
if self.plan.data.mirrored then
|
||||
param2_dir = node.rotation_wallmounted_mirrored_map[param2_dir]
|
||||
end
|
||||
mapped.param2 = node.rotation_wallmounted_map[facedir][param2_dir] + param2_color
|
||||
|
@ -61,15 +60,14 @@ function node_class:rotate_facedir(facedir)
|
|||
-- rotate facedir
|
||||
local param2_dir = mapped.param2 % 32
|
||||
local param2_color = mapped.param2 - param2_dir
|
||||
if self.plan.mirrored then
|
||||
if self.plan.data.mirrored then
|
||||
param2_dir = node.rotation_facedir_mirrored_map[param2_dir]
|
||||
end
|
||||
mapped.param2 = node.rotation_facedir_map[facedir][param2_dir] + param2_color
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------
|
||||
-- Get all information to build the node
|
||||
--------------------------------------
|
||||
|
@ -77,21 +75,20 @@ function node_class:get_mapped()
|
|||
if self.mapped == 'unknown' then
|
||||
return
|
||||
end
|
||||
|
||||
local mappedinfo = self.nodeinfo.mapped
|
||||
local mappedinfo = self.plan.mapping_cache[self.name]
|
||||
if not mappedinfo then
|
||||
mappedinfo = mapping.map(self.name, self.plan)
|
||||
self.nodeinfo.mapped = mappedinfo
|
||||
self.plan.mapping_cache[self.name] = mappedinfo
|
||||
self.mapped = nil
|
||||
end
|
||||
|
||||
if not mappedinfo or mappedinfo == 'unknown' then
|
||||
self.nodeinfo.mapped = 'unknown'
|
||||
self.plan.mapping_cache[self.name] = 'unknown'
|
||||
self.mapped = 'unknown'
|
||||
return
|
||||
end
|
||||
|
||||
if self.mapped and self.mapped.name == mappedinfo.name_orig then
|
||||
if self.mapped then
|
||||
return self.mapped
|
||||
end
|
||||
|
||||
|
@ -110,7 +107,7 @@ function node_class:get_mapped()
|
|||
self.mapped = mapped
|
||||
self.cost_item = mapped.cost_item -- workaround / backwards compatibility to npcf_builder
|
||||
|
||||
self:rotate_facedir(self.plan.facedir)
|
||||
self:rotate_facedir(self.plan.data.facedir)
|
||||
return mapped
|
||||
end
|
||||
|
||||
|
@ -122,6 +119,13 @@ function node_class:get_under()
|
|||
return self.plan:get_node({x=self._plan_pos.x, y=self._plan_pos.y-1, z=self._plan_pos.z})
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- get node above this one if exists
|
||||
--------------------------------------
|
||||
function node_class:get_above()
|
||||
return self.plan:get_node({x=self._plan_pos.x, y=self._plan_pos.y+1, z=self._plan_pos.z})
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- get plan_pos of attached node, or nil if not attached
|
||||
--------------------------------------
|
||||
|
@ -143,14 +147,6 @@ function node_class:get_attached_to()
|
|||
end
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- get node above this one if exists
|
||||
--------------------------------------
|
||||
function node_class:get_above()
|
||||
return self.plan:get_node({x=self._plan_pos.x, y=self._plan_pos.y+1, z=self._plan_pos.z})
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------
|
||||
-- add/build a node
|
||||
--------------------------------------
|
||||
|
|
12
npc_ai.lua
12
npc_ai.lua
|
@ -6,14 +6,12 @@ local mapping = schemlib.mapping
|
|||
local npc_ai = {}
|
||||
|
||||
local npc_ai_class = {}
|
||||
npc_ai_class.__index = npc_ai_class
|
||||
|
||||
local mpc_ai_class_mt = {__index = npc_ai_class}
|
||||
--------------------------------------
|
||||
-- Create NPC-AI object
|
||||
--------------------------------------
|
||||
function npc_ai.new(plan, build_distance)
|
||||
local self = setmetatable({}, npc_ai_class)
|
||||
self.__index = npc_ai_class
|
||||
local self = setmetatable({}, mpc_ai_class_mt)
|
||||
self.plan = plan
|
||||
self.build_distance = build_distance or 3
|
||||
return self
|
||||
|
@ -93,7 +91,6 @@ end
|
|||
-- Get rating for node which one should be built at next
|
||||
--------------------------------------
|
||||
function npc_ai_class:get_node_rating(node, npcpos)
|
||||
|
||||
local world_pos = node:get_world_pos()
|
||||
local mapped = node:get_mapped()
|
||||
local distance_pos = table.copy(world_pos)
|
||||
|
@ -204,7 +201,10 @@ function npc_ai_class:plan_target_get(npcpos)
|
|||
local chunk_nodes, min_world_pos, max_world_pos = self.plan:get_chunk_nodes(npcpos_plan)
|
||||
-- add last selection to the current chunk to compare
|
||||
if self.lasttarget_pos then
|
||||
table.insert(chunk_nodes, self.plan:get_node(self.lasttarget_pos))
|
||||
local last_target_node = self.plan:get_node(self.lasttarget_pos)
|
||||
if last_target_node then
|
||||
table.insert(chunk_nodes, last_target_node)
|
||||
end
|
||||
end
|
||||
dprint("Chunk loaeded: nodes:", #chunk_nodes)
|
||||
self.plan:load_region(min_world_pos, max_world_pos)
|
||||
|
|
678
plan.lua
678
plan.lua
|
@ -8,82 +8,98 @@ local node = schemlib.node
|
|||
local mapping = schemlib.mapping
|
||||
|
||||
--------------------------------------
|
||||
-- Plan class
|
||||
-- Plan class
|
||||
--------------------------------------
|
||||
local plan_class = {}
|
||||
plan_class.__index = plan_class
|
||||
|
||||
local plan_class_mt = {__index = plan_class}
|
||||
--------------------------------------
|
||||
-- Plan class-methods and attributes
|
||||
-- Plan class-methods and attributes
|
||||
--------------------------------------
|
||||
local plan = {
|
||||
mapgen_process = {}
|
||||
mapgen_process = {},
|
||||
plan_class = plan_class
|
||||
}
|
||||
local mapgen_process = plan.mapgen_process
|
||||
|
||||
--------------------------------------
|
||||
-- Create new plan object
|
||||
-- Create new plan object
|
||||
--------------------------------------
|
||||
function plan.new(plan_id , anchor_pos)
|
||||
local self = setmetatable({}, plan_class)
|
||||
self.__index = plan_class
|
||||
local self = setmetatable({}, plan_class_mt)
|
||||
self.plan_id = plan_id
|
||||
self.anchor_pos = anchor_pos
|
||||
self.facedir = 0
|
||||
self.mirrored = false
|
||||
self.scm_data_cache = {}
|
||||
self.mapping_cache = {}
|
||||
self.data = {
|
||||
status = "new",
|
||||
min_pos = {},
|
||||
max_pos = {},
|
||||
groundnode_count = 0,
|
||||
ground_y = -1, --if nothing defined, it is under the building
|
||||
scm_data_cache = {},
|
||||
nodeinfos = {},
|
||||
nodecount = 0,
|
||||
ground_y = -1, --if nothing defined, it is under the building
|
||||
|
||||
facedir = 0,
|
||||
mirrored = false,
|
||||
anchor_pos = anchor_pos,
|
||||
}
|
||||
self.status = "new"
|
||||
return self -- the plan object
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Add node to plan
|
||||
-- Add node to plan
|
||||
--------------------------------------
|
||||
function plan_class:add_node(plan_pos, node)
|
||||
-- build 3d cache tree
|
||||
self.data.scm_data_cache[plan_pos.y] = self.data.scm_data_cache[plan_pos.y] or {}
|
||||
self.data.scm_data_cache[plan_pos.y][plan_pos.x] = self.data.scm_data_cache[plan_pos.y][plan_pos.x] or {}
|
||||
local replaced_node = self.data.scm_data_cache[plan_pos.y][plan_pos.x][plan_pos.z]
|
||||
if not replaced_node then
|
||||
self.data.nodecount = self.data.nodecount + 1
|
||||
-- check if any old node is replaced
|
||||
local replaced_node = self:get_node(plan_pos)
|
||||
if replaced_node then
|
||||
self.data.nodeinfos[replaced_node.name].count = self.data.nodeinfos[replaced_node.name].count - 1
|
||||
else
|
||||
self.data.nodeinfos[replaced_node.name].count = self.data.nodeinfos[replaced_node.name].count-1
|
||||
self.data.nodecount = self.data.nodecount + 1
|
||||
self.scm_data_cache[plan_pos.y] = self.scm_data_cache[plan_pos.y] or {}
|
||||
self.scm_data_cache[plan_pos.y][plan_pos.x] = self.scm_data_cache[plan_pos.y][plan_pos.x] or {}
|
||||
end
|
||||
|
||||
-- insert to nodeinfos
|
||||
local nodeinfo = self.data.nodeinfos[node.name]
|
||||
-- Parse input data
|
||||
local node_name, node_data, node_meta
|
||||
if type(node) == "string" then
|
||||
node_name = node
|
||||
node_data = node
|
||||
else
|
||||
node_name = node.name
|
||||
if node.meta then
|
||||
if (node.meta.fields and next(node.meta.fields)) or
|
||||
(node.meta.inventory and next(node.meta.inventory)) then
|
||||
node_meta = node.meta
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Adjust nodeinfo and prepare mapping cache
|
||||
local nodeinfo = self.data.nodeinfos[node_name]
|
||||
if not nodeinfo then
|
||||
nodeinfo = {name_orig = node.name, count = 1}
|
||||
self.data.nodeinfos[node.name] = nodeinfo
|
||||
nodeinfo = {name = node_name, count = 1}
|
||||
self.data.nodeinfos[node_name] = nodeinfo
|
||||
else
|
||||
nodeinfo.count = nodeinfo.count + 1
|
||||
end
|
||||
node.nodeinfo = nodeinfo
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
if not node.data.meta and not node.data.prob and def and
|
||||
(not def.paramtype2 or def.paramtype2 == "none") then
|
||||
-- Deduplicated node
|
||||
nodeinfo.deduplicated_node = nodeinfo.deduplicated_node or {
|
||||
name = node.name,
|
||||
nodeinfo = nodeinfo,
|
||||
deduplicated = true,
|
||||
}
|
||||
self.data.scm_data_cache[plan_pos.y][plan_pos.x][plan_pos.z] = nodeinfo.deduplicated_node
|
||||
else
|
||||
self.data.scm_data_cache[plan_pos.y][plan_pos.x][plan_pos.z] = node
|
||||
|
||||
-- Check if storage could be stripped to name only
|
||||
if not node_data and not node_meta and not node.prob then
|
||||
local def = minetest.registered_nodes[node_name]
|
||||
if def and (not def.paramtype2 or def.paramtype2 == "none") then
|
||||
node_data = node.name
|
||||
end
|
||||
end
|
||||
|
||||
if not node_data then
|
||||
node_data = { name = node_name, meta = node_meta, prob = node.prob, param2 = node.param2 }
|
||||
end
|
||||
self.scm_data_cache[plan_pos.y][plan_pos.x][plan_pos.z] = node_data
|
||||
self.modified = true
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Adjust building size and ground info
|
||||
-- Adjust building size and ground info
|
||||
--------------------------------------
|
||||
function plan_class:adjust_building_info(plan_pos, node)
|
||||
-- adjust min/max position information
|
||||
|
@ -121,236 +137,53 @@ end
|
|||
-- Get node from plan
|
||||
--------------------------------------
|
||||
function plan_class:get_node(plan_pos)
|
||||
local cached_node = self.data.scm_data_cache[plan_pos.y] and
|
||||
self.data.scm_data_cache[plan_pos.y][plan_pos.x] and
|
||||
self.data.scm_data_cache[plan_pos.y][plan_pos.x][plan_pos.z]
|
||||
local cached_node = self.scm_data_cache[plan_pos.y] and
|
||||
self.scm_data_cache[plan_pos.y][plan_pos.x] and
|
||||
self.scm_data_cache[plan_pos.y][plan_pos.x][plan_pos.z]
|
||||
if not cached_node then
|
||||
return
|
||||
end
|
||||
local dedup_node = cached_node
|
||||
if cached_node.deduplicated then
|
||||
dedup_node = node.new(cached_node)
|
||||
dedup_node.nodeinfo = cached_node.nodeinfo
|
||||
local ret_node
|
||||
if type(cached_node) == "string" then
|
||||
ret_node = node.new({name = cached_node})
|
||||
else
|
||||
ret_node = node.new(cached_node)
|
||||
end
|
||||
dedup_node.plan = self
|
||||
dedup_node._plan_pos = plan_pos
|
||||
return dedup_node
|
||||
ret_node.nodeinfo = self.data.nodeinfos[ret_node.name]
|
||||
ret_node.plan = self
|
||||
ret_node._plan_pos = plan_pos
|
||||
return ret_node
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Delete node from plan
|
||||
-- Delete node from plan
|
||||
--------------------------------------
|
||||
function plan_class:del_node(pos)
|
||||
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
|
||||
local oldnode = self.data.scm_data_cache[pos.y][pos.x][pos.z]
|
||||
self.data.nodeinfos[oldnode.name].count = self.data.nodeinfos[oldnode.name].count - 1
|
||||
self.data.nodecount = self.data.nodecount - 1
|
||||
self.data.scm_data_cache[pos.y][pos.x][pos.z] = nil
|
||||
end
|
||||
if not next(self.data.scm_data_cache[pos.y][pos.x]) then
|
||||
self.data.scm_data_cache[pos.y][pos.x] = nil
|
||||
end
|
||||
end
|
||||
if not next(self.data.scm_data_cache[pos.y]) then
|
||||
self.data.scm_data_cache[pos.y] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Flood ta buildingplan with air
|
||||
--------------------------------------
|
||||
function plan_class:apply_flood_with_air(add_max, add_min, add_top)
|
||||
self.data.ground_y = math.floor(self.data.ground_y)
|
||||
add_max = add_max or 3
|
||||
add_min = add_min or 0
|
||||
add_top = add_top or 5
|
||||
|
||||
-- cache air_id
|
||||
local air_id
|
||||
|
||||
dprint("create flatting plan")
|
||||
for y = self.data.min_pos.y, self.data.max_pos.y + add_top do
|
||||
--calculate additional grounding
|
||||
if y > self.data.ground_y then --only over ground
|
||||
local high = y-self.data.ground_y
|
||||
add_min = high + 1
|
||||
if add_min > add_max then --set to max
|
||||
add_min = add_max
|
||||
end
|
||||
end
|
||||
|
||||
dprint("flat level:", y)
|
||||
local air_node = {name = "air"}
|
||||
for x = self.data.min_pos.x - add_min, self.data.max_pos.x + add_min do
|
||||
for z = self.data.min_pos.z - add_min, self.data.max_pos.z + add_min do
|
||||
local pos = {x=x, y=y, z=z}
|
||||
if not self:get_node(pos) then
|
||||
self:add_node(pos, node.new(air_node))
|
||||
if self.scm_data_cache[pos.y] then
|
||||
if self.scm_data_cache[pos.y][pos.x] then
|
||||
if self.scm_data_cache[pos.y][pos.x][pos.z] then
|
||||
local oldnode = self.scm_data_cache[pos.y][pos.x][pos.z]
|
||||
if type(oldnode) == "table" then
|
||||
oldnode = oldnode.name
|
||||
end
|
||||
self.data.nodeinfos[oldnode].count = self.data.nodeinfos[oldnode].count - 1
|
||||
self.data.nodecount = self.data.nodecount - 1
|
||||
self.scm_data_cache[pos.y][pos.x][pos.z] = nil
|
||||
end
|
||||
if not next(self.scm_data_cache[pos.y][pos.x]) then
|
||||
self.scm_data_cache[pos.y][pos.x] = nil
|
||||
end
|
||||
end
|
||||
if not next(self.scm_data_cache[pos.y]) then
|
||||
self.scm_data_cache[pos.y] = nil
|
||||
end
|
||||
end
|
||||
dprint("flatting plan done")
|
||||
self.modified = true
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Get world position relative to plan position
|
||||
-- Get a random position of an existing node in plan
|
||||
--------------------------------------
|
||||
function plan_class:get_world_pos(plan_pos, anchor_pos)
|
||||
local apos = anchor_pos or self.anchor_pos
|
||||
local pos
|
||||
if self.mirrored then
|
||||
pos = table.copy(plan_pos)
|
||||
pos.x = -pos.x
|
||||
else
|
||||
pos = plan_pos
|
||||
end
|
||||
local facedir_rotated = {
|
||||
[0] = function(pos,apos) return {
|
||||
x=pos.x+apos.x,
|
||||
y=pos.y+apos.y,
|
||||
z=pos.z+apos.z,
|
||||
}end,
|
||||
[1] = function(pos,apos) return {
|
||||
x=pos.z+apos.x,
|
||||
y=pos.y+apos.y,
|
||||
z=-pos.x+apos.z,
|
||||
} end,
|
||||
[2] = function(pos,apos) return {
|
||||
x=-pos.x+apos.x,
|
||||
y=pos.y+apos.y,
|
||||
z=-pos.z+apos.z,
|
||||
} end,
|
||||
[3] = function(pos,apos) return {
|
||||
x=-pos.z+apos.x,
|
||||
y=pos.y+apos.y,
|
||||
z=pos.x+apos.z,
|
||||
} end,
|
||||
}
|
||||
local ret = facedir_rotated[self.facedir](pos, apos)
|
||||
ret.y = ret.y - self.data.ground_y - 1
|
||||
return ret
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Get world minimum position relative to plan position
|
||||
--------------------------------------
|
||||
function plan_class:get_world_minp(anchor_pos)
|
||||
local pos = self:get_world_pos(self.data.min_pos, anchor_pos)
|
||||
local pos2 = self:get_world_pos(self.data.max_pos, anchor_pos)
|
||||
if pos2.x < pos.x then
|
||||
pos.x = pos2.x
|
||||
end
|
||||
if pos2.y < pos.y then
|
||||
pos.y = pos2.y
|
||||
end
|
||||
if pos2.z < pos.z then
|
||||
pos.z = pos2.z
|
||||
end
|
||||
return pos
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Get world maximum relative to plan position
|
||||
--------------------------------------
|
||||
function plan_class:get_world_maxp(anchor_pos)
|
||||
local pos = self:get_world_pos(self.data.max_pos, anchor_pos)
|
||||
local pos2 = self:get_world_pos(self.data.min_pos, anchor_pos)
|
||||
if pos2.x > pos.x then
|
||||
pos.x = pos2.x
|
||||
end
|
||||
if pos2.y > pos.y then
|
||||
pos.y = pos2.y
|
||||
end
|
||||
if pos2.z > pos.z then
|
||||
pos.z = pos2.z
|
||||
end
|
||||
return pos
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Check if world position is in plan
|
||||
--------------------------------------
|
||||
function plan_class:contains(chkpos, anchor_pos)
|
||||
local minp = self:get_world_minp(anchor_pos)
|
||||
local maxp = self:get_world_maxp(anchor_pos)
|
||||
|
||||
return (chkpos.x >= minp.x) and (chkpos.x <= maxp.x) and
|
||||
(chkpos.y >= minp.y) and (chkpos.y <= maxp.y) and
|
||||
(chkpos.z >= minp.z) and (chkpos.z <= maxp.z)
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------
|
||||
--Check if the plan overlaps with given area
|
||||
--------------------------------------
|
||||
function plan_class:check_overlap(minp, maxp, add_distance, anchor_pos)
|
||||
|
||||
add_distance = add_distance or 0
|
||||
|
||||
local minp_a = vector.subtract(minp, add_distance)
|
||||
local maxp_a = vector.add(maxp, add_distance)
|
||||
|
||||
local minp_b = vector.subtract(self:get_world_minp(anchor_pos), add_distance)
|
||||
local maxp_b = vector.add(self:get_world_maxp(anchor_pos), add_distance)
|
||||
|
||||
return ((minp_a.x >= minp_b.x and minp_a.x <= maxp_b.x) or
|
||||
(maxp_a.x >= minp_b.x and maxp_a.x <= maxp_b.x) or
|
||||
(minp_b.x >= minp_a.x and minp_b.x <= maxp_a.x) or
|
||||
(maxp_b.x >= minp_a.x and maxp_b.x <= maxp_a.x))
|
||||
and
|
||||
((minp_a.y >= minp_b.y and minp_a.y <= maxp_b.y) or
|
||||
(maxp_a.y >= minp_b.y and maxp_a.y <= maxp_b.y) or
|
||||
(minp_b.y >= minp_a.y and minp_b.y <= maxp_a.y) or
|
||||
(maxp_b.y >= minp_a.y and maxp_b.y <= maxp_a.y))
|
||||
and
|
||||
((minp_a.z >= minp_b.z and minp_a.z <= maxp_b.z) or
|
||||
(maxp_a.z >= minp_b.z and maxp_a.z <= maxp_b.z) or
|
||||
(minp_b.z >= minp_a.z and minp_b.z <= maxp_a.z) or
|
||||
(maxp_b.z >= minp_a.z and maxp_b.z <= maxp_a.z))
|
||||
end
|
||||
--------------------------------------
|
||||
--Get plan position relative to world position
|
||||
--------------------------------------
|
||||
function plan_class:get_plan_pos(world_pos, anchor_pos)
|
||||
local apos = anchor_pos or self.anchor_pos
|
||||
local facedir_rotated = {
|
||||
[0] = function(pos,apos) return {
|
||||
x=pos.x-apos.x,
|
||||
y=pos.y-apos.y,
|
||||
z=pos.z-apos.z
|
||||
} end,
|
||||
[1] = function(pos,apos) return {
|
||||
x=-(pos.z-apos.z),
|
||||
y=pos.y-apos.y,
|
||||
z=(pos.x-apos.x),
|
||||
} end,
|
||||
[2] = function(pos,apos) return {
|
||||
x=-(pos.x-apos.x),
|
||||
y=pos.y-apos.y,
|
||||
z=-(pos.z-apos.z),
|
||||
} end,
|
||||
[3] = function(pos,apos) return {
|
||||
x=pos.z-apos.z,
|
||||
y=pos.y-apos.y,
|
||||
z=-(pos.x-apos.x),
|
||||
} end,
|
||||
}
|
||||
local ret = facedir_rotated[self.facedir](world_pos, apos)
|
||||
|
||||
if self.mirrored then
|
||||
ret.x = -ret.x
|
||||
end
|
||||
ret.y = ret.y + self.data.ground_y + 1
|
||||
return ret
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Get a random position of an existing node in plan
|
||||
--------------------------------------
|
||||
-- get nodes for selection which one should be build
|
||||
-- skip parameter is randomized
|
||||
function plan_class:get_random_plan_pos()
|
||||
|
@ -358,7 +191,7 @@ function plan_class:get_random_plan_pos()
|
|||
|
||||
-- get random existing y
|
||||
local keyset = {}
|
||||
for k in pairs(self.data.scm_data_cache) do table.insert(keyset, k) end
|
||||
for k in pairs(self.scm_data_cache) do table.insert(keyset, k) end
|
||||
if #keyset == 0 then --finished
|
||||
return
|
||||
end
|
||||
|
@ -366,12 +199,12 @@ function plan_class:get_random_plan_pos()
|
|||
|
||||
-- get random existing x
|
||||
keyset = {}
|
||||
for k in pairs(self.data.scm_data_cache[y]) do table.insert(keyset, k) end
|
||||
for k in pairs(self.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
|
||||
for k in pairs(self.scm_data_cache[y][x]) do table.insert(keyset, k) end
|
||||
local z = keyset[math.random(#keyset)]
|
||||
|
||||
if z then
|
||||
|
@ -379,44 +212,6 @@ function plan_class:get_random_plan_pos()
|
|||
end
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Get a nodes list for a world chunk
|
||||
--------------------------------------
|
||||
function plan_class:get_chunk_nodes(plan_pos, anchor_pos)
|
||||
-- calculate the begin of the chunk
|
||||
--local BLOCKSIZE = core.MAP_BLOCKSIZE
|
||||
local BLOCKSIZE = 16
|
||||
local wpos = self:get_world_pos(plan_pos, anchor_pos)
|
||||
local minp = {}
|
||||
minp.x = (math.floor(wpos.x/BLOCKSIZE))*BLOCKSIZE
|
||||
minp.y = (math.floor(wpos.y/BLOCKSIZE))*BLOCKSIZE
|
||||
minp.z = (math.floor(wpos.z/BLOCKSIZE))*BLOCKSIZE
|
||||
local maxp = vector.add(minp, 16)
|
||||
|
||||
dprint("nodes for chunk (real-pos)", minetest.pos_to_string(minp), minetest.pos_to_string(maxp))
|
||||
|
||||
local minv = self:get_plan_pos(minp)
|
||||
local maxv = self:get_plan_pos(maxp)
|
||||
dprint("nodes for chunk (plan-pos)", minetest.pos_to_string(minv), minetest.pos_to_string(maxv))
|
||||
|
||||
local ret = {}
|
||||
for y = minv.y, maxv.y do
|
||||
if self.data.scm_data_cache[y] then
|
||||
for x = minv.x, maxv.x do
|
||||
if self.data.scm_data_cache[y][x] then
|
||||
for z = minv.z, maxv.z do
|
||||
if self.data.scm_data_cache[y][x][z] then
|
||||
table.insert(ret, self:get_node({x=x, y=y,z=z}))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
dprint("nodes in chunk to build", #ret)
|
||||
return ret, minp, maxp -- minp/maxp are worldpos
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Generate a plan from schematics file
|
||||
--------------------------------------
|
||||
|
@ -442,9 +237,8 @@ function plan_class:read_from_schem_file(filename)
|
|||
y = math.floor((i-1)/schematic.size.x) % schematic.size.y,
|
||||
x = (i-1) % schematic.size.x
|
||||
}
|
||||
local new_node = node.new(ent)
|
||||
self:add_node(pos, new_node)
|
||||
self:adjust_building_info(pos, new_node)
|
||||
self:add_node(pos, ent)
|
||||
self:adjust_building_info(pos, ent)
|
||||
end
|
||||
end
|
||||
-- WorldEdit files
|
||||
|
@ -458,15 +252,50 @@ function plan_class:read_from_schem_file(filename)
|
|||
-- analyze the file
|
||||
for i, ent in ipairs( nodes ) do
|
||||
local pos = {x=ent.x, y=ent.y, z=ent.z}
|
||||
local new_node = node.new(ent)
|
||||
self:add_node(pos, new_node)
|
||||
self:adjust_building_info(pos, new_node)
|
||||
self:add_node(pos, ent)
|
||||
self:adjust_building_info(pos, ent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--Propose anchor position for the plan
|
||||
-- Flood ta buildingplan with air
|
||||
--------------------------------------
|
||||
function plan_class:apply_flood_with_air(add_max, add_min, add_top)
|
||||
self.data.ground_y = math.floor(self.data.ground_y)
|
||||
add_max = add_max or 3
|
||||
add_min = add_min or 0
|
||||
add_top = add_top or 5
|
||||
|
||||
-- cache air_id
|
||||
local air_id
|
||||
|
||||
dprint("create flatting plan")
|
||||
for y = self.data.min_pos.y, self.data.max_pos.y + add_top do
|
||||
--calculate additional grounding
|
||||
if y > self.data.ground_y then --only over ground
|
||||
local high = y-self.data.ground_y
|
||||
add_min = high + 1
|
||||
if add_min > add_max then --set to max
|
||||
add_min = add_max
|
||||
end
|
||||
end
|
||||
|
||||
dprint("flat level:", y)
|
||||
for x = self.data.min_pos.x - add_min, self.data.max_pos.x + add_min do
|
||||
for z = self.data.min_pos.z - add_min, self.data.max_pos.z + add_min do
|
||||
local pos = {x=x, y=y, z=z}
|
||||
if not self:get_node(pos) then
|
||||
self:add_node(pos, "air")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
dprint("flatting plan done")
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Propose anchor position for the plan
|
||||
--------------------------------------
|
||||
function plan_class:propose_anchor(world_pos, do_check, add_xz)
|
||||
add_xz = add_xz or 4 --distance to other buildings to check should be the same additional air filler + distance
|
||||
|
@ -605,7 +434,218 @@ function plan_class:propose_anchor(world_pos, do_check, add_xz)
|
|||
end
|
||||
|
||||
--------------------------------------
|
||||
--add/build a chunk
|
||||
-- Get world position relative to plan position
|
||||
--------------------------------------
|
||||
function plan_class:get_world_pos(plan_pos, anchor_pos)
|
||||
local apos = anchor_pos or self.data.anchor_pos
|
||||
local pos
|
||||
if self.data.mirrored then
|
||||
pos = {x=plan_pos.x, y=plan_pos.y, z=plan_pos.z}
|
||||
pos.x = -pos.x
|
||||
else
|
||||
pos = plan_pos
|
||||
end
|
||||
local facedir_rotated = {
|
||||
[0] = function(pos,apos) return {
|
||||
x=pos.x+apos.x,
|
||||
y=pos.y+apos.y,
|
||||
z=pos.z+apos.z,
|
||||
}end,
|
||||
[1] = function(pos,apos) return {
|
||||
x=pos.z+apos.x,
|
||||
y=pos.y+apos.y,
|
||||
z=-pos.x+apos.z,
|
||||
} end,
|
||||
[2] = function(pos,apos) return {
|
||||
x=-pos.x+apos.x,
|
||||
y=pos.y+apos.y,
|
||||
z=-pos.z+apos.z,
|
||||
} end,
|
||||
[3] = function(pos,apos) return {
|
||||
x=-pos.z+apos.x,
|
||||
y=pos.y+apos.y,
|
||||
z=pos.x+apos.z,
|
||||
} end,
|
||||
}
|
||||
local ret = facedir_rotated[self.data.facedir](pos, apos)
|
||||
ret.y = ret.y - self.data.ground_y - 1
|
||||
return ret
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Get plan position relative to world position
|
||||
--------------------------------------
|
||||
function plan_class:get_plan_pos(world_pos, anchor_pos)
|
||||
local apos = anchor_pos or self.data.anchor_pos
|
||||
local facedir_rotated = {
|
||||
[0] = function(pos,apos) return {
|
||||
x=pos.x-apos.x,
|
||||
y=pos.y-apos.y,
|
||||
z=pos.z-apos.z
|
||||
} end,
|
||||
[1] = function(pos,apos) return {
|
||||
x=-(pos.z-apos.z),
|
||||
y=pos.y-apos.y,
|
||||
z=(pos.x-apos.x),
|
||||
} end,
|
||||
[2] = function(pos,apos) return {
|
||||
x=-(pos.x-apos.x),
|
||||
y=pos.y-apos.y,
|
||||
z=-(pos.z-apos.z),
|
||||
} end,
|
||||
[3] = function(pos,apos) return {
|
||||
x=pos.z-apos.z,
|
||||
y=pos.y-apos.y,
|
||||
z=-(pos.x-apos.x),
|
||||
} end,
|
||||
}
|
||||
local ret = facedir_rotated[self.data.facedir](world_pos, apos)
|
||||
if self.data.mirrored then
|
||||
ret.x = -ret.x
|
||||
end
|
||||
ret.y = ret.y + self.data.ground_y + 1
|
||||
return ret
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Get world minimum position relative to plan position
|
||||
--------------------------------------
|
||||
function plan_class:get_world_minp(anchor_pos)
|
||||
local pos = self:get_world_pos(self.data.min_pos, anchor_pos)
|
||||
local pos2 = self:get_world_pos(self.data.max_pos, anchor_pos)
|
||||
if pos2.x < pos.x then
|
||||
pos.x = pos2.x
|
||||
end
|
||||
if pos2.y < pos.y then
|
||||
pos.y = pos2.y
|
||||
end
|
||||
if pos2.z < pos.z then
|
||||
pos.z = pos2.z
|
||||
end
|
||||
return pos
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Get world maximum relative to plan position
|
||||
--------------------------------------
|
||||
function plan_class:get_world_maxp(anchor_pos)
|
||||
local pos = self:get_world_pos(self.data.max_pos, anchor_pos)
|
||||
local pos2 = self:get_world_pos(self.data.min_pos, anchor_pos)
|
||||
if pos2.x > pos.x then
|
||||
pos.x = pos2.x
|
||||
end
|
||||
if pos2.y > pos.y then
|
||||
pos.y = pos2.y
|
||||
end
|
||||
if pos2.z > pos.z then
|
||||
pos.z = pos2.z
|
||||
end
|
||||
return pos
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Check if world position is in plan
|
||||
--------------------------------------
|
||||
function plan_class:contains(chkpos, anchor_pos)
|
||||
local minp = self:get_world_minp(anchor_pos)
|
||||
local maxp = self:get_world_maxp(anchor_pos)
|
||||
|
||||
return (chkpos.x >= minp.x) and (chkpos.x <= maxp.x) and
|
||||
(chkpos.y >= minp.y) and (chkpos.y <= maxp.y) and
|
||||
(chkpos.z >= minp.z) and (chkpos.z <= maxp.z)
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Check if the plan overlaps with given area
|
||||
--------------------------------------
|
||||
function plan_class:check_overlap(minp, maxp, add_distance, anchor_pos)
|
||||
add_distance = add_distance or 0
|
||||
|
||||
local minp_a = vector.subtract(minp, add_distance)
|
||||
local maxp_a = vector.add(maxp, add_distance)
|
||||
|
||||
local minp_b = vector.subtract(self:get_world_minp(anchor_pos), add_distance)
|
||||
local maxp_b = vector.add(self:get_world_maxp(anchor_pos), add_distance)
|
||||
|
||||
local overlap_pos = {}
|
||||
|
||||
overlap_pos.x =
|
||||
(minp_a.x >= minp_b.x and minp_a.x <= maxp_b.x) and math.floor((minp_a.x+maxp_b.x)/2) or
|
||||
(maxp_a.x >= minp_b.x and maxp_a.x <= maxp_b.x) and math.floor((maxp_a.x+minp_b.x)/2) or
|
||||
(minp_b.x >= minp_a.x and minp_b.x <= maxp_a.x) and math.floor((minp_b.x+maxp_a.x)/2) or
|
||||
(maxp_b.x >= minp_a.x and maxp_b.x <= maxp_a.x) and math.floor((maxp_b.x+minp_a.x)/2)
|
||||
|
||||
if not overlap_pos.x then
|
||||
return
|
||||
end
|
||||
|
||||
overlap_pos.z =
|
||||
(minp_a.z >= minp_b.z and minp_a.z <= maxp_b.z) and math.floor((minp_a.z+maxp_b.z)/2) or
|
||||
(maxp_a.z >= minp_b.z and maxp_a.z <= maxp_b.z) and math.floor((maxp_a.z+minp_b.z)/2) or
|
||||
(minp_b.z >= minp_a.z and minp_b.z <= maxp_a.z) and math.floor((minp_b.z+maxp_a.z)/2) or
|
||||
(maxp_b.z >= minp_a.z and maxp_b.z <= maxp_a.z) and math.floor((maxp_b.z+minp_a.z)/2)
|
||||
if not overlap_pos.z then
|
||||
return
|
||||
end
|
||||
|
||||
overlap_pos.y =
|
||||
(minp_a.y >= minp_b.y and minp_a.y <= maxp_b.y) and math.floor((minp_a.y+maxp_b.y)/2) or
|
||||
(maxp_a.y >= minp_b.y and maxp_a.y <= maxp_b.y) and math.floor((maxp_a.y+minp_b.y)/2) or
|
||||
(minp_b.y >= minp_a.y and minp_b.y <= maxp_a.y) and math.floor((minp_b.y+maxp_a.y)/2) or
|
||||
(maxp_b.y >= minp_a.y and maxp_b.y <= maxp_a.y) and math.floor((maxp_b.y+minp_a.y)/2)
|
||||
if not overlap_pos.y then
|
||||
return
|
||||
end
|
||||
|
||||
dprint("Overlap",
|
||||
"minp_a:"..minetest.pos_to_string(minp_a),
|
||||
"maxp_a:"..minetest.pos_to_string(maxp_a),
|
||||
"minp_b:"..minetest.pos_to_string(minp_b),
|
||||
"maxp_b:"..minetest.pos_to_string(maxp_b),
|
||||
"=>"..minetest.pos_to_string(overlap_pos))
|
||||
return overlap_pos
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Get a nodes list for a world chunk
|
||||
--------------------------------------
|
||||
function plan_class:get_chunk_nodes(plan_pos, anchor_pos)
|
||||
-- calculate the begin of the chunk
|
||||
--local BLOCKSIZE = core.MAP_BLOCKSIZE
|
||||
local BLOCKSIZE = 16
|
||||
local wpos = self:get_world_pos(plan_pos, anchor_pos)
|
||||
local minp = {}
|
||||
minp.x = (math.floor(wpos.x/BLOCKSIZE))*BLOCKSIZE
|
||||
minp.y = (math.floor(wpos.y/BLOCKSIZE))*BLOCKSIZE
|
||||
minp.z = (math.floor(wpos.z/BLOCKSIZE))*BLOCKSIZE
|
||||
local maxp = vector.add(minp, 16)
|
||||
|
||||
dprint("nodes for chunk (real-pos)", minetest.pos_to_string(minp), minetest.pos_to_string(maxp))
|
||||
|
||||
local minv = self:get_plan_pos(minp)
|
||||
local maxv = self:get_plan_pos(maxp)
|
||||
dprint("nodes for chunk (plan-pos)", minetest.pos_to_string(minv), minetest.pos_to_string(maxv))
|
||||
|
||||
local ret = {}
|
||||
for y = minv.y, maxv.y do
|
||||
if self.scm_data_cache[y] then
|
||||
for x = minv.x, maxv.x do
|
||||
if self.scm_data_cache[y][x] then
|
||||
for z = minv.z, maxv.z do
|
||||
if self.scm_data_cache[y][x][z] then
|
||||
table.insert(ret, self:get_node({x=x, y=y,z=z}))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
dprint("nodes in chunk to build", #ret)
|
||||
return ret, minp, maxp -- minp/maxp are worldpos
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Add/build a chunk
|
||||
--------------------------------------
|
||||
function plan_class:do_add_chunk_place(plan_pos)
|
||||
dprint("---build chunk", minetest.pos_to_string(plan_pos))
|
||||
|
@ -617,7 +657,7 @@ function plan_class:do_add_chunk_place(plan_pos)
|
|||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Load a region to the voxel
|
||||
-- Load a region to the voxel
|
||||
--------------------------------------
|
||||
function plan_class:load_region(min_world_pos, max_world_pos)
|
||||
if not max_world_pos then
|
||||
|
@ -631,7 +671,7 @@ function plan_class:load_region(min_world_pos, max_world_pos)
|
|||
end
|
||||
|
||||
--------------------------------------
|
||||
-- add/build a chunk using VoxelArea (internal usage)
|
||||
-- Add/build a chunk using VoxelArea (internal usage)
|
||||
--------------------------------------
|
||||
function plan_class:do_add_chunk_voxel_int()
|
||||
local meta_fix = {}
|
||||
|
@ -684,6 +724,9 @@ function plan_class:do_add_chunk_voxel_int()
|
|||
end
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Local function for emergeblocks callback
|
||||
--------------------------------------
|
||||
local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
|
||||
if not ctx.total_blocks then
|
||||
ctx.total_blocks = num_calls_remaining + 1
|
||||
|
@ -703,7 +746,7 @@ local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
|
|||
end
|
||||
|
||||
--------------------------------------
|
||||
-- add/build a chunk using VoxelArea
|
||||
-- Add/build a chunk using VoxelArea
|
||||
--------------------------------------
|
||||
function plan_class:do_add_chunk_voxel(plan_pos, after_call_func)
|
||||
-- Register for on_generate build
|
||||
|
@ -720,7 +763,7 @@ function plan_class:do_add_chunk_voxel(plan_pos, after_call_func)
|
|||
end
|
||||
|
||||
--------------------------------------
|
||||
-- add/build a chunk using VoxelArea called from mapgen
|
||||
-- Add/build a chunk using VoxelArea called from mapgen
|
||||
--------------------------------------
|
||||
function plan_class:do_add_chunk_mapgen()
|
||||
plan._vm, plan._vm_minp, plan._vm_maxp = minetest.get_mapgen_object("voxelmanip")
|
||||
|
@ -757,7 +800,7 @@ function plan_class:do_add_all_voxel_async()
|
|||
end
|
||||
|
||||
--------------------------------------
|
||||
---add/build a chunk using VoxelArea
|
||||
-- Add/build a chunk using VoxelArea
|
||||
--------------------------------------
|
||||
function plan_class:do_add_all_mapgen_async()
|
||||
-- Register for on_generate build
|
||||
|
@ -781,33 +824,30 @@ function plan_class:do_add_all_mapgen_async()
|
|||
end
|
||||
|
||||
--------------------------------------
|
||||
--- Get the building status. (new, build, pause, finished)
|
||||
-- Get the building status. (new, build, pause, finished)
|
||||
--------------------------------------
|
||||
function plan_class:get_status()
|
||||
if self.status == "build" then
|
||||
if self.data.status == "build" then
|
||||
if self.data.nodecount == 0 then
|
||||
dprint("finished by nodecount 0 in get_status")
|
||||
self.status = "finished"
|
||||
self.data.status = "finished"
|
||||
end
|
||||
end
|
||||
if self.on_status then -- trigger updates trough this hook
|
||||
self:on_status(self.status)
|
||||
end
|
||||
return self.status
|
||||
return self.data.status
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
--- Set the building status. (new, build, pause, finished)
|
||||
-- Set the building status. (new, build, pause, finished)
|
||||
--------------------------------------
|
||||
function plan_class:set_status(status)
|
||||
self.status = status
|
||||
if self.on_status then -- trigger updates trough this hook
|
||||
self:on_status(self.status)
|
||||
self.data.status = status
|
||||
if status == "build" then
|
||||
minetest.after(0.1, self.do_add_all_voxel_async, self)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
---Process registered on generated chunks
|
||||
-- Process registered on generated chunks
|
||||
--------------------------------------
|
||||
minetest.register_on_generated(function(minp, maxp, blockseed)
|
||||
local pos_hash = minetest.hash_node_position(minp)
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
local save_interval = minetest.settings:get("schemlib.save_interval") or 10
|
||||
local save_maxnodes = minetest.settings:get("schemlib.save_maxnodes") or 10000
|
||||
|
||||
local storage = minetest.get_mod_storage()
|
||||
|
||||
local plan_manager = {}
|
||||
local plan_list = {}
|
||||
local plan_meta_list = minetest.deserialize(storage:get_string("$PLAN_META_LIST$")) or {}
|
||||
|
||||
-- light methods without access to scm_data_cache
|
||||
local plan_meta_class = {
|
||||
adjust_building_info = schemlib.plan.plan_class.adjust_building_info,
|
||||
get_world_pos = schemlib.plan.plan_class.get_world_pos,
|
||||
get_plan_pos = schemlib.plan.plan_class.get_plan_pos,
|
||||
get_world_minp = schemlib.plan.plan_class.get_world_minp,
|
||||
get_world_maxp = schemlib.plan.plan_class.get_world_maxp,
|
||||
contains = schemlib.plan.plan_class.contains,
|
||||
check_overlap = schemlib.plan.plan_class.check_overlap,
|
||||
}
|
||||
local plan_meta_class_mt = {__index = plan_meta_class}
|
||||
|
||||
plan_manager.plan_meta_list = plan_meta_list
|
||||
|
||||
-----------------------------------------------
|
||||
-- Get separate stored plan metadata for plan
|
||||
-----------------------------------------------
|
||||
function plan_manager.get_plan_meta(plan_id)
|
||||
if plan_meta_list[plan_id] then
|
||||
local plan_meta = setmetatable({}, plan_meta_class_mt)
|
||||
plan_meta.plan_id = plan_id
|
||||
plan_meta.data = plan_meta_list[plan_id]
|
||||
return plan_meta
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------------------------------
|
||||
-- Get persistant plan
|
||||
-----------------------------------------------
|
||||
function plan_manager.get_plan(plan_id)
|
||||
if plan_list[plan_id] then
|
||||
return plan_list[plan_id]
|
||||
end
|
||||
|
||||
if not plan_meta_list[plan_id] then
|
||||
return
|
||||
end
|
||||
|
||||
local plan = schemlib.plan.new(plan_id)
|
||||
plan.data = plan_meta_list[plan_id]
|
||||
|
||||
if not plan.data.save_chunk_count then
|
||||
plan.scm_data_cache = minetest.deserialize(storage:get_string(plan_id))
|
||||
else
|
||||
plan.scm_data_cache = {}
|
||||
for i = 1, plan.data.save_chunk_count do
|
||||
local chunk = minetest.deserialize(storage:get_string(plan_id.."$"..i))
|
||||
for y, ydata in pairs(chunk) do
|
||||
plan.scm_data_cache[y] = ydata
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local nodecount = 0
|
||||
for y, ydata in pairs(plan.scm_data_cache) do
|
||||
for x, xdata in pairs(ydata) do
|
||||
for z, node in pairs(xdata) do
|
||||
nodecount = nodecount + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
plan.data.nodecount = nodecount
|
||||
plan_list[plan_id] = plan
|
||||
return plan
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Set/Save plan in plan manager
|
||||
--------------------------------------
|
||||
function plan_manager.set_plan(plan)
|
||||
plan_manager.delete_plan(plan.plan_id)
|
||||
|
||||
plan_list[plan.plan_id] = plan
|
||||
plan_meta_list[plan.plan_id] = plan.data
|
||||
|
||||
if plan.data.nodecount <= 50000 then
|
||||
plan.data.save_chunk_count = nil
|
||||
storage:set_string(plan.plan_id, minetest.serialize(plan.scm_data_cache))
|
||||
else
|
||||
-- Split data to avoid error main function has more than 65536 constants
|
||||
local chunk = {}
|
||||
local chunk_size = 0
|
||||
local chunk_count = 0
|
||||
for y, ydata in pairs(plan.scm_data_cache) do
|
||||
chunk[y] = ydata
|
||||
for x, xdata in pairs(ydata) do
|
||||
for z, node in pairs(xdata) do
|
||||
chunk_size = chunk_size + 1
|
||||
end
|
||||
end
|
||||
if chunk_size > 50000 then
|
||||
chunk_count = chunk_count + 1
|
||||
storage:set_string(plan.plan_id.."$"..chunk_count, minetest.serialize(chunk))
|
||||
chunk = {}
|
||||
chunk_size = 0
|
||||
end
|
||||
end
|
||||
if chunk_size > 0 then
|
||||
chunk_count = chunk_count + 1
|
||||
storage:set_string(plan.plan_id.."$"..chunk_count, minetest.serialize(chunk))
|
||||
end
|
||||
plan.data.save_chunk_count = chunk_count
|
||||
end
|
||||
plan.modified = false
|
||||
storage:set_string("$PLAN_META_LIST$", minetest.serialize(plan_meta_list))
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Remove plan from manager
|
||||
--------------------------------------
|
||||
function plan_manager.delete_plan(plan_id)
|
||||
local plan_meta = plan_manager.get_plan_meta(plan_id)
|
||||
if not plan_meta then
|
||||
return
|
||||
end
|
||||
storage:set_string(plan_id, "")
|
||||
if plan_meta.data.save_chunk_count then
|
||||
for i = 1, plan_meta.data.save_chunk_count do
|
||||
storage:set_string(plan_id.."$"..i,"")
|
||||
end
|
||||
end
|
||||
plan_list[plan_id] = nil
|
||||
plan_meta_list[plan_id] = nil
|
||||
storage:set_string("$PLAN_META_LIST$", minetest.serialize(plan_meta_list))
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Do trigger processing
|
||||
--------------------------------------
|
||||
for plan_id, data in pairs(plan_meta_list) do
|
||||
if data.status == "build" then
|
||||
local plan = plan_manager.get_plan(plan_id)
|
||||
minetest.after(0, plan.do_add_all_voxel_async, plan)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------
|
||||
-- Save all on shutdown
|
||||
--------------------------------------
|
||||
minetest.register_on_shutdown(function()
|
||||
for plan_id, plan in pairs(plan_list) do
|
||||
if plan.modified then
|
||||
plan_manager.set_plan(plan)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
--------------------------------------
|
||||
-- Save in intervals
|
||||
--------------------------------------
|
||||
local function save_chain()
|
||||
for plan_id, plan in pairs(plan_list) do
|
||||
if plan.modified and
|
||||
(save_maxnodes == 0 or plan.data.nodecount <= save_maxnodes) then
|
||||
plan_manager.set_plan(plan)
|
||||
end
|
||||
end
|
||||
minetest.after(save_interval, save_chain)
|
||||
end
|
||||
|
||||
if save_interval > 0 then
|
||||
minetest.after(save_interval, save_chain)
|
||||
end
|
||||
|
||||
|
||||
return plan_manager
|
|
@ -1,27 +0,0 @@
|
|||
local save_restore = {}
|
||||
|
||||
function save_restore.save_data(filename, data )
|
||||
local path = minetest.get_worldpath()..'/'..filename
|
||||
local file = io.open( path, 'w' )
|
||||
if file then
|
||||
file:write( minetest.serialize( data ))
|
||||
file:close()
|
||||
else
|
||||
print("[save_restore] Error: Savefile '"..tostring( path ).."' could not be written.")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
save_restore.restore_data = function( filename )
|
||||
local file = io.open( minetest.get_worldpath()..'/'..filename, 'r' )
|
||||
if file then
|
||||
local data = file:read("*all")
|
||||
file:close()
|
||||
return minetest.deserialize( data )
|
||||
else
|
||||
print("[save_restore] Error: Savefile '"..tostring( filename ).."' not found.")
|
||||
return {} -- return empty table
|
||||
end
|
||||
end
|
||||
|
||||
return save_restore
|
|
@ -0,0 +1,9 @@
|
|||
# Save interval in seconds for plan changes in manager.
|
||||
# 0 disables the interval saving
|
||||
# Note: all modified plans will be saved on shutdown
|
||||
schemlib.save_interval (Plan manager save interval) int 10 0
|
||||
|
||||
# Maximum building size in nodes that are handled in interval save
|
||||
# to avoid performance issue. 0 enables saving for all sizes.
|
||||
# Note: all (including big) modified plans will be saved on shutdown
|
||||
schemlib.save_maxnodes (Maximum building size for autosave) int 10000 0
|
Loading…
Reference in New Issue