打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

本站正在进行早期测试,目前仍存在许多内容的缺失。

模块:CharChat

来自星砂岛百科

概述

CharChat 提供角色日常提问、赠礼回应与日常寒暄的展示能力。

用法

{{#invoke:CharChat|askBlock|枫汐}}
{{#invoke:CharChat|giftBlock|枫汐}}
{{#invoke:CharChat|dailyBlock|枫汐}}

函数

  • `askBlock`:输出日常提问区块。
  • `giftBlock`:输出赠礼回应区块。
  • `dailyBlock`:输出日常寒暄区块。
  • `chatBlocks`:返回供模板复用的 JSON 区块数据。

示例

{{#invoke:CharChat|giftBlock|枫汐}}

local common = require('Module:Common')
local character_common = require('Module:CharacterCommon')

local p = {}

local data_cache
local mapping_cache
local section_cache = {}

local function load_data()
    if data_cache then
        return
    end
    data_cache, mapping_cache = character_common.loadDomainData({
        '数据:Character/character_chat_index.json',
        '数据:Character/character chat index.json',
    })
end

local function find_record(key)
    load_data()
    return character_common.findRecord(data_cache, mapping_cache, key)
end

local function render_squote(text)
    local value = common.trim(common.toText(text))
    if value == '' then
        return ''
    end
    return mw.getCurrentFrame():expandTemplate {
        title = 'Squote',
        args = { value },
    }
end

local function render_reply_text(options)
    local ordered = character_common.orderedNumericValues(options)
    local parts = {}
    for _, option in ipairs(ordered) do
        local reply_list = character_common.orderedNumericValues(option.replies)
        if #reply_list == 0 then
            local reply = common.trim(common.toText(option.reply or ''))
            if reply ~= '' then
                reply_list = { reply }
            end
        end
        for _, reply in ipairs(reply_list) do
            local text = common.trim(common.toText(reply))
            if text ~= '' then
                parts[#parts + 1] = render_squote(text)
            end
        end
    end
    return table.concat(parts, '<br />')
end

local function render_section(title, body)
    local heading = common.trim(common.toText(title))
    local content = common.trim(common.toText(body))
    if content == '' then
        return ''
    end
    local out = { '<div class="char-card char-chat-section">' }
    if heading ~= '' then
        out[#out + 1] = '<div class="char-card-title">' .. mw.text.encode(heading) .. '</div>'
    end
    out[#out + 1] = content
    out[#out + 1] = '</div>'
    return table.concat(out, '\n')
end

local function split_weekday_prompt(prompt, weekday_override)
    local weekday_text = common.trim(common.toText(weekday_override or ''))
    local text = common.trim(common.toText(prompt))
    if weekday_text ~= '' then
        local rest = text
        if mw.ustring.sub(rest, 1, mw.ustring.len(weekday_text)) == weekday_text then
            rest = common.trim(mw.ustring.sub(rest, mw.ustring.len(weekday_text) + 1))
        end
        rest = rest:gsub('^/%s*', '')
        rest = rest:gsub('^|%s*', '')
        rest = common.trim(rest)
        if rest == '默认' then
            rest = ''
        end
        return weekday_text, rest
    end
    if text == '' then
        return '默认', ''
    end
    local weekdays = { '星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日' }
    for _, weekday in ipairs(weekdays) do
        if mw.ustring.sub(text, 1, mw.ustring.len(weekday)) == weekday then
            local rest = common.trim(mw.ustring.sub(text, mw.ustring.len(weekday) + 1))
            rest = rest:gsub('^/%s*', '')
            rest = rest:gsub('^|%s*', '')
            rest = common.trim(rest)
            return weekday, rest
        end
    end
    if text == '默认' then
        return '默认', ''
    end
    return '默认', text
end

local function weekday_sort_key(weekday)
    local order = {
        ['默认'] = 0,
        ['星期一'] = 1,
        ['星期二'] = 2,
        ['星期三'] = 3,
        ['星期四'] = 4,
        ['星期五'] = 5,
        ['星期六'] = 6,
        ['星期日'] = 7,
    }
    return order[weekday] or 99
end

local function build_html_table(headers, rows)
    local wrapper = mw.html.create('div'):addClass('char-table-wrap')
    local root = wrapper:tag('table'):addClass('wikitable'):addClass('char-data-table'):addClass('char-chat-table')
    local head = root:tag('tr')
    for _, header in ipairs(headers) do
        head:tag('th'):wikitext(header)
    end
    for _, row_values in ipairs(rows) do
        local row = root:tag('tr')
        for _, cell in ipairs(row_values) do
            row:tag('td'):wikitext(cell)
        end
    end
    return tostring(wrapper)
end

local function render_favor_items(items)
    local out = {}
    for _, entry in ipairs(items) do
        local prompt = common.trim(common.toText(entry.prompt or ''))
        local block = { '<div class="char-card char-chat-card">' }
        if prompt ~= '' then
            block[#block + 1] = '<div class="char-chat-prompt">' .. mw.text.encode(prompt) .. '</div>'
        end
        local options = character_common.orderedNumericValues(entry.options)
        if #options > 0 then
            local rows = {}
            for _, option in ipairs(options) do
                rows[#rows + 1] = {
                    mw.text.encode(common.trim(common.toText(option.choice or '—'))),
                    common.toText(option.reply or '—'),
                }
            end
            block[#block + 1] = build_html_table({ '选项', '角色回应' }, rows)
        end
        block[#block + 1] = '</div>'
        out[#out + 1] = table.concat(block, '\n')
    end
    return table.concat(out)
end

local function render_gift_items(items)
    local out = {}
    for _, entry in ipairs(items) do
        local prompt = common.trim(common.toText(entry.condition_label or entry.prompt or ''))
        local heart_label = common.trim(common.toText(entry.heart_label or ''))
        local relationship_stage = common.trim(common.toText(entry.relationship_stage or ''))
        local title = common.trim(common.toText(entry.title or ''))
        out[#out + 1] = '<div class="char-card char-chat-group">'
        if title ~= '' then
            out[#out + 1] = '<div class="char-card-title">' .. mw.text.encode(title) .. '</div>'
        end
        if prompt ~= '' and prompt ~= '默认' then
            out[#out + 1] = '<div class="char-chat-prompt">' .. mw.text.encode(prompt) .. '</div>'
        elseif heart_label ~= '' or relationship_stage ~= '' then
            local parts = {}
            if heart_label ~= '' then
                parts[#parts + 1] = heart_label
            end
            if relationship_stage ~= '' then
                parts[#parts + 1] = relationship_stage
            end
            if #parts > 0 then
                out[#out + 1] = '<div class="char-chat-prompt">' .. mw.text.encode(table.concat(parts, '|')) .. '</div>'
            end
        end
        local options = character_common.orderedNumericValues(entry.options)
        if #options > 0 then
            local rows = {}
            for _, option in ipairs(options) do
                rows[#rows + 1] = {
                    mw.text.encode(common.trim(common.toText(option.choice or '—'))),
                    common.toText(option.reply or '—'),
                }
            end
            out[#out + 1] = build_html_table({ '互动项', '角色回应' }, rows)
        end
        out[#out + 1] = '</div>'
    end
    return table.concat(out)
end

local function render_daily_items(items)
    local weekday_groups = {}
    for _, entry in ipairs(items) do
        local title = common.trim(common.toText(entry.title or ''))
        local prompt = common.trim(common.toText(entry.condition_label or entry.prompt or ''))
        local weekday, remainder = split_weekday_prompt(prompt, entry.weekday)
        local time_label = common.trim(common.toText(entry.time_label or ''))
        local weather_label = common.trim(common.toText(entry.weather_label or ''))
        local heart_label = common.trim(common.toText(entry.heart_label or ''))
        weekday_groups[weekday] = weekday_groups[weekday] or {}
        local subgroup_parts = {}
        if title ~= '' and title ~= '日常寒暄' then
            subgroup_parts[#subgroup_parts + 1] = title
        end
        if remainder ~= '' and remainder ~= time_label and remainder ~= weather_label and remainder ~= heart_label then
            subgroup_parts[#subgroup_parts + 1] = remainder
        end
        if time_label ~= '' and time_label ~= remainder then
            subgroup_parts[#subgroup_parts + 1] = time_label
        end
        if weather_label ~= '' and weather_label ~= remainder then
            subgroup_parts[#subgroup_parts + 1] = weather_label
        end
        if heart_label ~= '' and heart_label ~= remainder then
            subgroup_parts[#subgroup_parts + 1] = heart_label
        end
        local subgroup = table.concat(subgroup_parts, ' / ')
        weekday_groups[weekday][subgroup] = weekday_groups[weekday][subgroup] or {}
        weekday_groups[weekday][subgroup][#weekday_groups[weekday][subgroup] + 1] = render_reply_text(entry.options)
    end

    local weekday_list = {}
    for weekday in pairs(weekday_groups) do
        weekday_list[#weekday_list + 1] = weekday
    end
    table.sort(weekday_list, function(a, b)
        return weekday_sort_key(a) < weekday_sort_key(b)
    end)

    local out = {}
    for _, weekday in ipairs(weekday_list) do
        out[#out + 1] = '<div class="char-card char-chat-weekday">'
        out[#out + 1] = '<div class="char-card-title char-chat-weekday-title">' .. mw.text.encode(weekday) .. '</div>'
        local subgroup_keys = {}
        for subgroup in pairs(weekday_groups[weekday]) do
            subgroup_keys[#subgroup_keys + 1] = subgroup
        end
        table.sort(subgroup_keys)
        for _, subgroup in ipairs(subgroup_keys) do
            if subgroup ~= '' then
                out[#out + 1] = '<div class="char-chat-subgroup">' .. mw.text.encode(common.toText(subgroup)) .. '</div>'
            end
            for _, reply in ipairs(weekday_groups[weekday][subgroup]) do
                if common.trim(reply) ~= '' then
                    out[#out + 1] = '<div class="char-chat-reply">' .. reply .. '</div>'
                end
            end
        end
        out[#out + 1] = '</div>'
    end
    return table.concat(out)
end

local function build_chat_sections(frame)
    local key = common.getArg(frame, 1, '')
    local lookup_key = character_common.normalizeKey(character_common.resolveLookupKey(key))
    if section_cache[lookup_key] then
        return section_cache[lookup_key].sections, section_cache[lookup_key].err
    end

    local record = find_record(key)
    if not record then
        section_cache[lookup_key] = {
            sections = nil,
            err = '未找到角色日常对话数据',
        }
        return nil, '未找到角色日常对话数据'
    end

    local cache_key = character_common.normalizeKey(record.id or lookup_key)
    if section_cache[cache_key] then
        section_cache[lookup_key] = section_cache[cache_key]
        return section_cache[cache_key].sections, section_cache[cache_key].err
    end

    local entries = character_common.orderedNumericValues(record.chat)
    if #entries == 0 then
        section_cache[lookup_key] = {
            sections = nil,
            err = '该角色当前未发现爱好问答类日常对话',
        }
        return nil, '该角色当前未发现爱好问答类日常对话'
    end
    local grouped = { favor = {}, gift = {}, daily = {} }
    for _, entry in ipairs(entries) do
        local key = common.trim(common.toText(entry['group'] or 'favor'))
        if key ~= 'gift' and key ~= 'daily' then
            key = 'favor'
        end
        grouped[key][#grouped[key] + 1] = entry
    end
    local cached = {
        sections = {
        favor = common.trim(render_favor_items(grouped.favor)),
        gift = common.trim(render_gift_items(grouped.gift)),
        daily = common.trim(render_daily_items(grouped.daily)),
        },
        err = nil,
    }
    section_cache[lookup_key] = cached
    section_cache[cache_key] = cached
    return cached.sections, nil
end

function p.chatBlocks(frame)
    local sections, err = build_chat_sections(frame)
    if not sections then
        return '<div class="char-empty char-chat-empty">' .. mw.text.encode(err or '未找到角色日常对话数据') .. '</div>'
    end
    return mw.text.jsonEncode(sections)
end

function p.askBlock(frame)
    local sections = build_chat_sections(frame)
    if not sections then
        return '<div class="char-empty char-chat-empty">该角色当前未发现日常提问</div>'
    end
    return sections.favor ~= '' and sections.favor or '<div class="char-empty char-chat-empty">该角色当前未发现日常提问</div>'
end

function p.giftBlock(frame)
    local sections = build_chat_sections(frame)
    if not sections then
        return '<div class="char-empty char-chat-empty">该角色当前未发现互动回应</div>'
    end
    return sections.gift ~= '' and sections.gift or '<div class="char-empty char-chat-empty">该角色当前未发现互动回应</div>'
end

function p.dailyBlock(frame)
    local sections = build_chat_sections(frame)
    if not sections then
        return '<div class="char-empty char-chat-empty">该角色当前未发现日常寒暄</div>'
    end
    return sections.daily ~= '' and sections.daily or '<div class="char-empty char-chat-empty">该角色当前未发现日常寒暄</div>'
end

function p.lifeBlock(frame)
    local sections = build_chat_sections(frame)
    if not sections then
        return '<div class="char-empty char-chat-empty">该角色当前未发现日常对话</div>'
    end
    local out = {}
    if sections.gift ~= '' then
        out[#out + 1] = render_section('互动回应', sections.gift)
    end
    if sections.daily ~= '' then
        out[#out + 1] = render_section('日常寒暄', sections.daily)
    end
    local content = common.trim(table.concat(out, '\n'))
    return content ~= '' and content or '<div class="char-empty char-chat-empty">该角色当前未发现日常对话</div>'
end

return p