From 762337cc6d3f61d1fdac77c54c623458cbdc6e44 Mon Sep 17 00:00:00 2001 From: zorman2000 Date: Thu, 1 Dec 2016 14:37:00 -0500 Subject: [PATCH] Dialogues: Added function to select random dialogues for NPCs from random data. Modified NPCs so random dialogues are chosen on initialization. Added function to start a dialogue, choosing randomly NPCs dialogues. Changed NPC code to use new dialogue code --- chat.lua | 37 ++-------- dialogue.lua | 189 ++++++++++++++++++++++++++++++++++++++++++++++++ init.lua | 2 +- npc.lua | 9 ++- random_data.lua | 6 +- 5 files changed, 206 insertions(+), 37 deletions(-) create mode 100644 dialogue.lua diff --git a/chat.lua b/chat.lua index f864020..d390cc0 100644 --- a/chat.lua +++ b/chat.lua @@ -1,28 +1,4 @@ --- NPC Chat code by Zorman2000 --- Chat system consists of chatline and options objects that are re-used to create a conversation. --- The objects are the following: --- --- chatline = { text = "", options = {}, name = "", flag = "" } --- 1. text: What the NPC says on this chat line --- 2. options: What the player can answer to the chat line. Options are of another type of chat lines. --- If you want the player to have no options to answer, set to nil --- 3. name: The name of the NPC that will use this line. Use this when you want a specific NPC --- to speak this line. --- 4. flag: When the NPC speaks this line, this flag will be set into the entity's metadata. --- --- options = { opt = "", answer = { chatline } --- 1. opt: The text that the player can say to the NPC --- 2. answer: A chatline object (as described above) --- --- Example of conversation hierarchy --- chat_options = { --- chatline1 = { text = "Q1", options = { --- option1 = { opt = "O1", answer = { --- chatline = { text = "A1", options = nil } --- } --- }, --- chatline2 = { text = "Q2", options = nil } --- } +-- NPC dialogue code by Zorman2000 npc.dialogue = {} @@ -34,9 +10,11 @@ npc.dialogue.dialogue_results = { yes_no_dialogue = {} } ---------------------------------------------------------------------- --- Creates a formspec for dialog +-- Dialogue box definitions +-- The dialogue boxes are used for the player to interact with the +-- NPC in dialogues. -------------------------------------------------------------------- +-- Multi-option dialogue local function create_formspec(options, close_option) local options_length = table.getn(options) + 1 local formspec_height = (options_length * 0.7) + 1 @@ -76,10 +54,9 @@ function npc.dialogue.show_yes_no_dialogue(prompt, minetest.show_formspec(player_name, "advanced_npc:yes_no", formspec) end ---------------------------------------------------------------------- +-- Dialogue methods -- Returns all chatlines for a specific NPC ---------------------------------------------------------------------- -local function get_chatline_for_npc(chat_options, npc_name) +function npc.dialogue.select_random_dialogues_for_npc(sex, ) local result = {} for i,chatline in ipairs(chat_options) do if chatline.name == npc_name then diff --git a/dialogue.lua b/dialogue.lua new file mode 100644 index 0000000..82eb929 --- /dev/null +++ b/dialogue.lua @@ -0,0 +1,189 @@ +-- NPC dialogue code by Zorman2000 + +npc.dialogue = {} + +npc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX = "Yes, give " +npc.dialogue.NEGATIVE_ANSWER_LABEL = "Nevermind" + +npc.dialogue.MIN_DIALOGUES = 2 +npc.dialogue.MAX_DIALOGUES = 4 + +-- This table contains the answers of dialogue boxes +npc.dialogue.dialogue_results = { + yes_no_dialogue = {} +} + +-- Dialogue box definitions +-- The dialogue boxes are used for the player to interact with the +-- NPC in dialogues. +-------------------------------------------------------------------- +-- Multi-option dialogue +local function create_formspec(options, close_option) + local options_length = table.getn(options) + 1 + local formspec_height = (options_length * 0.7) + 1 + local formspec = "size[7,"..tostring(formspec_height).."]" + for i, opt in ipairs(options) do + local y = 0.7; + if i > 1 then + y = (y * i) + end + formspec = formspec.."button[0.5,"..y..";6,0.5;opt"..tostring(i)..";"..options[i].opt.."]" + end + formspec = formspec.."button_exit[0.5,"..(formspec_height - 1)..";6,0.5;exit;"..close_option.."]" + return formspec +end + +-- This function is used for showing a yes/no dialogue formspec +function npc.dialogue.show_yes_no_dialogue(prompt, + positive_answer_label, + positive_callback, + negative_answer_label, + negative_callback, + player_name) + + local formspec = "size[7,3]".. + "label[0.5,0.1;"..prompt.."]".. + "button_exit[0.5,1.15;6,0.5;yes_option;"..positive_answer_label.."]".. + "button_exit[0.5,1.95;6,0.5;no_option;"..negative_answer_label.."]" + + -- Create entry into responses table + npc.dialogue.dialogue_results.yes_no_dialogue[1] = { + name = player_name, + response = "", + yes_callback = positive_callback, + no_callback = negative_callback + } + + minetest.show_formspec(player_name, "advanced_npc:yes_no", formspec) +end + +-- Dialogue methods +-- Select random dialogue objects for an NPC based on sex +-- and the relationship phase with player +function npc.dialogue.select_random_dialogues_for_npc(sex, phase) + local result = {} + local dialogues = npc.data.DIALOGUES.female + if sex == npc.MALE then + dialogues = npc.data.DIALOGUES.male + end + dialogues = dialogues[phase] + + -- Determine how many dialogue lines the NPC will have + local number_of_dialogues = math.random(npc.dialogue.MIN_DIALOGUES, npc.dialogue.MAX_DIALOGUES) + + for i = 1,number_of_dialogues do + result[i] = dialogues[math.random(1, #dialogues)] + end + + return result +end + +-- This function will choose randomly a dialogue from the NPC data +-- and process it. +function npc.dialogue.start_dialogue(self, player) + -- Choose a dialogue randomly + local dialogue = self.dialogues[math.random(1, #self.dialogues)] + npc.dialogue.process_dialogue(dialogue, player:get_player_name()) +end + +-- This function processes a dialogue object and performs +-- actions depending on what is defined in the object +function npc.dialogue.process_dialogue(dialogue, player_name) + -- Send dialogue line + if dialogue.text then + minetest.chat_send_player(player_name, dialogue.text) + end + -- TODO: Add support for flag, multi-option dialogue + -- and their actions +end + +----------------------------------------------------------------------------- +-- Functions for rotating NPC to look at player (taken from the API itself) +----------------------------------------------------------------------------- +local atan = function(x) + if x ~= x then + return 0 + else + return math.atan(x) + end +end + + +local function rotate_npc_to_player(self) + local s = self.object:getpos() + local objs = minetest.get_objects_inside_radius(s, 4) + local lp = nil + local yaw = 0 + + for n = 1, #objs do + if objs[n]:is_player() then + lp = objs[n]:getpos() + break + end + end + if lp then + local vec = { + x = lp.x - s.x, + y = lp.y - s.y, + z = lp.z - s.z + } + + yaw = (atan(vec.z / vec.x) + math.pi / 2) - self.rotate + + if lp.x > s.x then + yaw = yaw + math.pi + end + end + self.object:setyaw(yaw) +end + +--------------------------------------------------------------------- +-- Drives conversation +--------------------------------------------------------------------- +local function show_chat_option(npc_name, self, player_name, chat_options, close_option) + rotate_npc_to_player(self) + self.order = "stand" + + local chatline = get_random_chatline(chat_options) + minetest.chat_send_player(player_name, chatline.text) + if chatline.options ~= nil then + minetest.log("Current options: "..dump(chatline.options)) + local formspec = create_formspec(chatline.options, close_option) + minetest.show_formspec(player_name, "rndform", formspec) + end + + self.order = "follow" +end + +-- Function to get response by player name +local function get_yes_no_dialogue_response_by_player_name(player_name) + for i = 1,#npc.dialogue.dialogue_results.yes_no_dialogue do + local current_result = npc.dialogue.dialogue_results.yes_no_dialogue[i] + if current_result.name == player_name then + return current_result + end + end + return nil +end + +-- Handler for chat formspec +minetest.register_on_player_receive_fields(function (player, formname, fields) + -- Additional checks for other forms should be handled here + if formname == "advanced_npc:yes_no" then + local player_name = player:get_player_name() + + if fields then + local player_response = get_yes_no_dialogue_response_by_player_name(player_name) + if fields.yes_option then + player_response.response = true + player_response.yes_callback() + elseif fields.no_option then + player_response.response = false + player_response.no_callback() + end + minetest.log(player_name.." chose response: " + ..dump(get_yes_no_dialogue_response_by_player_name(player_name).response)) + end + end + +end) diff --git a/init.lua b/init.lua index fd18368..6a855ab 100755 --- a/init.lua +++ b/init.lua @@ -25,7 +25,7 @@ mobs.intllib = S -- NPC dofile(path .. "/npc.lua") -dofile(path .. "/chat.lua") +dofile(path .. "/dialogue.lua") dofile(path .. "/random_data.lua") --dofile(path .. "/trader.lua") diff --git a/npc.lua b/npc.lua index c885fe7..52811f9 100755 --- a/npc.lua +++ b/npc.lua @@ -613,17 +613,17 @@ mobs:register_mob("advanced_npc:npc", { npc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX..item_name, function() if receive_gift(self, clicker) == false then - start_chat(self, clicker) + npc.dialogue.start_dialogue(self, clicker) end end, npc.dialogue.NEGATIVE_ANSWER_LABEL, function() - start_chat(self, clicker) + npc.dialogue.start_dialogue(self, clicker) end, name ) else - start_chat(self, clicker) + npc.dialogue.start_dialogue(self, clicker) end end, @@ -702,6 +702,9 @@ local function npc_spawn(self, pos) -- Determines if NPC is married or not ent.is_married_to = nil + + -- Initialize dialogues + ent.dialogues = npc.dialogue.select_random_dialogues_for_npc(ent.sex, "phase1") minetest.log(dump(ent)) diff --git a/random_data.lua b/random_data.lua index f95667e..ce448cf 100644 --- a/random_data.lua +++ b/random_data.lua @@ -4,7 +4,7 @@ npc.data = {} npc.data.DIALOGUES = { - female = {} + female = {}, male = {} } @@ -33,8 +33,8 @@ npc.data.DIALOGUES.female["phase1"] = { text = "No, never before", action_type = "function", action = function(player_name, item) - minetest.chat_send_player(player_name, "Oh, never? How come! You should. - \nHere, take this. It will guide you to the sea...") + minetest.chat_send_player(player_name, "Oh, never? How come! You should.".. + "\nHere, take this. It will guide you to the sea...") end }, {