From ee4cb7435b8784e38fae9f898c4f0b1563485d88 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sat, 21 Jul 2018 10:49:51 +0000 Subject: [PATCH] Git --- README.md | 6 + depends.txt | 2 + example.lua | 67 +++++++++ init.lua | 270 +++++++++++++++++++++++++++++++++++++ metafile.lua | 51 +++++++ mod.conf | 4 + schematics/house.meta.conf | 3 + schematics/house.mts | Bin 0 -> 469 bytes 8 files changed, 403 insertions(+) create mode 100644 README.md create mode 100644 depends.txt create mode 100644 example.lua create mode 100644 init.lua create mode 100644 metafile.lua create mode 100644 mod.conf create mode 100644 schematics/house.meta.conf create mode 100644 schematics/house.mts diff --git a/README.md b/README.md new file mode 100644 index 0000000..b95085b --- /dev/null +++ b/README.md @@ -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). diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..44b7e3e --- /dev/null +++ b/depends.txt @@ -0,0 +1,2 @@ +mobs +mobs_npc? diff --git a/example.lua b/example.lua new file mode 100644 index 0000000..018f4cc --- /dev/null +++ b/example.lua @@ -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) diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..4fd0e09 --- /dev/null +++ b/init.lua @@ -0,0 +1,270 @@ +-- NPCBuildQueue +-- Copyright (c) 2018 Evert "Diamond" Prants + +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") diff --git a/metafile.lua b/metafile.lua new file mode 100644 index 0000000..bd3790a --- /dev/null +++ b/metafile.lua @@ -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 diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..a163446 --- /dev/null +++ b/mod.conf @@ -0,0 +1,4 @@ +name = npcbuildqueue +description = NPC Build Queues. Make your NPCs build things! +depends = mobs +optional_depends = mobs_npc diff --git a/schematics/house.meta.conf b/schematics/house.meta.conf new file mode 100644 index 0000000..b61e4e2 --- /dev/null +++ b/schematics/house.meta.conf @@ -0,0 +1,3 @@ +schematic=house.mts +size=(8,7,8) +floor=1 diff --git a/schematics/house.mts b/schematics/house.mts new file mode 100644 index 0000000000000000000000000000000000000000..383163483c226957b6251f3b001da3a40e5d4e26 GIT binary patch literal 469 zcmeYb3HD`RVc=xoVBoBW0tOxi@s!lG#L}D+tCY;5lKAq>l8pHDqQv522Ij=fA_no| z5+GG<1*YP&O7qflQp@x6Qy9cx5;=)U$l^T6#xcNoB}J*J3_>tDBy)ISV(B>`OW{fq ziwjco;=xwI#Y*yvk~8AV6LWGH1XJ?!fzASvU`6qXK)b*~8JQ_5sd*K1-iF-fJFLLL z`X+U{ooV*Boxi_Eit21R9kbiCRzju8eaUhzrZX(uQ}*1FZ1gTxU%hA1;<>Yq%f886 z$uz%8DANB}&jd$}#5K2nge
(w~kTc^!2bH*9dAt9*W$ZN>sB59dcE=9{NwTeiWnkaaKG$VoUwcT z&FC$R+}Em4GFfcybuXK+=<(;0*z4b0E5&|IeH`#8@S9@Iq~&@iHh)Q5^YHU-nWKo!(IS_l1pr*gMb50Qxh{LjV8( literal 0 HcmV?d00001