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

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

模块:Gifting

来自星砂岛百科
Sizau留言 | 贡献2026年3月9日 (一) 14:34的版本 (创建页面)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)

概述

Gifting 提供赠礼偏好查询、角色页标签页渲染与物品反查渲染,供 {{GiftsByNPC}}{{GiftsByItem}} 等模板调用。

用法

{{#invoke:Gifting|renderNpcTabs|晨星}}
{{#invoke:Gifting|renderGiftsByItem|蜂蜜}}
{{#invoke:Gifting|preferenceList|晨星|loved}}

示例

{{#invoke:Gifting|preferenceListByItem|蜂蜜|liked}}

函数

  • getField:读取角色赠礼记录中的基础字段。
  • preferenceList:按偏好层级返回角色喜欢或讨厌的物品列表。
  • renderNpcTabs:渲染角色页的赠礼标签页。
  • preferenceListByItem:按物品反查对应的角色列表。
  • renderGiftsByItem:渲染物品页赠礼表格。

数据来源


local common = require('Module:Common')

local p = {}

local character_cache
local character_records
local character_ids
local character_mapping
local global_rules
local tag_items

local item_cache
local item_by_index
local item_mapping
local item_redirects

local id_registry
local registry_character_ids
local registry_item_ids

local level_to_bucket = {
    resonance = 'resonance',
    special = 'special',
    loved = 'loved',
    liked = 'liked',
    neutral = 'neutral',
    disliked = 'disliked',
    hated = 'hated',
    r = 'resonance',
    s = 'special',
    v = 'loved',
    k = 'liked',
    n = 'neutral',
    d = 'disliked',
    h = 'hated',
}

local bucket_to_short = {
    resonance = 'r',
    special = 's',
    loved = 'v',
    liked = 'k',
    neutral = 'n',
    disliked = 'd',
    hated = 'h',
}

local function normalize_key(value)
    return common.normalizeKey(value)
end

local function resolve_level_bucket(level)
    if level == nil then
        return nil
    end

    if type(level) == 'string' then
        local normalized = normalize_key(level)
        return level_to_bucket[normalized]
    end

    local numeric = tonumber(level)
    if numeric == 4 then
        return 'resonance'
    end
    if numeric == 3 then
        return 'special'
    end
    if numeric == 2 then
        return 'loved'
    end
    if numeric == 1 then
        return 'liked'
    end
    if numeric == 0 then
        return 'neutral'
    end
    if numeric == -1 then
        return 'disliked'
    end
    if numeric == -2 then
        return 'hated'
    end
    return nil
end

local function empty_summary()
    return {
        resonance = { personal = {}, global = {} },
        special = { personal = {}, global = {} },
        loved = { personal = {}, global = {} },
        liked = { personal = {}, global = {} },
        neutral = { personal = {}, global = {} },
        disliked = { personal = {}, global = {} },
        hated = { personal = {}, global = {} },
    }
end

local function load_id_registry()
    if id_registry then
        return
    end
    id_registry = common.readJsonPage('数据:Gifting/gift_id_registry.json') or {}
    registry_character_ids = id_registry.characters or {}
    registry_item_ids = id_registry.items or {}
end

local function item_id_from_index(index)
    load_id_registry()
    local n = tonumber(index)
    if not n then
        return ''
    end
    return common.trim(registry_item_ids[n + 1] or '')
end

local function character_id_from_index(index)
    load_id_registry()
    local n = tonumber(index)
    if not n then
        return ''
    end
    return common.trim(registry_character_ids[n + 1] or '')
end

local function load_character_data()
    if character_cache then
        return
    end

    character_cache = common.readJsonPage('数据:Gifting/gift_preferences.json') or {}
    character_records = character_cache.characters or {}
    global_rules = character_cache.global or {}
    tag_items = character_cache.tag_items or {}
    load_id_registry()

    character_ids = {}
    for idx, record in ipairs(character_records) do
        local cid = character_id_from_index(idx - 1)
        if cid ~= '' then
            character_ids[normalize_key(cid)] = idx
        end
    end

    character_mapping = common.readJsonPage('数据:Gifting/gifting_mapping.json') or {
        name_to_id = {},
        id_to_name = {},
        aliases = {},
        overrides = {
            name_to_id = {},
            aliases = {},
        },
    }
end

local function load_item_data()
    if item_cache then
        return
    end

    item_cache = common.readJsonPage('数据:Gifting/gift_preferences_by_item.json') or {}
    item_by_index = item_cache.items or {}
    item_redirects = item_cache.item_redirects or {}
    item_mapping = common.readJsonPage('数据:Item/item_mapping.json') or {
        name_to_id = {},
        id_to_name = {},
        aliases = {},
        overrides = {
            name_to_id = {},
            aliases = {},
        },
    }
end

local function find_character_record(key)
    load_character_data()

    local resolved = common.trim(key)
    if resolved == '' then
        resolved = common.getCurrentTitleText()
    end

    local normalized = normalize_key(resolved)
    local idx = character_ids[normalized]

    if not idx then
        local override_id = character_mapping.overrides and character_mapping.overrides.name_to_id and character_mapping.overrides.name_to_id[normalized]
        if override_id then
            idx = character_ids[normalize_key(override_id)]
        end
    end

    if not idx then
        local override_alias = character_mapping.overrides and character_mapping.overrides.aliases and character_mapping.overrides.aliases[normalized]
        if override_alias then
            idx = character_ids[normalize_key(override_alias)]
        end
    end

    if not idx then
        local mapped_id = character_mapping.name_to_id and character_mapping.name_to_id[normalized]
        if mapped_id then
            idx = character_ids[normalize_key(mapped_id)]
        end
    end

    if not idx then
        local alias_id = character_mapping.aliases and character_mapping.aliases[normalized]
        if alias_id then
            idx = character_ids[normalize_key(alias_id)]
        end
    end

    if not idx then
        return nil
    end
    return character_records[idx], idx - 1
end

local function resolve_item_index(index)
    local key = tostring(index)
    if item_by_index[key] then
        return key
    end
    local redirected = item_redirects and item_redirects[key]
    if redirected then
        local redirected_key = tostring(redirected)
        if item_by_index[redirected_key] then
            return redirected_key
        end
    end
    return nil
end

local function find_item_record(key)
    load_item_data()
    load_id_registry()

    local resolved = common.trim(key)
    if resolved == '' then
        resolved = common.getCurrentTitleText()
    end

    local normalized = normalize_key(resolved)
    local item_id = ''

    for idx, id in ipairs(registry_item_ids) do
        if normalize_key(id) == normalized then
            item_id = id
            break
        end
    end

    if item_id == '' then
        local override_id = item_mapping.overrides and item_mapping.overrides.name_to_id and item_mapping.overrides.name_to_id[normalized]
        if override_id and override_id ~= '' then
            item_id = override_id
        end
    end

    if item_id == '' then
        local override_alias = item_mapping.overrides and item_mapping.overrides.aliases and item_mapping.overrides.aliases[normalized]
        if override_alias and override_alias ~= '' then
            item_id = override_alias
        end
    end

    if item_id == '' then
        local mapped_id = item_mapping.name_to_id and item_mapping.name_to_id[normalized]
        if mapped_id and mapped_id ~= '' then
            item_id = mapped_id
        end
    end

    if item_id == '' then
        local alias_id = item_mapping.aliases and item_mapping.aliases[normalized]
        if alias_id and alias_id ~= '' then
            item_id = alias_id
        end
    end

    if item_id == '' then
        return nil
    end

    local item_index = nil
    for idx, id in ipairs(registry_item_ids) do
        if id == item_id then
            item_index = idx - 1
            break
        end
    end
    if item_index == nil then
        return nil
    end

    local resolved_index = resolve_item_index(item_index)
    if not resolved_index then
        return nil
    end

    return item_by_index[resolved_index]
end

local function append_unique_indexes(values, seen, target)
    if type(values) ~= 'table' then
        return
    end
    for _, value in ipairs(values) do
        local index = tonumber(value)
        if index ~= nil and not seen[index] then
            seen[index] = true
            target[#target + 1] = index
        end
    end
end

local function expand_rule_items(rule)
    local result = {}
    local seen = {}
    for _, tag in ipairs(rule.t or {}) do
        append_unique_indexes(tag_items[tag], seen, result)
    end
    append_unique_indexes(rule.s, seen, result)
    return result
end

local function build_character_summary(record)
    local summary = empty_summary()
    local global_seen = {
        resonance = {}, special = {}, loved = {}, liked = {}, neutral = {}, disliked = {}, hated = {},
    }
    local personal_seen = {
        resonance = {}, special = {}, loved = {}, liked = {}, neutral = {}, disliked = {}, hated = {},
    }

    for _, rule in ipairs(global_rules) do
        local bucket = resolve_level_bucket(rule.l)
        if bucket and summary[bucket] then
            for _, item_idx in ipairs(expand_rule_items(rule)) do
                if not global_seen[bucket][item_idx] then
                    global_seen[bucket][item_idx] = true
                    summary[bucket].global[#summary[bucket].global + 1] = item_idx
                end
            end
        end
    end

    for _, rule in ipairs(record.p or {}) do
        local bucket = resolve_level_bucket(rule.l)
        if bucket and summary[bucket] then
            for _, item_idx in ipairs(expand_rule_items(rule)) do
                if not global_seen[bucket][item_idx] and not personal_seen[bucket][item_idx] then
                    personal_seen[bucket][item_idx] = true
                    summary[bucket].personal[#summary[bucket].personal + 1] = item_idx
                end
            end
        end
    end

    return summary
end

local function get_summary_bucket(record, bucket)
    if type(record) ~= 'table' then
        return nil
    end
    local resolved_bucket = level_to_bucket[bucket] or normalize_key(bucket)
    local summary = build_character_summary(record)
    return summary[resolved_bucket]
end

local function item_label_from_index(item_index)
    local item_id = item_id_from_index(item_index)
    if item_id == '' then
        return ''
    end
    return common.trim(item_mapping.id_to_name and item_mapping.id_to_name[normalize_key(item_id)] or '')
end

local function character_label_from_index(character_index)
    local character_id = character_id_from_index(character_index)
    if character_id == '' then
        return ''
    end
    return common.trim(character_mapping.id_to_name and character_mapping.id_to_name[normalize_key(character_id)] or '')
end

local function join_indexes(values, label_resolver, fallback_resolver)
    if type(values) ~= 'table' or #values == 0 then
        return ''
    end

    local parts = {}
    for _, value in ipairs(values) do
        local idx = tonumber(value)
        if idx ~= nil then
            local label = label_resolver and label_resolver(idx) or ''
            if label ~= '' then
                parts[#parts + 1] = ('[[%s]]'):format(label)
            else
                local fallback = fallback_resolver and fallback_resolver(idx) or ''
                if fallback ~= '' then
                    parts[#parts + 1] = fallback
                end
            end
        end
    end
    return table.concat(parts, '、')
end

local function render_bucket(bucket_data, personal_label, global_label, label_resolver, fallback_resolver)
    if type(bucket_data) ~= 'table' then
        return ''
    end
    local parts = {}
    local personal = join_indexes(bucket_data.personal, label_resolver, fallback_resolver)
    local global = join_indexes(bucket_data.global, label_resolver, fallback_resolver)
    if personal ~= '' then
        parts[#parts + 1] = personal_label .. ':' .. personal
    end
    if global ~= '' then
        parts[#parts + 1] = global_label .. ':' .. global
    end
    return table.concat(parts, '<br />')
end

function p.getField(frame)
    local key = common.getArg(frame, 1, '')
    local field = common.getArg(frame, 2, '')
    if field == '' then
        return ''
    end
    local record, character_index = find_character_record(key)
    if not record then
        return ''
    end

    if field == 'id' then
        return character_id_from_index(character_index)
    end
    if field == 'name' then
        return common.toText(record.n)
    end
    if field == 'name_en' then
        return common.toText(record.en)
    end
    if field == 'character_type' then
        return common.toText(record.t)
    end
    if field == 'personal_rules' then
        return common.toText(record.p)
    end
    return common.toText(record[field])
end

function p.preferenceList(frame)
    local key = common.getArg(frame, 1, '')
    local field = common.getArg(frame, 2, '')
    if field == '' then
        return ''
    end
    local record = find_character_record(key)
    if not record then
        return ''
    end
    return render_bucket(get_summary_bucket(record, field), '个人喜好', '全局通用喜好', item_label_from_index, item_id_from_index)
end

function p.preferenceListByItem(frame)
    local key = common.getArg(frame, 1, '')
    local field = common.getArg(frame, 2, '')
    if field == '' then
        return ''
    end

    local record = find_item_record(key)
    if not record then
        return ''
    end

    local bucket = level_to_bucket[field] or normalize_key(field)
    local short = bucket_to_short[bucket]
    local compact_bucket = record.s and short and record.s[short] or nil
    if type(compact_bucket) ~= 'table' then
        return ''
    end

    local decoded = {
        personal = compact_bucket.p or {},
        global = compact_bucket.g or {},
    }

    return render_bucket(decoded, '个人喜好角色', '全局通用喜好角色', character_label_from_index, character_id_from_index)
end

return p