From e40c464af8a56419735d8ebe3c12893c58e09c2d Mon Sep 17 00:00:00 2001 From: Sokomine Date: Tue, 27 Apr 2021 22:17:09 +0200 Subject: [PATCH] first draft of alternate edit mode for players with npc_talk_owner priv --- functions.lua | 227 ++++++++++++++++++++++++++++++++++++++++++++++---- privs.lua | 10 ++- 2 files changed, 221 insertions(+), 16 deletions(-) diff --git a/functions.lua b/functions.lua index caa3250..4487781 100644 --- a/functions.lua +++ b/functions.lua @@ -1,7 +1,42 @@ +-- TODO: just for the apple quest...needs to be deleted later on +yl_sokomine = {} +yl_sokomine["q_apple_explorer"] = {} +-- if player has npc_talk_owner priv AND is owner of this particular npc (second one: TODO): +-- chat option: "I am your owner. I have new orders for you. +-- -> enters edit mode +-- when edit_mode has been enabled, the following chat options are added to the options: +-- chat option: "Add new answer/option to this dialog." +-- -> adds a new option TODO +-- chat option: "That was all. I'm finished with giving you new orders. Remember them!" +-- -> ends edit mode +-- +-- TODO: add the actual edit menu for options +-- can be entered from the edit, conditions and effects button; +-- style: Answer/Option: +-- Lets the NPC answer with: [Button GOTO target dialog] +-- +-- Only offer this answer if all the following conditions are true: +-- +-- If at least one condition is not fulfilled, .. +-- +-- +-- When this answer has been selected, apply the following effects: +-- +-- +-- [Store] [Delete this option] [Back to dialog xyz] [GOTO target diaglog] +-- +-- TODO: check if security is ok (minetest.formspec_escape etc) +-- TODO: actually process changed entries +-- TODO: allow owner to mute/unmute npc (would be bad if players can already see what is going +-- to happen while the owner creates a long quest) + --### -- Init --### +-- store if the player is editing a particular NPC; format: yl_speak_up.edit_mode[pname] = npc_id +yl_speak_up.edit_mode = {} + function yl_speak_up.init_mob_table() return false end @@ -9,12 +44,14 @@ end minetest.register_on_leaveplayer( function(player) yl_speak_up.speak_to[player:get_player_name()] = nil + yl_speak_up.edit_mode[player:get_player_name()] = nil end ) minetest.register_on_joinplayer( function(player) yl_speak_up.speak_to[player:get_player_name()] = nil + yl_speak_up.edit_mode[player:get_player_name()] = nil end ) @@ -949,16 +986,67 @@ local function get_fs_talkdialog(player, n_id, d_id) "image[15.5,0.5;4,4;", portrait, "]", - "hypertext[0.2,5;19.6,17.8;d_text;", - minetest.formspec_escape(active_dialog.d_text) .. "\n", - "]", - "tooltip[d_text;", - minetest.formspec_escape(active_dialog.d_text):trim(), - ";#000000;#FFFFFF]", - "scrollbaroptions[min=0;max=14;smallstep=1;largestep=2;arrows=show]", - "scrollbar[0.2,24.2;0.2,7;vertical;scr0;0]", - "scroll_container[0,24;56,7;scr0;vertical;1]" } + + -- Is the player working on this particular npc? + local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) + + -- this is used to build a list of all available dialogs for a dropdown menu in edit mode + -- (only relevant in edit mode) + local dialog_list = yl_speak_up.text_new_dialog_id + -- find the right index for the dialog_list dropdown above + local d_id_to_dropdown_index = {} + + -- display the window with the text the NPC is saying + -- TODO: make name and description likewise editable? + if(edit_mode) then + + -- build the list of available dialogs for the dropdown list(s) + -- if there are dialogs defined + if(dialog and dialog.n_dialogs) then + -- the first entry will be "New dialog" + local n = 1 + for k, v in pairs(dialog.n_dialogs) do + dialog_list = dialog_list .. "," .. v.d_id + -- which one is the current dialog? + n = n + 1 + d_id_to_dropdown_index[v.d_id] = n + end + end + + local i = (d_id_to_dropdown_index[c_d_id] or 0) + table.insert(formspec, "label[0.2,4.6;Dialog:]") -- "..minetest.formspec_escape(c_d_id)..":]") + table.insert(formspec, "dropdown[3.0,4.0;5,1;d_id;"..dialog_list..";"..i..",]") + table.insert(formspec, "tooltip[3.0,4.0;5,1;Select the dialog you want to edit. Currently, dialog "..c_d_id.." is beeing displayed.;#FFFFFF;#000000]") + -- add previous/next buttons for previous/next dialog (sorted as in the dropdown menu); + -- this is just an alternate way of walking through the dialogs + for k,v in pairs(d_id_to_dropdown_index) do + if( v == i-1) then + table.insert(formspec, "button[8.5,4.0;2,0.9;dialog_"..k..";<]") + -- add a tooltip + table.insert(formspec, "tooltip[dialog_" .. k .. ";Go to previous dialog "..k..".]") + elseif(v == i+1) then + table.insert(formspec, "button[11,4.0;2,0.9;dialog_"..k..";>]") + -- add a tooltip + table.insert(formspec, "tooltip[dialog_" .. k .. ";Go to next dialog "..k..".]") + end + end + + + table.insert(formspec, "textarea[0.2,5;19.6,17.8;d_text;;") + table.insert(formspec, minetest.formspec_escape(active_dialog.d_text)) + table.insert(formspec, "]") + else + table.insert(formspec, "hypertext[0.2,5;19.6,17.8;d_text;") + table.insert(formspec, minetest.formspec_escape(active_dialog.d_text) .. "\n") + table.insert(formspec, "]") + table.insert(formspec, "tooltip[d_text;") + table.insert(formspec, minetest.formspec_escape(active_dialog.d_text):trim()) + table.insert(formspec, ";#000000;#FFFFFF]") + end + table.insert(formspec, "scrollbaroptions[min=0;max=14;smallstep=1;largestep=2;arrows=show]") + table.insert(formspec, "scrollbar[0.2,24.2;0.2,7;vertical;scr0;0]") + table.insert(formspec, "scroll_container[0,24;56,7;scr0;vertical;1]") h = -0.8 -- Let#s sort the options by o_sort @@ -969,7 +1057,62 @@ local function get_fs_talkdialog(player, n_id, d_id) end for _, sb_v in pairs(sorted_buttons) do - if allowed[sb_v.o_id] == true then + -- in edit_mode: show all options + if edit_mode then + h = h + 1 + -- add a button "o_:" that leads to an edit formspec for this option + table.insert(formspec, "button[1.8," .. h .. ";2,0.9;edit_option_" .. sb_v.o_id .. ";"..sb_v.o_id..":]") + -- add a tooltip "Edit target dialog, pre(C)onditions and (E)ffects for option o_" + table.insert(formspec, "tooltip[edit_option_" .. sb_v.o_id .. ";Edit target dialog, pre(C)onditions and (E)ffects for option "..sb_v.o_id..".]") + + -- find the right target dialog for this option (if it exists): + local target_dialog = nil + local results = active_dialog.d_options[sb_v.o_id].o_results + -- has this option more results/effects than just switching to another dialog? + local has_other_results = false + if(results ~= nil) then + for k, v in pairs(results) do + if v.r_type == "dialog" and dialog.n_dialogs[v.r_value] ~= nil then + -- there may be more than one in the data structure + target_dialog = v.r_value + elseif v.r_type ~= "dialog" then + has_other_results = true + end + end + end + if(target_dialog) then + -- add a button "-> d_" that leads to the target dialog (if one is set) + table.insert(formspec, "button[8.5," .. h .. ";1,0.9;button_" .. sb_v.o_id .. ";->]") + -- add a tooltip "Go to target dialog d_" + table.insert(formspec, "tooltip[button_" .. sb_v.o_id .. ";Go to target dialog "..target_dialog.." that will be shown when this option ("..sb_v.o_id..") is selected.]") + + -- selecting an option this way MUST NOT execute the pre(C)onditions or (E)ffects! + end + -- allow to set a new target dialog + table.insert(formspec, "dropdown[3.9,"..h..";4.7,1;d_id_"..sb_v.o_id..";"..dialog_list..";"..(d_id_to_dropdown_index[target_dialog] or "0")..",]") + -- add a tooltip "Change target dialog" + table.insert(formspec, "tooltip[3.9,"..h..";4.7,1;Change target dialog for option "..sb_v.o_id..".;#FFFFFF;#000000]") + + -- are there any prerequirements? + local prereq = active_dialog.d_options[sb_v.o_id].o_prerequisites + if(prereq) then + table.insert(formspec, "button[0.5," .. h .. ";0.5,0.9;conditions_"..sb_v.o_id..";C]") + -- label: "There are pre(C)onditions required for showing this option. Display them." + table.insert(formspec, "tooltip[conditions_" .. sb_v.o_id .. ";There are pre(C)onditions required for showing this option. Display them.]") + end + if(has_other_results) then + table.insert(formspec, "button[1.1," .. h .. ";0.6,0.9;effects_"..sb_v.o_id..";Ef]") + -- label: "There are further (E)ffects (apart from switching to a new dialog) set for this option. Display them." + table.insert(formspec, "tooltip[effects_" .. sb_v.o_id .. ";There are further (E)ffects (apart from switching to a new dialog) set for this option. Display them.]") + end + + -- show the actual text for the option + table.insert(formspec, "field[9.4," .. h .. ";44.9,0.9;text_option_" .. sb_v.o_id .. ";;"..sb_v.o_text_when_prerequisites_met.."]") + -- add a tooltip "Edit the text that is displayed on button o_." + table.insert(formspec, "tooltip[text_option_" .. sb_v.o_id .. ";Edit the text that is displayed on button "..sb_v.o_id..".]") + + -- normal mode: show an option if the prerequirements (if any are defined) are met + elseif allowed[sb_v.o_id] == true then h = h + 1 table.insert(formspec, "button[0.5," .. h .. ";53.8,0.9;button_" .. sb_v.o_id .. ";]") table.insert( @@ -1045,6 +1188,32 @@ local function get_fs_talkdialog(player, n_id, d_id) end end end + + -- If in edit mode, add two new menu entries: "add new options" and "end edit mode". + if(edit_mode) then + -- chat option: "Add a new answer/option to this dialog." + h = h + 1 + table.insert(formspec, "button_exit[0.5," .. h .. ";53.8,0.9;button_add_option;]") + table.insert(formspec, "tooltip[button_add_option;Adds a new option to this dialog. You can delete options via the option edit menu.]") + table.insert(formspec, "label[0.7,"..(h+0.45)..";Add a new option/answer to this dialog. You can delete options via the option edit menu.]") + -- chat option: "That was all. I'm finished with giving you new orders. Remember them!" + h = h + 1 + table.insert(formspec, "button_exit[0.5," .. h .. ";53.8,0.9;button_end_edit_mode;]") + table.insert(formspec, "tooltip[button_end_edit_mode;Ends edit mode. From now on, your NPC will talk to you like he talks to other players. You can always give him new orders by entering edit mode again.]") + table.insert(formspec, "label[0.7,"..(h+0.45)..";That was all. I'm finished with giving you new orders. Remember them!]") + + -- Offer to enter edit mode if the player has the npc_talk_owner priv AND owns the npc. + -- The npc_master priv allows to edit all NPC. + -- TODO: the player also has to be owner of that particular npc + elseif((minetest.check_player_privs(player, {npc_talk_owner=true})) + or (minetest.check_player_privs(player, {npc_master=true}))) then + -- chat option: "I am your owner. I have new orders for you. + h = h + 1 + table.insert(formspec, "button_exit[0.5," .. h .. ";53.8,0.9;button_start_edit_mode;]") + table.insert(formspec, "tooltip[button_start_edit_mode;Enters edit mode. In this mode, you can edit the texts the NPC says and the answers that can be given.]") + table.insert(formspec, "label[0.7,"..(h+0.45)..";I am your owner. I have new orders for you.]") + end + h = h + 1 table.insert(formspec, "button_exit[0.5," .. h .. ";53.8,0.9;button_exit;]") table.insert(formspec, "tooltip[button_exit;" .. yl_speak_up.message_button_option_exit .. "]") @@ -1495,6 +1664,29 @@ minetest.register_on_player_receive_fields( local pname = player:get_player_name() local o = "" + -- start edit mode (requires npc_talk_owner) + if fields.button_start_edit_mode then + if( not(minetest.check_player_privs(player, {npc_talk_owner=true})) + and not(minetest.check_player_privs(player, {npc_master=true}))) then + minetest.chat_send_player(pname, "Sorry. You do not have the npc_talk_owner or npc_master priv.") + return + end + -- TODO: check if this particular NPC is really owned by this player + -- enter edit mode with that particular NPC + yl_speak_up.edit_mode[pname] = yl_speak_up.speak_to[pname].n_id + -- start a new chat - but this time in edit mode + -- TODO: this continues where the last chat was and does not start from the beginning + minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(player, yl_speak_up.speak_to[pname].n_id)) + return + -- end edit mode (does not require the priv; will only switch back to normal behaviour) + elseif fields.button_end_edit_mode then + yl_speak_up.edit_mode[pname] = nil + -- start a new chat - but this time in normal mode + -- TODO: this continues where the last chat was and does not start from the beginning + minetest.show_formspec(pname, "yl_speak_up:talk", get_fs_talkdialog(player, yl_speak_up.speak_to[pname].n_id)) + return + end + if fields.quit or fields.button_exit then -- Formspec was quit yl_speak_up.speak_to[pname] = nil @@ -1542,8 +1734,11 @@ minetest.register_on_player_receive_fields( return end + -- Is the player working on this particular npc? + local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) + -- Let's check if the button was among the "allowed buttons". Only those may be executed - if yl_speak_up.speak_to[pname].allowed and yl_speak_up.speak_to[pname].allowed[o] == false then + if not(edit_mode) and (yl_speak_up.speak_to[pname].allowed and yl_speak_up.speak_to[pname].allowed[o] == false) then return end @@ -1560,7 +1755,9 @@ minetest.register_on_player_receive_fields( -- Let's do something if results exist if d_option.o_results ~= nil then for k, v in pairs(d_option.o_results) do - if v.r_type == "give_item" then + -- in edit_mode, only the switching to the next dialog is executed (we want to edit, + -- not solve quests, get/take items, teleport...) + if not(edit_mode) and v.r_type == "give_item" then local item = ItemStack(v.r_value) if minetest.registered_items[item:get_name()] ~= nil then @@ -1569,7 +1766,7 @@ minetest.register_on_player_receive_fields( say("Item not found!") end end - if v.r_type == "take_item" then + if not(edit_mode) and v.r_type == "take_item" then local item = ItemStack(v.r_value) if minetest.registered_items[item:get_name()] ~= nil then @@ -1578,7 +1775,7 @@ minetest.register_on_player_receive_fields( say("Item not found!") end end - if v.r_type == "move" then + if not(edit_mode) and v.r_type == "move" then local target_pos = nil local target_pos_valid = false @@ -1630,7 +1827,7 @@ minetest.register_on_player_receive_fields( end end - if v.r_type == "function" then + if not(edit_mode) and v.r_type == "function" then local code = v.r_value if code:byte(1) == 27 then local obj = yl_speak_up.speak_to[pname].obj diff --git a/privs.lua b/privs.lua index e27f5dd..7355763 100644 --- a/privs.lua +++ b/privs.lua @@ -4,4 +4,12 @@ local npc_master_priv_definition = { give_to_admin = true, } -minetest.register_privilege("npc_master", npc_master_priv_definition) \ No newline at end of file +minetest.register_privilege("npc_master", npc_master_priv_definition) + +local npc_talk_owner_priv_definition = { + description="Can edit the dialogs of his/her own NPCs", + give_to_singleplayer = false, + give_to_admin = true, +} + +minetest.register_privilege("npc_talk_owner", npc_talk_owner_priv_definition)