Documentation for this module may be created at Module:Dialogue/doc
local p = {}
local utils = require("Module:Utils")
-- Dialogue options in excess of this number will not be processed.
local MAX_OPTIONS = 12
--[[
Contains all mappings from dialogue option type names (parameter values) to
internal dialogue IDs.
Internal IDs include:
• `more`: represented by a speech balloon icon. Used when advancing dialogue.
• `accept-quest`: represented by a green check mark. Typically used when the
player accepts a new quest or a quest objective. This ends the dialogue.
• `decline-quest`: represented by a red diagonal cross. Typically used when the
player declines a quest or a quest objective. This ends the dialogue.
• `end`: represented by an arrow pointing into an open door. Ends the dialogue,
and in rare cases does something beyond that (e. g. makes the NPC hostile).
This table has several uses:
• Should a type alias ever need to be defined, it can be added very easily.
• Similarly, should a non-English translation of this module be made, it can
use localized names for option types (to be used in the template) without
affecting logic based on internal IDs.
• Even if no type aliases or translations are ever made, having a table allows
to easily check whether an option type is valid (by indexing the table).
]]
local option_type_names = {
["more"] = "more",
["accept-quest"] = "accept-quest",
["decline-quest"] = "decline-quest",
["end"] = "end"
}
--[[
Returns the HTML ID of a dialogue section.
One of the design ideas behind this module was that users shouldn't ever need to
type section IDs manually. This also helps minimize if not nullify damage in
case breaking changes to the section ID format become needed.
Parameters:
• npc_name (string), dialogue_id (string), section_id (string):
the parameters of the specified dialogue section, whether provided
explicitly as template arguments or inferred from variables stored in
previous calls
Returns a string containing the HTML ID of the dialogue section.
]]
local function get_full_section_id(npc_name, dialogue_id, section_id)
return "dialogue-" .. npc_name .. "-" .. dialogue_id .. "-" .. section_id
end
--[[
Returns the mw.html instance for a dialogue icon.
Parameters:
• icon_type (string):
the type for this dialogue option, assumed to already be valid
Returns an mw.html instance with the relevant icon.
]]
local function get_icon(icon_type)
return mw.html.create("span")
:addClass("dialogue-icon dialogue-icon-" .. icon_type)
end
--[[
Returns a string with the text that says the player has the only option to ask
the NPC to continue their speech.
]]
local function get_continue_text()
local root_span = mw.html.create("span")
:addClass("dialogue-continue")
:tag("span")
:addClass("dialogue-continue-text")
:wikitext('(The player has to select')
:done()
:tag("span")
:addClass("dialogue-continue-option-wrap")
:node(get_icon("more"))
:tag("span")
:addClass("dialogue-continue-option-text")
:wikitext("Continue")
:done()
:done()
:tag("span")
:addClass("dialogue-continue-text")
:wikitext('to advance dialogue.)')
:done()
return tostring(root_span)
end
--[=[
Preprocesses the text of the dialogue (the NPC's speech) to replace special
tokens with final wikitext.
For the description of all such tokens, see the documentation for
[[Template:Dialogue section]].
Parameters:
• text (string):
the unprocessed text of the character's speech
Returns a string that contains the processed text.
]=]
local function process_text(text)
-- Standard format for "Continue." separators
text = text:gsub("$continue", get_continue_text())
-- ensure that all paragraphs of the text are proper <p> HTML tags in the
-- output
text = "\n" .. text .. "\n"
return text
end
--[=[
Preprocesses the text of a dialogue option description to replace special tokens
with final wikitext.
For the description of all such tokens, see the documentation for
[[Template:Dialogue section]].
Parameters:
• npc_name (string), dialogue_id (string):
the parameters of the processed dialogue section
• desc (string):
the unprocessed dialogue option description
Returns a string that contains the processed text.
]=]
local function process_option_desc(npc_name, dialogue_id, desc)
-- Short syntax for advancing dialogue
desc = desc:gsub("%$jump:([^ ]+)", function(section_id)
local target_id = get_full_section_id(npc_name, dialogue_id, section_id)
local link_text = "section " .. section_id:upper()
local link = "[[#" .. target_id .. "|" .. link_text .. "]]"
return "Dialogue advances to " .. link .. "."
end)
-- Short syntax for a jump to another dialogue
desc = desc:gsub("%$farjump:([^/]+)/([^%$]+)%$([^%$]+)%$", function(target_dialogue_id, section_id, dialogue_desc)
local target_id = get_full_section_id(npc_name, target_dialogue_id, section_id)
local link_text = "section " .. section_id:upper()
local link = "[[#" .. target_id .. "|" .. link_text .. "]]"
return "Dialogue immediately advances to " .. link .. " of the " .. dialogue_desc .. "."
end)
-- Short syntax for a link to another dialogue section
desc = desc:gsub("%$link:([^/]+)/([^%$]+)%$([^%$]+)%$", function(target_dialogue_id, section_id, link_text)
local target_id = get_full_section_id(npc_name, target_dialogue_id, section_id)
local link = "[[#" .. target_id .. "|" .. link_text .. "]]"
return link
end)
-- Short syntax for ending dialogue
desc = desc:gsub("%$end", "Dialogue ends.")
return desc
end
--[[
Processes the args and page variables to get the dialogue name and a flag for
whether this section is the first in the dialogue.
Parameters:
• args (table):
the argument table as provided to the entry point in the module
Returns:
1) a string containing the ID of the current dialogue;
2) a boolean that is true if and only if this is the first section of the
current dialogue.
]]
local function get_dialogue_id(args)
local dialogue_id = utils.get_arg(args, "dialogue_id")
local is_first_section -- for the "back to top" link
if not dialogue_id then
is_first_section = false
dialogue_id = assert(utils.get_var("dialogue-id"), "Could not get dialogue ID")
else
utils.set_var("dialogue-id", dialogue_id)
is_first_section = true
end
return dialogue_id, is_first_section
end
--[[
Processes the args and page variables to get the name of the NPC the dialogue is
with.
Parameters:
• args (table):
the argument table as provided to the entry point in the module
Returns a string containing the name of the NPC being spoken to.
]]
local function get_npc_name(args)
local npc_name = utils.get_arg(args, "npc_name")
if not npc_name then
npc_name = utils.get_var("dialogue-npc-name") or mw.title.getCurrentTitle().baseText
else
utils.set_var("dialogue-npc-name", npc_name)
end
return npc_name
end
--[[
Returns an mw.html instance representing the section's initial state, with the
character's name and their speech filled in.
Parameters:
• id (string):
the complete HTML ID of the dialogue section
• npc_name (string):
the name of the NPC being spoken to, as supplied to the template
• text (string):
the unprocessed text of the NPC's speech
Returns an mw.html instance with a partially filled in dialogue section.
]]
--
local function initialize_section(id, npc_name, text)
return mw.html.create("div")
:addClass("dialogue-section")
:attr("id", id)
:tag("span")
:addClass("dialogue-npc-name")
:wikitext(npc_name)
:done()
:tag("div")
:addClass("dialogue-text")
:wikitext(process_text(text))
:done()
end
--[[
Processes the args table to get a list of options.
Dialogue option structure format:
• type (string):
the type of the option (a fallback type of "more" is used)
• text (string):
the text of the dialogue option
• desc (string):
the unprocessed description of the dialogue option
Parameters:
• args (table):
the argument table as provided to the entry point in the module
Returns an array of dialogue option structures (see above).
]]
local function get_options(args)
local options = {}
for option_index = 1, MAX_OPTIONS do
local arg_prefix = "option" .. option_index .. "_"
local option_text = utils.get_arg(args, arg_prefix .. "text")
if not option_text then
break
end
local option_type = utils.get_arg(args, arg_prefix .. "type", "more")
local option_description = utils.get_arg(args, arg_prefix .. "desc")
-- Use default descriptions for declining quests and end options if no description is specified
if (option_type == "end" or option_type == "decline-quest") and not option_description then
-- this will be processed into the actual description later
option_description = "$end"
end
table.insert(options, {type = option_type, text = option_text, desc = option_description})
end
return options
end
--[[
Returns an mw.html instance representing a dialogue option's initial state, with
the description not yet provided.
Parameters:
• option_type (string):
a valid type for this dialogue option
• option_text (string):
the text for this option
Returns an mw.html instance for this dialogue option (without the description).
]]
local function initialize_option_html(option_type, option_text)
return mw.html.create("li")
:addClass("dialogue-option dialogue-option-" .. option_type)
:tag("span")
:addClass("dialogue-option-wrap")
:node(get_icon(option_type))
:tag("span")
:addClass("dialogue-option-text")
:wikitext(option_text)
:done()
:done()
end
--[[
Modifies the provided option mw.html instance to add the description.
Parameters:
• option_html (mw.html instance):
the descriptionless option HTML structure
• option_desc (string):
the unprocessed description for the option
• npc_name (string), dialogue_id (string):
the parameters of the processed dialogue section
Returns the original mw.html instance with the description appended.
]]
local function add_option_description(option_html, option_desc, npc_name, dialogue_id)
option_html:tag("span")
:addClass("dialogue-option-description")
:wikitext(process_option_desc(npc_name, dialogue_id, option_desc))
:done()
end
--[[
Returns an mw.html instance representing the list of dialogue options.
Parameters:
• args (table):
the argument table as provided to the entry point in the module
• npc_name (string), dialogue_id (string):
the parameters of the processed dialogue section
Returns an mw.html instance with the complete option list.
]]
local function get_option_list_html(args, npc_name, dialogue_id)
local option_list = mw.html.create("ol")
:addClass("dialogue-option-list")
for index, option in ipairs(get_options(args)) do
local option_element = initialize_option_html(option.type, option.text)
if option.desc then
add_option_description(option_element, option.desc, npc_name, dialogue_id)
end
option_list:node(option_element)
end
return option_list
end
--[[
Returns an mw.html instance with the link to the top section of this dialogue.
]]
local function get_top_section_link_html()
return mw.html.create("div")
:addClass("dialogue-back-to-top")
:wikitext("[[#" .. utils.get_var("dialogue-top-section-id") .. "| ]]")
end
--[=[
Entry point for [[Template:Dialogue section]]. For information on parameters,
refer to the documentation for that template.
]=]
function p.main(f)
local args = f
if f == mw.getCurrentFrame() then
args = f:getParent().args
end
local dialogue_id, is_first_section = get_dialogue_id(args)
local section_id = assert(utils.get_arg(args, "section_id"), "Dialogue section ID not specified")
local npc_name = get_npc_name(args)
local id = get_full_section_id(npc_name, dialogue_id, section_id)
if is_first_section then
utils.set_var("dialogue-top-section-id", id)
end
local text = args["text"]
local section = initialize_section(id, npc_name, text)
section:node(get_option_list_html(args, npc_name, dialogue_id))
if not is_first_section then
section:node(get_top_section_link_html())
end
return tostring(section)
end
--[=[
Entry point for [[Template:Dialogue section ID]]. For information on parameters,
refer to the documentation for that template.
]=]
function p.section_id(f)
local args = f
if f == mw.getCurrentFrame() then
args = f:getParent().args
end
local npc_name = assert(
utils.get_arg(args, 1, utils.get_var("dialogue-npc-name")),
"Could not retrieve NPC name"
)
local dialogue_id = assert(
utils.get_arg(args, 2, utils.get_var("dialogue-id")),
"Could not retrieve dialogue ID"
)
local section_id = assert(utils.get_arg(args, 3), "Could not retrieve section ID")
return get_full_section_id(npc_name, dialogue_id, section_id)
end
return p