NPC builder ported to npcf

master
Alexander Weber 2016-11-17 19:36:47 +01:00
parent 2cd8ed4ded
commit 2aff4ce4cf
7 changed files with 375 additions and 438 deletions

View File

@ -1 +1,2 @@
default
npcf

View File

@ -6,7 +6,7 @@ townchest.modpath = minetest.get_modpath(minetest.get_current_modname())
-- debug. Used for debug messages. In production the function should be empty
local dprint = function(...)
-- debug print. Comment out the next line if you don't need debug out
-- print(unpack(arg))
print(unpack(arg))
end
local dprint_off = function(...)
end
@ -32,7 +32,7 @@ dofile(townchest.modpath.."/".."nodes.lua")
dofile(townchest.modpath.."/".."plan.lua")
-- NPC's
dofile(townchest.modpath.."/".."npc.lua")
dofile(townchest.modpath.."/".."npcf-worker.lua")
-----------------------------------------------

434
npc.lua
View File

@ -1,434 +0,0 @@
local dprint = townchest.dprint_off --debug
--[[
local __die = function(this)
dprint("npc:die")
townchest.npc.entity_list[this.lua.npc_key] = nil --not needed, already no this
this.entity:remove()
end
]]--
-- API
-- self: the lua entity
-- pos: the position to move to
-- range: the distance within pos the npc will go to
-- range_y: the height within pos the npc will go to
-- speed: the speed at which the npc will move
-- after: callback function(self) which is triggered when the npc gets within range of pos
local __moveto = function(self, pos)
-- self.target = pos
self.target = {} --independend table/reference
self.target.x = pos.x
self.target.y = pos.y + 1.5 --always try to be over the working place
self.target.z = pos.z
self.speed = 1
self.range = 0.5
self.range_y = 0.5
self.speed = 1
end
local __get_staticdata = function(this)
if this.data then
return minetest.serialize(this.data)
end
end
local __on_activate = function(this, staticdata)
dprint("npc: on_activate")
this.data = minetest.deserialize(staticdata)
if not this.data then
this.data = {}
end
local data = this.data
end
local __on_punch = function(this)
--[[
-- remove npc from the list of npcs when they die
if self.object:get_hp() <= 0 and self.npc_pos then
townchest.npc.entity_list[self.npc_pos] = nil
end
]]--
end
local __select_chest = function(this)
-- do nothing if the chest not ready
if not this.data.chestpos
or not townchest.chest.list[this.data.chestpos.x..","..this.data.chestpos.y..","..this.data.chestpos.z] --chest position not valid
or not this.chest
or not this.chest:npc_build_allowed() then --chest buid not ready
local npcpos = this.object:getpos()
local selectedchest = nil
for key, chest in pairs(townchest.chest.list) do
if (not selectedchest or vector.distance(npcpos, chest.pos) < vector.distance(npcpos, selectedchest.pos)) and chest:npc_build_allowed() then
selectedchest = chest
end
end
if selectedchest then
this.data.chestpos = selectedchest.pos
this.chest = selectedchest
dprint("Now I will build for chest",this.chest)
else --stay if no chest assigned
this.chest = nil
this.chestpos = nil
this.target = nil
this.speed = nil
end
else
dprint("Chest ok:",this.chest)
end
end
local __get_if_buildable = function(this, realpos)
local pos = this.chest.plan:get_plan_pos(realpos)
-- dprint("in plan", pos.x.."/"..pos.y.."/"..pos.z)
local node = this.chest.plan.building_full[pos.x..","..pos.y..","..pos.z]
if not node then
return nil
end
-- skip the chest position
if realpos.x == this.chest.pos.x and realpos.y == this.chest.pos.y and realpos.z == this.chest.pos.z then --skip chest pos
this.chest.plan:set_node_processed(node)
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:", realpos.x.."/"..realpos.y.."/"..realpos.z)
end
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")
return nil
end
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
return node
elseif not node.meta then
--same item without metadata. nothing to do
this.chest.plan:set_node_processed(node)
return nil
elseif townchest.nodes.is_equal_meta(minetest.get_meta(realpos):to_table(), node.meta) then
--metadata adjustment
this.chest.plan:set_node_processed(node)
return nil
elseif node.matname == townchest.nodes.c_free_item then
-- TODO: check if nearly nodes are already built
return node
else
return node
end
else
-- no right node at place
return node
end
end
local function prefer_target(npc, t1, t2)
if not t1 then
return t2
end
local npcpos = npc.object:getpos()
npcpos.y = npcpos.y - 2.5 -- npc is 1.5 blocks over the work, so we need to be "lower" in calculation
local prefer_sameitem = 0 --prefer same items in building order
if npc.lastnode then
if npc.lastnode.name == t1.name then
prefer_sameitem = prefer_sameitem + 2
end
if npc.lastnode.name == t2.name then
prefer_sameitem = prefer_sameitem - 2
end
end
if (vector.distance(npcpos, t2.pos) + prefer_sameitem) < vector.distance(npcpos, t1.pos) then
return t2
else
return t1
end
end
local __get_target = function(this)
local npcpos = this.object:getpos()
local plan = this.chest.plan
npcpos.y = npcpos.y - 2.5 -- npc is 1.5 blocks over the work, so we need to be "lower" in calculation
-- prefer lower building nodes, so we check the distance to the next 1.5 blocks lower
local selectednode
-- first try: look for nearly buildable nodes
dprint("search for nearly node")
for x=math.floor(npcpos.x)-3, math.floor(npcpos.x)+3 do
for y=math.floor(npcpos.y)-3, math.floor(npcpos.y)+3 do
for z=math.floor(npcpos.z)-3, math.floor(npcpos.z)+3 do
local node = __get_if_buildable(this,{x=x,y=y,z=z})
if node then
node.pos = plan:get_world_pos(node)
selectednode = prefer_target(this, selectednode, node)
end
end
end
end
if not selectednode then
-- get the old target to compare
if this.targetnode and this.targetnode.pos then -- this.targetnode.pos extra check because on building reload the target is there but the position is away
-- minetest.forceload_block(this.targetnode.pos) --keep the target node loaded
selectednode = __get_if_buildable(this, this.targetnode.pos)
-- minetest.forceload_free_block(this.targetnode.pos)
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(this, plan:get_world_pos(nodeplan))
if node then
node.pos = plan:get_world_pos(node)
selectednode = prefer_target(this, selectednode, node)
end
end
--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", startingnode[1].x.."/"..startingnode[1].y.."/"..startingnode[1].z)
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(this, node_wp)
if node then
node.pos = node_wp
selectednode = prefer_target(this, selectednode, node)
end
end
else
dprint("something wrong with startningnode")
end
end
if selectednode then
selectednode.pos = plan:get_world_pos(selectednode)
return selectednode
end
end
local __on_step = function(this, dtime)
-- handle frequency
if not this.timer then
this.timer = 0
end
this.timer = this.timer + dtime;
if this.timer > 1 then
--it's time to check/get target
this.timer = 0
--get the chest assignment
__select_chest(this)
if not this.chest then
dprint("npc: No chest :(" )
this.object:setvelocity({x=0, y=0, z=0})
this.target = nil
this.speed = nil
return
end
if not this.chest.plan or this.chest.plan.building_size == 0 then
dprint("building done, disable them")
this.chest.info.npc_build = nil
return
end
this.targetnode = __get_target(this)
local npcpos = this.object:getpos()
npcpos.y = npcpos.y - 1.5 -- npc is 1.5 blocks over the work, so we need to be "lower" in calculations
if this.targetnode then
dprint("npc: Move to", this.targetnode.pos.x.."/"..this.targetnode.pos.y.."/"..this.targetnode.pos.z )
__moveto(this, this.targetnode.pos)
else
dprint("npc: No destination :(" )
this.object:setvelocity({x=0, y=0, z=0})
this.target = nil
this.speed = nil
end
dprint ("---", this.chest.plan.building_size, "Nodes in building left---")
if this.targetnode and vector.distance(npcpos, this.targetnode.pos) < 2 then
dprint("target reached. build",this.targetnode.name)
--- Place node
-- minetest.forceload_block(this.targetnode.pos)
minetest.env:add_node(this.targetnode.pos, this.targetnode)
this.lastnode = this.targetnode
if this.targetnode.meta then
minetest.env:get_meta(this.targetnode.pos):from_table(this.targetnode.meta)
end
this.chest.plan:set_node_processed(this.targetnode)
this.chest:update_statistics()
end
end
-- walk to target destination
if this.target and this.speed then
local s = this.object:getpos()
local t = this.target
local diff = {x=t.x-s.x, y=t.y-s.y, z=t.z-s.z}
--yaw calculation (http://dev.minetest.net/Player)
local yaw
if diff.z<0 then yaw = -math.atan(diff.x/diff.z)
elseif diff.z>0 then yaw = math.pi-math.atan(diff.x/diff.z)
elseif diff.x<0 then yaw = 0
else yaw = math.pi end
--yaw calculation end
this.object:setyaw(yaw) -- turn and look in given direction
-- check if destination reached, reset target in this case
if math.abs(diff.x) < this.range and math.abs(diff.y) < this.range_y and math.abs(diff.z) < this.range then
dprint("npc: destination reached")
this.object:setvelocity({x=0, y=0, z=0})
this.target = nil
this.speed = nil
else
local v = this.speed
-- if self.food > 0 then
-- self.food = self.food - dtime
-- v = v*4
-- end
local amount = (diff.x^2+diff.y^2+diff.z^2)^0.5
local vec = {x=0, y=0, z=0}
vec.x = diff.x*v/amount
vec.y = diff.y*v/amount
vec.z = diff.z*v/amount
this.object:setvelocity(vec) -- walk in given direction
end
else
this.object:setvelocity({x=0, y=0, z=0})
this.target = nil
this.speed = nil
-- look around if idle
if math.random(50) == 1 then
this.object:setyaw(this.object:getyaw()+((math.random(0,360)-180)/180*math.pi))
end
end
end
--------------------------------------
-- class attributes and methods
--------------------------------------
townchest.npc = {
-- entity_list = {}, --global entity list
-- get_npc = __get_npc
}
--------------------------------------
-- object definition / constructor
--------------------------------------
townchest.npc.new = function()
local this = {}
-- this.die = __die
return this
end
local function x(val) return ((val -80) / 160) end
local function z(val) return ((val -80) / 160) end
local function y(val) return ((val + 80) / 160) end
minetest.register_node("townchest:builder_box", {
tiles = {
"towntest_npc_builder_top.png",
"towntest_npc_builder_bottom.png",
"towntest_npc_builder_front.png",
"towntest_npc_builder_back.png",
"towntest_npc_builder_left.png",
"towntest_npc_builder_right.png",
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
--head
{x(95),y(-10), z(65), x(65), y(-40), z(95)},
--neck
{x(90),y(-40),z(70) , x(70), y(-50),z(90) },
--body
{x(90),y(-50), z(60), x(70), y(-100), z(100)},
--legs
{x(90),y(-100), z(60),x(70), y(-160),z(79) },
{x(90),y(-100), z(81),x(70), y(-160), z(100)},
--shoulders
{x(89),y(-50), z(58), x(71),y(-68),z(60)},
{x(89),y(-50), z(100),x(71) ,y(-68),z(102)},
--left arm
{x(139),y(-50),z(45),x(71),y(-63),z(58)},
--right arm
{x(89),y(-50),z(102),x(71),y(-100),z(115)},
{x(115),y(-87),z(102),x(71),y(-100),z(115)},
}
},
})
-- register template (static data) to minetest
minetest.register_entity("townchest:builder", {
hp_max = 1,
physical = false,
makes_footstep_sound = true,
collisionbox = {-0.4, -1, -0.4, 0.4, 1, 0.4},
visual_size = nil,
visual = "wielditem",
textures = {"townchest:builder_box"},
target = nil,
speed = nil,
range = nil,
range_y = nil,
after = nil,
after_param = nil,
food = 0,
get_staticdata = __get_staticdata,
on_activate = __on_activate,
on_punch = __on_punch,
on_step = __on_step,
moveto = __moveto
})

371
npcf-worker.lua Normal file
View File

@ -0,0 +1,371 @@
local dprint = townchest.dprint --debug
local MAX_SPEED = 5
townchest.npc = {
spawn_nearly = function(pos, owner)
local npcid = tostring(math.random(10000))
npcf.index[npcid] = owner --owner
local ref = {
id = npcid,
pos = {x=(pos.x+math.random(0,4)-4),y=(pos.y + 0.5),z=(pos.z+math.random(0,4)-4)},
yaw = math.random(math.pi),
name = "townchest:npcf_builder",
owner = owner,
}
local npc = npcf:add_npc(ref)
npcf:save(ref.id)
if npc then
npc:update()
end
end
}
local function get_speed(distance)
local speed = distance * 0.5
if speed > MAX_SPEED then
speed = MAX_SPEED
end
return speed
end
local select_chest = function(self)
-- do nothing if the chest not ready
if not self.metadata.chestpos
or not townchest.chest.list[self.metadata.chestpos.x..","..self.metadata.chestpos.y..","..self.metadata.chestpos.z] --chest position not valid
or not self.chest
or not self.chest:npc_build_allowed() then --chest buid not ready
local npcpos = self.object:getpos()
local selectedchest = nil
for key, chest in pairs(townchest.chest.list) do
if (not selectedchest or vector.distance(npcpos, chest.pos) < vector.distance(npcpos, selectedchest.pos)) and chest:npc_build_allowed() then
selectedchest = chest
end
end
if selectedchest then
self.metadata.chestpos = selectedchest.pos
self.chest = selectedchest
dprint("Now I will build for chest",self.chest)
else --stay if no chest assigned
self.metadata.chestpos = nil
self.chest = nil
self.chestpos = nil
end
else
dprint("Chest ok:",self.chest)
end
end
local get_if_buildable = function(self, realpos)
local pos = self.chest.plan:get_plan_pos(realpos)
-- dprint("in plan", pos.x.."/"..pos.y.."/"..pos.z)
local node = self.chest.plan.building_full[pos.x..","..pos.y..","..pos.z]
if not node then
return nil
end
-- skip the chest position
if realpos.x == self.chest.pos.x and realpos.y == self.chest.pos.y and realpos.z == self.chest.pos.z then --skip chest pos
self.chest.plan:set_node_processed(node)
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:", realpos.x.."/"..realpos.y.."/"..realpos.z)
end
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")
return nil
end
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
return node
elseif not node.meta then
--same item without metadata. nothing to do
self.chest.plan:set_node_processed(node)
return nil
elseif townchest.nodes.is_equal_meta(minetest.get_meta(realpos):to_table(), node.meta) then
--metadata adjustment
self.chest.plan:set_node_processed(node)
return nil
elseif node.matname == townchest.nodes.c_free_item then
-- TODO: check if nearly nodes are already built
return node
else
return node
end
else
-- no right node at place
return node
end
end
local function prefer_target(npc, t1, t2)
if not t1 then
return t2
end
local npcpos = npc.object:getpos()
local prefer = 0
--prefer same items in building order
if npc.lastnode then
if npc.lastnode.name == t1.name then
prefer = prefer + 2.5
end
if npc.lastnode.name == t2.name then
prefer = prefer - 2.5
end
end
local t1_c = {x=t1.pos.x, y=t1.pos.y, z=t1.pos.z}
local t2_c = {x=t2.pos.x, y=t2.pos.y, z=t2.pos.z}
-- note: npc is higher by y+1.5
-- in case of clanup task prefer higher node
if t1.name ~= "air" then
t1_c.y = t1_c.y + 3 -- calculate as over the npc by additional 1.5. no change means lower then npc by 1.5
else
prefer = prefer + 2 -- prefer air
t1_c.y = t1_c.y - 1
end
if t2.name ~= "air" then
t2_c.y = t2_c.y + 3 -- calculate as over the npc by additional 1.5. no change means lower then npc by 1.5
else
prefer = prefer - 2 -- prefer air
t2_c.y = t2_c.y - 1
end
if (vector.distance(npcpos, t2_c) + prefer) < vector.distance(npcpos, t1_c) then
return t2
else
return t1
end
end
local get_target = function(self)
local npcpos = self.object:getpos()
local plan = self.chest.plan
npcpos.y = npcpos.y - 1.5 -- npc is 1.5 blocks over the work
local 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
local node = get_if_buildable(self,{x=x,y=y,z=z})
if node then
node.pos = plan:get_world_pos(node)
selectednode = prefer_target(self, selectednode, node)
end
end
end
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)
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))
if node then
node.pos = plan:get_world_pos(node)
selectednode = prefer_target(self, selectednode, node)
end
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", startingnode[1].x.."/"..startingnode[1].y.."/"..startingnode[1].z)
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)
end
end
else
dprint("something wrong with startningnode")
end
end
if selectednode then
selectednode.pos = plan:get_world_pos(selectednode)
return selectednode
end
end
npcf:register_npc("townchest:npcf_builder" ,{
description = "Townchest Builder NPC",
textures = {"npcf_builder_skin.png"},
stepheight = 1.1,
inventory_image = "npcf_builder_inv.png",
on_step = function(self)
if self.timer > 1 then
self.timer = 0
select_chest(self)
self.target_prev = self.targetnode
if self.chest and self.chest.plan and self.chest.plan.building_size > 0 then
self.targetnode = get_target(self)
self.dest_type = "build"
else
if self.dest_type ~= "home_reached" then
self.targetnode = self.origin
self.dest_type = "home"
end
end
-- simple check if target reached
elseif self.targetnode then
local pos = self.object:getpos()
local target_distance = vector.distance(pos, self.targetnode.pos)
if target_distance < 1 then
local yaw = self.object:getyaw()
local speed = 0
self.object:setvelocity(npcf:get_walk_velocity(speed, self.object:getvelocity().y, yaw))
end
return
end
if not self.targetnode then
return
end
local pos = self.object:getpos()
local yaw = self.object:getyaw()
local state = NPCF_ANIM_STAND
local speed = 0
local acceleration = {x=0, y=-10, z=0}
if self.targetnode then
local target_distance = vector.distance(pos, self.targetnode.pos)
local target_direcion = vector.direction(pos, self.targetnode.pos)
local real_distance = 0
local real_direction = {x=0, y=0, z=0}
local last_distance = 0
if self.var.last_pos then
real_distance = vector.distance(self.var.last_pos, pos)
real_direction = vector.direction(self.var.last_pos, pos)
last_distance = vector.distance(self.var.last_pos, self.targetnode.pos)
end
yaw = npcf:get_face_direction(pos, self.targetnode.pos)
-- target reached build
if target_distance < 3 and self.dest_type == "build" then
-- do the build
local soundspec
if minetest.registered_items[self.targetnode.name].sounds then
soundspec = minetest.registered_items[self.targetnode.name].sounds.place
elseif self.targetnode.name == "air" then --TODO: should be determinated on old node, if the material handling is implemented
soundspec = default.node_sound_leaves_defaults({place = {name = "default_place_node", gain = 0.25}})
end
if soundspec then
soundspec.pos = pos
minetest.sound_play(soundspec.name, soundspec)
end
minetest.env:add_node(self.targetnode.pos, self.targetnode)
if self.targetnode.meta then
print("meta:", self.targetnode.name, dump(self.targetnode.meta))
minetest.env:get_meta(self.targetnode.pos):from_table(self.targetnode.meta)
end
self.chest.plan:set_node_processed(self.targetnode)
self.chest:update_statistics()
local cur_pos = {x=pos.x, y=pos.y - 0.5, z=pos.z}
local cur_node = minetest.registered_items[minetest.get_node(cur_pos).name]
if cur_node.walkable then
pos = {x=pos.x, y=pos.y + 1.5, z=pos.z}
self.object:setpos(pos)
end
if target_distance > 2 then
speed = 1
state = NPCF_ANIM_WALK_MINE
-- jump
if self.targetnode.name ~= "air" and (self.targetnode.pos.y -(pos.y-1.5)) > 0 and (self.targetnode.pos.y -(pos.y-1.5)) < 2 then
acceleration = {x=0, y=0, z=0}
pos = {x=pos.x, y=self.targetnode.pos.y + 1.5, z=pos.z}
self.object:setpos(pos)
end
else
speed = 0
state = NPCF_ANIM_MINE
end
self.timer = 0
self.lastnode = self.targetnode
self.laststep = "build"
self.targetnode = nil
-- home reached
elseif target_distance < 4 and self.dest_type == "home" then
-- self.object:setpos(self.origin.pos)
yaw = self.origin.yaw
speed = 0
self.dest_type = "home_reached"
self.targetnode = nil
else
--target not reached
-- 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
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)
acceleration = {x=0, y=0, z=0}
end
state = NPCF_ANIM_WALK
self.var.last_pos = pos
speed = get_speed(target_distance)
self.laststep = "walk"
end
else
dprint("no target")
end
self.object:setacceleration(acceleration)
self.object:setvelocity(npcf:get_walk_velocity(speed, self.object:getvelocity().y, yaw))
self.object:setyaw(yaw)
npcf:set_animation(self, state)
end,
})

View File

@ -221,8 +221,7 @@ local _build_status = function(state)
-- spawn NPC button
local spawn_bt = state:button(1,4,3,0.5,"spawn_bt", "Spawn NPC")
spawn_bt:onClick(function(self, state, player)
local pos = state.location.pos
minetest.add_entity({x=(pos.x+math.random(0,4)-2),y=(pos.y+math.random(0,2)),z=(pos.z+math.random(0,4)-2)}, "townchest:builder")
townchest.npc.spawn_nearly(state.location.pos, player)
end)
state:onInput(function(self, fields)

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB