Grim Dawn Wiki
Advertisement

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") .. "|&nbsp;]]")
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
Advertisement