Модуль:PlayerStats
| Этот модуль реализован на основе базы данных Lua, Cron-скрипта и кэширования JSON. |
Общее описание
Module:PlayerStats — это центральный фронтенд-контроллер для генерации полной личной страницы игрока ЧТМ. Модуль мгновенно выводит абсолютно всю статистику по игроку за все годы, достижения, рекорды, мегарейтинг, медали, навигационные шаблоны и хронологию матчей.
Главное достижение системы — архитектура глобального JSON-кэширования.
Модуль больше не занимается тяжелыми математическими вычислениями и не перебирает базы данных на лету. Вместо этого он за долю секунды читает предварительно сгенерированный и сжатый текстовый файлModule:Data/GrandStats.json.Это позволило снизить потребление оперативной памяти сервером в 6 раз, а скорость загрузки страниц увеличить в десятки раз.
Использование
На странице игрока (например, Диман) модуль вызывается через шаблон (или напрямую через #invoke):
{{#invoke:PlayerStats|main|Диман}}
Или с указанием всех тонких параметров:
{{#invoke:PlayerStats|main<!-- Запуск модуля
-->|Диман<!-- Имя игрока, обязательный параметр, НЕ ПЕРЕНОСИТЕ СТРОКУ ПОСЛЕ ИМЕНИ, если нужен визуальный перенос, то используйте комментарии, как в этом примере<!--
-->|achievements=yes<!-- Вычислять и показывать ли блок «Достижения и Рекорды» (по умолчанию-yes)<!--
-->|ach_list=yes<!-- Если achievements=no, то показать только блок достижений: ach_list=yes. Если achievements=yes, то не показывать блок достижений: ach_list=no.
-->|records_list=yes<!-- Если achievements=no, то показать только блок рекордов: records_list=yes. Если achievements=yes, то не показывать блок рекордов: records_list=no.<!--
-->|main_table=yes<!-- Отключает расчёт и вывод таблицы статистики (по умолчанию — yes)
-->|sortable=no<!-- Отключает сортировку таблицы статистики, делая её более компактной (по умолчанию — no)
-->|medals=yes<!-- Показывать ли медали в таблице статистики (по умолчанию — no)
-->|matches_list=no<!-- Отключает расчёт и вывод полного списка матчей (по умолчанию — yes)
-->|matches_link=<!-- В случае отключения списка матчей необходимо задать ссылку на подстраницу.
-->|navboxes=yes<!-- Отключает расчёт и вывод навигационных шаблонов (по умолчанию — yes)
-->|СГ=1<!-- Если статья заняла какое-то место в голосовании за статью года, то оно указывается
-->|Год=2020<!-- Год, когда происходило голосование
-->|Качество=ИС<!-- Избранная (ИС) или хорошая (ХС) статья
-->}}
Оптимизация (Разделение контента)
Для удобства навигации (и чтобы не перегружать страницу визуально) рекомендуется выносить рекорды, достижения и полный список матчей ветеранов турнира на отдельную подстраницу.
Заполняйте шаблон основной статьи так:
== Достижения и рекорды ==
{{ОС|Достижения, рекорды и список матчей}}.
{{#invoke:PlayerStats|main<!--
-->|ИМЯ<!--
-->|achievements = no
|main_table = yes
|sortable = no
|medals = yes
|matches_list = no
|matches_link = Достижения, рекорды и список матчей
|navboxes = yes
}}
А на подстранице (например, /Достижения, рекорды и список матчей) заполните так:
{{#invoke:PlayerStats|main<!--
-->|ИМЯ<!--
-->|achievements = yes
|main_table = no
|matches_list = yes
|navboxes = no
}}
Архитектура системы (MVC + Cache)
Система разделена на независимые слои для обеспечения максимальной производительности:
Слой 1: Базы Данных (Data)
Module:Data/2006...Module:Data/2046: Сырые массивы матчей. Никакой логики, только факты (кто играл, кто забил, минуты, пенальти).Module:Data/Teams: Справочник команд (коды, полные названия, падежи).
Слой 2: Настройки и Правила (Config)
Module:Config: Мозг проекта. Здесь лежат списки годов, массивы чемпионов, цвета для таблиц, параметры эпох (с какого года считается статистика), а также «Костыли» (ручные корректировки вроде +11 к полезности для отдельного матча).
Слой 3: Вычислительные движки (Engines)
Module:StatEngine/Pure(Харвестер): Математик. Берет сырые базы и перемалывает их в готовую статистику.Module:StatEngine/TournamentAwards: Логика судейства (кто получил Башмак, кто MVP игрового дня, как разрешать равенство очков).Module:Megarating: Сложный калькулятор мегарейтинга по формулам ЧТМ.Module:StatEngine/StreaksCore: Анализатор серий (сухие серии, победные серии, матчи подряд).
Слой 4: Генератор и Кэш (Backend)
Module:Cron/GenerateStats: Тяжеловоз. Вызывается только вручную администратором. Загружает БД, прогоняет через все движки и собираетGrandStatsи глобальные рекорды. Сжимает результат в текст формата JSON.Module:Data/GrandStats.json: Тетрадка бухгалтера. Обычная текстовая страница, хранящая итог работы генератора. Именно её читают страницы игроков.
Слой 5: Интерфейс (Frontend)
Module:PlayerStats: Роутер. Быстро считывает нужный кусок изGrandStats.jsonи раздает данные отрисовщикам.Module:PlayerAchievements: Строит маркированные списки текстовых достижений и рекордов.Module:Автоматическая статистика: Рисует таблицу по годам и сворачиваемый список матчей.
Инструкция по поддержке и обслуживанию
⚠️ ГЛАВНОЕ ПРАВИЛО НОВОЙ АРХИТЕКТУРЫ:
Поскольку PlayerStats берет данные из закэшированного файла, любое изменение в базах данных (добавили гол) или в конфигах (изменили эпоху) требует ручного перевыпуска кэша! Без этого страницы игроков не увидят изменений.
🔄 Как обновить кэш (Пересчитать всю вики)
Если вы сыграли новый игровой день или исправили ошибку в старом матче:
- Перейдите на служебную страницу Служебная:Развёртка_шаблонов (
Special:ExpandTemplates). - В верхнее окно вставьте код:
{{#invoke:Cron/GenerateStats|main}} - Нажмите «ОК».
- В нижнем окне появится огромный текстовый массив (начинается с
{"Players":{...). Скопируйте его целиком. - Откройте на редактирование
Module:Data/GrandStats.json. - Удалите старый текст, вставьте новый и сохраните. Страницы всех игроков мгновенно обновятся!
➕ Как добавить новый турнир (например, ЧТМ-2050)
- Создайте
Module:Data/2050и залейте туда матчи. - Откройте
Module:Config:- Добавьте
2050вConfig.yearsиConfig.all_years. - Если турнир не закончен, поставьте
Config.is_latest_finished = false. Как только сыгран финал — меняйте наtrue. - Пропишите чемпионов в
Config.champions_teamsиConfig.champions_players(когда они станут известны).
- Добавьте
- Обновите кэш (см. инструкцию выше).
🛠 Как скорректировать статистику (ручные добавки)
Иногда история ЧТМ требует ручного вмешательства (матчи без протоколов, технические решения). Для этого не нужно ломать код:
- Зайдите в
Module:Config. - В массиве
Config.metricsнайдите нужный показатель (например,clearances). - Добавьте игрока в секцию
adjustments -> players:
["clearances"] = {
start = 2022,
get_val = function(stats) return stats.clearances end,
adjustments = {
players = {
["Имя_Игрока"] = { [2050] = 5 }, -- Добавит 5 выносов в 2050 году
}
}
}
- Обновите кэш (см. инструкцию выше). Система сама плюсанёт эти цифры в общую историю и в конкретный год.
📈 Что делать, если появляется совершенно новый показатель?
Если в 2050 году вы решите считать, например, «Голы с центра поля»:
- В
Module:StatEngine/Pureв функцииcreate_empty_statsдобавьте полеcenter_goals = 0. - Там же в
extract_goalsпропишите условие:if goal.goal_type == "с центра" then p.goals.center_goals = ... end. - В
Module:ConfigвConfig.metricsсоздайте словарь["center_goals"]. - В
Module:Автоматическая статистикадобавьте строчку вp.row_defs. - Обновите кэш. Колонка автоматически появится во всех таблицах, а лучший по этому показателю получит медаль.
-- ==========================================
-- Модуль:PlayerStats
-- Архитектура "Модуль-Монстр": Единая точка расчётов
-- ==========================================
local PlayerStats = {}
local Config = require('Module:Config')
local Teams = require('Module:Data/Teams')
local StatEngine = require('Module:StatEngine/Pure')
local TournamentAwards = require('Module:StatEngine/TournamentAwards')
local ModuleMegarating = require('Module:Megarating')
local PA = require('Module:PlayerAchievements')
local AS = require('Module:Автоматическая статистика')
-- Утилиты подсчёта дат (необходимы для определения паузы в выступлениях)
local function plural(n, w1, w2, w5)
local n10 = n % 10; local n100 = n % 100
if n100 >= 11 and n100 <= 19 then return w5 end
if n10 == 1 then return w1 end
if n10 >= 2 and n10 <= 4 then return w2 end
return w5
end
local function date_diff(date1, date2)
local y1, m1, d1 = date1:match("(%d+)%-(%d+)%-(%d+)")
local y2, m2, d2 = date2:match("(%d+)%-(%d+)%-(%d+)")
if not y1 or not y2 then return 0, "" end
y1, m1, d1 = tonumber(y1), tonumber(m1), tonumber(d1)
y2, m2, d2 = tonumber(y2), tonumber(m2), tonumber(d2)
local dy = y2 - y1; local dm = m2 - m1; local dd = d2 - d1
if dd < 0 then dm = dm - 1; dd = dd + 30 end
if dm < 0 then dy = dy - 1; dm = dm + 12 end
local total_days = dy * 365 + dm * 30 + dd
local parts = {}
if dy > 0 then table.insert(parts, dy .. " " .. plural(dy, "год", "года", "лет")) end
if dm > 0 then table.insert(parts, dm .. " " .. plural(dm, "месяц", "месяца", "месяцев")) end
if dd > 0 then table.insert(parts, dd .. " " .. plural(dd, "день", "дня", "дней")) end
return total_days, table.concat(parts, " ")
end
-- ==========================================
-- ОСНОВНОЙ ВЫЗОВ
-- ==========================================
function PlayerStats.main(frame)
local args = frame.args[1] and frame.args or frame:getParent().args
local player_name = args[1] or args.player or args["игрок"]
if not player_name or player_name == "" then return "Ошибка: не указано имя игрока." end
-- Гибкие настройки рендера
local show_achievements = (args.achievements ~= "no" and args.achievements ~= "0")
local show_main = (args.main_table ~= "no" and args.main_table ~= "0")
local show_matches = (args.matches_list ~= "no" and args.matches_list ~= "0")
local show_navboxes = (args.navboxes ~= "no" and args.navboxes ~= "0")
local show_medals = (args.medals == "yes" or args.medals == "1")
-- 1. ЭКСКЛЮЗИВНАЯ ЗАГРУЗКА БАЗ ДАННЫХ
local databases = {}
for _, year in ipairs(Config.years) do
local success, year_db = pcall(require, 'Module:Data/' .. year)
if success and type(year_db) == "table" then databases[year] = year_db end
end
-- 2. ЕДИНСТВЕННЫЙ ПРОГОН ЧЕРЕЗ КОМБАЙН (O(1))
local GrandStats = StatEngine.Harvester.run_all_time(databases, {
need_players = true, need_teams = true, need_combos = true, keep_years = true, keep_matches = false
})
-- Ручные компенсации (adjustments) для голов/ассистов
if Config.award_adjustments then
for year, year_adj in pairs(Config.award_adjustments) do
for award_type, combos in pairs(year_adj) do
for combo_key, adj in pairs(combos) do
local p_name, t_code = string.match(combo_key, "^(.*)_(.*)$")
if p_name and t_code and adj.total then
local add = adj.total
if GrandStats.Players[p_name] then
if award_type == "goals" then GrandStats.Players[p_name].goals.total = GrandStats.Players[p_name].goals.total + add
elseif award_type == "assists" then GrandStats.Players[p_name].assists.total = GrandStats.Players[p_name].assists.total + add end
end
if GrandStats.Years[year] and GrandStats.Years[year].Players[p_name] then
if award_type == "goals" then GrandStats.Years[year].Players[p_name].goals.total = GrandStats.Years[year].Players[p_name].goals.total + add
elseif award_type == "assists" then GrandStats.Years[year].Players[p_name].assists.total = GrandStats.Years[year].Players[p_name].assists.total + add end
end
if GrandStats.PlayerTeam[combo_key] then
if award_type == "goals" then GrandStats.PlayerTeam[combo_key].goals.total = GrandStats.PlayerTeam[combo_key].goals.total + add
elseif award_type == "assists" then GrandStats.PlayerTeam[combo_key].assists.total = GrandStats.PlayerTeam[combo_key].assists.total + add end
end
if GrandStats.Years[year] and GrandStats.Years[year].PlayerTeam[combo_key] then
if award_type == "goals" then GrandStats.Years[year].PlayerTeam[combo_key].goals.total = GrandStats.Years[year].PlayerTeam[combo_key].goals.total + add
elseif award_type == "assists" then GrandStats.Years[year].PlayerTeam[combo_key].assists.total = GrandStats.Years[year].PlayerTeam[combo_key].assists.total + add end
end
end
end
end
end
end
-- 3. ПОДГОТОВКА СТРУКТУР (Слоты удлинителей для AS и PA)
local AS_compiled = {
metrics = {}, played_years = {}, last_played_year = 0, played_before_2022 = false,
player_matches_list = {},
navbox_data = { champs = {}, superchamps = {}, zshar = {}, zbashmak = {}, elnur = {}, final_goals = {}, final_assists = {}, loyalty = {}, debuts = {}, champ_counts = {}, global_goals = {}, team_matches = {} }
}
for _, row in ipairs(AS.row_defs) do
AS_compiled.metrics[row.id] = { total_val = 0, total_num = 0, total_den = 0, years = {} }
for _, y in ipairs(Config.years) do AS_compiled.metrics[row.id].years[y] = { val = 0, num = 0, den = 0, color = "" } end
end
if show_navboxes then
local hardcoded_loyalty = { {2010, "Герыч", "Мьянма"}, {2014, "Антон", "Доминика"}, {2018, "Диман", "Доминика"}, {2018, "Антон", "Доминика"}, {2018, "Диман", "Теркс и Кайкос"}, {2018, "Антон", "Теркс и Кайкос"} }
for _, l in ipairs(hardcoded_loyalty) do table.insert(AS_compiled.navbox_data.loyalty, l) end
end
local PA_Players = {}
local PA_GlobalRecords = { AllTime = {}, PerTournament = {} }
local PA_CustomStats = { club100 = {}, stadiums = {}, finals = {} }
local PA_Leaders, PA_TeamLeaders, PA_StadiumLeaders = {}, {}, {}
local PA_ShoeTeams, PA_AssistTeams = {}, {}
local function get_p(name)
if not PA_Players[name] then
PA_Players[name] = {
name = name,
champs = 0, champs_years = {}, champ_captain = 0, champ_captain_years = {},
champ_streak = 0, cur_champ_streak = 0, last_champ_year = nil, champ_streak_end_year = nil,
champ_team_streak = 0, cur_champ_team_streak = 0, last_champ_team = nil, champ_team_streak_code = "", champ_team_streak_end_year = nil,
superchamps = 0, superchamps_years = {}, golden_goals = 0, golden_goals_years = {},
final_goals_real = 0, final_assists_real = 0, final_ga_real = 0,
max_final_goals = 0, max_final_goals_occurences = {}, finals_played = 0, mvp_final = 0, mvp_final_years = {},
goals = 0, max_goals_team = 0, max_goals_team_code = "", max_goals_match = 0, max_goals_match_occurences = {},
mega_tricks = 0, max_mega_tricks_team = 0, max_mega_tricks_team_code = "",
classic_hat_tricks = 0, pokers = 0, pentas = 0, hexas = 0, pens_scored = 0, pens_in_game_scored = 0,
max_assists_match = 0, max_assists_match_occurences = {}, pm = 0, md_prizes = 0, md_places = 0,
assists = 0, heel_goals = 0, golden_spheres = 0, golden_spheres_years = {}, total_spheres = 0,
golden_shoes = 0, golden_shoes_years = {}, total_shoes = 0, mvp = 0, max_team_mvp = 0, max_team_mvp_code = "",
mvp_goalie = 0, mr_points_pct = 0, pens_saved = 0, matches = 0, field_matches = 0, goalie_matches = 0, goalie_goals = 0, head_goals = 0, clearances = 0,
max_streaks = {}, cur_streaks = {}, last_played_hist = nil, cur_team = nil, max_streaks_team_matches_code = "", max_streaks_goalie_clean_code = "",
max_gap_days = 0, max_gap_str = "", last_date = nil,
wins = { goals = 0, goals_years = {}, mega_tricks = 0, mega_tricks_years = {}, pm = 0, pm_years = {}, mvp = 0, mvp_years = {}, mvp_goalie = 0, mvp_goalie_years = {}, head_goals = 0, head_goals_years = {}, clearances = 0, clearances_years = {}, gaa = 0, gaa_years = {} },
tournaments = {}
}
end
return PA_Players[name]
end
local function get_t(p, year)
if not p.tournaments[year] then
p.tournaments[year] = {
year = year, goals = 0, max_goals_team = 0, max_goals_team_code = "",
mega_tricks = 0, pm = 0, matches = 0, pts_pct = 0, md_prizes = 0, md_places = 0,
heel_goals = 0, mvp = 0, max_team_mvp = 0, max_team_mvp_code = "",
playoff_wins = 0, head_goals = 0, clearances = 0, gaa = 0, playoff_goals = 0, winning_goals = 0, avg_goals = 0,
max_team_assists = 0, max_team_assists_code = "", mvp_goalie = 0, mr_points_pct = 0, mr_points = 0, assists = 0, avg_assists = 0,
ga = 0, max_team_ga = 0, max_team_ga_code = "", goalie_matches = 0, field_matches = 0, max_mega_tricks_team = 0, max_mega_tricks_team_code = ""
}
end
return p.tournaments[year]
end
-- Формирование линейного списка матчей для последовательных расчётов (стрики, паузы и т.д.)
local flat_matches = {}
local global_goals = {}
for _, year in ipairs(Config.years) do
AS_compiled.played_years[year] = false
local year_db = databases[year]
if year_db then
PA_CustomStats.finals[year] = {}
for id, m in pairs(year_db) do
table.insert(flat_matches, { year = year, id = id, m = m, num = m.num_hist or 0 })
end
end
end
table.sort(flat_matches, function(a, b)
if a.year ~= b.year then return a.year < b.year end
return tostring(a.id) < tostring(b.id)
end)
local StreaksEngine = nil
if show_achievements then
local StreaksCore = require('Module:StatEngine/StreaksCore')
StreaksEngine = StreaksCore.new()
end
local current_scorer = nil
local consecutive_goals = 0
local N = AS_compiled.navbox_data
-- 4. ЕДИНСТВЕННЫЙ ЛИНЕЙНЫЙ ПРОХОД ПО ВСЕМ ИСТОРИЧЕСКИМ МАТЧАМ (Основа ядра Монстра)
for _, item in ipairs(flat_matches) do
local year = item.year; local match = item.m; local match_id = item.id
local is_playoff = Config.playoff_stages[match.stage] == true
local is_final = (match.stage == "Финал" or match.stage == "финал")
if show_achievements then StreaksEngine:process_match(year, match, match.num_hist) end
local p_teams = StatEngine.Harvester.get_all_teams(match)
if show_navboxes then
for p_name, _ in pairs(p_teams) do if not N.debuts[p_name] then N.debuts[p_name] = year end end
if year >= 2022 then
local p_teams_starters = {}
if match.squad1 then
if type(match.squad1.starters) == "table" then for _, p_n in ipairs(match.squad1.starters) do p_teams_starters[p_n] = match.team1 end end
if type(match.squad1.substitutes) == "table" then for _, p_n in ipairs(match.squad1.substitutes) do p_teams_starters[p_n] = match.team1 end end
end
if match.squad2 then
if type(match.squad2.starters) == "table" then for _, p_n in ipairs(match.squad2.starters) do p_teams_starters[p_n] = match.team2 end end
if type(match.squad2.substitutes) == "table" then for _, p_n in ipairs(match.squad2.substitutes) do p_teams_starters[p_n] = match.team2 end end
end
for p_n, t_code in pairs(p_teams_starters) do
N.team_matches[year] = N.team_matches[year] or {}; N.team_matches[year][p_n] = N.team_matches[year][p_n] or {}
N.team_matches[year][p_n][t_code] = (N.team_matches[year][p_n][t_code] or 0) + 1
end
end
end
-- Формирование списка матчей ТОЛЬКО для целевого игрока
if show_matches and year >= 2022 and p_teams[player_name] then
local m_row = {
year = year, num_hist = match.num_hist or 0, match_num_id = tonumber(match_id:match("%-(%d+)")) or 0,
date = match.date or "", stage = match.stage or "", letter = match.letter,
team1 = match.team1, team2 = match.team2, score1 = match.score1, score2 = match.score2,
aet = match.aet, shootout1 = match.shootout_score1, shootout2 = match.shootout_score2,
wikilink = match.wikilink, role_team = p_teams[player_name], is_official = true,
goals = 0, assists = 0, is_mvp = (match.mvp and match.mvp.player == player_name),
is_goalie = false, field = false, events = {}
}
local t_id = p_teams[player_name]
if (t_id == 1 and match.squad1 and match.squad1.full_match_goalie == player_name) or
(t_id == 2 and match.squad2 and match.squad2.full_match_goalie == player_name) or
(t_id == 0 and match.neutral_gk and Config.utils.has_value(match.neutral_gk.starters, player_name)) then
m_row.is_goalie = true
else
m_row.field = true
end
local clearances = 0
if match.clearances then for _, cl in ipairs(match.clearances) do if cl.player == player_name then clearances = clearances + 1 end end end
if type(match.goals) == "table" then
for _, g in ipairs(match.goals) do
if g.scorer == player_name then
m_row.goals = m_row.goals + 1
if g.goal_type == "голова" or g.goal_type2 == "голова" then table.insert(m_row.events, "Гол головой") end
if g.goal_type == "пенальти" or g.goal_type2 == "пенальти" then table.insert(m_row.events, "Гол с пенальти") end
end
if g.assist == player_name then m_row.assists = m_row.assists + 1 end
if g.own_scorer == player_name then table.insert(m_row.events, "Автогол") end
end
end
if clearances > 0 then table.insert(m_row.events, clearances == 1 and "Вынос из пустых" or (clearances .. " выноса из пустых")) end
if type(match.subs) == "table" then
for _, s in ipairs(match.subs) do
local sub_s = s.score or ""
if m_row.role_team == 2 and sub_s ~= "" then local p1, p2 = sub_s:match("(%d+):(%d+)"); if p1 and p2 then sub_s = p2 .. ":" .. p1 end end
if s.player_in == player_name and s.player_out ~= "none" then table.insert(m_row.events, "Вышел на замену при счёте " .. sub_s) end
if s.player_out == player_name then table.insert(m_row.events, "Заменён при счёте " .. sub_s) end
end
end
if type(match.shootout) == "table" then
local taken, scored, saves = 0, 0, 0; local saved_by, misses = nil, {}
for _, sh in ipairs(match.shootout) do
if sh.taker == player_name then taken = taken + 1; if sh.result == "гол" then scored = scored + 1 elseif sh.result == "вратарь" then saved_by = sh.goalie elseif sh.result == "штанга" or sh.result == "перекладина" then table.insert(misses, "1 штанга") end end
if sh.goalie == player_name and sh.result == "вратарь" then saves = saves + 1 end
end
if taken > 0 or saves > 0 then
local lines = {}
if taken > 0 then
local txt = scored .. " из " .. taken; local m_details = {}
if saved_by then table.insert(m_details, "1 отбил [[" .. saved_by .. "]]") end
for _, m_item in ipairs(misses) do table.insert(m_details, m_item) end
if #m_details > 0 then txt = txt .. " (" .. table.concat(m_details, ", ") .. ")" end
table.insert(lines, txt)
end
if saves > 0 then table.insert(lines, saves .. " сэйв" .. (saves > 1 and "а" or "")) end
if #lines > 0 then table.insert(m_row.events, "Серия пенальти:<br>" .. table.concat(lines, ", ")) end
end
end
if m_row.is_goalie and m_row.role_team ~= 0 and not m_row.field then table.insert(m_row.events, "Вратарь") end
table.insert(AS_compiled.player_matches_list, m_row)
end
if show_achievements and match.date then
for p_name, _ in pairs(p_teams) do
local p = get_p(p_name)
if p.last_date then
local days, str = date_diff(p.last_date, match.date)
if days > p.max_gap_days then p.max_gap_days = days; p.max_gap_str = str end
end
p.last_date = match.date
end
end
local match_g = {}; local match_a = {}
if match.goals then
for _, g in ipairs(match.goals) do
if show_achievements then
if g.scorer and not g.own_scorer then
if g.scorer == current_scorer then
consecutive_goals = consecutive_goals + 1
if consecutive_goals == 3 then get_p(g.scorer).classic_hat_tricks = get_p(g.scorer).classic_hat_tricks + 1 end
else current_scorer = g.scorer; consecutive_goals = 1 end
else current_scorer = nil; consecutive_goals = 0 end
end
if g.scorer then
if show_achievements then
local p = get_p(g.scorer); local t = get_t(p, year)
global_goals[g.scorer] = (global_goals[g.scorer] or 0) + 1
if global_goals[g.scorer] == 100 then table.insert(PA_CustomStats.club100, {name = g.scorer, year = year}) end
if match.stadium then
PA_CustomStats.stadiums[match.stadium] = PA_CustomStats.stadiums[match.stadium] or {}
PA_CustomStats.stadiums[match.stadium][g.scorer] = (PA_CustomStats.stadiums[match.stadium][g.scorer] or 0) + 1
end
match_g[g.scorer] = (match_g[g.scorer] or 0) + 1
local mg = match_g[g.scorer]
if mg > p.max_goals_match then p.max_goals_match = mg; p.max_goals_match_occurences = {year}
elseif mg == p.max_goals_match and mg > 0 then table.insert(p.max_goals_match_occurences, year) end
if is_final then
if mg > p.max_final_goals then p.max_final_goals = mg; p.max_final_goals_occurences = {year}
elseif mg == p.max_final_goals and mg > 0 then table.insert(p.max_final_goals_occurences, year) end
end
if is_playoff then t.playoff_goals = t.playoff_goals + 1 end
end
if show_navboxes then N.global_goals[g.scorer] = (N.global_goals[g.scorer] or 0) + 1 end
if show_navboxes and is_final and not g.own_scorer then N.final_goals[g.scorer] = (N.final_goals[g.scorer] or 0) + 1 end
end
if g.assist then
if show_achievements then
local p = get_p(g.assist)
match_a[g.assist] = (match_a[g.assist] or 0) + 1
local ma = match_a[g.assist]
if ma > p.max_assists_match then p.max_assists_match = ma; p.max_assists_match_occurences = {year}
elseif ma == p.max_assists_match and ma > 0 then table.insert(p.max_assists_match_occurences, year) end
end
if show_navboxes and is_final then N.final_assists[g.assist] = (N.final_assists[g.assist] or 0) + 1 end
end
end
end
if is_final then
if show_navboxes then
local function add_to_nav(list, val)
if type(val) == "table" then for _, v in ipairs(val) do table.insert(list, {year, v}) end elseif val then table.insert(list, {year, val}) end
end
add_to_nav(N.superchamps, match.superchampions); add_to_nav(N.zshar, match.golden_sphere); add_to_nav(N.elnur, match.elnur_award)
local match_res = StatEngine.Harvester.evaluate_match(match)
if match_res then
local w_team = (match_res[1].pts > match_res[2].pts) and 1 or ((match_res[2].pts > match_res[1].pts) and 2 or nil)
if w_team then
N.champs[year] = {}
local w_sq = (w_team == 1) and match.squad1 or match.squad2
if w_sq then
if type(w_sq.starters) == "table" then for _, p_n in ipairs(w_sq.starters) do N.champ_counts[p_n] = (N.champ_counts[p_n] or 0) + 1; table.insert(N.champs[year], p_n) end end
if type(w_sq.substitutes) == "table" then for _, p_n in ipairs(w_sq.substitutes) do N.champ_counts[p_n] = (N.champ_counts[p_n] or 0) + 1; table.insert(N.champs[year], p_n) end end
else
local seen = {}
if type(match.goals) == "table" then for _, g in ipairs(match.goals) do if g.team == w_team and g.scorer and not seen[g.scorer] then seen[g.scorer]=true; N.champ_counts[g.scorer]=(N.champ_counts[g.scorer] or 0)+1; table.insert(N.champs[year], g.scorer) end end end
end
end
end
end
if show_achievements then
local f = PA_CustomStats.finals[year]
f.goals_count = match_g; f.assists_count = match_a
if match.goals then
for _, g in ipairs(match.goals) do
if g.scorer then get_p(g.scorer).final_goals_real = get_p(g.scorer).final_goals_real + 1 end
if g.assist then get_p(g.assist).final_assists_real = get_p(g.assist).final_assists_real + 1 end
end
end
local function parse_aw(aw_data)
if type(aw_data) == "table" then return aw_data end
if type(aw_data) == "string" then return {aw_data} end return {}
end
f.superchampions = parse_aw(match.superchampions); f.golden_sphere = parse_aw(match.golden_sphere); f.silver_sphere = parse_aw(match.silver_sphere)
f.bronze_sphere = parse_aw(match.bronze_sphere); f.wooden_sphere = parse_aw(match.wooden_sphere)
f.golden_shoe = parse_aw(match.golden_shoe); f.silver_shoe = parse_aw(match.silver_shoe)
f.bronze_shoe = parse_aw(match.bronze_shoe); f.wooden_shoe = parse_aw(match.wooden_shoe)
f.golden_assistant = parse_aw(match.golden_assistant); f.silver_assistant = parse_aw(match.silver_assistant)
f.bronze_assistant = parse_aw(match.bronze_assistant); f.wooden_assistant = parse_aw(match.wooden_assistant)
f.elnur_award = parse_aw(match.elnur_award); f.best_goal = parse_aw(match.best_goal); f.mvp = match.mvp and match.mvp.player or nil
f.has_shootout = (match.shootout_score1 ~= nil); f.shootout_winner = nil
if match.shootout and f.has_shootout then
local w_team = match.shootout_score1 > match.shootout_score2 and 1 or 2
local w_score = 0; local max_score = math.max(match.shootout_score1, match.shootout_score2)
for _, shot in ipairs(match.shootout) do
if shot.team == w_team and shot.result == "гол" then
w_score = w_score + 1; if w_score == max_score then f.shootout_winner = shot.taker; break end
end
end
end
for _, p_n in ipairs(f.superchampions or {}) do get_p(p_n).superchamps = get_p(p_n).superchamps + 1; table.insert(get_p(p_n).superchamps_years, year) end
for _, p_n in ipairs(f.golden_sphere or {}) do get_p(p_n).golden_spheres = get_p(p_n).golden_spheres + 1; table.insert(get_p(p_n).golden_spheres_years, year); get_p(p_n).total_spheres = get_p(p_n).total_spheres + 1 end
for _, p_n in ipairs(f.silver_sphere or {}) do get_p(p_n).total_spheres = get_p(p_n).total_spheres + 1 end
for _, p_n in ipairs(f.bronze_sphere or {}) do get_p(p_n).total_spheres = get_p(p_n).total_spheres + 1 end
for _, p_n in ipairs(f.wooden_sphere or {}) do get_p(p_n).total_spheres = get_p(p_n).total_spheres + 1 end
for _, p_n in ipairs(f.golden_shoe or {}) do get_p(p_n).golden_shoes = get_p(p_n).golden_shoes + 1; table.insert(get_p(p_n).golden_shoes_years, year); get_p(p_n).total_shoes = get_p(p_n).total_shoes + 1 end
for _, p_n in ipairs(f.silver_shoe or {}) do get_p(p_n).total_shoes = get_p(p_n).total_shoes + 1 end
for _, p_n in ipairs(f.bronze_shoe or {}) do get_p(p_n).total_shoes = get_p(p_n).total_shoes + 1 end
for _, p_n in ipairs(f.wooden_shoe or {}) do get_p(p_n).total_shoes = get_p(p_n).total_shoes + 1 end
if f.mvp then get_p(f.mvp).mvp_final = get_p(f.mvp).mvp_final + 1; table.insert(get_p(f.mvp).mvp_final_years, year) end
if f.shootout_winner then get_p(f.shootout_winner).golden_goals = get_p(f.shootout_winner).golden_goals + 1; table.insert(get_p(f.shootout_winner).golden_goals_years, year) end
local m_res = StatEngine.Harvester.evaluate_match(match)
local win_team = (m_res and m_res[1].pts > m_res[2].pts) and 1 or ((m_res and m_res[2].pts > m_res[1].pts) and 2 or 0)
local cap1 = match.squad1 and match.squad1.captain; local cap2 = match.squad2 and match.squad2.captain
for p_n, t_id in pairs(p_teams) do
local p = get_p(p_n)
p.finals_played = p.finals_played + 1
if t_id == win_team then
p.champs = p.champs + 1; table.insert(p.champs_years, year)
if (t_id == 1 and cap1 == p_n) or (t_id == 2 and cap2 == p_n) then
p.champ_captain = p.champ_captain + 1; table.insert(p.champ_captain_years, year)
end
local prev_idx = 0
for i, y in ipairs(Config.years) do if y == year then prev_idx = i - 1 break end end
local prev_year = Config.years[prev_idx]
local cur_t_code = (t_id == 1) and match.team1 or match.team2
if p.last_champ_year == prev_year then
p.cur_champ_streak = p.cur_champ_streak + 1
if p.last_champ_team == cur_t_code then p.cur_champ_team_streak = p.cur_champ_team_streak + 1 else p.cur_champ_team_streak = 1 end
else
p.cur_champ_streak = 1; p.cur_champ_team_streak = 1
end
p.last_champ_year = year; p.last_champ_team = cur_t_code
if p.cur_champ_streak > p.champ_streak then p.champ_streak = p.cur_champ_streak; p.champ_streak_end_year = year end
if p.cur_champ_team_streak > p.champ_team_streak then p.champ_team_streak = p.cur_champ_team_streak; p.champ_team_streak_code = cur_t_code; p.champ_team_streak_end_year = year end
end
end
end
end
end
if show_achievements then
StreaksEngine:finish()
local PA_GlobalStreaks = StreaksEngine:get_results()
for name, ep in pairs(PA_GlobalStreaks.players) do
local p = get_p(name)
p.max_streaks.team_scored = ep.team_scored or 0; p.max_streaks.matches_played = ep.seq or 0
p.max_streaks.goals = ep.goals or 0; p.max_streaks.double = ep.double or 0
p.max_streaks.hat_trick = ep.hat_trick or 0; p.max_streaks.poker = ep.poker or 0
p.max_streaks.both_ga = ep.both_ga or 0; p.max_streaks.ga = ep.ga or 0
p.max_streaks.missed_matches = ep.missed_matches or 0; p.max_streaks.unbeaten = ep.unbeaten or 0
p.max_streaks.win = ep.win or 0
end
for name, et in pairs(PA_GlobalStreaks.team_streaks) do
local p = get_p(name); p.max_streaks.team_matches = et.len or 0; p.max_streaks_team_matches_code = et.team or ""
end
for name, egk in pairs(PA_GlobalStreaks.goalie_clean) do
local p = get_p(name); p.max_streaks.goalie_clean = egk.len or 0; p.max_streaks_goalie_clean_code = egk.team or ""
end
end
local md_cache = {}
local award_cache = {}
local p_global = GrandStats.Players[player_name]
for year, year_db in pairs(databases) do
local p_year = p_global and p_global.years and p_global.years[year]
if p_year and p_year.matches_total > 0 then
AS_compiled.played_years[year] = true
if year > AS_compiled.last_played_year then AS_compiled.last_played_year = year end
if year < 2022 then AS_compiled.played_before_2022 = true end
end
if show_main and p_year then
local map = {
matches = p_year.matches_total, field_matches = p_year.matches_field, goals = p_year.goals.total, assists = p_year.assists.total,
mvp = p_year.mvp.is_mvp, plus_minus = p_year.plus_minus or 0,
mega_tricks = p_year.goals.hat_trick + p_year.goals.poker + p_year.goals.penta + p_year.goals.hexa,
assist_mega_tricks = p_year.assists.hat_trick + p_year.assists.poker + p_year.assists.penta,
head_goals = p_year.goals.head, heel_goals = p_year.goals.heel, free_kick_goals = p_year.goals.free_kick, goalie_goals = p_year.goals.goalie,
clearances = p_year.clearances, pens_scored = p_year.penalties.in_game.g + p_year.penalties.shootout.g,
pens_missed = (p_year.penalties.in_game.u - p_year.penalties.in_game.g) + (p_year.penalties.shootout.u - p_year.penalties.shootout.g),
pens_saved = p_year.penalties.saved_as_goalie, caused_pens = p_year.penalties.caused_pens,
mvp_goalie = p_year.mvp.is_goalie_mvp, clean_sheets = p_year.clean_sheets,
yellow_cards = p_year.cards.yellow, red_cards = p_year.cards.red, own_goals = p_year.own_goals
}
for k, v in pairs(map) do if AS_compiled.metrics[k] then AS_compiled.metrics[k].years[year].val = AS_compiled.metrics[k].years[year].val + v end end
if AS_compiled.metrics.avg_goals then AS_compiled.metrics.avg_goals.years[year].num = AS_compiled.metrics.avg_goals.years[year].num + p_year.avg.goals_num; AS_compiled.metrics.avg_goals.years[year].den = AS_compiled.metrics.avg_goals.years[year].den + p_year.avg.goals_den end
if AS_compiled.metrics.avg_assists then AS_compiled.metrics.avg_assists.years[year].num = AS_compiled.metrics.avg_assists.years[year].num + p_year.avg.assists_num; AS_compiled.metrics.avg_assists.years[year].den = AS_compiled.metrics.avg_assists.years[year].den + p_year.avg.assists_den end
end
if (show_main or show_achievements) and year >= 2022 then
local mds = {}
for _, match in pairs(year_db) do if match.matchday then mds[match.matchday] = true end end
for md, _ in pairs(mds) do
if not md_cache[year] then md_cache[year] = {} end
if not md_cache[year][md] then md_cache[year][md] = TournamentAwards.evaluateMatchdayPrizes(year_db, md) end
if show_main and AS_compiled.played_years[year] then
local summary = md_cache[year][md].summary[player_name]
if summary then
AS_compiled.metrics.matchday_prizes.years[year].val = AS_compiled.metrics.matchday_prizes.years[year].val + summary.prizes
AS_compiled.metrics.matchday_places.years[year].val = AS_compiled.metrics.matchday_places.years[year].val + summary.places
end
end
if show_achievements then
local prizes = md_cache[year][md]
for _, cat in ipairs({"mvp", "scorer", "assistant", "productive", "effective", "useful", "goalie_good", "goalie_bad"}) do
if prizes[cat] then
for _, p in ipairs(prizes[cat]) do
local pl = get_p(p.name); local t = get_t(pl, year)
if p.prize then pl.md_prizes = pl.md_prizes + 1; t.md_prizes = t.md_prizes + 1 end
if p.prize_place then pl.md_places = pl.md_places + 1; t.md_places = t.md_places + 1 end
end
end
end
end
end
end
local shoes = TournamentAwards.getTournamentAwards(year, year_db, "goals")
if show_achievements then
PA_ShoeTeams[year] = {}; for _, p in ipairs(shoes) do if p.rank <= 4 and not PA_ShoeTeams[year][p.player] then PA_ShoeTeams[year][p.player] = p.team_code end end
end
if show_navboxes then
for _, w in ipairs(shoes) do if w.rank == 1 then table.insert(N.zbashmak, {year, w.player, w.total, w.team_code}) end end
end
if show_achievements then
local assists = TournamentAwards.getTournamentAwards(year, year_db, "assists")
PA_AssistTeams[year] = {}; for _, p in ipairs(assists) do if p.rank <= 4 and not PA_AssistTeams[year][p.player] then PA_AssistTeams[year][p.player] = p.team_code end end
end
local can_give_medals = true
if year == Config.years[#Config.years] and Config.is_latest_finished == false then can_give_medals = false end
if can_give_medals and show_main and show_medals and AS_compiled.played_years[year] then
for _, row in ipairs(AS.row_defs) do
if year >= row.era then
local m_data = AS_compiled.metrics[row.id].years[year]
local val_for_medal = row.is_avg and m_data.num or m_data.val
if val_for_medal ~= 0 then
local best_color = ""
local m_conf = Config.metrics[row.id]
if m_conf and not m_conf.no_medals then
local is_worst = (row.id == "plus_minus" and val_for_medal < 0)
local cache_key = row.id .. (is_worst and "_worst" or "")
award_cache[year] = award_cache[year] or {}
if not award_cache[year][cache_key] then
if row.id == "goals" then
award_cache[year][cache_key] = shoes
elseif row.id == "assists" then
award_cache[year][cache_key] = TournamentAwards.getTournamentAwards(year, year_db, "assists")
else
award_cache[year][cache_key] = TournamentAwards.getGenericMetric(row.id, GrandStats.Years[year].Players, { worst = is_worst })
end
end
local best_rank = 999
for _, p_aw in ipairs(award_cache[year][cache_key]) do
if p_aw.player == player_name and type(p_aw.rank) == "number" and p_aw.rank < best_rank then
best_rank = p_aw.rank; best_color = p_aw.color or ""
end
end
end
m_data.color = best_color
end
end
end
end
end
local mr_hist = ModuleMegarating.get_public_history()
if show_achievements then
for p_name, gs_p in pairs(GrandStats.Players) do
local p = get_p(p_name)
p.goals = gs_p.goals.total; p.assists = gs_p.assists.total; p.matches = gs_p.matches_total
p.field_matches = gs_p.matches_field; p.goalie_matches = gs_p.matches_goalie
p.pm = gs_p.plus_minus; p.clearances = gs_p.clearances; p.head_goals = gs_p.goals.head
p.heel_goals = gs_p.goals.heel; p.goalie_goals = gs_p.goals.goalie
p.pens_scored = gs_p.penalties.in_game.g + gs_p.penalties.shootout.g
p.pens_in_game_scored = gs_p.penalties.in_game.g; p.pens_saved = gs_p.penalties.saved_as_goalie
p.mvp = gs_p.mvp.is_mvp; p.mvp_goalie = gs_p.mvp.is_goalie_mvp
p.mega_tricks = gs_p.goals.hat_trick + gs_p.goals.poker + gs_p.goals.penta + gs_p.goals.hexa
p.pokers = gs_p.goals.poker + gs_p.goals.penta + gs_p.goals.hexa
p.pentas = gs_p.goals.penta + gs_p.goals.hexa; p.hexas = gs_p.goals.hexa
p.final_ga_real = p.final_goals_real + p.final_assists_real
p.golden_goals = p.golden_goals + gs_p.megarating.final_gold_goals
p.avg_goals = (gs_p.avg.goals_den >= 20) and (gs_p.avg.goals_num / gs_p.avg.goals_den) or 0
p.avg_assists = (gs_p.avg.assists_den >= 20) and (gs_p.avg.assists_num / gs_p.avg.assists_den) or 0
p.pts_pct = (p.matches >= 20 and gs_p.megarating.mr_matches > 0) and (gs_p.megarating.mr_points / (gs_p.megarating.mr_matches * 3) * 100) or 0
p.gaa = (p.goalie_matches >= 20) and (gs_p.weighted_ga / p.goalie_matches) or 9999
end
for year, year_stats in pairs(GrandStats.Years) do
for p_name, gs_t in pairs(year_stats.Players) do
local p = get_p(p_name); local t = get_t(p, year)
t.goals = gs_t.goals.total; t.assists = gs_t.assists.total; t.ga = t.goals + t.assists
t.matches = gs_t.matches_total; t.field_matches = gs_t.matches_field; t.goalie_matches = gs_t.matches_goalie
t.pm = gs_t.plus_minus; t.clearances = gs_t.clearances
t.head_goals = gs_t.goals.head; t.heel_goals = gs_t.goals.heel
t.mvp = gs_t.mvp.is_mvp; t.mvp_goalie = gs_t.mvp.is_goalie_mvp
t.mega_tricks = gs_t.goals.hat_trick + gs_t.goals.poker + gs_t.goals.penta + gs_t.goals.hexa
t.winning_goals = gs_t.advanced.winning_goals
t.playoff_wins = gs_t.megarating.playoff_wins.r16 + gs_t.megarating.playoff_wins.qf + gs_t.megarating.playoff_wins.sf + (gs_t.megarating.is_champion and 1 or 0)
if gs_t.megarating and gs_t.megarating.final_gold_goals and gs_t.megarating.final_gold_goals > 0 then
for _ = 1, gs_t.megarating.final_gold_goals do table.insert(p.golden_goals_years, year) end
end
t.avg_goals = (gs_t.avg.goals_den >= 20) and (gs_t.avg.goals_num / gs_t.avg.goals_den) or 0
t.avg_assists = (gs_t.avg.assists_den >= 20) and (gs_t.avg.assists_num / gs_t.avg.assists_den) or 0
t.pts_pct = (t.matches >= 20 and gs_t.megarating.mr_matches > 0) and (gs_t.megarating.mr_points / (gs_t.megarating.mr_matches * 3) * 100) or 0
t.gaa = (t.goalie_matches >= 5) and (gs_t.weighted_ga / t.goalie_matches) or 0
end
end
for combo, c_data in pairs(GrandStats.PlayerTeam) do
local pl = c_data.name; local t = c_data.team; local goals = c_data.goals.total
if t ~= "Нейтрал" and goals > 0 then
PA_TeamLeaders[t] = PA_TeamLeaders[t] or {players = {}, goals = 0}
if goals > PA_TeamLeaders[t].goals then PA_TeamLeaders[t].goals = goals; PA_TeamLeaders[t].players = {pl}
elseif goals == PA_TeamLeaders[t].goals then table.insert(PA_TeamLeaders[t].players, pl) end
end
end
for p_name, hp in pairs(mr_hist.players) do
local p = get_p(p_name)
p.mr_points_pct = hp.total_pct
for year, ydata in pairs(hp.years) do local t = get_t(p, year); t.mr_points_pct = ydata.pct; t.mr_points = ydata.pts end
end
local function evaluate_leader(year, metric_func)
local max_val = -9999; local leaders = {}
for pl, p_stats in pairs(GrandStats.Years[year].Players) do
local val = metric_func(p_stats)
if val and val > 0 then
if val > max_val then max_val = val; leaders = {pl}
elseif val == max_val then table.insert(leaders, pl) end
end
end
return leaders
end
local function evaluate_min_leader(year, metric_func, min_req)
local min_val = 9999; local leaders = {}
for pl, p_stats in pairs(GrandStats.Years[year].Players) do
if min_req(p_stats) then
local val = metric_func(p_stats)
if val then
if val < min_val then min_val = val; leaders = {pl}
elseif val == min_val then table.insert(leaders, pl) end
end
end
end
return leaders
end
for _, year in ipairs(Config.years) do
if GrandStats.Years[year] then
PA_Leaders.goals = PA_Leaders.goals or {}; PA_Leaders.goals[year] = evaluate_leader(year, function(s) return s.goals.total end)
PA_Leaders.assists = PA_Leaders.assists or {}; PA_Leaders.assists[year] = evaluate_leader(year, function(s) return s.assists.total end)
PA_Leaders.matches = PA_Leaders.matches or {}; PA_Leaders.matches[year] = evaluate_leader(year, function(s) return s.matches_total end)
PA_Leaders.mvp = PA_Leaders.mvp or {}; PA_Leaders.mvp[year] = evaluate_leader(year, function(s) return s.mvp.is_mvp end)
PA_Leaders.mvp_goalie = PA_Leaders.mvp_goalie or {}; PA_Leaders.mvp_goalie[year] = evaluate_leader(year, function(s) return s.mvp.is_goalie_mvp end)
PA_Leaders.mega_tricks = PA_Leaders.mega_tricks or {}; PA_Leaders.mega_tricks[year] = evaluate_leader(year, function(s) return (s.goals.hat_trick + s.goals.poker + s.goals.penta + s.goals.hexa) end)
PA_Leaders.head_goals = PA_Leaders.head_goals or {}; PA_Leaders.head_goals[year] = evaluate_leader(year, function(s) return s.goals.head end)
PA_Leaders.pens_saved = PA_Leaders.pens_saved or {}; PA_Leaders.pens_saved[year] = evaluate_leader(year, function(s) return s.penalties.saved_as_goalie end)
PA_Leaders.clearances = PA_Leaders.clearances or {}; PA_Leaders.clearances[year] = evaluate_leader(year, function(s) return s.clearances end)
PA_Leaders.playoff_wins = PA_Leaders.playoff_wins or {}; PA_Leaders.playoff_wins[year] = evaluate_leader(year, function(s) return s.megarating.playoff_wins.r16 + s.megarating.playoff_wins.qf + s.megarating.playoff_wins.sf + (s.megarating.is_champion and 1 or 0) end)
PA_Leaders.plus_minus = PA_Leaders.plus_minus or {}; PA_Leaders.plus_minus[year] = evaluate_leader(year, function(s) return s.plus_minus end)
PA_Leaders.pens_scored = PA_Leaders.pens_scored or {}; PA_Leaders.pens_scored[year] = evaluate_leader(year, function(s) return s.penalties.in_game.g + s.penalties.shootout.g end)
PA_Leaders.avg_goals = PA_Leaders.avg_goals or {}; PA_Leaders.avg_goals[year] = evaluate_leader(year, function(s) return s.avg.goals_den >= 20 and s.avg.goals_num / s.avg.goals_den or 0 end)
PA_Leaders.gaa = PA_Leaders.gaa or {}; PA_Leaders.gaa[year] = evaluate_min_leader(year, function(s) return s.weighted_ga / s.matches_goalie end, function(s) return s.matches_goalie and s.matches_goalie >= 5 end)
end
end
for st, p_list in pairs(PA_CustomStats.stadiums) do
local max = 0; local best = {}
for pl, goals in pairs(p_list) do
if goals > max then max = goals; best = {pl}
elseif goals == max then table.insert(best, pl) end
end
if max > 0 then PA_StadiumLeaders[st] = {players = best, goals = max} end
end
for _, p in pairs(PA_Players) do
for year, list in pairs(PA_Leaders.goals or {}) do if Config.utils.has_value(list, p.name) then p.wins.goals = p.wins.goals + 1; table.insert(p.wins.goals_years, year) end end
for year, list in pairs(PA_Leaders.mega_tricks or {}) do if Config.utils.has_value(list, p.name) then p.wins.mega_tricks = p.wins.mega_tricks + 1; table.insert(p.wins.mega_tricks_years, year) end end
for year, list in pairs(PA_Leaders.plus_minus or {}) do if Config.utils.has_value(list, p.name) then p.wins.pm = p.wins.pm + 1; table.insert(p.wins.pm_years, year) end end
for year, list in pairs(PA_Leaders.mvp or {}) do if Config.utils.has_value(list, p.name) then p.wins.mvp = p.wins.mvp + 1; table.insert(p.wins.mvp_years, year) end end
for year, list in pairs(PA_Leaders.mvp_goalie or {}) do if Config.utils.has_value(list, p.name) then p.wins.mvp_goalie = p.wins.mvp_goalie + 1; table.insert(p.wins.mvp_goalie_years, year) end end
for year, list in pairs(PA_Leaders.head_goals or {}) do if Config.utils.has_value(list, p.name) then p.wins.head_goals = p.wins.head_goals + 1; table.insert(p.wins.head_goals_years, year) end end
for year, list in pairs(PA_Leaders.clearances or {}) do if Config.utils.has_value(list, p.name) then p.wins.clearances = p.wins.clearances + 1; table.insert(p.wins.clearances_years, year) end end
for year, list in pairs(PA_Leaders.gaa or {}) do if Config.utils.has_value(list, p.name) then p.wins.gaa = p.wins.gaa + 1; table.insert(p.wins.gaa_years, year) end end
end
local function get_val(p, key)
if string.sub(key, 1, 12) == "max_streaks_" then return p.max_streaks[string.sub(key, 13)] or 0 end
if string.sub(key, 1, 5) == "wins_" then return p.wins[string.sub(key, 6)] or 0 end
return p[key]
end
local metrics_all = { {"champs", false}, {"champ_captain", false}, {"champ_streak", false}, {"champ_team_streak", false}, {"superchamps", false}, {"golden_goals", false}, {"final_goals_real", false}, {"final_assists_real", false}, {"final_ga_real", false}, {"max_final_goals", false}, {"finals_played", false}, {"goals", false}, {"max_goals_team", false}, {"max_goals_match", false}, {"mega_tricks", false}, {"max_mega_tricks_team", false}, {"classic_hat_tricks", false}, {"pokers", false}, {"pentas", false}, {"hexas", false}, {"pens_scored", false}, {"pens_in_game_scored", false}, {"max_assists_match", false}, {"pm", false}, {"pts_pct", false}, {"md_prizes", false}, {"md_places", false}, {"assists", false}, {"avg_assists", false}, {"heel_goals", false}, {"golden_spheres", false}, {"total_spheres", false}, {"golden_shoes", false}, {"total_shoes", false}, {"avg_goals", false}, {"gaa", true}, {"mvp", false}, {"max_team_mvp", false}, {"mvp_goalie", false}, {"mr_points_pct", false}, {"pens_saved", false}, {"matches", false}, {"field_matches", false}, {"goalie_matches", false}, {"head_goals", false}, {"clearances", false}, {"max_gap_days", false} }
for _, row in ipairs(metrics_all) do
local key = row[1]; local is_min = row[2]; local best = is_min and 9999 or -9999
for _, p in pairs(PA_Players) do
local v = get_val(p, key)
if v and v ~= 0 and (not is_min or v < 9999) then
if (is_min and v < best) or (not is_min and v > best) then best = v end
end
end
PA_GlobalRecords.AllTime[key] = best
end
local streak_wins_keys = {"max_streaks_team_scored", "max_streaks_matches_played", "max_streaks_goals", "max_streaks_double", "max_streaks_hat_trick", "max_streaks_poker", "max_streaks_both_ga", "max_streaks_ga", "max_streaks_missed_matches", "max_streaks_goalie_clean", "max_streaks_team_matches", "max_streaks_unbeaten", "max_streaks_win", "wins_goals", "wins_mega_tricks", "wins_pm", "wins_mvp", "wins_mvp_goalie", "wins_head_goals", "wins_clearances", "wins_gaa"}
for _, key in ipairs(streak_wins_keys) do
local best = -9999
for _, p in pairs(PA_Players) do
local v = get_val(p, key)
if v and v ~= 0 and v > best then best = v end
end
PA_GlobalRecords.AllTime[key] = best
end
local metrics_tourney = { {"goals", false}, {"max_goals_team", false}, {"mega_tricks", false}, {"pm", false}, {"matches", false}, {"pts_pct", false}, {"md_prizes", false}, {"md_places", false}, {"heel_goals", false}, {"mvp", false}, {"max_team_mvp", false}, {"playoff_wins", false}, {"head_goals", false}, {"clearances", false}, {"gaa", true}, {"playoff_goals", false}, {"winning_goals", false}, {"avg_goals", false}, {"max_team_assists", false}, {"mvp_goalie", false}, {"mr_points", false}, {"assists", false}, {"avg_assists", false}, {"ga", false}, {"max_team_ga", false} }
for _, row in ipairs(metrics_tourney) do
local key = row[1]; local is_min = row[2]; local best = is_min and 9999 or -9999
for _, p in pairs(PA_Players) do
for _, t in pairs(p.tournaments) do
local v = t[key]
if v and v ~= 0 and (not is_min or v < 9999) then
if key == "gaa" and t.goalie_matches < 5 then v = nil end
if (key == "avg_goals" or key == "avg_assists") and t.field_matches < 20 then v = nil end
if key == "pts_pct" and t.matches < 20 then v = nil end
if v and ((is_min and v < best) or (not is_min and v > best)) then best = v end
end
end
end
PA_GlobalRecords.PerTournament[key] = best
end
end -- конец if show_achievements
-- 5. ФИНАЛЬНЫЕ КОРРЕКТИРОВКИ (Adjustments) И ПОДСЧЁТ СУММ
for metric_id, metric_cfg in pairs(Config.metrics) do
if metric_cfg.adjustments and metric_cfg.adjustments.players then
for p_name, p_adjs in pairs(metric_cfg.adjustments.players) do
for y, val in pairs(p_adjs) do
if type(y) == "number" then
if metric_id == "goals" and show_navboxes then N.global_goals[p_name] = (N.global_goals[p_name] or 0) + val end
if show_main and p_name == player_name and AS_compiled.metrics[metric_id] and AS_compiled.metrics[metric_id].years[y] then
AS_compiled.metrics[metric_id].years[y].val = AS_compiled.metrics[metric_id].years[y].val + val
AS_compiled.played_years[y] = true
if y > AS_compiled.last_played_year then AS_compiled.last_played_year = y end
if y < 2022 then AS_compiled.played_before_2022 = true end
end
end
end
end
end
end
if show_navboxes then N.global_goals["Геныч"] = (N.global_goals["Геныч"] or 0) + 4 end
if show_main then
for _, year in ipairs(Config.years) do
for _, row in ipairs(AS.row_defs) do
if year >= row.era then
local m = AS_compiled.metrics[row.id]
m.total_val = m.total_val + m.years[year].val
m.total_num = m.total_num + m.years[year].num
m.total_den = m.total_den + m.years[year].den
end
end
end
end
if show_navboxes then
for y, p_data in pairs(N.team_matches) do
for p_name_nav, t_data in pairs(p_data) do
for t, count in pairs(t_data) do
if count >= 7 then table.insert(N.loyalty, {y, p_name_nav, t}) end
end
end
end
end
-- 6. СБОРКА И РЕНДЕР
local final_output = {}
-- Отдаём готовый кэш в модуль Достижений
if show_achievements then
local PA_data = {
Players = PA_Players,
GlobalRecords = PA_GlobalRecords,
CustomStats = PA_CustomStats,
Leaders = PA_Leaders,
TeamLeaders = PA_TeamLeaders,
StadiumLeaders = PA_StadiumLeaders,
ShoeTeams = PA_ShoeTeams,
AssistTeams = PA_AssistTeams,
GrandStats = GrandStats,
MegaratingHistory = mr_hist
}
local ach_html = PA.render(player_name, PA_data)
if ach_html and ach_html ~= "" then table.insert(final_output, ach_html) end
end
-- Отдаём готовый кэш в модуль Статистики
local as_html = AS.build_ui(player_name, args, AS_compiled)
if as_html and as_html ~= "" then
if #final_output > 0 then table.insert(final_output, "\n") end
table.insert(final_output, as_html)
end
-- Возвращаем всё это единым куском текста и скармливаем парсеру
return frame:preprocess(table.concat(final_output, "\n"))
end
return PlayerStats