diff --git a/npcf/README.md b/npcf/README.md index 4d680d9..e359087 100644 --- a/npcf/README.md +++ b/npcf/README.md @@ -84,12 +84,20 @@ Permanently unload and delete named NPC. (requires server priv) Loads the NPC at the specified postion. (requires ownership or server priv) - /npcf load npc_name 0, 5, 0 + /npcf load npc_name 0, 1.5, 0 Use 'here' to load the NPC at the player's current position. /npcf load npc_name here +### setpos npc_name pos | here + +Set named NPC location. (x, y, z) + + /npcf setpos npc_name 0, 1.5, 0 + +Use 'here' to load the NPC at the player's current position. + ### setskin npc_name skin_filename | random Set the skin texture of the named NPC. (requires server priv) @@ -100,12 +108,6 @@ If you have Zeg9's skins mod installed you can select a random texture from said /npcf setskin npc_name random -### setpos npc_name pos - -Set named NPC location. (x, y, z) - - /npcf setpos npc_name 0, 5, 0 - API Reference ------------- @@ -142,7 +144,7 @@ Additional properties included by the framework. (defaults) armor_groups = {immortal=1}, inventory_image = "npcf_info_inv.png", show_nametag = true, - nametag_color = "white", + nametag_color = "white", --textcolors mod adds red, blue, green, cyan, yellow and magenta metadata = {}, var = {}, timer = 0, @@ -165,10 +167,7 @@ where it may be desireable update the statically saved position. Callbacks --------- -Additional callbacks included by the framework. - -### on_registration = function(self, pos, sender) -Only ever called once by the framework upon successful NPC registration. +Additional callbacks provided by the framework. ### on_construct = function(self) This is called before the slightly delayed inbuilt on_activate callback. @@ -176,12 +175,19 @@ Please note that the self.npc_name, self.owner and self.origin properties may not be available or nil at the time of registration. ### on_receive_fields = function(self, fields, sender) -Called when a button is pressed in the NPC's formspec. +Called when a button is pressed in the NPC's formspec. text fields, dropdown, +list and checkbox selections are automatically stored in the self.metadata table. + +### on_registration = function(self, pos, sender) +Only ever called once upon successful NPC registration using a spawner. +Currently not used anywhere and may be removed from future version. + npcf ---- +The global NPC framework namespace. -The global NPC framework namespace. Other global variables include. +### Global Constants NPCF_ANIM_STAND = 1 NPCF_ANIM_SIT = 2 @@ -210,21 +216,42 @@ The metadata table is persistent following a reload and automatically stores sub The var table should be used for non-persistent data storage only. Note that self.timer is automatically incremented by the framework but should be reset externally. -### npcf:clear_npc(npc_name) +### npcf:spawn(pos, name, def) + +Spawns and registers a NPC entity at the specified position. Returns a minetest ObjectRef on success. + + local pos = player:getpos() + local yaw = player:get_look_yaw() + local player_name = player:get_player_name() + local luaentity = npcf:spawn(pos, "npcf:guard_npc", { + owner = player_name, + npc_name = "Sam", + origin = {pos=pos, yaw=yaw}, --optional + }) + +Note that the on_registration callback will not be issued when spawning NPC's this way. + +### npcf:clear(npc_name) Clear (unload) named NPC. -### npcf:load_npc(npc_name, pos) +### npcf:load(npc_name, pos) Loads the NPC at the specified postion. If pos is nil then the NPC is loaded at the last saved origin. -### npcf:save_npc(luaentity) +### npcf:save(luaentity) Save current NPC state to file. + on_receive_fields = function(self, fields, sender) + if fields.save then + npcf:save(self) + end + end, + ### npcf:set_animation(luaentity, state) -Sets the entity animation state. +Sets the NPC's animation state. on_activate = function(self, staticdata, dtime_s) npcf:set_animation(self, NPCF_ANIM_STAND) @@ -238,7 +265,7 @@ Returns a table of all registered NPCs. (loaded or unloaded) ### npcf:get_luaentity(npc_name) -Returns a minetest ObjectRef of the npc entity. +Returns a minetest ObjectRef of the NPC entity. ### npcf:get_face_direction(v1, v2) @@ -254,6 +281,6 @@ Returns a velocity vector for the given speed, y velocity and yaw. Shows a formspec, similar to minetest.show_formspec() but with the npc_name included. Submitted data can then be captured in the NPC's own 'on_receive_fields' callback. -Note that Form text fields, dropdown, list and checkbox selections are automatically +Note that form text fields, dropdown, list and checkbox selections are automatically stored in the NPC's metadata table. Image/Button clicks, however, are not. diff --git a/npcf/chatcommands.lua b/npcf/chatcommands.lua index 388bfc5..2f3b385 100644 --- a/npcf/chatcommands.lua +++ b/npcf/chatcommands.lua @@ -9,6 +9,12 @@ minetest.register_chatcommand("npcf", { if cmd == "setpos" then if admin or name == index[npc_name] then local pos = minetest.string_to_pos(args) + if args == "here" then + local player = minetest.get_player_by_name(name) + if player then + pos = player:getpos() + end + end if pos then pos.y = pos.y + 1 local luaentity = npcf:get_luaentity(npc_name) @@ -16,9 +22,13 @@ minetest.register_chatcommand("npcf", { if admin or luaentity.owner == name then luaentity.object:setpos(pos) luaentity.origin.pos = pos - npcf:save_npc(luaentity) + npcf:save(luaentity) + pos = minetest.pos_to_string(pos) + minetest.log("action", name.." moves NPC "..npc_name.." to "..pos) end end + else + minetest.chat_send_player(name, "Invalid position "..args) end end elseif cmd == "load" then @@ -32,11 +42,13 @@ minetest.register_chatcommand("npcf", { end if pos then pos.y = pos.y + 1 - if npcf:load_npc(npc_name, pos) then + if npcf:load(npc_name, pos) then minetest.after(1, function() local luaentity = npcf:get_luaentity(npc_name) if luaentity then - npcf:save_npc(luaentity) + npcf:save(luaentity) + pos = minetest.pos_to_string(pos) + minetest.log("action", name.." loads NPC "..npc_name.." at "..pos) else minetest.chat_send_player(name, "Unable to load "..npc_name) end @@ -74,7 +86,8 @@ minetest.register_chatcommand("npcf", { cmd, npc_name = string.match(param, "([^ ]+) (.+)") if cmd and npc_name then if cmd == "delete" and admin then - npcf:clear_npc(npc_name) + npcf:clear(npc_name) + minetest.log("action", name.." deletes NPC "..npc_name) local input = io.open(NPCF_DATADIR.."/"..npc_name..".npc", "r") if input then io.close(input) @@ -90,11 +103,14 @@ minetest.register_chatcommand("npcf", { end elseif cmd == "clear" then if admin or name == index[npc_name] then - npcf:clear_npc(npc_name) + npcf:clear(npc_name) + minetest.log("action", name.." clears NPC "..npc_name) end elseif cmd == "reload" then if admin or name == index[npc_name] then - if not npcf:load_npc(npc_name, nil) then + if npcf:load(npc_name, nil) then + minetest.log("action", name.." reloads NPC "..npc_name) + else minetest.chat_send_player(name, "Unable to reload "..npc_name) end end @@ -103,8 +119,10 @@ minetest.register_chatcommand("npcf", { local saved = false local luaentity = npcf:get_luaentity(npc_name) if luaentity then - if npcf:save_npc(luaentity) then + if npcf:save(luaentity) then saved = true + minetest.chat_send_player(name, npc_name.." has been saved") + minetest.log("action", name.." saves NPC "..npc_name) end end if saved == false then @@ -147,14 +165,16 @@ minetest.register_chatcommand("npcf", { end minetest.chat_send_player(name, "NPC List: "..msg) elseif cmd == "clearobjects" and admin then + minetest.log("action", name.." clears all NPC objects") for _,ref in pairs(minetest.luaentities) do if ref.object and ref.npcf_id then ref.object:remove() end end elseif cmd == "loadobjects" and admin then + minetest.log("action", name.." reloads all NPC objects") for npc_name,_ in pairs(index) do - npcf:load_npc(npc_name, nil) + npcf:load(npc_name, nil) end end end diff --git a/npcf/npcf.lua b/npcf/npcf.lua index 6545b27..1b96719 100644 --- a/npcf/npcf.lua +++ b/npcf/npcf.lua @@ -104,6 +104,10 @@ local function get_valid_player_name(player) end end +local function get_valid_npc_name(npc_name) + return npc_name:len() <= 12 and npc_name:match("^[A-Za-z0-9%_%-]+$") +end + local function get_valid_entity(luaentity) if luaentity then if luaentity.name and luaentity.owner and luaentity.origin then @@ -326,10 +330,8 @@ function npcf:register_npc(name, def) local owner = meta:get_string("owner") local player_name = sender:get_player_name() if player_name == owner then - if fields.name:len() <= 12 and fields.name:match("^[A-Za-z0-9%_%-]+$") then - local input = io.open(NPCF_DATADIR.."/"..fields.name..".npc", "r") - if input then - io.close(input) + if get_valid_npc_name(fields.name) then + if index[fields.name] then minetest.chat_send_player(player_name, "Error: Name Already Taken!") return end @@ -339,70 +341,89 @@ function npcf:register_npc(name, def) end minetest.dig_node(pos) local npc_pos = {x=pos.x, y=pos.y + 0.5, z=pos.z} - local npc = minetest.add_entity(npc_pos, name) - if npc then - local luaentity = npc:get_luaentity() - if luaentity then - luaentity.owner = player_name - luaentity.npc_name = fields.name - luaentity.origin = {pos=npc_pos, yaw=luaentity.object:getyaw()} - save_npc(luaentity) - index[fields.name] = owner - local output = io.open(NPCF_DATADIR.."/index.txt", 'w') - if output then - output:write(minetest.serialize(index)) - io.close(output) - else - minetest.log("error", "Failed to add "..fields.name.." to npc index") - end - if luaentity.show_nametag then - add_nametag(luaentity) - end - if type(def.on_registration) == "function" then - def.on_registration(luaentity, pos, sender) - end - end + local luaentity = npcf:spawn(npc_pos, name, { + owner = player_name, + npc_name = fields.name, + }) + if luaentity and type(def.on_registration) == "function" then + def.on_registration(luaentity, pos, sender) end end end, }) end -function npcf:clear_npc(npc_name) - for _,ref in pairs(minetest.luaentities) do - if ref.object and ref.npc_name == npc_name then - ref.object:remove() +function npcf:spawn(pos, name, def) + if pos and name and def.npc_name and def.owner then + if get_valid_npc_name(def.npc_name) and index[def.npc_name] == nil then + local entity = minetest.add_entity(pos, name) + if entity then + local luaentity = entity:get_luaentity() + if luaentity then + luaentity.owner = def.owner + luaentity.npc_name = def.npc_name + luaentity.origin = def.origin or {pos=pos, yaw=0} + index[def.npc_name] = def.owner + if npcf:save(luaentity) then + local output = io.open(NPCF_DATADIR.."/index.txt", 'w') + if output then + output:write(minetest.serialize(index)) + io.close(output) + if NPCF_SHOW_NAMETAGS == true and luaentity.show_nametag == true then + add_nametag(luaentity) + end + return luaentity + else + minetest.log("error", "Failed to add "..def.npc_name.." to NPC index") + end + else + minetest.log("error", "Failed to save NPC "..def.npc_name) + end + end + end end end end -function npcf:load_npc(npc_name, pos) - npcf:clear_npc(npc_name) - local input = io.open(NPCF_DATADIR.."/"..npc_name..".npc", "r") - if input then - local data = minetest.deserialize(input:read('*all')) - io.close(input) - if data then - if pos and data.origin then - data.origin.pos = pos +function npcf:clear(npc_name) + if get_valid_npc_name(npc_name) then + for _,ref in pairs(minetest.luaentities) do + if ref.object and ref.npc_name == npc_name then + ref.object:remove() end - if data.origin.pos then - local npc = minetest.add_entity(data.origin.pos, data.name) - if npc then - local luaentity = npc:get_luaentity() - if luaentity then - luaentity.owner = data.owner - luaentity.npc_name = npc_name - luaentity.origin = data.origin - luaentity.animation = data.animation - luaentity.metadata = data.metadata - luaentity.object:setyaw(data.origin.yaw) - luaentity.properties = data.properties - luaentity.object:set_properties(data.properties) - if NPCF_SHOW_NAMETAGS == true and luaentity.show_nametag == true then - add_nametag(luaentity) + end + end +end + +function npcf:load(npc_name, pos) + if get_valid_npc_name(npc_name) then + npcf:clear(npc_name) + local input = io.open(NPCF_DATADIR.."/"..npc_name..".npc", "r") + if input then + local data = minetest.deserialize(input:read('*all')) + io.close(input) + if data then + if pos and data.origin then + data.origin.pos = pos + end + if data.origin.pos then + local npc = minetest.add_entity(data.origin.pos, data.name) + if npc then + local luaentity = npc:get_luaentity() + if luaentity then + luaentity.owner = data.owner + luaentity.npc_name = npc_name + luaentity.origin = data.origin + luaentity.animation = data.animation + luaentity.metadata = data.metadata + luaentity.object:setyaw(data.origin.yaw) + luaentity.properties = data.properties + luaentity.object:set_properties(data.properties) + if NPCF_SHOW_NAMETAGS == true and luaentity.show_nametag == true then + add_nametag(luaentity) + end + return 1 end - return 1 end end end @@ -411,27 +432,29 @@ function npcf:load_npc(npc_name, pos) minetest.log("error", "Failed to load "..npc_name) end -function npcf:save_npc(luaentity) - local npc = { - name = luaentity.name, - owner = luaentity.owner, - origin = luaentity.origin, - animation = luaentity.animation, - metadata = luaentity.metadata, - properties = luaentity.properties, - } - local npc_name = luaentity.npc_name - local output = io.open(NPCF_DATADIR.."/"..npc_name..".npc", 'w') - if output then - output:write(minetest.serialize(npc)) - io.close(output) - return 1 +function npcf:save(luaentity) + if get_valid_entity(luaentity) then + local npc = { + name = luaentity.name, + owner = luaentity.owner, + origin = luaentity.origin, + animation = luaentity.animation, + metadata = luaentity.metadata, + properties = luaentity.properties, + } + local npc_name = luaentity.npc_name + local output = io.open(NPCF_DATADIR.."/"..npc_name..".npc", 'w') + if output then + output:write(minetest.serialize(npc)) + io.close(output) + return 1 + end end minetest.log("error", "Failed to save "..npc_name) end function npcf:set_animation(luaentity, state) - if luaentity and state then + if get_valid_entity(luaentity) and state then if state ~= luaentity.state then local speed = luaentity.animation_speed local anim = luaentity.animation @@ -460,7 +483,7 @@ function npcf:get_index() end function npcf:get_luaentity(npc_name) - if npc_name then + if get_valid_npc_name(npc_name) then for _,ref in pairs(minetest.luaentities) do if ref.object then if ref.npcf_id == "npc" and ref.npc_name == npc_name then @@ -472,10 +495,12 @@ function npcf:get_luaentity(npc_name) end function npcf:get_face_direction(v1, v2) - if v1.x and v2.x and v1.z and v2.z then - dx = v1.x - v2.x - dz = v2.z - v1.z - return math.atan2(dx, dz) + if v1 and v2 then + if v1.x and v2.x and v1.z and v2.z then + dx = v1.x - v2.x + dz = v2.z - v1.z + return math.atan2(dx, dz) + end end end