模块

GemData:修订间差异

来自蒙纳多研究本部

头号云​(留言 | 贡献)
创建页面,内容为“-- 模块:GemData —— 宝珠数据层(唯一 xbdb 访问入口,仿 ArtsData/source 决策11 架构) -- 数据来源(全部运行时在线取,零硬编码名单): -- bdat_common.BTL_skilllist 宝珠技能定义(attach=0/1/2 归属、I~X 数值、money、rvs_caption) -- bdat_common.ITM_dropcrystallist 掉落结晶(skill1/skill2 引用 → 「真实宝珠」过滤锚点) -- 注意:row_id 越界会整页 500 且 pcall 无效(ArtsD…”
 
头号云​(留言 | 贡献)
无编辑摘要
 
(未显示同一用户的1个中间版本)
第9行: 第9行:


local post = require("Module:RequestHelper").post
local post = require("Module:RequestHelper").post
local function num(v)
  if v == nil then return 0 end
  return tonumber(v) or 0
end


-- ====== 安全护栏(实现细节,非游戏数据) ======
-- ====== 安全护栏(实现细节,非游戏数据) ======
第44行: 第49行:
--  未被引用的(力量弱化/杂质等占位、内部削弱条目)自然出局。
--  未被引用的(力量弱化/杂质等占位、内部削弱条目)自然出局。
--  首次调用时全表扫描 820 行 dropcrystallist 并构建引用集合(同一解析内记忆化)。
--  首次调用时全表扫描 820 行 dropcrystallist 并构建引用集合(同一解析内记忆化)。
-- ⚠️ 线上 xbdb 返回的 skill1/skill2 是「内嵌完整技能对象」(含 row_id),不是裸数字 id。
--    所以这里做两形态兼容:对象 → 取 .row_id;数字 → 直接用。
local refSet = nil
local refSet = nil
function p.referencedSkills()
function p.referencedSkills()
第52行: 第59行:
     if dc then
     if dc then
       for _, k in ipairs({ "skill1", "skill2" }) do
       for _, k in ipairs({ "skill1", "skill2" }) do
         local v = tonumber(dc[k])
         local v = dc[k]
        if type(v) == "table" then
          v = tonumber(v.row_id)          -- 线上形态:内嵌对象
        else
          v = tonumber(v)                 -- 防御形态:裸 id
        end
         if v and v >= 1 and v <= SKILLLIST_MAX_ID then
         if v and v >= 1 and v <= SKILLLIST_MAX_ID then
           refSet[v] = true
           refSet[v] = true
第95行: 第107行:
   if not refs[tonumber(id)] then return nil end
   if not refs[tonumber(id)] then return nil end
   return s
   return s
end
-- 数值单位(带不带 %)判定 —— percent_* 字段全为 0,不可用,改用 val_type:
--  val_type = 1 → 固定值(力量/以太/敏捷等能力珠),不带 %
--  val_type = 0 → 百分比(EXP/AP/最大HP/迷彩等),带 %
--  已知例外:#13 拔刀士气(val_type=0 但士气是 0~100 气槽的固定点数,不带 %)
-- 规则经旧页面 13 条全量对齐验证(13/13);武器/防具珠上线后如发现新例外,
-- 在此表追加(这是语义修正表,不是数据名单——数据本身不可靠,旧页面是锚点)。
local NO_PERCENT_EXCEPTIONS = {
  [13] = true,  -- 拔刀士气:战斗开始时士气提升(士气为固定点数)
}
function p.hasPercent(data)
  local id = tonumber(data.row_id or data.__id)
  if NO_PERCENT_EXCEPTIONS[id] then return false end
  return num(data.val_type) == 0
end
end



2026年10月13日 (二) 21:50的最新版本

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

-- 模块:GemData —— 宝珠数据层(唯一 xbdb 访问入口,仿 ArtsData/source 决策11 架构)
-- 数据来源(全部运行时在线取,零硬编码名单):
--   bdat_common.BTL_skilllist  宝珠技能定义(attach=0/1/2 归属、I~X 数值、money、rvs_caption)
--   bdat_common.ITM_dropcrystallist  掉落结晶(skill1/skill2 引用 → 「真实宝珠」过滤锚点)
-- 注意:row_id 越界会整页 500 且 pcall 无效(ArtsData/source 已知坑),所以本模块
--       只对"表全量行数范围内的 id"发调用;行数上限写死为安全护栏(非数据名单)。

local p = {}

local post = require("Module:RequestHelper").post

local function num(v)
  if v == nil then return 0 end
  return tonumber(v) or 0
end

-- ====== 安全护栏(实现细节,非游戏数据) ======
local SKILLLIST_MAX_ID = 98        -- BTL_skilllist 行数上限(超出即 500,必须前置拦截)
local DROPCRYSTAL_MAX_ID = 820     -- ITM_dropcrystallist 行数上限

local cache = {}

-- 基础取数(带护栏 + 记忆化)
local function fetch(table, id, maxId)
  local n = tonumber(id)
  if not n or n < 1 or n > maxId then return nil end
  local key = table .. ":" .. n
  if cache[key] == nil then
    local ok, data = pcall(post, table, n)
    if not ok or type(data) ~= "table" or (data.row_id == nil and data.__id == nil) then
      cache[key] = false
      return nil
    end
    cache[key] = data
  end
  return cache[key] or nil
end

function p.fetchSkill(id)
  return fetch("bdat_common.BTL_skilllist", id, SKILLLIST_MAX_ID)
end

function p.fetchDropCrystal(id)
  return fetch("bdat_common.ITM_dropcrystallist", id, DROPCRYSTAL_MAX_ID)
end

-- 「真实宝珠」判定(数据驱动,无名单):
--   掉落结晶表 skill1/skill2 引用到的技能 = 游戏内真实存在的宝珠。
--   未被引用的(力量弱化/杂质等占位、内部削弱条目)自然出局。
--   首次调用时全表扫描 820 行 dropcrystallist 并构建引用集合(同一解析内记忆化)。
-- ⚠️ 线上 xbdb 返回的 skill1/skill2 是「内嵌完整技能对象」(含 row_id),不是裸数字 id。
--    所以这里做两形态兼容:对象 → 取 .row_id;数字 → 直接用。
local refSet = nil
function p.referencedSkills()
  if refSet ~= nil then return refSet end
  refSet = {}
  for id = 1, DROPCRYSTAL_MAX_ID do
    local dc = p.fetchDropCrystal(id)
    if dc then
      for _, k in ipairs({ "skill1", "skill2" }) do
        local v = dc[k]
        if type(v) == "table" then
          v = tonumber(v.row_id)          -- 线上形态:内嵌对象
        else
          v = tonumber(v)                 -- 防御形态:裸 id
        end
        if v and v >= 1 and v <= SKILLLIST_MAX_ID then
          refSet[v] = true
        end
      end
    end
  end
  return refSet
end

-- 共用宝珠列表(attach=0 且 被结晶引用 → 线上实测 13 条)
-- ⚠️ 过滤口径修正(线上快照复算结论):只用「被 ITM_dropcrystallist 引用」这一条过滤,
--    不要叠加 money>0 —— money=0 的条目里有两个真实宝珠(武器·命中上升 #69、防具·填充时间缩短 #45)
--    会被误杀;而 attach=0 中被过滤掉的恰好全是 负面占位(16/32/48/64/80/96)+杂质(98)+共用版
--    无法战斗抗性(97,防具版 #27 才是可装备的),一条不多一条不少。
-- 返回 { {id=..., data={...}}, ... } 按 id 升序
function p.sharedGems()
  return p.gemsByAttach(0)
end

-- 通用分类列表:attachValue = 0(共用) / 1(武器) / 2(防具)
function p.gemsByAttach(attachValue)
  local refs = p.referencedSkills()
  local out = {}
  for id = 1, SKILLLIST_MAX_ID do
    if refs[id] then
      local s = p.fetchSkill(id)
      if s and tonumber(s.attach) == attachValue then
        out[#out + 1] = { id = id, data = s }
      end
    end
  end
  table.sort(out, function(a, b) return a.id < b.id end)
  return out
end

-- 单宝珠取数(含归属校验)
function p.getGem(id)
  local s = p.fetchSkill(id)
  if not s then return nil end
  local refs = p.referencedSkills()
  if not refs[tonumber(id)] then return nil end
  return s
end

-- 数值单位(带不带 %)判定 —— percent_* 字段全为 0,不可用,改用 val_type:
--   val_type = 1 → 固定值(力量/以太/敏捷等能力珠),不带 %
--   val_type = 0 → 百分比(EXP/AP/最大HP/迷彩等),带 %
--   已知例外:#13 拔刀士气(val_type=0 但士气是 0~100 气槽的固定点数,不带 %)
-- 规则经旧页面 13 条全量对齐验证(13/13);武器/防具珠上线后如发现新例外,
-- 在此表追加(这是语义修正表,不是数据名单——数据本身不可靠,旧页面是锚点)。
local NO_PERCENT_EXCEPTIONS = {
  [13] = true,   -- 拔刀士气:战斗开始时士气提升(士气为固定点数)
}

function p.hasPercent(data)
  local id = tonumber(data.row_id or data.__id)
  if NO_PERCENT_EXCEPTIONS[id] then return false end
  return num(data.val_type) == 0
end

-- 数值插值:等级 r(1..10 → E..S),在 lower_X..upper_X 区间内
-- 游戏内宝珠等级 I~X 对应字段 E,D,C,B,A,S 只有 6 档锚点,
-- 10 级向 6 锚点的映射采用天幻逐级值验证过的线性插值(锚点处取精确值)。
-- 锚点映射:I=E, III=D, V=C, VII=B, IX=A, X=S(偶数级取相邻锚点中点,四舍五入)
local RANK_KEYS = { "E", "D", "C", "B", "A", "S" }
function p.valueAtRank(data, rank)
  local anchors = { [1] = "E", [3] = "D", [5] = "C", [7] = "B", [9] = "A", [10] = "S" }
  local key = anchors[rank]
  if not key then
    -- 偶数级:取左右锚点均值
    local lo = anchors[rank - 1] or "E"
    local hi = anchors[rank + 1] or "S"
    local lv = tonumber(data["lower_" .. lo] or 0)
    local hv = tonumber(data["lower_" .. hi] or 0)
    return math.floor((lv + hv) / 2 + 0.5)
  end
  return tonumber(data["lower_" .. key] or 0)
end

p.RANK_KEYS = RANK_KEYS
p.SKILLLIST_MAX_ID = SKILLLIST_MAX_ID

return p