Git
This commit is contained in:
commit
ee4cb7435b
6
README.md
Normal file
6
README.md
Normal 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
2
depends.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mobs
|
||||||
|
mobs_npc?
|
67
example.lua
Normal file
67
example.lua
Normal 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
270
init.lua
Normal 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
51
metafile.lua
Normal 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
4
mod.conf
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
name = npcbuildqueue
|
||||||
|
description = NPC Build Queues. Make your NPCs build things!
|
||||||
|
depends = mobs
|
||||||
|
optional_depends = mobs_npc
|
3
schematics/house.meta.conf
Normal file
3
schematics/house.meta.conf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
schematic=house.mts
|
||||||
|
size=(8,7,8)
|
||||||
|
floor=1
|
BIN
schematics/house.mts
Normal file
BIN
schematics/house.mts
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user