npcbuildqueue/init.lua

271 lines
6.7 KiB
Lua

-- 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")