打开/关闭菜单
8
231
3
1270
夜幕之下 Wiki - Reign of Nightfall 中文资料站
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

模块:WeaponAttackData

来自夜幕之下 Wiki - Reign of Nightfall 中文资料站

此模块的文档可以在模块:WeaponAttackData/doc创建

-- Module:WeaponAttackData
-- Normal-attack weapon rhythm formatting helpers for style pages and future tools.

local p = {}

local function textValue(value, fallback)
    if value == nil or value == "" then return fallback or "" end
    return tostring(value)
end

local function numberValue(value)
    local n = tonumber(value)
    if n then return n end
    return nil
end

local function round(value, digits)
    local n = numberValue(value)
    if not n then return nil end
    local scale = 10 ^ (digits or 2)
    return math.floor(n * scale + 0.5) / scale
end

local function formatNumber(value, digits)
    local n = round(value, digits)
    if not n then return "" end
    if (digits or 2) == 0 then
        return string.format("%.0f", n)
    end
    local fmt = "%." .. tostring(digits or 2) .. "f"
    local out = string.format(fmt, n)
    out = out:gsub("0+$", ""):gsub("%.$", "")
    return out
end

local function formatMs(value)
    local n = numberValue(value)
    if not n then return "" end
    return formatNumber(n, 0) .. " ms"
end

local function secondsText(ms, suffix)
    local n = numberValue(ms)
    if not n then return "" end
    return "约 " .. formatNumber(n / 1000, 2) .. " 秒" .. textValue(suffix)
end

local function verificationLabel(status)
    status = textValue(status)
    if status == "video_supported" then return "录像支持" end
    if status == "video_conflicts_with_candidate" then return "录像有冲突" end
    if status == "verified" then return "已验证" end
    return "配置推算"
end

local function safeId(value)
    local text = textValue(value, "weapon")
    text = text:gsub("[^%w%-_]", "-")
    if text == "" then text = "weapon" end
    return text
end

function p.pickProfile(profiles, skillData)
    if type(profiles) ~= "table" then return nil end
    local skillId = textValue(skillData and (skillData.source_primary_id or skillData.source_group_id), "")
    local firstPublished = nil
    for _, profile in ipairs(profiles) do
        if type(profile) == "table" and (profile.status == nil or profile.status == "published") then
            if not firstPublished then firstPublished = profile end
            if skillId ~= "" and textValue(profile.normal_skill_id) == skillId then
                return profile
            end
        end
    end
    return firstPublished
end

function p.payload(profile)
    if type(profile) ~= "table" then return nil end
    local template = type(profile.template) == "table" and profile.template or {}
    local hitRate = numberValue(profile.sustained_hit_events_per_sec_no_aim or profile.hit_events_per_sec or template.hit_events_per_sec)
    local actionRate = numberValue(profile.sustained_actions_per_sec_no_aim or profile.action_cycles_per_sec or template.action_cycles_per_sec)
    local avgMs = numberValue(profile.avg_ms_per_hit or template.avg_ms_per_hit)
    local reloadCount = numberValue(profile.reload_count or template.reload_count)
    local reloadTime = numberValue(profile.reload_time_ms or template.reload_time_ms)
    local hitCount = numberValue(profile.normal_hit_event_count or profile.hit_event_count or template.hit_count)
    local sustainedCycleMs = numberValue(profile.sustained_cycle_ms_no_aim)
    local actionIntervalMs = sustainedCycleMs or (actionRate and (1000 / actionRate) or (avgMs and hitCount and (avgMs * hitCount) or nil))
    local normalActionMs = numberValue(profile.normal_action_ms_tick or profile.real_attack_cycle_ms or template.real_attack_cycle_ms)
    local aimMs = numberValue(profile.real_aim_time_baseline_ms or profile.aim_time_ms)
    local attackRange = numberValue(profile.attack_range or template.attack_range)
    local weaponType = textValue(profile.weapon_type or template.weapon_type, "")
    local templateName = textValue(template.template_name, "")
    local popupId = "weapon-attack-" .. safeId(profile.source_style_id) .. "-" .. safeId(profile.normal_skill_id)

    return {
        popup_id = popupId,
        weapon_type = weaponType,
        template_name = templateName ~= "" and templateName or weaponType,
        normal_skill_name = textValue(profile.normal_skill_name),
        aim_time_ms = aimMs,
        aim_time_text = aimMs and secondsText(aimMs, " / 首次锁定或切换目标") or "",
        hit_events_per_sec = hitRate,
        hit_events_per_sec_text = hitRate and (formatNumber(hitRate, 2) .. " 发/秒") or "",
        avg_ms_per_hit = avgMs,
        avg_interval_text = avgMs and secondsText(avgMs, "") or "",
        action_cycles_per_sec = actionRate,
        action_interval_text = actionIntervalMs and secondsText(actionIntervalMs, " / 次普攻") or "",
        real_attack_cycle_ms = normalActionMs,
        normal_attack_speed_text = normalActionMs and secondsText(normalActionMs, " / 次普攻") or "",
        attack_range = attackRange,
        attack_range_text = attackRange and formatNumber(attackRange, 0) or "",
        hit_count = hitCount,
        reload_count = reloadCount,
        reload_time_ms = reloadTime,
        reload_per_action_ms = numberValue(profile.reload_per_action_ms),
        fire_frames = textValue(profile.fire_frames or template.fire_frames_pattern),
        formula_version = textValue(profile.speed_formula_version or profile.formula_version),
        verification_status = textValue(profile.verification_status, "config_candidate"),
        verification_label = verificationLabel(profile.verification_status),
        video_observed_hits_per_sec = numberValue(profile.video_observed_hits_per_sec),
        verification_note = textValue(profile.verification_note),
    }
end

local function addRow(panel, label, value, accent)
    if value == nil or value == "" then return end
    local row = panel:tag("div"):addClass("card_weapon-popover-row")
    row:tag("span"):addClass("card_weapon-popover-label"):wikitext(label)
    row:tag("span")
        :addClass(accent and "card_weapon-popover-value card_weapon-popover-value--accent" or "card_weapon-popover-value")
        :wikitext(tostring(value))
end

function p.makeTrigger(label, payload, extraClass)
    if not payload then
        return mw.html.create("span"):wikitext(textValue(label, ""))
    end
    local trigger = mw.html.create("span")
        :addClass("card_weapon-trigger")
        :attr("role", "button")
        :attr("tabindex", "0")
        :attr("aria-expanded", "false")
        :attr("aria-controls", payload.popup_id)
        :attr("data-card-weapon-popover", payload.popup_id)
        :wikitext(textValue(label, ""))
    if extraClass then trigger:addClass(extraClass) end
    return trigger
end

function p.makePopover(payload)
    if not payload then return nil end
    local panel = mw.html.create("div")
        :addClass("card_weapon-popover")
        :attr("id", payload.popup_id)
        :attr("data-card-weapon-popover-panel", payload.popup_id)
        :attr("aria-hidden", "true")

    local head = panel:tag("div"):addClass("card_weapon-popover-head")
    head:tag("div"):addClass("card_weapon-popover-title"):wikitext("普攻节奏")
    head:tag("div"):addClass("card_weapon-popover-subtitle"):wikitext(payload.template_name)

    addRow(panel, "瞄准时间", payload.aim_time_text, false)
    addRow(panel, "综合攻速", payload.action_interval_text, true)
    addRow(panel, "普攻速度", payload.normal_attack_speed_text, false)
    addRow(panel, "攻击范围", payload.attack_range_text, false)
    if payload.reload_count or payload.reload_time_ms then
        addRow(panel, "弹药 / 装填", tostring(payload.reload_count or "?") .. " 发后装填 " .. formatMs(payload.reload_time_ms), false)
    end
    if payload.hit_count then
        addRow(panel, "发射数量", tostring(payload.hit_count) .. " 发 / 次普攻", false)
    end

    return panel
end

return p