This commit is contained in:
Evert Prants 2018-07-21 10:49:51 +00:00
commit ee4cb7435b
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
8 changed files with 403 additions and 0 deletions

6
README.md Normal file
View File

@ -0,0 +1,6 @@
# NPC Build Queues
Instructs your NPCs to build schematics. Requires [Mobs Redo](https://notabug.org/tenplus1/mobs_redo) by Tenplus1.
The API is documented in `init.lua` and `metafile.lua`.
See the [demonstration video](https://www.youtube.com/watch?v=14YCZ0aA68I).

2
depends.txt Normal file
View File

@ -0,0 +1,2 @@
mobs
mobs_npc?

67
example.lua Normal file
View File

@ -0,0 +1,67 @@
-- Example entity that builds
mobs:register_mob("npcbuildqueue:building_npc", {
type = "npc",
passive = false,
damage = 3,
attack_type = "dogfight",
attacks_monsters = false,
attack_npcs = false,
owner_loyal = true,
pathfinding = true,
hp_min = 10,
hp_max = 20,
armor = 100,
collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},
visual = "mesh",
mesh = "mobs_character.b3d",
drawtype = "front",
textures = {
{"mobs_npc.png"},
{"mobs_npc2.png"}, -- female by nuttmeg20
},
child_texture = {
{"mobs_npc_baby.png"}, -- derpy baby by AmirDerAssassine
},
makes_footstep_sound = true,
sounds = {},
walk_velocity = 2,
run_velocity = 3,
jump = true,
drops = {
{name = "default:wood", chance = 1, min = 1, max = 3},
{name = "default:apple", chance = 2, min = 1, max = 2},
{name = "default:axe_stone", chance = 5, min = 1, max = 1},
},
water_damage = 0,
lava_damage = 2,
light_damage = 0,
view_range = 15,
owner = "",
fear_height = 3,
animation = {
speed_normal = 30,
speed_run = 30,
stand_start = 0,
stand_end = 79,
walk_start = 168,
walk_end = 187,
run_start = 168,
run_end = 187,
punch_start = 200,
punch_end = 219,
},
on_rightclick = function(self, clicker)
if self.order == "build" then
return
end
local pos = self.object:get_pos()
nbq.schedule_build(self, nbq.modpath .. "/schematics/house.meta.conf",
vector.add(pos, {x = 1, y = 0, z = 1}), "0", 1)
end,
do_custom = nbq.build_process,
})
mobs:register_egg("npcbuildqueue:building_npc", "Building NPC", "default_brick.png", 1)

270
init.lua Normal file
View File

@ -0,0 +1,270 @@
-- NPCBuildQueue
-- Copyright (c) 2018 Evert "Diamond" Prants <evert@lunasqu.ee>
local modpath = minetest.get_modpath("npcbuildqueue")
local ZERO_POS = {x = 0, y = 0, z = 0}
nbq = {}
nbq.modpath = modpath
-- Ensure correct mobs mod
if not rawget(_G, "mobs") then
error("Mobs missing!")
elseif not mobs.mod or mobs.mod ~= "redo" then
error("You have the wrong mobs mod installed. NBQ requires Mobs Redo by Tenplus1!")
end
-- Used to keep track of building tasks
nbq.queues = {}
-- Convert VoxelManip to a table of layers.
function nbq.schematic_build_data_tree(qid)
local sdata = nbq.queues[qid]
if not sdata then return nil end
if nbq.queues[qid]["dt"] then
nbq.queues[qid].vm = nil
return nbq.queues[qid]["dt"]
end
local vm = sdata.vm
local size = sdata.size
local origin = sdata.origin
local data = vm:get_data()
local param2 = vm:get_param2_data()
local e1, e2 = vm:read_from_map(ZERO_POS, sdata.size)
local area = VoxelArea:new{MinEdge=e1, MaxEdge=e2}
local node_list = {}
for y = 0, size.y do
node_list[y + 1] = {}
for x = 0, size.x do
for z = 0, size.z do
local npid = area:index(x, y, z)
local node = minetest.get_name_from_content_id(data[npid])
local p2 = param2[npid]
node_list[y + 1][#node_list[y + 1] + 1] = {
node, p2, vector.add(origin, {x = x, y = y - (sdata.floor + 1), z = z})
}
end
end
end
-- Save the generated data structure
nbq.queues[qid].vm = nil
nbq.queues[qid]["dt"] = node_list
return node_list
end
-- Instruct mob `self` to start building `schematic`.
-- `schematic` MUST be a path to a metadata file! (See `metafile.lua` for help)
-- `rotation` can equal `"0"`, `"90"`, `"180"`, `"270"`, or `"random"`.
-- `speed` is build delay in seconds.
function nbq.build_schematic(self, schematic, pos, rot, speed)
if self.state == "building" then return nil end
local schematic_data = nbq.get_schematic_data(schematic)
local vm = VoxelManip(ZERO_POS, schematic_data.size)
local loaded = minetest.place_schematic_on_vmanip(
vm, -- VoxelManip
ZERO_POS, -- Position in the VM
schematic_data.path, -- Schematic path
rot or "0", -- Rotation
nil, -- Replacements
true, -- Force Replacement
{} -- Flags
)
if not loaded then
return nil
end
-- Generate an unique Queue ID.
local qpid = (math.random(1, 1000) * math.random(1, 10000)) .. self.name .. (math.random(1, 1000) ^ 2)
-- Add to queue.
nbq.queues[qpid] = {
vm = vm,
schematic = schematic,
speed = speed,
origin = pos,
size = schematic_data.size,
floor = schematic_data.floor,
}
-- Build the NPC-readable data tree.
local dtree = nbq.schematic_build_data_tree(qpid)
-- Instruct the NPC to start building the next time it ticks.
self.build_queue_id = qpid
self.build_height = #dtree
-- Set NPC's variables.
if self.build_in_layer == nil then
self.build_in_height = 1
self.build_in_layer = 1
end
-- Instructs the NPC to resume building if the world was reloaded.
self.build_queue_resume = {
schematic = schematic,
speed = speed,
origin = pos,
rot = rot,
}
return qpid
end
-- Remove all build related data from the mob
local function clear_build_state(self, wipe_cache)
self.state = "stand"
-- Remove from queue
if wipe_cache then
if nbq.queues[self.build_queue_id] then
nbq.queues[self.build_queue_id] = nil
end
end
self.build_queue_resume = nil
self.build_queue_id = nil
self.build_in_layer = nil
end
-- Call this from `do_custom`
function nbq.build_process(self, dtime)
-- Build queue present but no order, order to build
if self.build_queue_id and not self.state == "building" then
self.state = "building"
print("Build queue present but no order, ordering NPC to build..")
end
-- No build queue, abort
if self.state == "building" and not self.build_queue_id then
self.state = "stand"
print("No build queue, abort")
return
end
if not self.build_queue_id then return end
-- Resume building in case the world was reloaded.
if not nbq.queues[self.build_queue_id] then
if self.build_queue_resume then
local resume = self.build_queue_resume
nbq.build_schematic(self, resume.schematic, resume.origin,
resume.rot, resume.speed)
return
end
clear_build_state(self)
print("Invalid build queue")
return
end
-- Layered node data object
if not nbq.queues[self.build_queue_id]["dt"] then
clear_build_state(self)
print("No layered data object in queued build")
return
end
-- Build queue variables
local bq = nbq.queues[self.build_queue_id]
local dt = bq.dt
local layer = dt[self.build_in_height]
-- Timer
self.build_tick = (self.build_tick or 0) + dtime
if self.build_tick < bq.speed then return end
self.build_tick = 0
----------------------
-- Proceed to build --
----------------------
-- Skip queue if layer is invalid
if not layer then
clear_build_state(self, true)
return
end
local caret = layer[self.build_in_layer]
-- Determine the position we're building at
if not caret then
if self.build_in_height < self.build_height then
-- Proceed to next layer
self.build_in_height = self.build_in_height + 1
self.build_in_layer = 1
return
else
-- Finished
print("Build finished")
clear_build_state(self, true)
return
end
end
-- Node parameters at caret
local caret_pos = caret[3]
local caret_node = caret[1]
local caret_param2 = caret[2]
-- Determine if the NPC needs to move to the location or not
local pos = self.object:get_pos()
local dist_bld = vector.distance(caret[3], pos)
-- Make the NPC look towards the node they're interacting with
-- TODO: Make NPC movements more realistic
local dir = vector.direction(pos, caret_pos)
mobs:yaw(self, math.atan2(-dir.x, dir.z))
-- Get the node at the caret
local node_at_caret = minetest.get_node(caret_pos).name
-- Check if we need to break or place
local place = true
if (caret_node == "air" and node_at_caret ~= "air") or
(caret_node ~= "air" and node_at_caret ~= "air") then
place = false
end
-- Skip air/existing node
-- Don't replace grass with dirt
if ((caret_node == "air" and node_at_caret == "air") or (caret_node == node_at_caret)) or
(caret_node:match("dirt") ~= nil and node_at_caret:match("dirt") ~= nil) then
self.build_in_layer = self.build_in_layer + 1
self.build_tick = bq.speed
return
end
-- Punch animation
mobs:set_animation(self, "punch")
-- "Break"
if not place then
minetest.set_node(caret_pos, {name = "air"})
return
end
-- Place
minetest.set_node(caret_pos, {name = caret_node, param2 = caret_param2})
-- Go to next position in the next tick :)
self.build_in_layer = self.build_in_layer + 1
end
-- Metadata file
dofile(modpath.."/metafile.lua")
-- Example code
--dofile(modpath.."/example.lua")

51
metafile.lua Normal file
View File

@ -0,0 +1,51 @@
-- Schematics that NPCs can build MUST have a metadata file.
-- Metadata files are in the format of minetest.conf
-- Here are the required params:
--[[
# Schematic file name. NOT A PATH! Relative paths work.
schematic (Schematic) string
# Size of the bounding box. If the box is too big, you may see
# unwanted nodes appear.
size (Bounding Box Size) v3f
# Floor of the building. Decides how much of the build is inset into the ground.
floor (Floor Offset) int 0
]]
nbq.schemmetacache = {}
function nbq.get_schematic_data(schemfile)
if nbq.schemmetacache[schemfile] then
return nbq.schemmetacache[schemfile]
end
-- Use minetest's Settings class.
local stn = Settings(schemfile)
local schem = stn:get("schematic")
local size = stn:get("size")
local floor = stn:get("floor") or 0
if not schem then
error("Schematic file name is not defined in the schematic metadata file.")
end
if not size or not minetest.string_to_pos(size) then
error("Size parameter is required.")
end
size = minetest.string_to_pos(size)
local dir = schemfile:gsub("/([%w._]+)$", "")
local resp = {
schematic = schem,
path = dir .. "/" .. schem,
size = size,
floor = floor,
}
nbq.schemmetacache[schemfile] = resp
return resp
end

4
mod.conf Normal file
View File

@ -0,0 +1,4 @@
name = npcbuildqueue
description = NPC Build Queues. Make your NPCs build things!
depends = mobs
optional_depends = mobs_npc

View File

@ -0,0 +1,3 @@
schematic=house.mts
size=(8,7,8)
floor=1

BIN
schematics/house.mts Normal file

Binary file not shown.