dev
Documentation icon Module documentation
[create]

The documentation for this module is missing. Click here to create it.

-- Author:		t7ru [[User:Gabonnie]]
-- Version:		1.1
-- Description:	Create an interactive conversation interface between people.
--				Forked from 0.2 with MIT License [[w:c:alter-ego:Module:Converse]]
local p = {}
local getArgs = require('Dev:Arguments').getArgs

local function getSortedSteps(args)
	local steps = {}
	local seen = {}
	for k, _ in pairs(args) do
		local numStr = string.match(k, '^step(%d+)[%-]')
		if numStr then
			local num = tonumber(numStr)
			if not seen[num] then
				table.insert(steps, num)
				seen[num] = true
			end
		end
	end
	table.sort(steps)
	return steps
end

function p._renderDialogue(args)
	local type = args.type or 'npc'
	local text = args.text or ''

	if type == 'npc' then
		local container = mw.html.create('div')
			:addClass('conv-row conv-npc')

		if args.image then
			container:tag('div')
				:addClass('conv-portrait')
				:wikitext('[[File:' .. args.image .. '|64px]]')
		end

		local bubble = container:tag('div')
			:addClass('conv-bubble')
		
		if args.name then
			bubble:tag('strong')
				:addClass('conv-name')
				:wikitext(args.name)
		end
		
		bubble:wikitext(text)
		return tostring(container)

	elseif type == 'player' then
		local container = mw.html.create('div')
			:addClass('conv-row conv-player')
		
		container:tag('div')
			:addClass('conv-bubble')
			:css('max-width', args.width or '80%')
			:wikitext(text)
			
		return tostring(container)

	elseif type == 'action' then
		local container = mw.html.create('div')
			:addClass('conv-action')
			:wikitext("''" .. text .. "''")
			
		return tostring(container)

	elseif type == 'empty' then
		return tostring(mw.html.create('div'):addClass('conv-spacer'))
	end
	
	return ''
end

function p._renderSequence(args, sortedSteps, branchFilter, runID, gcRef)
	local html = ''
	local openDivs = 0

	local levelSteps = {}
	for _, stepNum in ipairs(sortedSteps) do
		if args['step' .. stepNum .. '-branch'] == branchFilter then
			table.insert(levelSteps, stepNum)
		end
	end

	local k = 1
	while k <= #levelSteps do
		local stepNum  = levelSteps[k]
		local stepType = args['step' .. stepNum .. '-type'] or 'npc'

		if stepType == 'option' then
			gcRef[1] = gcRef[1] + 1
			local gc      = gcRef[1]
			local groupID = runID .. '-group-' .. gc

			local options = {}
			while k <= #levelSteps do
				local currStep = levelSteps[k]
				if (args['step' .. currStep .. '-type'] or 'npc') == 'option' then
					local contVal = args['step' .. currStep .. '-continue']
					local isStop  = (contVal == 'no' or contVal == 'false')
					table.insert(options, {
						text        = args['step' .. currStep .. '-text'],
						result      = args['step' .. currStep .. '-result-text'],
						resultType  = args['step' .. currStep .. '-result-type'],
						resultName  = args['step' .. currStep .. '-result-name'],
						resultImg   = args['step' .. currStep .. '-result-img'],
						branchLabel = not isStop and contVal or nil,
						noFlow      = isStop,
					})
					k = k + 1
				else
					break
				end
			end

			local hasBranches = false
			for _, opt in ipairs(options) do
				if opt.branchLabel then hasBranches = true; break end
			end

			local needsShared = false
			if not hasBranches then
				for _, opt in ipairs(options) do
					if not opt.noFlow then needsShared = true; break end
				end
			end
			local sharedNextID = needsShared and (runID .. '-group-content-' .. gc) or nil

			html = html
				.. '<div id="mw-customcollapsible-' .. groupID .. '" class="mw-collapsible">'
				.. '<div class="conv-options-list">'

			local resultsHtml = ''

			for i, opt in ipairs(options) do
				local resultID    = groupID .. '-opt-' .. i
				local branchDivID = opt.branchLabel and (runID .. '-branch-' .. opt.branchLabel) or nil

				local toggles = 'mw-customtoggle-' .. groupID
					.. ' mw-customtoggle-' .. resultID
				if branchDivID then toggles = toggles .. ' mw-customtoggle-' .. branchDivID end
				if sharedNextID and not opt.noFlow then
					toggles = toggles .. ' mw-customtoggle-' .. sharedNextID
				end

				html = html
					.. '<div class="conv-option ' .. toggles .. '">'
					.. (opt.text or 'Option') .. '</div>'

				resultsHtml = resultsHtml
					.. '<div id="mw-customcollapsible-' .. resultID .. '" class="mw-collapsible mw-collapsed">'
					.. '<div class="conv-response-wrapper">'
					.. '<div class="conv-option-selected ' .. toggles .. '">'
					.. (opt.text or 'Option') .. '</div>'

				if (opt.result and opt.result ~= '') or opt.resultType == 'empty' then
					resultsHtml = resultsHtml .. p._renderDialogue({
						type  = opt.resultType or 'player',
						text  = opt.result,
						name  = opt.resultName,
						image = opt.resultImg,
					})
				end

				resultsHtml = resultsHtml .. '</div></div>'
			end

			html = html .. '</div></div>' .. resultsHtml

			if hasBranches then
				local seenBranches = {}
				for _, opt in ipairs(options) do
					if opt.branchLabel and not seenBranches[opt.branchLabel] then
						seenBranches[opt.branchLabel] = true
						local branchDivID = runID .. '-branch-' .. opt.branchLabel
						html = html
							.. '<div id="mw-customcollapsible-' .. branchDivID .. '" class="mw-collapsible mw-collapsed">'
							.. p._renderSequence(args, sortedSteps, opt.branchLabel, runID, gcRef)
							.. '</div>'
					end
				end
			else
				if sharedNextID then
					html = html
						.. '<div id="mw-customcollapsible-' .. sharedNextID .. '" class="mw-collapsible mw-collapsed">'
					openDivs = openDivs + 1
				end
			end

		else
			html = html .. p._renderDialogue({
				type  = stepType,
				text  = args['step' .. stepNum .. '-text'] or '',
				image = args['step' .. stepNum .. '-img'],
				name  = args['step' .. stepNum .. '-name'],
			})

			local contVal = args['step' .. stepNum .. '-continue']
			if contVal and contVal ~= 'no' and contVal ~= 'false' then
				html = html .. p._renderSequence(args, sortedSteps, contVal, runID, gcRef)
			end

			k = k + 1
		end
	end

	for _ = 1, openDivs do html = html .. '</div>' end
	return html
end

function p.heeho(frame)
	local args = getArgs(frame)

	math.randomseed(os.clock() * 777777777)
	local runID = 'conv-' .. math.random(100000, 999999)
	
	local root = mw.html.create('div')
		:addClass('conv-frame')

	root:tag('div')
		:addClass('conv-header')
		:wikitext(args.header or 'Conversation Start')

	local content = root:tag('div')
		:addClass('conv-body')

	if args.content then
		content:wikitext(args.content)
	else
		local gcRef = {0}
		content:wikitext(p._renderSequence(args, getSortedSteps(args), nil, runID, gcRef))
	end

	root:tag('div')
		:addClass('conv-footer')
		:wikitext(args.footer or 'Conversation End')

	return tostring(root)
end

function p.npc(frame) return p._renderDialogue({type='npc', text=frame.args.text, name=frame.args.name, image=frame.args.image}) end
function p.player(frame) return p._renderDialogue({type='player', text=frame.args.text}) end
function p.action(frame) return p._renderDialogue({type='action', text=frame.args.text}) end

return p