diff --git a/depends.txt b/depends.txt index 4ad96d5..9454ee8 100644 --- a/depends.txt +++ b/depends.txt @@ -1 +1,2 @@ default +npcf diff --git a/init.lua b/init.lua index e492de9..6f81a96 100644 --- a/init.lua +++ b/init.lua @@ -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") ----------------------------------------------- diff --git a/npc.lua b/npc.lua deleted file mode 100644 index ea9578f..0000000 --- a/npc.lua +++ /dev/null @@ -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 -}) - diff --git a/npcf-worker.lua b/npcf-worker.lua new file mode 100644 index 0000000..590b1a5 --- /dev/null +++ b/npcf-worker.lua @@ -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, +}) + diff --git a/smartfs-forms.lua b/smartfs-forms.lua index 620658f..c81b30a 100644 --- a/smartfs-forms.lua +++ b/smartfs-forms.lua @@ -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) diff --git a/textures/npcf_builder_inv.png b/textures/npcf_builder_inv.png new file mode 100644 index 0000000..6d37a67 Binary files /dev/null and b/textures/npcf_builder_inv.png differ diff --git a/textures/npcf_builder_skin.png b/textures/npcf_builder_skin.png new file mode 100644 index 0000000..4952d55 Binary files /dev/null and b/textures/npcf_builder_skin.png differ