Module:LocUnitData: Difference between revisions
From Heroes of Might and Magic: Olden Era Official Wiki
No edit summary |
No edit summary |
||
| Line 342: | Line 342: | ||
local content = frame:preprocess( | local content = frame:preprocess( | ||
"{{#invoke:LocUnitData|getUnit|" .. unit .. "|lang=" .. lang .. "}}") | "{{#invoke:LocUnitData|getUnit|" .. unit .. "|lang=" .. lang .. "}}") | ||
-- Очищаем и проверяем содержимое | |||
content = tostring(content):gsub("{%s*$", "") -- Убираем висящие { | |||
content = content:gsub("}{", "} {") -- Разделяем слипшиеся теги | |||
if content:find("{") and not content:find("}") then | |||
-- Есть незакрытые фигурные скобки - добавляем закрывающую | |||
content = content .. "}" | |||
end | |||
-- добавляем таб в таблицу | -- добавляем таб в таблицу | ||
Revision as of 14:35, 15 November 2025
Documentation for this module may be created at Module:LocUnitData/doc
local p = {}
-- Подключаем зависимости
local LangStrings = require("Module:LocGetString")
local LocGameEntities = require("Module:LocGameEntities")
local dataCache = nil
-----------------------------------------------------
-- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
-----------------------------------------------------
local function loadJson(title)
local page = mw.title.new(title)
local content = page and page:getContent() or ""
if content == "" then return {} end
local ok, data = pcall(mw.text.jsonDecode, content)
if not ok then return {} end
return data
end
local function loadUnits()
if dataCache then return dataCache end
dataCache = {}
local stats = loadJson("Data:UnitsStats.json")
local data = loadJson("Data:UnitsData.json")
-- Индексируем stats по id
local statsById = {}
for key, item in pairs(stats) do
local id = item.id or key
statsById[id] = item
end
-- Объединяем
for name, item in pairs(data) do
if item.id and statsById[item.id] then
local merged = {}
for k, v in pairs(statsById[item.id]) do merged[k] = v end
for k, v in pairs(item) do merged[k] = v end
dataCache[name] = merged
end
end
return dataCache
end
local function getLabel(sid, lang)
local text = LangStrings.getText({ args = { sid = sid, lang = lang } })
return text or "—"
end
local function getUnitLabels(lang)
return {
Attack = getLabel("unit_attack", lang),
Defense = getLabel("unit_defence", lang),
Damage = getLabel("unit_damage", lang),
Health = getLabel("unit_health", lang),
Speed = getLabel("unit_speed", lang),
Initiative = getLabel("unit_init", lang),
Morale = getLabel("unit_moral", lang),
Luck = getLabel("unit_luck", lang),
Tier = getLabel("tooltipRang", lang),
Fraction = getLabel("faction_laws", lang),
Price = "[[File:Skill Economy.png|24px|alt=Price]]",
Experience = getLabel("hero_ui_exp", lang),
SquadValue = "SquadValue",
Range = "[[File:Battle icon Onesword.png|24px|alt=Attack Type]]",
Growth = "[[File:Skill Recruitment.png|24px|alt=Growth]]",
Movement = "[[File:Icon Stats Moving.png|24px|alt=Attack Type]]"
}
end
local function getLocFraction(code, lang)
local valid = {
human = true, demon = true, dungeon = true,
nature = true, undead = true, unfrozen = true
}
if not valid[code] then return "-" end
local sid = "world_cheat_dropdown_fraction_" .. code
return LangStrings.getText({ args = { sid = sid, lang = lang } })
end
function formatCost(cost)
if not cost then return "" end
local resourceOrder = {"gold", "gemstones", "mercury", "crystals"}
local resources = {
gold = {icon = "Icon Resource Gold.png", link = "Resources#Gold", name = "Gold"},
gemstones = {icon = "Icon Resource GemStone.png", link = "Resources#Gems", name = "Gems"},
mercury = {icon = "Icon Resource Mercury.png", link = "Resources#Mercury", name = "Mercury"},
crystals = {icon = "Icon Resource Crystal.png", link = "Resources#crystals", name = "Crystals"}
}
local parts = {}
for _, resource in ipairs(resourceOrder) do
if cost[resource] then
table.insert(parts, cost[resource] .. " [[File:" .. resources[resource].icon .. "|24px|link=" .. resources[resource].link .. "|" .. resources[resource].name .. "]]")
end
end
return table.concat(parts, " ")
end
function getUnitNamebyId(id)
local units = loadUnits()
for name, unit in pairs(units) do
if unit.id == id then
return name
end
end
return nil
end
-- Возвращает:
-- baseUnitName, mainUpgName, altUpgName
function resolveUpgrades(unit)
if not unit or not unit.id then
return nil, nil, nil
end
local id = unit.id
-- определяем корневой id
local rootId = id
:gsub("_upg_alt$", "")
:gsub("_upg$", "")
-- id апгрейдов
local mainUpgId = rootId .. "_upg"
local altUpgId = rootId .. "_upg_alt"
-- имена
local baseUnitName = getUnitNamebyId(rootId)
local mainUpgName = getUnitNamebyId(mainUpgId)
local altUpgName = getUnitNamebyId(altUpgId)
return baseUnitName, mainUpgName, altUpgName
end
-----------------------------------------------------
-- ОСНОВНАЯ ЛОГИКА: получение данных юнита
-----------------------------------------------------
function p.getUnitData(name, lang)
if not name then return nil, "No unit name provided" end
lang = lang or "en"
local units = loadUnits()
local unit = units[name]
if not unit then return nil, "Unit not found: " .. name end
local stats = unit.stats or {}
local cost = formatCost(unit.unitCost) or ""
-- локализованные данные
local locName = LocGameEntities.getLocName({ args = { name = name, lang = lang } })
local locDesc = LocGameEntities.getLocDescription({ args = { name = name, lang = lang } })
local image = LocGameEntities.getImage({ args = { name = name, lang = lang } })
local icon = LocGameEntities.getIcon({ args = { name = name, lang = lang } })
local fraction = getLocFraction(unit.fraction, lang)
-- способности
local abilities = {}
if stats.moveType then
local moveType = "movetype_" .. stats.moveType
table.insert(unit.abilities, 1, moveType)
unit.moveType = LocGameEntities.getLocName({ args = { name = moveType, lang = lang } })
end
for _, aid in ipairs(unit.abilities or {}) do
local aname = LocGameEntities.getLocName({ args = { name = aid, lang = lang } })
local adesc = LocGameEntities.getLocDescription({ args = { name = aid, lang = lang } })
local aicon = LocGameEntities.getIcon({ args = { name = aid, lang = lang } })
if aname and aname ~= "" then
table.insert(abilities, { id = aid, name = aname, description = adesc, icon = aicon })
if aid == "attack_melee" or aid == "attack_range" or aid == "attack_shoot" then
unit.attack_type = aname
end
end
end
local baseUnit, upgUnit, upgAltUnit = resolveUpgrades(unit)
return {
id = unit.id,
upgradeLine = { baseUnit, upgUnit, upgAltUnit },
name = name,
locName = locName,
locDesc = locDesc,
image = image,
icon = icon,
tier = unit.tier,
fraction = fraction,
attack_type = unit.attack_type,
stats = stats,
cost = cost,
growth = unit.growth,
moveType = unit.moveType,
expBonus = unit.expBonus,
squadValue = unit.squadValue,
abilities = abilities
}
end
-----------------------------------------------------
-- РИСОВАНИЕ ТАБЛИЦЫ
-----------------------------------------------------
function p.renderUnitTable(unit, lang, baseUnit)
if not unit then return "Invalid unit data" end
lang = lang or "en"
local L = getUnitLabels(lang)
local function val(x)
if x == nil or x == "" then return "—" end
return tostring(x)
end
local function diffStat(stat)
if not baseUnit then return val(unit.stats[stat]) end
local newVal = tonumber(unit.stats[stat])
local oldVal = tonumber(baseUnit.stats[stat])
if not newVal or not oldVal then return val(unit.stats[stat]) end
if newVal > oldVal then
return string.format("%s <span style='color:green;font-weight:bold;'>↑</span>", val(newVal))
elseif newVal < oldVal then
return string.format("%s <span style='color:red;font-weight:bold;'>↓</span>", val(newVal))
else
return val(newVal)
end
end
local stats = unit.stats or {}
local cost = unit.cost or {}
text = "<div style='max-width:1000px;margin:0 auto;'>\n"
-- заголовок юнита
text = text .. "<div style='text-align:left;line-height:1.4;margin-bottom:8px;'>"
text = text .. "<div style='font-size:1.5em;font-weight:bold;'>" .. unit.locName .. "</div>"
text = text .. "<div style='font-size:0.9em;color:gray;'>" .. val(unit.locDesc) .. "</div>"
text = text .. "</div>\n"
-- основной блок: таблица статов + картинка
text = text .. "<div style='display:flex;flex-wrap:wrap;justify-content:center;gap:10px;'>\n"
-- левая часть: таблица статов
text = text .. "<div style='flex:1 1 400px;min-width:280px;'>\n"
text = text .. "{| class='wikitable' style='width:100%;text-align:left;margin:0;'\n"
text = text .. "|-\n|" .. "[[File:Health Icon.png|24px|link=Combat#Health|alt=Health]] ".. L.Health .. " || " .. diffStat("hp") .. " || " .. L.Tier .. " || " .. val(unit.tier) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Attack.png|24px|link=Combat#Attack|alt=Attack]] ".. L.Attack .. " || " .. diffStat("offence") .. " || " .. L.Fraction .. " || " .. val(unit.fraction) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Defence.png|24px|link=Combat#Defence|alt=Defense]] ".. L.Defense .. " || " .. diffStat("defence") .. " || " .. L.Range .. " || " .. val(unit.attack_type) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Damage.png|24px|link=Combat#Damage|alt=Damage]] ".. L.Damage .. " || " .. diffStat("damageMin") .. "–" .. diffStat("damageMax") .. " || " .. L.Movement .. " || " .. val(unit.moveType) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Speed.png|24px|link=Combat#Speed|alt=Speed]] ".. L.Speed .. " || " .. diffStat("speed") .. " || " .. L.Price .. " || " .. cost .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Initiative.png|24px|link=Combat#Initiative|alt=Initiative]] ".. L.Initiative .. " || " .. diffStat("initiative") .. " || " .. L.Growth .. " || " .. val(unit.growth) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Morale.png|24px|link=Combat#Morale|alt=Morale]] ".. L.Morale .. " || " .. diffStat("moral") .. " || " .. L.Experience .. " || " .. val(unit.expBonus) .. "\n"
text = text .. "|-\n|" .. "[[File:Icon Stats Luck.png|24px|link=Combat#Luck|alt=Luck]] ".. L.Luck .. " || " .. diffStat("luck") .. " || " .. L.SquadValue .. " || " .. val(unit.squadValue) .. "\n"
text = text .. "|}\n"
text = text .. "</div>\n"
-- правая часть: картинка
text = text .. "<div style='flex:1 1 300px;min-width:260px;text-align:center;'>[[File:" .. (unit.image or "Temple_Swordsman_Battlefield.png") .. "|400px]]</div>\n"
text = text .. "</div>\n" -- закрытие блока flex
-- способности
text = text .. "<div style='margin-top:10px;'>\n"
if unit.abilities and #unit.abilities > 0 then
text = text .. "{| class='wikitable' style='width:100%;text-align:left;margin:0;'\n"
for _, ab in ipairs(unit.abilities) do
local icon = "[[File:" .. (ab.icon ~= "" and ab.icon or "icon bug.png") .. "|60px]]"
text = text .. "|-\n| style='text-align:center;width:80px;vertical-align:middle;' | " .. icon ..
" || style='padding:10px;' | <b>" .. ab.name .. "</b> — " .. ab.description .. "\n"
end
text = text .. "|}\n"
else
text = text .. "—\n"
end
text = text .. "</div>\n"
text = text .. "</div>\n"
return text
end
-----------------------------------------------------
-- Главная точка входа для шаблона
-----------------------------------------------------
function p.getUnit(frame)
local name = frame.args[1] or frame.args.name
local lang = frame.args.lang or "en"
local unit, err = p.getUnitData(name, lang)
if not unit then return err end
return p.renderUnitTable(unit, lang)
end
function p.getUnits(frame)
local name = frame.args[1] or frame.args.name
local lang = frame.args.lang or "en"
local unit, err = p.getUnitData(name, lang)
if not unit then
return err
end
-- основной юнит
local text = p.renderUnitTable(unit, lang)
-- связанные из линейки
for _, unitName in ipairs(unit.upgradeLine) do
if unitName and unitName ~= name then
local relatedUnit = p.getUnitData(unitName, lang)
text = text .. "\n\n" .. p.renderUnitTable(relatedUnit, lang, unit)
end
end
return text
end
-- Units in tabber
function p.unitTabs(frame)
local lang = frame.args.lang or "en"
local tabs = {}
for i = 1, math.huge do
local unit = frame.args[i]
if not unit then break end -- конец списка
unit = mw.text.trim(unit)
local title = LangStrings.translate{ args = { text = unit, lang = lang } }
-- local content = p.getUnit{ args = { name = unit, lang = lang } }
local content = frame:preprocess(
"{{#invoke:LocUnitData|getUnit|" .. unit .. "|lang=" .. lang .. "}}")
-- Очищаем и проверяем содержимое
content = tostring(content):gsub("{%s*$", "") -- Убираем висящие {
content = content:gsub("}{", "} {") -- Разделяем слипшиеся теги
if content:find("{") and not content:find("}") then
-- Есть незакрытые фигурные скобки - добавляем закрывающую
content = content .. "}"
end
-- добавляем таб в таблицу
table.insert(tabs, {
label = title,
content = content
})
end
if #tabs == 0 then return "" end
return mw.ext.tabber.render(tabs)
end
return p


