模块

模块:ArtsShop

来自蒙纳多研究本部

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

-- Module:ArtsShop —— 商店战技书查询(独立模块,与 ArtsData 主链路解耦)
--
-- 用法:
--   {{#invoke:ArtsShop|main|12}}          渲染战技12的商店在售表
--   {{#invoke:ArtsShop|offers|12}}        返回原始数据(Lua 表,供其他模块调用)
--
-- 数据链路(XBDB 深度解析已验证 2026-10-08):
--   shop_index[战技ID] → 货架行号列表(静态:XBDB 无法枚举,行号映射离线建)
--   → post shoplist[货架行](实时)
--   → post FLD_npclist(shop_npc 货架行) → 商店名(实时中文,缓存)
--   → post ITM_artslist[战技书行] → 书名 + 实时价格
--
-- ⚠ 注释风格约定:本文件只用 -- 行注释,不用 --[[ ]] 块注释
--   (块注释会被正文里的 ]] 提前终止,已踩坑一次)

local p = {}

local post = require("Module:RequestHelper").post
local getArgs = require("Module:Arguments").getArgs
local html = require("Module:Html")
local compress = html.compress

local shopIndex = require("Module:ArtsShop/index")
local shopNpc = require("Module:ArtsShop/npc")
local lv1 = require("Module:ArtsData/lv1")

-- 货架行号 -> 商店名(NPC 名 + mob_name,缓存)
local npcCache = {}
local function fetchShopName(shopRow)
  local npcId = shopNpc[shopRow]
  if not npcId then return nil end
  local cached = npcCache[npcId]
  if cached ~= nil then return cached end
  local name = nil
  local ok, npc = pcall(post, "bdat_common.FLD_npclist", npcId)
  if ok and type(npc) == "table" then
    local parts = {}
    if npc.name and npc.name ~= "" then parts[#parts + 1] = npc.name end
    if npc.mob_name and npc.mob_name ~= "" and npc.mob_name ~= npc.name then
      parts[#parts + 1] = npc.mob_name
    end
    name = #parts > 0 and table.concat(parts, "·") or nil
  end
  npcCache[npcId] = name
  return name
end

-- 查询某战技的商店在售。返回数组或 nil:
--   { { shopRow=货架行, shopName=商店名, artName=书名, money=价格 }, ... }
function p.fetchOffers(artId)
  artId = tonumber(artId)
  local entries = artId and shopIndex[artId] or nil
  if not entries or #entries == 0 then
    return nil
  end
  local offers = {}
  for _, e in ipairs(entries) do
    local o = { shopRow = e.sr, money = e.m }
    local ok, row = pcall(post, "bdat_common.shoplist", e.sr)
    if ok and type(row) == "table" then
      o.shopName = fetchShopName(e.sr)
      local okB, book = pcall(post, "bdat_common.ITM_artslist", e.br)
      if okB and type(book) == "table" and book.name then
        o.artName = book.name
        if book.money then o.money = book.money end
      end
      offers[#offers + 1] = o
    end
  end
  if #offers == 0 then return nil end
  return offers
end

-- Lua 层调用别名
p.offers = p.fetchOffers

-- 渲染商店在售表
function p.main(frame)
  local args = getArgs(frame)
  local artId = args[1] and mw.text.trim(args[1]) or ""
  local offers = p.fetchOffers(artId)
  if not offers then
    return ""
  end

  return compress(p.renderOffers(artId, offers))

end

-- 按角色渲染全部有商店在售的战技:{{#invoke:ArtsShop|role|shulk}}
function p.role(frame)
  local args = getArgs(frame)
  local key = args[1] and mw.text.trim(args[1]) or ""
  local role = lv1.roles[key]
  if not role then
    return ""
  end

  local out = {}
  out[#out + 1] = html.h2(role.name .. " · 战技书商店在售")
  for _, id in ipairs(role.ids) do
    local offers = p.fetchOffers(id)
    if offers then
      out[#out + 1] = p.renderOffers(id, offers)
    end
  end
  if #out == 1 then
    return ""
  end
  return compress(table.concat(out, "\n"))
end

-- 从 main 提取的渲染核心(单个战技的商店表),供 main 与 role 复用
function p.renderOffers(artId, offers)
  local artName = offers[1].artName or ("战技 " .. artId)

  local out = {}
  out[#out + 1] = html.h3(artName .. " · 商店在售")
  out[#out + 1] = '<table class="wikitable" style="text-align:center;">'
  out[#out + 1] = "<tr><th>商店</th><th>战技书</th><th>价格</th></tr>"
  for _, o in ipairs(offers) do
    out[#out + 1] = "<tr>"
      .. "<td>" .. (o.shopName or ("货架 " .. tostring(o.shopRow))) .. "</td>"
      .. "<td>" .. (o.artName or "—") .. "</td>"
      .. "<td>" .. (o.money and (tostring(o.money) .. " G") or "—") .. "</td>"
      .. "</tr>"
  end
  out[#out + 1] = "</table>"
  return table.concat(out, "\n")
end

return p