307 lines
11 KiB
Lua
307 lines
11 KiB
Lua
--local dprint = print
|
|
local dprint = function() return end
|
|
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
|
local filepath = modpath.."/buildings/"
|
|
local plan_manager = schemlib.plan_manager
|
|
|
|
local BUILD_DISTANCE = 3
|
|
|
|
schemlib_builder_npcf = {
|
|
max_pause_duration = 10, -- pause between jobs in processing steps (second
|
|
architect_rarity = 2, -- create own random building plan if nothing found
|
|
walk_around_rarity = 3, -- Rarity for direction change in walk around without job
|
|
walk_around_duration = 10, -- Rarity for direction change in walk around without job
|
|
plan_max_distance = 100, -- Maximal distance to the next plan
|
|
check_anchor_rarity = 2, -- Rarity of check for anchor call --10
|
|
}
|
|
|
|
local building_checktable = {}
|
|
|
|
local tmp_next_plan
|
|
|
|
--------------------------------------
|
|
-- Get buildings list
|
|
--------------------------------------
|
|
local function get_buildings_list()
|
|
local list = {}
|
|
local building_files = minetest.get_dir_list(modpath.."/buildings/", false)
|
|
for _, file in ipairs(building_files) do
|
|
table.insert(list, {name=file, filename=modpath.."/buildings/"..file})
|
|
building_checktable[file] = true
|
|
end
|
|
return list
|
|
end
|
|
|
|
--------------------------------------
|
|
-- Load plan from file and configure them
|
|
--------------------------------------
|
|
local function get_plan_from_file(filename)
|
|
local plan = schemlib.plan.new()
|
|
plan:read_from_schem_file(filename)
|
|
plan:apply_flood_with_air()
|
|
return plan
|
|
end
|
|
|
|
--------------------------------------
|
|
-- NPC Enhancements
|
|
--------------------------------------
|
|
-- Get exsisting plan prefering the already assigned
|
|
local function get_existing_plan(self)
|
|
local mv_obj = npcf.movement.getControl(self)
|
|
if self.metadata.build_plan_id and (not tmp_next_plan or self.build_plan ~= tmp_next_plan) then
|
|
-- check if current plan is still valid / get them
|
|
dprint(self.npc_id,"check existing plan", self.metadata.build_plan_id )
|
|
self.build_plan = plan_manager:get_plan(self.metadata.build_plan_id)
|
|
if not self.build_plan then
|
|
self.metadata.build_plan_id = nil
|
|
end
|
|
end
|
|
|
|
-- The NPC is not a workaholic
|
|
if not self.build_plan and schemlib_builder_npcf.max_pause_duration > 0 then
|
|
dprint(self.npc_id,"check for pause")
|
|
if not self.metadata.schemlib_pause then
|
|
self.metadata.schemlib_pause = math.random(schemlib_builder_npcf.max_pause_duration)
|
|
self.metadata.schemlib_pause_counter = 0
|
|
dprint(self.npc_id,"take a pause:", self.metadata.schemlib_pause)
|
|
end
|
|
self.metadata.schemlib_pause_counter = self.metadata.schemlib_pause_counter + 1
|
|
if self.metadata.schemlib_pause_counter < self.metadata.schemlib_pause then
|
|
-- it is pause time
|
|
return
|
|
end
|
|
else
|
|
-- reset pause counter if plan exists to allow pause next time
|
|
self.metadata.schemlib_pause = nil
|
|
end
|
|
|
|
if not self.build_plan or self.build_plan == tmp_next_plan then
|
|
-- no plan assigned, check for neighboar plans / select existing plan
|
|
dprint(self.npc_id,"select existing plan")
|
|
local selected_plan = {}
|
|
for plan_id, meta in pairs(plan_manager.plan_meta_list) do
|
|
if meta.npc_build and meta.anchor_pos then
|
|
local distance = vector.distance(meta.anchor_pos, mv_obj.pos)
|
|
dprint(self.npc_id,"plan exists:", plan_id, meta.anchor_pos, distance)
|
|
if distance < schemlib_builder_npcf.plan_max_distance and (not selected_plan.distance or selected_plan.distance > distance) then
|
|
selected_plan.distance = distance
|
|
selected_plan.plan_id = plan_id
|
|
end
|
|
end
|
|
end
|
|
if selected_plan.plan_id then
|
|
self.build_plan = plan_manager.get_plan(selected_plan.plan_id)
|
|
end
|
|
if self.build_plan then
|
|
self.metadata.build_plan_id = self.build_plan.plan_id
|
|
dprint(self.npc_id,"Existing plan selected", selected_plan.plan_id)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function create_new_plan(self)
|
|
local mv_obj = npcf.movement.getControl(self)
|
|
if not tmp_next_plan then
|
|
-- no plan in list - and no plan temporary loaded - load them (maybe)
|
|
local building = schemlib_builder_npcf.buildings[math.random(#schemlib_builder_npcf.buildings)]
|
|
dprint(self.npc_id,"File selected for build", building.filename)
|
|
tmp_next_plan = get_plan_from_file(building.filename)
|
|
tmp_next_plan.data.facedir = math.random(4)-1
|
|
tmp_next_plan.data.mirrored = (math.random(2) == 1)
|
|
dprint(self.npc_id,"building loaded. Nodes:", tmp_next_plan.data.nodecount)
|
|
return
|
|
end
|
|
|
|
if math.random(schemlib_builder_npcf.check_anchor_rarity) == 1 then
|
|
-- dummy plan exists, search for anchor, but do not penetrate the map by propose_anchor()
|
|
dprint(self.npc_id,"Check anchor")
|
|
local chk_pos = vector.round(mv_obj.pos)
|
|
local anchor_pos, error_pos
|
|
-- check for possible overlaps with other plans
|
|
for plan_id, meta in pairs(plan_manager.plan_meta_list) do
|
|
local plan_meta = plan_manager.get_plan_meta(plan_id)
|
|
local minp = plan_meta:get_world_minp()
|
|
local maxp = plan_meta:get_world_maxp()
|
|
if minp and maxp then
|
|
error_pos = tmp_next_plan:check_overlap(minp, maxp, 3, chk_pos)
|
|
if error_pos then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if not error_pos then
|
|
-- take the anchor proposal
|
|
anchor_pos, error_pos = tmp_next_plan:propose_anchor(chk_pos, true)
|
|
end
|
|
if not anchor_pos then
|
|
dprint(self.npc_id,"not buildable nearly", minetest.pos_to_string(chk_pos))
|
|
if error_pos then
|
|
-- walk away from error position
|
|
self.prefered_direction = vector.direction(error_pos, mv_obj.pos)
|
|
end
|
|
return
|
|
end
|
|
dprint(self.npc_id,"proposed anchor", minetest.pos_to_string(anchor_pos), "nearly", minetest.pos_to_string(mv_obj.pos))
|
|
|
|
-- Prepare building plan to be build
|
|
self.build_plan = tmp_next_plan
|
|
self.build_plan.plan_id = "builder:"..minetest.pos_to_string(anchor_pos)
|
|
self.build_plan.data.managed_by_schemlib_builder = true
|
|
self.metadata.build_plan_id = self.build_plan.plan_id
|
|
self.build_plan.data.anchor_pos = anchor_pos
|
|
self.build_plan.data.npc_build = true
|
|
tmp_next_plan = nil
|
|
plan_manager.set_plan(self.build_plan)
|
|
dprint(self.npc_id,"building ready to build at:", self.metadata.build_plan_id)
|
|
end
|
|
end
|
|
|
|
|
|
|
|
npcf:register_npc("schemlib_builder_npcf:builder" ,{
|
|
description = "Larry Schemlib (NPC)",
|
|
textures = {"npcf_builder_skin.png"},
|
|
stepheight = 1.1,
|
|
inventory_image = "npcf_builder_inv.png",
|
|
on_step = function(self)
|
|
if self.timer < 1 then
|
|
return
|
|
end
|
|
self.timer = 0
|
|
local mv_obj = npcf.movement.getControl(self)
|
|
mv_obj:mine_stop()
|
|
|
|
-- check plan
|
|
get_existing_plan(self)
|
|
if self.build_plan then
|
|
if self.build_plan.data.nodecount == 0 then
|
|
if self.build_plan.data.managed_by_schemlib_builder then
|
|
plan_manager.delete_plan(self.build_plan.plan_id)
|
|
end
|
|
self.build_plan = nil
|
|
self.metadata.build_plan_id = nil
|
|
dprint(self.npc_id, "plan finished")
|
|
return
|
|
end
|
|
dprint(self.npc_id,"plan ready for build, get the next node")
|
|
if not self.build_npc_ai or self.build_npc_ai.plan ~= self.build_plan then
|
|
self.build_npc_ai = schemlib.npc_ai.new(self.build_plan, BUILD_DISTANCE)
|
|
end
|
|
self.target_node = self.build_npc_ai:plan_target_get(mv_obj.pos)
|
|
end
|
|
if (not self.build_plan) and schemlib_builder_npcf.architect_rarity > 0 and
|
|
math.random(schemlib_builder_npcf.architect_rarity) == 1 then
|
|
create_new_plan(self)
|
|
self.target_node = nil
|
|
end
|
|
|
|
if not self.build_plan then
|
|
dprint(self.npc_id,"plan not ready for build, or NPC does a pause")
|
|
self.target_node = nil
|
|
end
|
|
|
|
dprint(self.npc_id,"target selected", tostring(self.target_node))
|
|
if self.target_node then
|
|
-- at work
|
|
local targetpos = self.target_node:get_world_pos()
|
|
mv_obj:walk(targetpos, 1, {teleport_on_stuck = true})
|
|
dprint(self.npc_id,"work at:", minetest.pos_to_string(targetpos), self.target_node.name, "my pos", minetest.pos_to_string(mv_obj.pos))
|
|
if vector.distance(mv_obj.pos, targetpos) <= BUILD_DISTANCE then
|
|
dprint(self.npc_id,"build:", minetest.pos_to_string(targetpos))
|
|
mv_obj:mine()
|
|
mv_obj:set_walk_parameter({teleport_on_stuck = false})
|
|
self.build_npc_ai:place_node(self.target_node)
|
|
self.target_node = nil
|
|
end
|
|
else
|
|
-- walk around
|
|
|
|
-- check the timer
|
|
if self.metadata.walk_around_timer then
|
|
self.metadata.walk_around_counter = self.metadata.walk_around_counter + 1
|
|
if self.metadata.walk_around_counter > self.metadata.walk_around_timer then
|
|
self.walk_around_timer = nil
|
|
end
|
|
end
|
|
|
|
if schemlib_builder_npcf.walk_around_rarity > 0 and
|
|
(math.random(schemlib_builder_npcf.walk_around_rarity) == 1 and not self.walk_around_timer) then
|
|
-- set the timer
|
|
self.metadata.walk_around_timer = math.random(schemlib_builder_npcf.walk_around_duration)
|
|
self.metadata.walk_around_counter = 0
|
|
|
|
self.metadata.walk_around_counter = self.metadata.walk_around_counter + 1
|
|
if self.metadata.walk_around_counter > self.metadata.walk_around_timer then
|
|
self.walk_around_timer = nil
|
|
end
|
|
|
|
local walk_to = vector.add(mv_obj.pos,{x=math.random(41)-21, y=0, z=math.random(41)-21})
|
|
|
|
-- create prefered direction to nearest player
|
|
if math.random(10) == 1 then
|
|
local nearest_pos, nearest_distance
|
|
for _, player in ipairs(minetest.get_connected_players()) do
|
|
local playerpos = player:getpos()
|
|
local distance = vector.distance(mv_obj.pos, playerpos)
|
|
if distance < schemlib_builder_npcf.plan_max_distance and
|
|
(not nearest_pos or (nearest_distance > distance)) then
|
|
nearest_pos = playerpos
|
|
nearest_distance = distance
|
|
end
|
|
end
|
|
if nearest_pos then
|
|
self.prefered_direction = vector.direction(mv_obj.pos, nearest_pos)
|
|
end
|
|
end
|
|
|
|
-- create prefered direction to nearest other builder npc
|
|
if math.random(10) == 1 then
|
|
local nearest_pos, nearest_distance
|
|
for _, npc in pairs(npcf.npcs) do
|
|
if npc.name == "schemlib_builder_npcf:builder" and npc.pos then
|
|
local distance = vector.distance(mv_obj.pos, npc.pos)
|
|
if distance < schemlib_builder_npcf.plan_max_distance and
|
|
(not nearest_pos or (nearest_distance > distance)) then
|
|
nearest_pos = npc.pos
|
|
nearest_distance = distance
|
|
end
|
|
end
|
|
end
|
|
if nearest_pos then
|
|
self.prefered_direction = vector.direction(mv_obj.pos, nearest_pos)
|
|
end
|
|
end
|
|
|
|
-- prefer the streight way
|
|
if not self.prefered_direction then
|
|
local yaw = mv_obj.yaw + math.pi * 0.5
|
|
self.prefered_direction = {x=math.cos(yaw), y=0, z=math.sin(yaw)}
|
|
end
|
|
walk_to = vector.add(walk_to, vector.multiply(self.prefered_direction, 10))
|
|
|
|
-- prefer the high of the ground under the last building is built, to go down
|
|
if self.anchor_y then
|
|
walk_to.y = self.anchor_y
|
|
end
|
|
|
|
walk_to = npcf.movement.functions.get_walkable_pos(walk_to, 3)
|
|
if walk_to then
|
|
walk_to.y = walk_to.y + 1
|
|
mv_obj:walk(walk_to, 1, {teleport_on_stuck = true})
|
|
self.anchor_y = nil -- used once
|
|
dprint(self.npc_id,"walk to", minetest.pos_to_string(walk_to))
|
|
end
|
|
elseif math.random(200) == 1 then
|
|
mv_obj:sit()
|
|
elseif math.random(400) == 1 then
|
|
mv_obj:lay()
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
-- Restore data at init
|
|
schemlib_builder_npcf.buildings = get_buildings_list() -- at init!
|
|
|