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