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