From b9c8e3c3451f18a6845bd9aa16a7eff95181493a Mon Sep 17 00:00:00 2001 From: Sokomine Date: Wed, 26 Jul 2023 18:33:17 +0200 Subject: [PATCH] added npc_force_restore command for lost npc --- README.md | 8 +++ chat_commands.lua | 15 ++++- fs_npc_list.lua | 140 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 352ce34..3286b11 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,14 @@ and set its name). NPC, the priv will be considered granted if either the executing NPC or the the generic NPC has the priv. +#### `/npc_talk force_restore_npc []` Restore an NPC that got lost. + It may have got lost due to someone having misplaced its egg. + Or it might have been killed somehow. + The optional parameter `` is only used when the NPC + is *not* listed in `/npc_talk list`. You won't need it. It's for legacy NPC. + WARNING: If the egg or the NPC turns up elsewhere, be sure to have only + *ONE* NPC with that ID standing around! Else you'll get chaos. + #### `/npc_talk generic` Add or remove NPC from the list of generic dialog providers. `/npc_talk generic list` Lists all generic NPC `/npc_talk generic add n_3` Adds the dialog from NPC as a diff --git a/chat_commands.lua b/chat_commands.lua index f531a40..44baf05 100644 --- a/chat_commands.lua +++ b/chat_commands.lua @@ -33,6 +33,16 @@ yl_speak_up.command_npc_talk = function(pname, param) end -- implemented in add_generic_dialogs.lua: return yl_speak_up.command_npc_talk_generic(pname, rest) + -- restore an NPC that got lost + elseif(cmd and cmd == "force_restore_npc") then + if(not(minetest.check_player_privs(pname, {npc_talk_admin = true}))) then + minetest.chat_send_player(pname, "This command is used for restoring ".. + "NPC that somehow got lost (egg destroyed, killed, ..). You ".. + "lack the \"npc_talk_admin\" priv required to run this command.") + return + end + -- implemented in fs_npc_list.lua: + return yl_speak_up.command_npc_force_restore_npc(pname, rest) elseif(cmd and cmd == "privs") then -- TODO: make this available for npc_talk_admin? if(not(minetest.check_player_privs(pname, {privs = true}))) then @@ -54,8 +64,9 @@ yl_speak_up.command_npc_talk = function(pname, param) " list shows a list of NPC that you can edit\n".. " debug debug a particular NPC\n".. " force_edit forces edit mode for any NPC you talk to\n".. - " generic [requores npc_talk_admin priv] list, add or remove NPC as generic NPC\n".. - " privs [requires privs priv] list, grant or revoke privs for an NPC\n".. + " generic [requores npc_talk_admin priv] list, add or remove NPC as generic NPC\n".. + " force_restore_npc [requires npc_talk_admin priv] restore NPC that got lost\n".. + " privs [requires privs priv] list, grant or revoke privs for an NPC\n".. -- reload is fully handled in register_once "Note: /npc_talk_reload [requires privs priv] reloads the code of the mod without server ".. "restart.") diff --git a/fs_npc_list.lua b/fs_npc_list.lua index 6d9d3ab..bcb2eb0 100644 --- a/fs_npc_list.lua +++ b/fs_npc_list.lua @@ -84,6 +84,8 @@ yl_speak_up.update_npc_data = function(self, dialog, force_store) properties = properties, created_at = created_at, muted = self.yl_speak_up.talk, + animation = self.yl_speak_up.animation, + skin = self.yl_speak_up.skin, } -- the current object will change after deactivate; there is no point in storing -- it over server restart @@ -117,6 +119,144 @@ yl_speak_up.npc_list_store = function() end +-- emergency restore NPC that got lost (egg deleted, killed, ...) +yl_speak_up.command_npc_force_restore_npc = function(pname, rest) + if(not(pname)) then + return + end + if(not(minetest.check_player_privs(pname, {npc_talk_admin = true}))) then + minetest.chat_send_player(pname, "This command is used for restoring ".. + "NPC that somehow got lost (egg destroyed, killed, ..). You ".. + "lack the \"npc_talk_admin\" priv required to run this command.") + return + end + if(not(rest) or rest == "" or rest == "help" or rest == "?") then + minetest.chat_send_player(pname, "This command is used for restoring ".. + "NPC that somehow got lost (egg destroyed, killed, ..).\n".. + "WARNING: If the egg is found again later on, make sure that ".. + "this restored NPC and the NPC from the egg are not both placed!\n".. + " There can only be one NPC per ID.\n".. + "Syntax: /npc_talk force_restore_npc []\n".. + " is the ID (number! Without \"n_\") of the NPC to be restored.\n".. + " is only needed if the NPC is not listed in ".. + "\"/npc_talk list\" (=extremly old NPC).") + return + end + local parts = string.split(rest or "", " ", false, 1) + local id = tonumber(parts[1] or "") + if(not(id)) then + minetest.chat_send_player(pname, "Please provide the ID (number!) of the NPC ".. + "you wish to restore.") + return + elseif(not(yl_speak_up.number_of_npcs) or yl_speak_up.number_of_npcs < 1 + or id > yl_speak_up.number_of_npcs) then + minetest.chat_send_player(pname, "That ID is larger than the amount of existing NPC. ".. + "Restoring is for old NPC that got lost.") + return + elseif(id < 1) then + minetest.chat_send_player(pname, "That ID is smaller than 1. Can't restore negative NPC.") + return + end + local player = minetest.get_player_by_name(pname) + if(not(player)) then + return + end + -- if we've seen the NPC before: make sure he's not just unloaded because nobody is where he is + if(yl_speak_up.npc_list[id] and yl_speak_up.npc_list[id].pos + and yl_speak_up.npc_list[id].pos.x + and yl_speak_up.npc_list[id].pos.y + and yl_speak_up.npc_list[id].pos.z) then + local v_npc = vector.new(yl_speak_up.npc_list[id].pos) + local v_pl = vector.new(player:get_pos()) + if(vector.distance(v_npc, v_pl) > 6) then + minetest.chat_send_player(pname, "You are more than 6 m away from the last ".. + "known position of this NPC at ".. + minetest.pos_to_string(yl_speak_up.npc_list[id].pos).. + ". Please move closer to make sure the NPC isn't just not loaded ".. + "due to nobody beeing near!") + return + end + end + -- check the currently loaded mobs to make sure he wasn't loaded since last update of our list + for k,v in pairs(minetest.luaentities) do + if(v and v.yl_speak_up and v.yl_speak_up.id and v.yl_speak_up.id == id) then + minetest.chat_send_player(pname, "An NPC with the ID "..tostring(id).. + " is currently loaded. No restoring required!") + return + end + end + local data = nil + -- do we need a donator NPC because we have never seen this NPC and have no data? + local copy_from_id = tonumber(parts[2] or "") + if(not(yl_speak_up.npc_list[id])) then + if(not(copy_from_id) or not(yl_speak_up.npc_list[copy_from_id])) then + minetest.chat_send_player(pname, "We have no data on NPC "..tostring(id).. + ". Please provide the ID of an EXISTING NPC from which necessary ".. + "data can be copied!") + return + end + minetest.chat_send_player(pname, "Will use the data of the NPC with ID ".. + tostring(copy_from_id).." to set up the new/restored NPC.") + data = yl_speak_up.npc_list[copy_from_id] + else + data = yl_speak_up.npc_list[id] + end + -- ok..the NPC is not loaded. Perhaps he really got lost. + minetest.chat_send_player(pname, "Will try to restore the NPC with the ID "..tostring(id)..".") + + if(not(data.typ) or not(minetest.registered_entities[data.typ or "?"])) then + minetest.chat_send_player(pname, "Error: No NPC entity prototype found for \"".. + tostring(data.name).."\". Aborting.") + return + end + + -- this is an emergency fallback restore - so it's ok to drop the NPC where the admin is standing + local mob = minetest.add_entity(player:get_pos(), data.typ) + local ent = mob and mob:get_luaentity() + if(not(ent)) then + minetest.chat_send_player(pname, "Failed to create a new NPC entity of type \"".. + tostring(data.name).."\". Aborting.") + return + end + -- set up the new NPC + local npc_name = data.name + local npc_desc = data.npc_description + local npc_owner = data.owner + -- the dialog includes the trades, n_may_edit and other data + local dialog = yl_speak_up.load_dialog(id, nil) + -- restore name and description from dialog if possible + if(dialog and dialog.n_npc) then + npc_name = dialog.n_npc + npc_desc = dialog.n_description + npc_owner = dialog.npc_owner or data.owner + end + ent.yl_speak_up = { + id = id, + talk = data.muted, + properties = data.properties, + npc_name = npc_name, + npc_description = npc_desc, + infotext = yl_speak_up.infotext, -- will be set automaticly later + animation = data.animation, + } + -- This is at least useful for mobs_redo. Other mob mods may require adjustments. + ent.owner = npc_owner + ent.tamed = true + -- update nametag, infotext etc. + yl_speak_up.update_nametag(ent) + if(data.animation and ent.object) then + ent.object:set_animation(data.animation) + end + if(data.skin and ent.object) then + ent.object:set_properties({textures = table.copy(data.skin)}) + end + -- update the NPC list + yl_speak_up.update_npc_data(ent, dialog, true) + minetest.chat_send_player(pname, "Placed the restored NPC ID "..tostring(id).. + ", named "..tostring(data.name)..", right where you stand.") +end + + -- provides a list of NPC the player can edit yl_speak_up.command_npc_talk_list = function(pname, rest) if(not(pname)) then