模块:ItemCommon
来自星砂岛百科
更多操作
概述
ItemCommon 提供物品域共享的查找、身份回退与配方域构建能力,供各子域模块复用。
常用函数
loadItemIdentityData:读取 数据:Item/item_name_index.json 与 数据:Item/item_mapping.json。findItemRecord:按中文名、英文名或 ID 查找物品身份记录。resolveItemId:返回统一物品 ID。resolveItemName:返回统一中文名。resolveItemNameEn:返回统一英文名。sortItemKeys:按物品类型、系列、等级/稀有度对物品键进行稳定排序。sortRecordsByItemKey:按记录中的物品键复用同一套排序规则,适合商店、配方等列表域。buildRecipeDomain:为子域模块挂接getField / processRecipeList / productionRecipeList / machineList。getField:会统一处理部分显示映射与“0 视为空值”规则,例如宠物类型、摆放类型、可食用布尔值、发射类型等。
排序规则
- 先按物品类型聚合,再按系列分组。
- 同一系列内优先按等级或稀有度从低到高排列。
- 如果基础款没有
Lv1后缀,会按同系列最低档处理,便于把简易制造台 / 标准制造台 / 精良制造台这类条目排在一起。
数据来源
local common = require('Module:Common')
local p = {}
local item_name_cache
local item_mapping_cache
local ITEM_TYPE_FAMILY_RANKS = {
Currency = 10,
Seed = 20,
Plant = 30,
Animal = 40,
Collect = 50,
Mine = 60,
Craft = 70,
Food = 80,
DailyUse = 90,
Consumable = 95,
Tool = 100,
Electric = 105,
Electronics = 106,
Furniture = 110,
Decoration = 111,
Light = 112,
Book = 120,
Clothing = 130,
Music = 140,
Mics = 141,
Emoji = 142,
Vehicle = 150,
Mission = 160,
}
local ITEM_TYPE_SORT_RULES = {
{ prefix = 'ItemType.Craft_ProcessedProduct', rank = 70 },
{ prefix = 'ItemType.Craft_Loom', rank = 71 },
{ prefix = 'ItemType.Craft_FiberIngot', rank = 72 },
{ prefix = 'ItemType.Craft_SemiFinished', rank = 73 },
{ prefix = 'ItemType.Craft_ProductMod', rank = 74 },
{ prefix = 'ItemType.Craft_Separate_Show', rank = 76 },
{ prefix = 'ItemType.Craft_Separate', rank = 75 },
{ prefix = 'ItemType.Craft_Jam', rank = 77 },
{ prefix = 'ItemType.Food_Animal', rank = 78 },
{ prefix = 'ItemType.Food_Seasoning', rank = 79 },
{ prefix = 'ItemType.Craft_HandCraft', rank = 98 },
{ prefix = 'ItemType.Craft_Workbench', rank = 99 },
{ prefix = 'ItemType.Craft_ManufactureFacility', rank = 100 },
{ prefix = 'ItemType.Craft_FunctionFacility', rank = 101 },
{ prefix = 'ItemType.Craft_CollectFacility', rank = 102 },
{ prefix = 'ItemType.Craft_BreedingFacility', rank = 103 },
{ prefix = 'ItemType.Craft_CookingFacility', rank = 104 },
{ prefix = 'ItemType.Craft_ConversionFacility', rank = 105 },
{ prefix = 'ItemType.Craft_SewingTable', rank = 106 },
}
local RARITY_SORT_RANKS = {
['1'] = 1,
['2'] = 2,
['3'] = 3,
['4'] = 4,
['5'] = 5,
['6'] = 6,
common = 1,
normal = 1,
uncommon = 2,
rare = 3,
epic = 4,
legendary = 5,
myth = 6,
mythic = 6,
['普通'] = 1,
['优秀'] = 2,
['精良'] = 3,
['稀有'] = 4,
['史诗'] = 5,
['传说'] = 6,
}
local PLACEMENT_TEMPLATE_DISPLAY = {
['ItemPlacement.Dish_01'] = '菜肴',
['ItemPlacement.Drink_01'] = '饮品',
['ItemPlacement.Flower_01'] = '花卉',
['ItemPlacement.Ingredients_01'] = '食材',
['ItemPlacement.Snack_01'] = '零食',
}
local PET_TARGET_TAG_DISPLAY = {
cat = '猫',
dog = '狗',
EatMeat = '肉食宠物',
EatPlant = '草食宠物',
pet = '通用宠物',
}
local HUNTING_WEAPON_TYPE_DISPLAY = {
Arrow = '弓箭',
Crossbow = '弩',
FarmGun = '种植枪',
Slingshot = '弹弓',
}
local SEED_VARIANT_DISPLAY = {
Eternity = '不朽',
}
local BOOLEAN_DISPLAY = {
['0'] = '',
['1'] = '是',
['false'] = '',
['true'] = '是',
['no'] = '',
['yes'] = '是',
}
local ZERO_EMPTY_FIELDS = {
stamina_restore = true,
health_restore = true,
extra_health_restore = true,
pet_affection = true,
water_capacity = true,
target_distance = true,
fishing_capture_velocity = true,
fishing_line_threshold = true,
}
local function default_mapping()
return {
name_to_id = {},
id_to_name = {},
aliases = {},
overrides = {
name_to_id = {},
aliases = {},
},
}
end
local function find_mapped_record(data, mapping, normalized)
if type(mapping) ~= 'table' then
return nil
end
local override_id = mapping.overrides and mapping.overrides.name_to_id and mapping.overrides.name_to_id[normalized]
if override_id and data[p.normalizeKey(override_id)] then
return data[p.normalizeKey(override_id)]
end
local override_alias = mapping.overrides and mapping.overrides.aliases and mapping.overrides.aliases[normalized]
if override_alias and data[p.normalizeKey(override_alias)] then
return data[p.normalizeKey(override_alias)]
end
local mapped_id = mapping.name_to_id and mapping.name_to_id[normalized]
if mapped_id and data[p.normalizeKey(mapped_id)] then
return data[p.normalizeKey(mapped_id)]
end
local alias_id = mapping.aliases and mapping.aliases[normalized]
if alias_id and data[p.normalizeKey(alias_id)] then
return data[p.normalizeKey(alias_id)]
end
return nil
end
function p.normalizeKey(value)
return common.normalizeKey(value)
end
function p.loadDomainData(data_page, mapping_page)
local data = common.loadJsonData(data_page) or {}
local mapping = nil
if common.trim(mapping_page or '') ~= '' then
mapping = common.loadJsonData(mapping_page) or default_mapping()
end
return data, mapping
end
function p.loadItemIdentityData()
if item_name_cache then
return item_name_cache, item_mapping_cache
end
item_name_cache = common.loadJsonData('数据:Item/item_name_index.json') or {}
item_mapping_cache = common.loadJsonData('数据:Item/item_mapping.json') or default_mapping()
return item_name_cache, item_mapping_cache
end
function p.findRecord(data, mapping, key)
local resolved = common.trim(key)
if resolved == '' then
resolved = common.getCurrentTitleText()
end
local normalized = p.normalizeKey(resolved)
if data[normalized] then
return data[normalized]
end
local by_domain_mapping = find_mapped_record(data, mapping, normalized)
if by_domain_mapping then
return by_domain_mapping
end
local _, item_mapping = p.loadItemIdentityData()
local by_item_mapping = find_mapped_record(data, item_mapping, normalized)
if by_item_mapping then
return by_item_mapping
end
return nil
end
function p.findItemRecord(key)
local item_data, item_mapping = p.loadItemIdentityData()
return p.findRecord(item_data, item_mapping, key)
end
function p.getItemField(key, field)
local record = p.findItemRecord(key)
if not record then
return ''
end
return common.toText(record[field])
end
function p.getIdentityField(record, key, field)
if type(record) == 'table' and record[field] ~= nil and common.trim(record[field]) ~= '' then
return record[field]
end
local item_record = p.findItemRecord(key)
if type(item_record) == 'table' then
return item_record[field] or ''
end
if field == 'id' then
return common.trim(key)
end
return ''
end
function p.resolveItemId(record, key)
return common.trim(p.getIdentityField(record, key, 'id'))
end
function p.resolveItemName(record, key)
local value = common.trim(p.getIdentityField(record, key, 'name'))
if value ~= '' then
return value
end
return common.trim(key)
end
function p.resolveItemNameEn(record, key)
return common.trim(p.getIdentityField(record, key, 'name_en'))
end
function p.getField(record, field)
if type(record) ~= 'table' then
return ''
end
return record[field]
end
local function text_sort_value(value)
return mw.ustring.lower(common.trim(common.toText(value)))
end
local function map_display_value(value, mapping)
local resolved = common.trim(common.toText(value))
if resolved == '' then
return ''
end
return mapping[resolved] or resolved
end
local function normalize_optional_number(value)
local text = common.trim(common.toText(value))
if text == '' then
return ''
end
local number = tonumber(text)
if not number or number == 0 then
return ''
end
return text
end
local function resolve_related_item_name(item_name, item_id)
local resolved_name = common.trim(common.toText(item_name))
if resolved_name ~= '' then
return resolved_name
end
local resolved_id = common.trim(common.toText(item_id))
if resolved_id == '' then
return ''
end
return common.trim(p.getItemField(resolved_id, 'name'))
end
local function make_wikilink(title)
local resolved = common.trim(title)
if resolved == '' then
return ''
end
return '[[' .. resolved .. ']]'
end
local function format_related_item_link(item_name, item_id)
local resolved_name = resolve_related_item_name(item_name, item_id)
if resolved_name == '' then
return ''
end
return make_wikilink(resolved_name)
end
function p.getDisplayField(record, field)
if type(record) ~= 'table' then
return ''
end
if field == 'production_machines_display' then
return p.machineLinks(record.production_machines)
end
if field == 'process_machines_display' then
return p.machineLinks(record.process_machines)
end
if field == 'crop_item_link' then
return format_related_item_link(record.crop_item_name, record.crop_item_id)
end
if field == 'seed_item_link' then
return format_related_item_link(record.seed_item_name, record.seed_item_id)
end
if field == 'placement_template' then
return map_display_value(record.placement_template, PLACEMENT_TEMPLATE_DISPLAY)
end
if field == 'pet_target_tag' then
return map_display_value(record.pet_target_tag, PET_TARGET_TAG_DISPLAY)
end
if field == 'hunting_weapon_type' then
return map_display_value(record.hunting_weapon_type, HUNTING_WEAPON_TYPE_DISPLAY)
end
if field == 'seed_variant' then
return map_display_value(record.seed_variant, SEED_VARIANT_DISPLAY)
end
if field == 'is_edible' then
return map_display_value(record.is_edible, BOOLEAN_DISPLAY)
end
if ZERO_EMPTY_FIELDS[field] then
return normalize_optional_number(record[field])
end
return common.toText(p.getField(record, field))
end
local function extract_item_type_family(item_type)
local resolved_type = common.trim(item_type)
if resolved_type == '' then
return ''
end
local suffix = resolved_type:match('^ItemType%.(.+)$') or resolved_type
local family = suffix:match('^[^_%.]+')
return family or suffix
end
local function parse_sort_number(value)
local resolved = common.trim(common.toText(value))
if resolved == '' then
return 999
end
local numeric = tonumber(resolved)
if numeric then
return numeric
end
local mapped = RARITY_SORT_RANKS[text_sort_value(resolved)]
if mapped then
return mapped
end
return 999
end
local function extract_series_group(item_id)
local value = text_sort_value(item_id)
value = mw.ustring.gsub(value, '[_%-]?lv%d+', '')
value = mw.ustring.gsub(value, '%d+', '#')
value = mw.ustring.gsub(value, '[_%-]+$', '')
return value
end
local function extract_series_order(item_id)
local value = text_sort_value(item_id)
local level = mw.ustring.match(value, 'lv(%d+)')
if level then
return tonumber(level) or 999
end
local trailing = mw.ustring.match(value, '(%d+)[^%d]*$')
if trailing then
return tonumber(trailing) or 999
end
return 0
end
function p.getItemTypeSortRank(item_type)
local resolved_type = common.trim(item_type)
if resolved_type == '' then
return 999
end
for _, rule in ipairs(ITEM_TYPE_SORT_RULES) do
if resolved_type:sub(1, #rule.prefix) == rule.prefix then
return rule.rank
end
end
local family = extract_item_type_family(resolved_type)
return ITEM_TYPE_FAMILY_RANKS[family] or 999
end
function p.getItemSortMeta(record, key)
local item_record = record
local item_type = ''
local type_display = ''
local item_level = ''
local rarity = ''
if type(item_record) == 'table' then
item_type = common.trim(item_record.type or '')
type_display = common.trim(item_record.type_display or '')
item_level = common.trim(common.toText(item_record.item_level or item_record.lv or ''))
rarity = common.trim(common.toText(item_record.rarity or item_record.r or ''))
else
item_record = nil
end
if item_type == '' or type_display == '' or item_level == '' or rarity == '' then
local identity_record = p.findItemRecord(key)
if type(identity_record) == 'table' then
if item_type == '' then
item_type = common.trim(identity_record.type or '')
end
if type_display == '' then
type_display = common.trim(identity_record.type_display or '')
end
if item_level == '' then
item_level = common.trim(common.toText(identity_record.item_level or identity_record.lv or ''))
end
if rarity == '' then
rarity = common.trim(common.toText(identity_record.rarity or identity_record.r or ''))
end
if not item_record then
item_record = identity_record
end
end
end
local item_id = p.getIdentityField(item_record, key, 'id')
return {
type_rank = p.getItemTypeSortRank(item_type),
type_family = text_sort_value(extract_item_type_family(item_type)),
item_type = text_sort_value(item_type),
type_display = text_sort_value(type_display),
series_group = extract_series_group(item_id),
item_level = parse_sort_number(item_level),
rarity = parse_sort_number(rarity),
series_order = extract_series_order(item_id),
item_name = text_sort_value(p.getIdentityField(item_record, key, 'name')),
item_name_en = text_sort_value(p.getIdentityField(item_record, key, 'name_en')),
item_id = text_sort_value(item_id),
}
end
function p.compareItemKeys(left_key, right_key, left_record, right_record)
local left_meta = p.getItemSortMeta(left_record, left_key)
local right_meta = p.getItemSortMeta(right_record, right_key)
if left_meta.type_rank ~= right_meta.type_rank then
return left_meta.type_rank < right_meta.type_rank
end
if left_meta.type_family ~= right_meta.type_family then
return left_meta.type_family < right_meta.type_family
end
if left_meta.item_type ~= right_meta.item_type then
return left_meta.item_type < right_meta.item_type
end
if left_meta.series_group ~= right_meta.series_group then
return left_meta.series_group < right_meta.series_group
end
if left_meta.item_level ~= right_meta.item_level then
return left_meta.item_level < right_meta.item_level
end
if left_meta.rarity ~= right_meta.rarity then
return left_meta.rarity < right_meta.rarity
end
if left_meta.series_order ~= right_meta.series_order then
return left_meta.series_order < right_meta.series_order
end
if left_meta.item_name ~= right_meta.item_name then
return left_meta.item_name < right_meta.item_name
end
if left_meta.item_name_en ~= right_meta.item_name_en then
return left_meta.item_name_en < right_meta.item_name_en
end
if left_meta.item_id ~= right_meta.item_id then
return left_meta.item_id < right_meta.item_id
end
return false
end
function p.sortItemKeys(item_keys, record_lookup)
if type(item_keys) ~= 'table' or #item_keys <= 1 then
return item_keys
end
table.sort(item_keys, function(left_key, right_key)
local left_record = nil
local right_record = nil
if type(record_lookup) == 'function' then
left_record = record_lookup(left_key)
right_record = record_lookup(right_key)
end
return p.compareItemKeys(left_key, right_key, left_record, right_record)
end)
return item_keys
end
function p.sortRecordsByItemKey(records, key_getter, record_getter)
if type(records) ~= 'table' or #records <= 1 or type(key_getter) ~= 'function' then
return records
end
table.sort(records, function(left_record, right_record)
local left_key = key_getter(left_record)
local right_key = key_getter(right_record)
local left_item_record = nil
local right_item_record = nil
if type(record_getter) == 'function' then
left_item_record = record_getter(left_record, left_key)
right_item_record = record_getter(right_record, right_key)
end
return p.compareItemKeys(left_key, right_key, left_item_record, right_item_record)
end)
return records
end
function p.itemLink(frame, item_name, count)
if common.trim(item_name) == '' then
return ''
end
local args = { item_name }
if count and tonumber(count) and tonumber(count) ~= 1 then
args[2] = tostring(count)
end
return frame:expandTemplate{
title = 'Item',
args = args,
}
end
function p.machineLinks(machine_names)
if type(machine_names) ~= 'table' or #machine_names == 0 then
return ''
end
local parts = {}
for _, machine_name in ipairs(machine_names) do
if common.trim(machine_name) ~= '' then
parts[#parts + 1] = '[[' .. machine_name .. ']]'
end
end
return table.concat(parts, '、')
end
function p.renderRecipeList(frame, recipes)
if type(recipes) ~= 'table' or #recipes == 0 then
return ''
end
local out = {}
for _, recipe in ipairs(recipes) do
if type(recipe) == 'table' then
local row = {}
local machine_name = common.trim(recipe.machine)
if machine_name ~= '' then
row[#row + 1] = ('[[%s]]'):format(machine_name)
end
local material_parts = {}
if type(recipe.materials) == 'table' then
local material_names = {}
for material_name in pairs(recipe.materials) do
material_names[#material_names + 1] = material_name
end
p.sortItemKeys(material_names)
for _, material_name in ipairs(material_names) do
local count = recipe.materials[material_name]
material_parts[#material_parts + 1] = p.itemLink(frame, material_name, count)
end
end
if #material_parts > 0 then
row[#row + 1] = table.concat(material_parts, '')
end
if #row > 0 then
out[#out + 1] = table.concat(row, ':')
end
end
end
return table.concat(out, '<br>')
end
function p.buildRecipeDomain(module_table, data_page, mapping_page)
local data_cache
local mapping_cache
local function load_data()
if data_cache then
return
end
data_cache, mapping_cache = p.loadDomainData(data_page, mapping_page)
end
local function find_record(key)
load_data()
return p.findRecord(data_cache, mapping_cache, key)
end
module_table.findRecord = find_record
function module_table.getField(frame)
local key = common.getArg(frame, 1, '')
local field = common.getArg(frame, 2, '')
if field == '' then
return ''
end
local record = find_record(key)
if not record then
return ''
end
if field == 'id' or field == 'name' or field == 'name_en' then
return common.toText(p.getIdentityField(record, key, field))
end
if field == 'image' then
local item_id = p.resolveItemId(record, key)
if item_id == '' then
return ''
end
local image_name = item_id .. '.png'
if common.filePageExists(image_name) then
return image_name
end
return ''
end
return p.getDisplayField(record, field)
end
function module_table.productionRecipeList(frame)
local record = find_record(common.getArg(frame, 1, ''))
if not record then
return ''
end
return p.renderRecipeList(frame, record.production_recipes)
end
function module_table.processRecipeList(frame)
local record = find_record(common.getArg(frame, 1, ''))
if not record then
return ''
end
return p.renderRecipeList(frame, record.process_recipes)
end
function module_table.machineList(frame)
local record = find_record(common.getArg(frame, 1, ''))
if not record then
return ''
end
if common.getArg(frame, 2, 'process') == 'production' then
return p.machineLinks(record.production_machines)
end
return p.machineLinks(record.process_machines)
end
end
return p