Update to latest

master
LoneWolfHT 2019-04-12 19:18:40 -07:00
parent 67ccbbcfdd
commit 82c1ad48da
11 changed files with 612 additions and 0 deletions

2
modpack.conf Normal file
View File

@ -0,0 +1,2 @@
name = nc_npcs
description = Adds npcs tp NodeCore

View File

@ -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

View File

@ -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

22
nc_pathfinder/init.lua Normal file
View File

@ -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

112
npcs/functions.lua Normal file
View File

@ -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

221
npcs/init.lua Normal file
View File

@ -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
})

2
npcs/mod.conf Normal file
View File

@ -0,0 +1,2 @@
name = npcs
depends = nc_api, nc_terrain, nc_tree, nc_pathfinder

43
npcs/nodes.lua Normal file
View File

@ -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