模块: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