diff --git a/fs_npc_list.lua b/fs_npc_list.lua index d0cccb8..38d8799 100644 --- a/fs_npc_list.lua +++ b/fs_npc_list.lua @@ -9,6 +9,16 @@ yl_speak_up.npc_list_objects = {} -- file to store the list of NPC for the "/npc_talk list" command yl_speak_up.npc_list_path = minetest.get_worldpath().."/yl_speak_up_npc_list.data" +-- pre-calculated lines for the NPC list (only which ones are shown and how some columns +-- are colored varies per player) +yl_speak_up.cache_general_npc_list_lines = {} + +-- we need to remember which NPC was shown to the player in which order; index: player name +yl_speak_up.cache_npc_list_per_player = {} + +-- which table column is the player using for sorting? +yl_speak_up.sort_npc_list_per_player = {} + -- add/update data about NPC self with dialog dialog (optional) -- force_store ought to be true if something important has been changed yl_speak_up.update_npc_data = function(self, dialog, force_store) @@ -32,7 +42,7 @@ yl_speak_up.update_npc_data = function(self, dialog, force_store) end end end - elseif(is_knwon) then + elseif(is_known) then local old = yl_speak_up.npc_list[self.yl_speak_up.id] may_edit = old.may_edit desc = old.desc @@ -47,9 +57,13 @@ yl_speak_up.update_npc_data = function(self, dialog, force_store) local pos = {} if(self.object) then pos = self.object:get_pos() - pos.x = math.floor(pos.x or 0) - pos.y = math.floor(pos.y or 0) - pos.z = math.floor(pos.z or 0) + if(pos) then + pos = { x = math.floor(pos.x or 0), + y = math.floor(pos.y or 0), + z = math.floor(pos.z or 0)} + else + pos = { x=0, y=0, z=0} + end end -- only store real, important properties local properties = {} @@ -68,6 +82,8 @@ yl_speak_up.update_npc_data = function(self, dialog, force_store) trades = trades, pos = pos, properties = properties, + created_at = created_at, + muted = self.yl_speak_up.talk, } -- the current object will change after deactivate; there is no point in storing -- it over server restart @@ -106,6 +122,143 @@ yl_speak_up.command_npc_talk_list = function(pname, rest) if(not(pname)) then return end + + -- check if there are any loaded entities handled by yl_speak_up that + -- are *not* in the list yet + local liste = {} + for k,v in pairs(minetest.luaentities) do + if(v and v.yl_speak_up and v.yl_speak_up.id) then + if(not(yl_speak_up.npc_list[v.yl_speak_up.id])) then + local dialog = yl_speak_up.load_dialog(v.yl_speak_up.id, nil) + yl_speak_up.update_npc_data(v, dialog, false) + else + yl_speak_up.update_npc_data(v, nil, false) + end + end + end + -- store the updated list + yl_speak_up.npc_list_store() + -- update the information for display + yl_speak_up.build_cache_general_npc_list_lines() + -- clear the stored NPC list and calculate it anew + yl_speak_up.cache_npc_list_per_player[pname] = {} + -- Note: show_fs cannot be used here as that expects the player to be talking to an actual npc + yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_npc_list", + yl_speak_up.get_fs_show_npc_list(pname, nil)) +end + + +-- allow to sort the npc list, display more info on one NPC etc. +yl_speak_up.input_show_npc_list = function(player, formname, fields) + local pname = player:get_player_name() + if(fields.show_npc_list) then + local selected = minetest.explode_table_event(fields.show_npc_list) + -- sort by column + if(selected.row == 1) then + local old_sort = yl_speak_up.sort_npc_list_per_player[pname] or 0 + -- reverse sort + if(old_sort == selected.column) then + yl_speak_up.sort_npc_list_per_player[pname] = -1 * selected.column + else -- sort by new col + yl_speak_up.sort_npc_list_per_player[pname] = selected.column + end + -- show the update + yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_npc_list", + yl_speak_up.get_fs_show_npc_list(pname, nil)) + return + end + end + return +end + + + +-- the entries for the "/npc_talk list" NPC list are generally the same for all +-- - except that not all lines are shown to each player and that some +-- lines might be colored diffrently +yl_speak_up.build_cache_general_npc_list_lines = function() + + -- small helper function to suppress the display of zeros + local show_if_bigger_null = function(value, do_count) + if(do_count and value) then + local anz = 0 + for k, v in pairs(value) do + anz = anz + 1 + end + value = anz + end + if(value and value > 0) then + return tostring(value) + else + return "" + end + end + + -- the real priv names would be far too long + local short_priv_name = { + precon_exec_lua = 'pX', + effect_exec_lua = 'eX', + effect_give_item = 'eG', + effect_take_item = 'eT', + effect_move_player = 'eM', + } + + yl_speak_up.cache_general_npc_list_lines = {} + + for k, data in pairs(yl_speak_up.npc_list) do + local data = yl_speak_up.npc_list[k] + local n = (data.name or "- ? -") + if(data.desc and data.desc ~= "") then + n = n..', '..(data.desc or "") + end + -- is the NPC muted? + local npc_color = (yl_speak_up.nametag_color_when_not_muted or '#FFFFFF') + if(data.muted ~= nil and data.muted == false) then + npc_color = (yl_speak_up.nametag_color_when_muted or '#FFFFFF') + end + -- is the NPC loaded? + local is_loaded_color = '#777777' + if(yl_speak_up.npc_list_objects[k]) then + is_loaded_color = '#FFFFFF' + end + -- is it a generic NPC? + local n_id = 'n_'..tostring(k) + local is_generic = '' + if(yl_speak_up.generic_dialogs[n_id]) then + is_generic = 'G' + end + -- does the NPC have extra privs? + local priv_list = '' + if(yl_speak_up.npc_priv_table[n_id]) then + for priv, has_it in pairs(yl_speak_up.npc_priv_table[n_id]) do + priv_list = priv_list..tostring(short_priv_name[priv])..' ' + end + end + + yl_speak_up.cache_general_npc_list_lines[k] = { + id = k, -- keep for sorting + is_loaded_color = is_loaded_color, + n_id = n_id, + is_generic = is_generic, + npc_color = npc_color, -- muted or not + -- npc_color is diffrent for each player + n_name = minetest.formspec_escape(n), + owner = minetest.formspec_escape(data.owner or '- ? -'), + is_loaded_color = is_loaded_color, + anz_trades = show_if_bigger_null(#data.trades), + anz_properties = show_if_bigger_null(data.properties, true), + anz_editors = show_if_bigger_null(data.may_edit, true), + pos = minetest.formspec_escape(minetest.pos_to_string(data.pos)), + priv_list = priv_list, + } + end +end + + +-- allow to toggle between trade entries and full log +-- Note: takes pname instead of player(object) as first parameter +yl_speak_up.get_fs_show_npc_list = function(pname, param) + -- which NPC can the player edit? local level = 0 if( minetest.check_player_privs(pname, {npc_master=true}) or minetest.check_player_privs(pname, {npc_talk_master=true}) @@ -115,32 +268,114 @@ yl_speak_up.command_npc_talk_list = function(pname, rest) level = 1 end if(level < 1) then - minetest.chat_send_player(pname, "This command requires the npc_talk_owner priv or above.") - return + return "size[5,1]label[0,0;Error: You do not have the npc_talk_owner priv.]" end - minetest.chat_send_player(pname, "OK...building list.") + local formspec_start = 'size[18,12]'.. +-- 'button[0.5,11.1;17,0.8;', +-- back_link, +-- ']'.. + 'label[4.5,0.5;List of all NPC (that you can edit)]'.. +-- 'button[0.5,0.1;3,0.8;', +-- log_type_switch, +-- ']', + 'tablecolumns[' .. + 'color;text,align=right;'.. -- the ID + 'color;text,align=center;'.. -- is the NPC a generic one? + 'color;text,align=left;'.. -- the name of the NPC + 'color;text,align=center;'.. -- the name of the owner of the NPC + 'color;text,align=right;'.. -- number of trades offered + 'color;text,align=right;'.. -- number of properties set + 'color;text,align=right;'.. -- number of people who can edit NPC + 'color;text,align=center;'.. -- last known position + 'color;text,align=center]'.. -- does he have extra privs? + 'table[0.1,1.0;17.8,9.8;show_npc_list;' + local formspec = {} - -- sort by default by id descending? + -- TODO: blocks may also be talked to + -- TODO desc as mouseover?, created_at? + -- TODO: created_at, typ (race?)? - -- check if there are any loaded entities handled by yl_speak_up that - -- are *not* in the list yet - local liste = {} - for k,v in pairs(minetest.luaentities) do - if(v and v.yl_speak_up and v.yl_speak_up.id - and not(yl_speak_up.npc_list[v.yl_speak_up.id])) then - local dialog = yl_speak_up.load_dialog(v.yl_speak_up.id, nil) - yl_speak_up.update_npc_data(v, dialog, true) + + local tmp_liste = {} + for k, v in pairs(yl_speak_up.npc_list) do + if(level == 2 + or (v.owner and v.owner == pname) + or (v.may_edit and v.may_edit[pname])) then + table.insert(tmp_liste, k) end end - -- TODO: blocks may also be talked to - for k, v in pairs(yl_speak_up.npc_list) do - -- possible editors: dialog.n_may_edit[pname] - minetest.chat_send_player(pname, tostring(k).." ".. - tostring(v.name).." "..tostring(v.owner).. - " "..minetest.pos_to_string(v.pos)) + -- the columns with the colors count as well even though they can't be selected + -- (don't sort the first column by n_ STRING - sort by NUMBER) + local col_names = {"id", "id", "is_generic", "is_generic", "n_name", "n_name", + "owner", "owner", "anz_trades", "anz_trades", + "anz_properties", "anz_properties", "anz_editors", "anz_editors", + "pos", "pos", "priv_list", "priv_list"} + local sort_col = yl_speak_up.sort_npc_list_per_player[pname] + if(not(sort_col) or sort_col == 0) then + table.sort(tmp_liste) + elseif(sort_col > 0) then + -- it is often more helpful to sort in descending order + local col_name = col_names[sort_col] + table.sort(tmp_liste, function(a, b) + return yl_speak_up.cache_general_npc_list_lines[a][col_name] + > yl_speak_up.cache_general_npc_list_lines[b][col_name] + end) + else + local col_name = col_names[sort_col * -1] + table.sort(tmp_liste, function(a, b) + return yl_speak_up.cache_general_npc_list_lines[a][col_name] + < yl_speak_up.cache_general_npc_list_lines[b][col_name] + end) end + + local col_headers = {'n_id', 'G', 'Name', 'Owner', '#Tr', '#Pr', '#Ed', 'Position', 'Privs'} + for i, k in ipairs(col_headers) do + if( sort_col and sort_col == (i * 2)) then + table.insert(formspec, 'yellow') + table.insert(formspec, 'v '..k..' v') + elseif(sort_col and sort_col == (i * -2)) then + table.insert(formspec, 'yellow') + table.insert(formspec, '^ '..k..' ^') + else + table.insert(formspec, '#FFFFFF') + table.insert(formspec, k) + end + end + yl_speak_up.cache_npc_list_per_player = tmp_list + + for i, k in ipairs(tmp_liste) do + local data = yl_speak_up.npc_list[k] + local line = yl_speak_up.cache_general_npc_list_lines[k] + -- own NPC are colored green, others white + local owner_color = '#FFFFFF' + if(data.owner == pname) then + owner_color = '#00FF00' + elseif (data.may_edit and data.may_edit[pname]) then + owner_color = '#FFFF00' + end + table.insert(formspec, line.is_loaded_color) + table.insert(formspec, line.n_id) + table.insert(formspec, 'orange') + table.insert(formspec, line.is_generic) + table.insert(formspec, line.npc_color) + table.insert(formspec, line.n_name) + table.insert(formspec, owner_color) -- diffrent for each player + table.insert(formspec, line.owner) + table.insert(formspec, line.is_loaded_color) + table.insert(formspec, line.anz_trades) + table.insert(formspec, line.is_loaded_color) + table.insert(formspec, line.anz_properties) + table.insert(formspec, owner_color) -- diffrent for each player + table.insert(formspec, line.anz_editors) + table.insert(formspec, line.is_loaded_color) + table.insert(formspec, line.pos) + table.insert(formspec, line.is_loaded_color) + table.insert(formspec, line.priv_list) + end + table.insert(formspec, ";1]") + return formspec_start..table.concat(formspec, ',') end diff --git a/show_fs.lua b/show_fs.lua index f964f5a..f1d11f9 100644 --- a/show_fs.lua +++ b/show_fs.lua @@ -60,6 +60,9 @@ yl_speak_up.input_handler = function(player, formname, fields) elseif formname == "yl_speak_up:show_log" then yl_speak_up.input_show_log(player, formname, fields) return true + elseif formname == "yl_speak_up:show_npc_list" then + yl_speak_up.input_show_npc_list(player, formname, fields) + return true elseif formname == "yl_speak_up:edit_trade_limit" then yl_speak_up.input_edit_trade_limit(player, formname, fields) return true @@ -342,6 +345,11 @@ yl_speak_up.show_fs = function(player, fs_name, param) yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_log", yl_speak_up.get_fs_show_log(player, param.log_type)) + elseif(fs_name == "show_npc_list") then + yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_npc_list", + yl_speak_up.get_fs_show_npc_list(player, nil)) + + elseif(fs_name == "edit_trade_limit") then if(not(param)) then param = {}