Update to latest
parent
67ccbbcfdd
commit
82c1ad48da
|
@ -0,0 +1,2 @@
|
|||
name = nc_npcs
|
||||
description = Adds npcs tp NodeCore
|
|
@ -0,0 +1,114 @@
|
|||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local next, pairs
|
||||
= next, pairs
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
--[[
|
||||
A* Pathfinding Algorithm
|
||||
|
||||
Params:
|
||||
start = starting position.
|
||||
heur(p) = function to compute heuristic cost estimate from
|
||||
position p to goal; must return <= 0 if p IS goal.
|
||||
maxpts = maximum number of nodes to examine before giving up.
|
||||
edgecost(a, b) = function to get real cost of moving from a to b.
|
||||
neigh(p) = generator function to get all possible neighboring nodes of
|
||||
p. N.B. position values that are equal must be
|
||||
reference-equal, i.e. for non-scalars, interning is probably
|
||||
required.
|
||||
|
||||
Returns:
|
||||
- Path, as hash[from]=to, to look up next step given position.
|
||||
Nil if pathfinding failed entirely, or already at goal.
|
||||
- Truthy if a real solution was found, falsey if path is a
|
||||
partial estimated solution based on heuristic.
|
||||
- Total real cost of solution path given.
|
||||
- Number of maxpts NOT consumed by the search.
|
||||
--]]
|
||||
|
||||
local function result(solved, goal, from, cost, maxpts)
|
||||
if not goal then return end
|
||||
|
||||
local path = {}
|
||||
local prev
|
||||
while goal do
|
||||
if prev then
|
||||
path[goal] = prev
|
||||
end
|
||||
prev = goal
|
||||
goal = from[goal]
|
||||
end
|
||||
|
||||
return path, solved, cost, maxpts
|
||||
end
|
||||
|
||||
local function astar(start, heur, maxpts, edgecost, neigh)
|
||||
if heur(start) <= 0 then return end
|
||||
|
||||
local bestpos, bestscore
|
||||
local closed = {}
|
||||
local priq = {[1] = {[1] = start}}
|
||||
local from = {}
|
||||
local costs = {[start] = 0}
|
||||
while maxpts > 0 do
|
||||
-- Get the next set of points to process, sharing
|
||||
-- the same lowest estimated total cost so far.
|
||||
local minscore, curset
|
||||
for k, v in pairs(priq) do
|
||||
if not minscore or k < minscore then
|
||||
minscore = k
|
||||
curset = v
|
||||
end
|
||||
end
|
||||
if not curset then return result(nil, bestpos, from, bestscore, maxpts) end
|
||||
priq[minscore] = nil
|
||||
|
||||
-- Point/cost pairs pending addition to priq
|
||||
local addq = {}
|
||||
|
||||
-- Proces each point within the group, in reverse
|
||||
-- order (preferring later-found points, closer to
|
||||
-- depth-first).
|
||||
for idx = #curset, 1, -1 do
|
||||
local curpt = curset[idx]
|
||||
|
||||
maxpts = maxpts - 1
|
||||
if maxpts < 1 then break end
|
||||
closed[curpt] = true
|
||||
|
||||
local curptcost = costs[curpt]
|
||||
for n in neigh(curpt) do
|
||||
repeat
|
||||
if closed[n] then break end
|
||||
local newcost = curptcost + edgecost(curpt, n)
|
||||
local oldcost = costs[n]
|
||||
if oldcost and oldcost <= newcost then break end
|
||||
costs[n] = newcost
|
||||
from[n] = curpt
|
||||
addq[n] = {p = n, c = newcost}
|
||||
until false
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(addq) do
|
||||
local h = heur(v.p)
|
||||
if h <= 0 then return result(true, v.p, from, v.c, maxpts) end
|
||||
|
||||
local f = h + v.c
|
||||
if not bestscore or f < bestscore then
|
||||
bestscore = f
|
||||
bestpos = v.p
|
||||
end
|
||||
|
||||
local t = priq[f]
|
||||
if not t then
|
||||
t = {}
|
||||
priq[f] = t
|
||||
end
|
||||
t[#t + 1] = v.p
|
||||
end
|
||||
end
|
||||
return result(nil, bestpos, from, bestscore, maxpts)
|
||||
end
|
||||
|
||||
return astar
|
|
@ -0,0 +1,96 @@
|
|||
-- LUALOCALS < ---------------------------------------------------------
|
||||
local ipairs, minetest, pairs, vector
|
||||
= ipairs, minetest, pairs, vector
|
||||
-- LUALOCALS > ---------------------------------------------------------
|
||||
|
||||
local astar = ...
|
||||
|
||||
local alldirs = {
|
||||
{x = 1, y = 0, z = 0},
|
||||
{x = 1, y = 0, z = 1},
|
||||
{x = 1, y = 0, z = -1},
|
||||
{x = 0, y = 0, z = 1},
|
||||
{x = 0, y = 0, z = -1},
|
||||
{x = -1, y = 0, z = 0},
|
||||
{x = -1, y = 0, z = 1},
|
||||
{x = -1, y = 0, z = -1}
|
||||
}
|
||||
|
||||
local openspaces = {}
|
||||
minetest.after(0, function()
|
||||
for k, v in pairs(minetest.registered_nodes) do
|
||||
if not v.walkable then openspaces[k] = true
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function findpath(start, target, maxpts)
|
||||
|
||||
local pos_intern
|
||||
do
|
||||
local cache = {}
|
||||
pos_intern = function(pos)
|
||||
local key = minetest.pos_to_string(pos)
|
||||
local got = cache[key]
|
||||
if got then return got end
|
||||
cache[key] = pos
|
||||
return pos
|
||||
end
|
||||
end
|
||||
|
||||
local function walkable(pos) return not openspaces[minetest.get_node(pos).name] end
|
||||
local function addy(pos, y) return {x = pos.x, y = pos.y + y, z = pos.z} end
|
||||
local function check(pos)
|
||||
if walkable(pos) then
|
||||
local above = addy(pos, 1)
|
||||
if not walkable(above) and not walkable(addy(above, 1)) then
|
||||
return above
|
||||
end
|
||||
return
|
||||
end
|
||||
local below = addy(pos, -1)
|
||||
if walkable(below) then return pos end
|
||||
for y = 1, 5 do
|
||||
pos = below
|
||||
below = addy(pos, -1)
|
||||
if walkable(below) then return pos end
|
||||
end
|
||||
end
|
||||
|
||||
local checkmemo = {}
|
||||
local function memocheck(pos)
|
||||
pos = pos_intern(pos)
|
||||
local got = checkmemo[pos]
|
||||
if got then return got end
|
||||
got = check(pos)
|
||||
checkmemo[pos] = got
|
||||
return got
|
||||
end
|
||||
|
||||
local function neigh(pos)
|
||||
local t = {}
|
||||
for _, v in ipairs(alldirs) do
|
||||
local p = memocheck(vector.add(pos, v))
|
||||
if p then t[#t + 1] = p end
|
||||
end
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
local v = t[i]
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
local function heur(pos) return vector.distance(pos, target) end
|
||||
|
||||
local function cost(a, b)
|
||||
local c = vector.distance(a, b)
|
||||
if b.y > a.y then c = c + 1 end
|
||||
return c
|
||||
end
|
||||
|
||||
start = pos_intern(start)
|
||||
return start, astar(start, heur, maxpts, cost, neigh)
|
||||
end
|
||||
|
||||
return findpath
|
|
@ -0,0 +1,22 @@
|
|||
pathfinder = {}
|
||||
|
||||
local function include(n, ...)
|
||||
return loadfile(minetest.get_modpath("nc_pathfinder")
|
||||
.. "/" .. n .. ".lua")(...)
|
||||
end
|
||||
|
||||
pathfinder.get = include("astar_mt", include("astar_core"))
|
||||
|
||||
function pathfinder.find(pos1, pos2, maxpts)
|
||||
local pos, path, solved = pathfinder.get(pos1, pos2, maxpts)
|
||||
local npath = {}
|
||||
|
||||
if not path then return end
|
||||
|
||||
while pos do
|
||||
table.insert(npath, pos)
|
||||
pos = path[pos]
|
||||
end
|
||||
|
||||
return(npath)
|
||||
end
|
|
@ -0,0 +1,112 @@
|
|||
local anim = {
|
||||
stand = {x = 0, y = 0},
|
||||
sit = {x = 1, y = 1},
|
||||
walk = {x = 2, y = 42},
|
||||
}
|
||||
|
||||
function npcs.log(text)
|
||||
minetest.chat_send_all("[npcs] " .. text)
|
||||
end
|
||||
|
||||
-- Tasks
|
||||
|
||||
function npcs.register_task(name, def)
|
||||
npcs.tasks[name] = def
|
||||
end
|
||||
|
||||
function npcs.set_task(pos, name)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local npcname = meta:get_string("name")
|
||||
|
||||
meta:set_string("task", name)
|
||||
meta:set_string("infotext", string.format(npcs.tasks[name].info, npcname))
|
||||
npcs.tasks[name].func(pos)
|
||||
end
|
||||
|
||||
-- Movement
|
||||
|
||||
function npcs.move(pos1, pos2, def)
|
||||
local obj = npcs.active[minetest.pos_to_string(vector.round(pos1))]
|
||||
local dist = vector.new(2, 2, 2)
|
||||
|
||||
obj:get_luaentity().nodemeta = minetest.get_meta(pos1):to_table()
|
||||
obj:get_luaentity().def = def
|
||||
minetest.remove_node(pos1)
|
||||
minetest.remove_node(vector.new(pos1.x, pos1.y+1, pos1.z))
|
||||
|
||||
pos2 = minetest.find_nodes_in_area(vector.add(pos2, dist), vector.subtract(pos2, dist), "air")
|
||||
|
||||
if pos2 then
|
||||
for _, p in ipairs(pos2) do
|
||||
p.y = p.y + 1
|
||||
local p_up = vector.new(p.x, p.y+1, p.z)
|
||||
|
||||
if minetest.get_node(p_up).name == "air" then
|
||||
pos2 = p
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not pos2.x then
|
||||
npcs.log("Couldn't find any air near destination")
|
||||
npcs.stop_move(obj, false)
|
||||
return
|
||||
else
|
||||
local pos_down = vector.new(pos2.x, pos2.y-1, pos2.z)
|
||||
|
||||
while minetest.get_node(pos_down).name == "air" do
|
||||
pos2 = pos_down
|
||||
pos_down = vector.new(pos2.x, pos2.y-1, pos2.z)
|
||||
end
|
||||
end
|
||||
|
||||
obj:set_animation(anim.walk, 40)
|
||||
obj:get_luaentity().move(obj, pos1, pos2)
|
||||
else
|
||||
npcs.log("Couldn't find any air near destination")
|
||||
npcs.stop_move(obj, false)
|
||||
end
|
||||
end
|
||||
|
||||
function npcs.stop_move(obj, success)
|
||||
local pos = obj:get_pos()
|
||||
|
||||
minetest.set_node(pos, {name = "npcs:npc"})
|
||||
minetest.set_node(vector.new(pos.x, pos.y+1, pos.z), {name = "npcs:hidden"})
|
||||
minetest.get_meta(pos):from_table(obj:get_luaentity().nodemeta)
|
||||
obj:set_animation(anim.stand)
|
||||
|
||||
if success and obj:get_luaentity().def and obj:get_luaentity().def.on_end then
|
||||
obj:get_luaentity().def.on_end()
|
||||
obj:get_luaentity().def = nil
|
||||
end
|
||||
end
|
||||
|
||||
--Activation
|
||||
|
||||
function npcs.activate_npc(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local pos_up = vector.new(pos.x, pos.y+1, pos.z)
|
||||
local entpos = vector.new(pos.x, pos.y-0.5, pos.z)
|
||||
local inv = meta:get_inventory()
|
||||
|
||||
inv:set_size("main", 8)
|
||||
|
||||
if meta:get_string("name") == "" then
|
||||
meta:set_string("name", npcs.names[math.random(1, #npcs.names)])
|
||||
end
|
||||
|
||||
if minetest.get_node(pos_up).name ~= "npcs:hidden" then
|
||||
minetest.set_node(pos_up, {name = "npcs:hidden"})
|
||||
end
|
||||
|
||||
npcs.active[minetest.pos_to_string(vector.round(pos))] = minetest.add_entity(entpos, "npcs:npc_ent")
|
||||
|
||||
npcs.set_task(pos, "wait")
|
||||
npcs.log("Activated npc at "..minetest.pos_to_string(pos))
|
||||
end
|
||||
|
||||
function npcs.deactivate_npc(pos)
|
||||
npcs.active[minetest.pos_to_string(vector.round(pos))] = nil
|
||||
npcs.log("Deactivated npc at "..minetest.pos_to_string(pos))
|
||||
end
|
|
@ -0,0 +1,221 @@
|
|||
npcs = {
|
||||
tasks = {},
|
||||
active = {}
|
||||
}
|
||||
|
||||
npcs.names = { -- htps://www.fantasynamegenerators.com/
|
||||
"Aguth",
|
||||
"Awam",
|
||||
"Angash",
|
||||
"Mahang",
|
||||
"Zothos",
|
||||
"Phegar",
|
||||
"Niyelo",
|
||||
"Yezaddem",
|
||||
"Ayarris",
|
||||
"Suddahnihn",
|
||||
"Walehn",
|
||||
"Revrun",
|
||||
"Viwal",
|
||||
"Ihoth",
|
||||
"Sazas",
|
||||
"Gille",
|
||||
"Guwinush",
|
||||
"Kyithikun",
|
||||
"Therahno",
|
||||
"Kyabenru",
|
||||
"Misrol",
|
||||
"Oyoth",
|
||||
"Segyosh",
|
||||
"Vuddo",
|
||||
"Musu",
|
||||
"Egyirs",
|
||||
"Moshero",
|
||||
"Veseddan",
|
||||
"Umeso",
|
||||
"Thamoke",
|
||||
}
|
||||
|
||||
dofile(minetest.get_modpath("npcs").."/nodes.lua")
|
||||
dofile(minetest.get_modpath("npcs").."/functions.lua")
|
||||
|
||||
--
|
||||
--- NPCS
|
||||
--
|
||||
|
||||
npcs.register_task("wait", {
|
||||
info = "%s is planning their next move",
|
||||
func = function(pos)
|
||||
local tree = minetest.find_node_near(pos, 30, "nc_tree:root", false)
|
||||
local eggc = minetest.find_node_near(pos, 15, "nc_tree:eggcorn_planted", false)
|
||||
|
||||
if tree and not eggc then
|
||||
npcs.move(pos, tree, {
|
||||
on_end = function()
|
||||
npcs.set_task(pos, "dig_tree")
|
||||
end
|
||||
})
|
||||
elseif eggc and vector.distance(pos, eggc) > 5 then
|
||||
npcs.move(pos, eggc, {
|
||||
on_end = function()
|
||||
npcs.set_task(pos, "wait_tree")
|
||||
end
|
||||
})
|
||||
elseif not eggc and not tree then
|
||||
local pos_down = vector.new(pos.x, pos.y-1, pos.z)
|
||||
local node_below = minetest.get_node(pos_down).name
|
||||
local node_near = minetest.find_node_near(pos, 3, "group:soil", false)
|
||||
|
||||
if node_near then
|
||||
minetest.set_node(node_near, {name = "nc_tree:eggcorn_planted"})
|
||||
npcs.set_task(pos, "wait_tree")
|
||||
elseif minetest.registered_nodes[node_below].groups and minetest.registered_nodes[node_below].groups.soil then
|
||||
minetest.set_node(pos_down, {name = "nc_tree:eggcorn_planted"})
|
||||
npcs.set_task(pos, "wait_tree")
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
npcs.register_task("dig_tree", {
|
||||
info = "%s is cutting down a tree",
|
||||
func = function(pos)
|
||||
local rootpos = minetest.find_node_near(pos, 4.3, "nc_tree:root", false)
|
||||
local treepos = minetest.find_node_near(pos, 4.3, "nc_tree:root", false)
|
||||
local leaves = minetest.find_node_near(pos, 5, "nc_tree:leaves", false)
|
||||
local num = 0
|
||||
local inv = minetest.get_meta(pos):get_inventory()
|
||||
|
||||
treepos.y = treepos.y + 1
|
||||
|
||||
while leaves do
|
||||
npcs.move(pos, leaves, {
|
||||
on_end = function()
|
||||
minetest.remove_node(leaves)
|
||||
leaves = minetest.find_node_near(pos, 5, "nc_tree:leaves", false)
|
||||
end
|
||||
})
|
||||
|
||||
num = num + 5
|
||||
end
|
||||
|
||||
while minetest.get_node(treepos).name == "nc_tree:tree" do
|
||||
minetest.remove_node(treepos)
|
||||
inv:add_item("main", "nc_woodwork:plank 4")
|
||||
treepos.y = treepos.y + 1
|
||||
end
|
||||
|
||||
minetest.set_node(rootpos, {name = "nc_tree:eggcorn_planted"})
|
||||
end
|
||||
})
|
||||
|
||||
--
|
||||
--- Mapgen and (L/A)BMs
|
||||
--
|
||||
|
||||
minetest.register_ore({
|
||||
ore_type = "blob",
|
||||
ore = "npcs:npc_spawner",
|
||||
wherein = "nc_terrain:dirt_with_grass",
|
||||
clust_scarcity = 30 * 30 * 30,
|
||||
clust_num_ores = 1,
|
||||
clust_size = 1,
|
||||
y_max = 30,
|
||||
y_min = 0,
|
||||
})
|
||||
|
||||
minetest.register_lbm({
|
||||
label = "activate npcs",
|
||||
name = "npcs:spawner",
|
||||
nodenames = {"npcs:npc_spawner"},
|
||||
run_at_every_load = true,
|
||||
action = function(pos, node)
|
||||
local pos_up = vector.new(pos.x, pos.y+1, pos.z)
|
||||
|
||||
minetest.set_node(pos_up, {name = "npcs:npc"})
|
||||
minetest.set_node(pos, {name = "nc_terrain:dirt_with_grass"})
|
||||
npcs.activate_npc(pos_up)
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_lbm({
|
||||
label = "activate npcs",
|
||||
name = "npcs:activator",
|
||||
nodenames = {"npcs:npc"},
|
||||
run_at_every_load = true,
|
||||
action = function(pos)
|
||||
pos = vector.round(pos)
|
||||
|
||||
if not npcs.active[minetest.pos_to_string(pos)] then
|
||||
npcs.activate_npc(pos)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local deactivate_step = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
if deactivate_step <= 20 then
|
||||
deactivate_step = deactivate_step + dtime
|
||||
else
|
||||
for _, p in ipairs(npcs.active) do
|
||||
for _, player in ipairs(minetest.get_connected_players()) do
|
||||
if vector.distance(player:get_pos(), p) >= 40 then
|
||||
npcs.deactivate_npc(p)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
--
|
||||
--- Entity
|
||||
--
|
||||
|
||||
minetest.register_entity("npcs:npc_ent", {
|
||||
npc = true,
|
||||
physical = true,
|
||||
pointable = false,
|
||||
stepheight = 1.5,
|
||||
time = 0,
|
||||
visual = "mesh",
|
||||
mesh = "nc_player_model.b3d",
|
||||
static_save = false,
|
||||
textures = {"npcs_blockfoot.png"},
|
||||
collide_with_objects = false,
|
||||
collisionbox = {0.5, 1.5, 0.5, -0.5, -0.3, -0.5},
|
||||
nodemeta = {},
|
||||
move = function(obj, pos1, pos2, path)
|
||||
local pos = obj:get_pos()
|
||||
local ent_offset = vector.new(0, -0.5, 0)
|
||||
|
||||
if not path then
|
||||
path = pathfinder.find(pos1, pos2, 350)
|
||||
obj:set_pos(vector.add(pos1, ent_offset), false)
|
||||
else
|
||||
if not path[1] then
|
||||
npcs.stop_move(obj, false)
|
||||
return
|
||||
end
|
||||
|
||||
if minetest.get_node(path[1]).name == "air" then
|
||||
obj:set_pos(vector.add(path[1], ent_offset), false)
|
||||
table.remove(path, 1)
|
||||
else
|
||||
path = pathfinder.find(pos, pos2, 350)
|
||||
|
||||
if path then
|
||||
obj:set_pos(vector.add(path[1], ent_offset), false)
|
||||
table.remove(path, 1)
|
||||
else
|
||||
npcs.stop_move(obj, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if vector.distance(pos, pos2) > 1 and path then
|
||||
minetest.after(0.5, minetest.registered_entities["npcs:npc_ent"].move, obj, pos1, pos2, path)
|
||||
else
|
||||
npcs.stop_move(obj, true)
|
||||
end
|
||||
end
|
||||
})
|
|
@ -0,0 +1,2 @@
|
|||
name = npcs
|
||||
depends = nc_api, nc_terrain, nc_tree, nc_pathfinder
|
|
@ -0,0 +1,43 @@
|
|||
minetest.register_node("npcs:npc", {
|
||||
description = "You hacker you!!",
|
||||
drawtype = "airlike",
|
||||
paramtype = "light",
|
||||
diggable = false,
|
||||
light_source = 5,
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {0.45, 1.5, 0.25, -0.45, -0.5, -0.25},
|
||||
},
|
||||
collision_box = {
|
||||
type = "fixed",
|
||||
fixed = {0.45, 1.5, 0.25, -0.45, -0.5, -0.25},
|
||||
},
|
||||
on_punch = function(pos, node, puncher, pointed_thing)
|
||||
npcs.log(minetest.serialize(minetest.get_meta(pos):to_table().fields))
|
||||
end,
|
||||
on_rightclick = function(pos, _, clicker)
|
||||
minetest.show_formspec(clicker:get_player_name(), "npcs:inv", getform(pos))
|
||||
end
|
||||
})
|
||||
|
||||
function getform(pos)
|
||||
local spos = pos.x .. "," .. pos.y .. "," .. pos.z
|
||||
local formspec =
|
||||
"size[8,9]" ..
|
||||
"list[nodemeta:" .. spos .. ";main;0,0.3;8,4;]" ..
|
||||
"list[current_player;main;0,4.85;8,1;]" ..
|
||||
"list[current_player;main;0,6.08;8,3;8]" ..
|
||||
"listring[nodemeta:" .. spos .. ";main]" ..
|
||||
"listring[current_player;main]"
|
||||
return formspec
|
||||
end
|
||||
|
||||
minetest.register_node("npcs:hidden", {
|
||||
description = "You big hacker you!!",
|
||||
drawtype = "airlike",
|
||||
paramtype = "light",
|
||||
diggable = false,
|
||||
pointable = false,
|
||||
})
|
||||
|
||||
minetest.register_node("npcs:npc_spawner", minetest.registered_nodes["nc_terrain:dirt_with_grass"])
|
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Loading…
Reference in New Issue