Module:Dialogue: Difference between revisions

From Final Fantasy XIV Online Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Tag: Reverted
Line 242: Line 242:
             -- Start optional collapsible container
             -- Start optional collapsible container
             output = output .. "<div class='dialogue-optional-collapsible mw-collapsible mw-collapsed'>"
             output = output .. "<div class='dialogue-optional-collapsible mw-collapsible mw-collapsed'>"
             output = output .. "<div class='dialogue-optional-header mw-collapsible-toggle'>''Optional dialogue''</div>"
             output = output .. "<div class='dialogue-optional-header'>''Optional dialogue''</div>"
             output = output .. "<div class='mw-collapsible-content'>"
             output = output .. "<div class='mw-collapsible-content'>"
              
              

Revision as of 04:34, 4 January 2026

This module implements {{Dialogue}}.


local p = {}

-- Function to process Forename and Surname placeholders
local function processNames(text)
    -- Process '''Forename''' and '''Surname'''
    text = text:gsub("'''(Forename)'''", "<u>%1</u>")
    text = text:gsub("'''(Surname)'''", "<u>%1</u>")
    
    -- Process ''Forename'' and ''Surname''
    text = text:gsub("''(Forename)''", "<u>%1</u>")
    text = text:gsub("''(Surname)''", "<u>%1</u>")
    
    -- Process [Forename] and [Surname]
    text = text:gsub("%[(Forename)%]", "<u>%1</u>")
    text = text:gsub("%[(Surname)%]", "<u>%1</u>")
    
    -- Process "Forename" and "Surname"
    text = text:gsub('"(Forename)"', "<u>%1</u>")
    text = text:gsub('"(Surname)"', "<u>%1</u>")
    
    -- Process bare Forename and Surname (must be capitalized)
    -- Use word boundaries to avoid matching partial words
    text = text:gsub("(%W)(Forename)(%W)", "%1<u>%2</u>%3")
    text = text:gsub("(%W)(Surname)(%W)", "%1<u>%2</u>%3")
    
    -- Handle Forename/Surname at start of line
    text = text:gsub("^(Forename)(%W)", "<u>%1</u>%2")
    text = text:gsub("^(Surname)(%W)", "<u>%1</u>%2")
    
    -- Handle Forename/Surname at end of line
    text = text:gsub("(%W)(Forename)$", "%1<u>%2</u>")
    text = text:gsub("(%W)(Surname)$", "%1<u>%2</u>")
    
    -- Handle Forename/Surname as entire line
    if text == "Forename" then
        text = "<u>Forename</u>"
    elseif text == "Surname" then
        text = "<u>Surname</u>"
    end
    
    return text
end

function p.formatBlock(frame)
    local lines = mw.text.split(frame.args[1] or "", "\n")
    local output = ""
    local lastSpeaker = nil
    local lastSpeakerLink = nil
    local inContainer = false
    local inOptionResponse = false
    local inSpeakerBlock = false
    local isOptional = false
    local inOptionalSection = false
    local optionResponseBuffer = ""
    local currentOption = nil
    local optionCounter = 0
    
    for _, line in ipairs(lines) do
        line = mw.text.trim(line)
        
        -- Skip empty lines
        if line == "" then
            -- Do nothing
        
        -- Check for cutscene start marker
        elseif line == "-cutscene start" then
            -- Flush any buffered option response first
            if inOptionResponse and currentOption then
                optionCounter = optionCounter + 1
                -- Check if there's any content in the buffer
                if optionResponseBuffer ~= "" then
                    -- Has content - make it collapsible
                    output = output .. "<div class='dialogue-option-collapsible mw-collapsible mw-collapsed'>"
                    output = output .. "<div class='dialogue-option mw-collapsible-toggle'>" .. currentOption .. "</div>"
                    output = output .. "<div class='mw-collapsible-content'>"
                    output = output .. optionResponseBuffer
                    output = output .. "</div></div>\n"
                else
                    -- No content - just display the option
                    output = output .. "<div class='dialogue-option dialogue-option-no-response'>" .. currentOption .. " <span class='dialogue-no-response-label'>''No unique response''</span></div>\n"
                end
                
                optionResponseBuffer = ""
                currentOption = nil
                inOptionResponse = false
            end
            
            -- Close any open containers
            if inContainer then
                output = output .. "</div>\n"
                inContainer = false
            end
            if inSpeakerBlock then
                output = output .. "</div>\n"
                inSpeakerBlock = false
            end
            
            -- Add cutscene start marker
            output = output .. "<div class='dialogue-cutscene-marker'>"
            output = output .. "[[File:Cutscene icon.png|24px|link=]] ''Start of cutscene.''"
            output = output .. "</div>\n"
            
            -- Reset state
            lastSpeaker = nil
            lastSpeakerLink = nil
            
        -- Check for cutscene end marker
        elseif line == "-cutscene end" then
            -- Flush any buffered option response first
            if inOptionResponse and currentOption then
                optionCounter = optionCounter + 1
                -- Check if there's any content in the buffer
                if optionResponseBuffer ~= "" then
                    -- Has content - make it collapsible
                    output = output .. "<div class='dialogue-option-collapsible mw-collapsible mw-collapsed'>"
                    output = output .. "<div class='dialogue-option mw-collapsible-toggle'>" .. currentOption .. "</div>"
                    output = output .. "<div class='mw-collapsible-content'>"
                    output = output .. optionResponseBuffer
                    output = output .. "</div></div>\n"
                else
                    -- No content - just display the option
                    output = output .. "<div class='dialogue-option dialogue-option-no-response'>" .. currentOption .. " <span class='dialogue-no-response-label'>''No unique response''</span></div>\n"
                end
                
                optionResponseBuffer = ""
                currentOption = nil
                inOptionResponse = false
            end
            
            -- Close any open containers
            if inContainer then
                output = output .. "</div>\n"
                inContainer = false
            end
            if inSpeakerBlock then
                output = output .. "</div>\n"
                inSpeakerBlock = false
            end
            
            -- Add cutscene end marker
            output = output .. "<div class='dialogue-cutscene-marker'>''End of cutscene.''</div>\n"
            
            -- Reset state
            lastSpeaker = nil
            lastSpeakerLink = nil
            
        -- Check for quest complete marker
        elseif line == "-quest complete" then
            -- Flush any buffered option response first
            if inOptionResponse and currentOption then
                -- Close any open containers in the buffer
                if inContainer then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inContainer = false
                end
                if inSpeakerBlock then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inSpeakerBlock = false
                end
                
                optionCounter = optionCounter + 1
                -- Check if there's any content in the buffer
                if optionResponseBuffer ~= "" then
                    -- Has content - make it collapsible
                    output = output .. "<div class='dialogue-option-collapsible mw-collapsible mw-collapsed'>"
                    output = output .. "<div class='dialogue-option mw-collapsible-toggle'>" .. currentOption .. "</div>"
                    output = output .. "<div class='mw-collapsible-content'>"
                    output = output .. optionResponseBuffer
                    output = output .. "</div></div>\n"
                else
                    -- No content - just display the option
                    output = output .. "<div class='dialogue-option dialogue-option-no-response'>" .. currentOption .. " <span class='dialogue-no-response-label'>''No unique response''</span></div>\n"
                end
                
                optionResponseBuffer = ""
                currentOption = nil
                inOptionResponse = false
            end
            
            -- Close any open containers
            if inContainer then
                output = output .. "</div>\n"
                inContainer = false
            end
            if inSpeakerBlock then
                output = output .. "</div>\n"
                inSpeakerBlock = false
            end
            
            -- Add quest complete image
            output = output .. "<div class='dialogue-quest-complete'>"
            output = output .. "[[File:Quest Complete.png|500px|link=]]"
            output = output .. "</div>\n"
            
            -- Reset state
            lastSpeaker = nil
            lastSpeakerLink = nil
        
        -- Check for optional start marker
        elseif line == "-optional start" then
            -- Flush any buffered option response first
            if inOptionResponse and currentOption then
                -- Close any open containers in the buffer
                if inContainer then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inContainer = false
                end
                if inSpeakerBlock then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inSpeakerBlock = false
                end
                
                optionCounter = optionCounter + 1
                -- Check if there's any content in the buffer
                if optionResponseBuffer ~= "" then
                    -- Has content - make it collapsible
                    output = output .. "<div class='dialogue-option-collapsible mw-collapsible mw-collapsed'>"
                    output = output .. "<div class='dialogue-option mw-collapsible-toggle'>" .. currentOption .. "</div>"
                    output = output .. "<div class='mw-collapsible-content'>"
                    output = output .. optionResponseBuffer
                    output = output .. "</div></div>\n"
                else
                    -- No content - just display the option
                    output = output .. "<div class='dialogue-option dialogue-option-no-response'>" .. currentOption .. " <span class='dialogue-no-response-label'>''No unique response''</span></div>\n"
                end
                
                optionResponseBuffer = ""
                currentOption = nil
                inOptionResponse = false
            end
            
            -- Close any open containers
            if inContainer then
                output = output .. "</div>\n"
                inContainer = false
            end
            if inSpeakerBlock then
                output = output .. "</div>\n"
                inSpeakerBlock = false
            end
            
            -- Start optional collapsible container
            output = output .. "<div class='dialogue-optional-collapsible mw-collapsible mw-collapsed'>"
            output = output .. "<div class='dialogue-optional-header'>''Optional dialogue''</div>"
            output = output .. "<div class='mw-collapsible-content'>"
            
            inOptionalSection = true
            
            -- Reset state
            lastSpeaker = nil
            lastSpeakerLink = nil
            
        -- Check for optional end marker
        elseif line == "-optional end" then
            -- Flush any buffered option response first
            if inOptionResponse and currentOption then
                optionCounter = optionCounter + 1
                -- Check if there's any content in the buffer
                if optionResponseBuffer ~= "" then
                    -- Has content - make it collapsible
                    output = output .. "<div class='dialogue-option-collapsible mw-collapsible mw-collapsed'>"
                    output = output .. "<div class='dialogue-option mw-collapsible-toggle'>" .. currentOption .. "</div>"
                    output = output .. "<div class='mw-collapsible-content'>"
                    output = output .. optionResponseBuffer
                    output = output .. "</div></div>\n"
                else
                    -- No content - just display the option
                    output = output .. "<div class='dialogue-option dialogue-option-no-response'>" .. currentOption .. " <span class='dialogue-no-response-label'>''No unique response''</span></div>\n"
                end
                
                optionResponseBuffer = ""
                currentOption = nil
                inOptionResponse = false
            end
            
            -- Close any open containers
            if inContainer then
                output = output .. "</div>\n"
                inContainer = false
            end
            if inSpeakerBlock then
                output = output .. "</div>\n"
                inSpeakerBlock = false
            end
            
            -- Close optional collapsible container
            output = output .. "</div></div>\n"
            
            -- Reset state
            lastSpeaker = nil
            lastSpeakerLink = nil
            inOptionalSection = false
            isOptional = false
            
        -- Check if this is an end-of-option delimiter
        elseif line == "---" or line == "–––" or line == "—" then
            -- Flush any buffered option response
            if inOptionResponse and currentOption then
                -- Close any open containers in the buffer
                if inContainer then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inContainer = false
                end
                if inSpeakerBlock then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inSpeakerBlock = false
                end
                
                optionCounter = optionCounter + 1
                -- Check if there's any content in the buffer
                if optionResponseBuffer ~= "" then
                    -- Has content - make it collapsible
                    output = output .. "<div class='dialogue-option-collapsible mw-collapsible mw-collapsed'>"
                    output = output .. "<div class='dialogue-option mw-collapsible-toggle'>" .. currentOption .. "</div>"
                    output = output .. "<div class='mw-collapsible-content'>"
                    output = output .. optionResponseBuffer
                    output = output .. "</div></div>\n"
                else
                    -- No content - just display the option
                    output = output .. "<div class='dialogue-option dialogue-option-no-response'>" .. currentOption .. " <span class='dialogue-no-response-label'>''No unique response''</span></div>\n"
                end
                
                optionResponseBuffer = ""
                currentOption = nil
                inOptionResponse = false
            else
                -- Close any open containers in main output
                if inContainer then
                    output = output .. "</div>\n"
                    inContainer = false
                end
                if inSpeakerBlock then
                    output = output .. "</div>\n"
                    inSpeakerBlock = false
                end
            end
            
            -- Reset state
            lastSpeaker = nil
            lastSpeakerLink = nil
            
        -- Check if this is a wikitext header (skip processing, pass through)
        elseif line:match("^==+.+==+$") then
            -- Flush any buffered option response
            if inOptionResponse and currentOption then
                -- Close any open containers in the buffer
                if inContainer then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inContainer = false
                end
                if inSpeakerBlock then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inSpeakerBlock = false
                end
                
                optionCounter = optionCounter + 1
                -- Check if there's any content in the buffer
                if optionResponseBuffer ~= "" then
                    -- Has content - make it collapsible
                    output = output .. "<div class='dialogue-option-collapsible mw-collapsible mw-collapsed'>"
                    output = output .. "<div class='dialogue-option mw-collapsible-toggle'>" .. currentOption .. "</div>"
                    output = output .. "<div class='mw-collapsible-content'>"
                    output = output .. optionResponseBuffer
                    output = output .. "</div></div>\n"
                else
                    -- No content - just display the option
                    output = output .. "<div class='dialogue-option dialogue-option-no-response'>" .. currentOption .. " <span class='dialogue-no-response-label'>''No unique response''</span></div>\n"
                end
                
                optionResponseBuffer = ""
                currentOption = nil
                inOptionResponse = false
            else
                -- Close any open containers in main output
                if inContainer then
                    output = output .. "</div>\n"
                    inContainer = false
                end
                if inSpeakerBlock then
                    output = output .. "</div>\n"
                    inSpeakerBlock = false
                end
            end
            
            -- Pass the header through as-is
            output = output .. line .. "\n"
            
            -- Reset state
            lastSpeaker = nil
            lastSpeakerLink = nil
            
        -- Check if this is a dialogue option (starts with >)
        elseif line:match("^>%s*(.+)$") then
            local optionText = line:match("^>%s*(.+)$")
            
            -- Process names in option text
            optionText = processNames(optionText)
            
            -- Flush previous option response if exists
            if inOptionResponse and currentOption then
                -- Close any open containers in the buffer
                if inContainer then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inContainer = false
                end
                if inSpeakerBlock then
                    optionResponseBuffer = optionResponseBuffer .. "</div>\n"
                    inSpeakerBlock = false
                end
                
                optionCounter = optionCounter + 1
                -- Check if there's any content in the buffer
                if optionResponseBuffer ~= "" then
                    -- Has content - make it collapsible
                    output = output .. "<div class='dialogue-option-collapsible mw-collapsible mw-collapsed'>"
                    output = output .. "<div class='dialogue-option mw-collapsible-toggle'>" .. currentOption .. "</div>"
                    output = output .. "<div class='mw-collapsible-content'>"
                    output = output .. optionResponseBuffer
                    output = output .. "</div></div>\n"
                else
                    -- No content - just display the option
                    output = output .. "<div class='dialogue-option dialogue-option-no-response'>" .. currentOption .. " <span class='dialogue-no-response-label'>''No unique response''</span></div>\n"
                end
                
                optionResponseBuffer = ""
            end
            
            -- Set current option and start buffering
            currentOption = optionText
            inOptionResponse = true
            
            -- Reset last speaker
            lastSpeaker = nil
            lastSpeakerLink = nil
            
        else
            -- Match "Speaker: text" format, with optional link override
            local speakerPart, text = line:match("^(.-):%s*(.+)$")
            
            if speakerPart and text then
                local speaker, linkOverride
                
                -- Check if speaker has a link override (format: "Link@Speaker" or "x@Speaker")
                if speakerPart:match("@") then
                    linkOverride, speaker = speakerPart:match("^(.-)@(.+)$")
                    linkOverride = mw.text.trim(linkOverride)
                    speaker = mw.text.trim(speaker)
                else
                    speaker = mw.text.trim(speakerPart)
                    linkOverride = nil
                end
                
                -- Remove bold markup (''') from speaker name
                speaker = speaker:gsub("'''", "")
                speaker = mw.text.trim(speaker)
                
                -- Process names in dialogue text
                text = processNames(text)
                
                -- Determine the link target
                local speakerLink
                if linkOverride == "x" then
                    speakerLink = nil  -- No link
                elseif linkOverride then
                    speakerLink = linkOverride  -- Custom link
                elseif speaker == "System" then
                    speakerLink = nil  -- Don't link System
                else
                    speakerLink = speaker  -- Default: link to speaker name
                end
                
                -- Determine output target (direct output or buffer)
                local targetOutput = inOptionResponse and function(str) optionResponseBuffer = optionResponseBuffer .. str end 
                                      or function(str) output = output .. str end
                
                -- Check if we have a new speaker OR if we're entering an optional section
                if speaker ~= lastSpeaker or (inOptionalSection and not isOptional) then
                    -- Close previous speaker's containers if they exist
                    if inContainer then
                        targetOutput("</div>\n")
                        inContainer = false
                    end
                    if inSpeakerBlock then
                        targetOutput("</div>\n")
                        inSpeakerBlock = false
                    end
                    
                    -- Determine if this should be indented (response to option)
                    local blockClass = "dialogue-speaker-block"
                    if inOptionResponse then
                        blockClass = blockClass .. " dialogue-speaker-block-indented"
                    end
                    if inOptionalSection then
                        isOptional = true
                    end
                    
                    -- Start new speaker block
                    targetOutput("<div class='" .. blockClass .. "'>\n")
                    inSpeakerBlock = true
                    
                    -- Add speaker header
                    targetOutput("<div class='dialogue-speaker-header'>\n")
                    
                    -- Create speaker name (linked or not)
                    if speakerLink then
                        targetOutput("<div class='dialogue-speaker'>'''[[" .. speakerLink .. "|" .. speaker .. "]]'''</div>\n")
                    else
                        targetOutput("<div class='dialogue-speaker'>'''" .. speaker .. "'''</div>\n")
                    end
                    
                    targetOutput("</div>\n")
                    
                    -- Start new dialogue container
                    targetOutput("<div class='dialogue-container'>\n")
                    
                    lastSpeaker = speaker
                    lastSpeakerLink = speakerLink
                    inContainer = true
                end
                
                -- Add the dialogue line
                targetOutput("<div class='dialogue-line'>" .. text .. "</div>\n")
            else
                -- Line without a speaker
                local targetOutput = inOptionResponse and function(str) optionResponseBuffer = optionResponseBuffer .. str end 
                                      or function(str) output = output .. str end
                
                -- Close any open containers first
                if inContainer then
                    targetOutput("</div>\n")
                    inContainer = false
                end
                if inSpeakerBlock then
                    targetOutput("</div>\n")
                    inSpeakerBlock = false
                end
                
                -- Process names in standalone line
                line = processNames(line)
                
                -- Display as a standalone dialogue line
                local lineClass = "dialogue-line dialogue-line-standalone"
                if inOptionalSection then
                    lineClass = lineClass .. " dialogue-line-optional"
                end
                targetOutput("<div class='" .. lineClass .. "'>" .. line .. "</div>\n")
                
                -- Reset state
                lastSpeaker = nil
                lastSpeakerLink = nil
            end
        end
    end
    
    -- Flush any remaining buffered option response
    if inOptionResponse and currentOption then
        -- Close any open containers in the buffer
        if inContainer then
            optionResponseBuffer = optionResponseBuffer .. "</div>\n"
        end
        if inSpeakerBlock then
            optionResponseBuffer = optionResponseBuffer .. "</div>\n"
        end
        
        optionCounter = optionCounter + 1
        -- Check if there's any content in the buffer
        if optionResponseBuffer ~= "" then
            -- Has content - make it collapsible
            output = output .. "<div class='dialogue-option-collapsible mw-collapsible mw-collapsed'>"
            output = output .. "<div class='dialogue-option mw-collapsible-toggle'>" .. currentOption .. "</div>"
            output = output .. "<div class='mw-collapsible-content'>"
            output = output .. optionResponseBuffer
            output = output .. "</div></div>\n"
        else
            -- No content - just display the option
            output = output .. "<div class='dialogue-option dialogue-option-no-response'>" .. currentOption .. " <span class='dialogue-no-response-label'>''No unique response''</span></div>\n"
        end
    else
        -- Close any open containers in main output
        if inContainer then
            output = output .. "</div>\n"
        end
        if inSpeakerBlock then
            output = output .. "</div>\n"
        end
    end
    
    return output
end

return p